diff --git a/data/commands.xml b/data/commands.xml index f9e25b61683..6c64e3bee34 100644 --- a/data/commands.xml +++ b/data/commands.xml @@ -148,14 +148,14 @@ here later. For now, please refer to the code for the strings being used. --> permissions-verbose="everyone" description="Lists players whom you blocked using /mute command." /> - - state-scope="SS_LOBBY" /> - - -#include -#include -#include -#include -#include -#include - // TODO: kimden: should decorators use acting_peer? namespace @@ -74,13 +60,14 @@ namespace static const std::string g_type_soccer = "soccer"; const std::vector g_queue_names = { - "", "mqueue", "mcyclic", "mboth", - "kqueue", "qregular", "", "", - "kcyclic", "", "qcyclic", "", - "kboth", "", "", "qboth" + "", "mqueue", "mcyclic", "mboth", + "kqueue", "qregular", "", "", + "kcyclic", "", "qcyclic", "", + "kboth", "", "", "qboth" }; - enum QueueMask: int { + enum QueueMask: int + { QM_NONE = -1, QM_MAP_ONETIME = 1, QM_MAP_CYCLIC = 2, @@ -99,7 +86,7 @@ namespace return i; return QueueMask::QM_NONE; } // get_queue_mask - // ==================================================================== + //------------------------------------------------------------------------- std::string get_queue_name(int x) { @@ -114,7 +101,7 @@ namespace return StringUtils::insertValues( "[Error QN%d: please report with /tell about it] queue", x); } // get_queue_name - // ==================================================================== + //------------------------------------------------------------------------- int another_cyclic_queue(int x) { @@ -124,7 +111,27 @@ namespace // return QM_KART_CYCLIC; return QM_NONE; } // another_cyclic_queue - // ==================================================================== + //------------------------------------------------------------------------- + + template + void forAllQueuesInMask(int mask, Fn&& fn) + { + for (int x = QM_START; x < QM_END; x <<= 1) + { + if (mask & x) + fn(x); + } + } // forAllQueuesInMask + //------------------------------------------------------------------------- + + void restoreCmdFromArgv(std::string& cmd, + std::vector& argv, char c, char d, char e, char f, + int from = 0) + { + cmd = StringUtils::quoteEscapeArray(argv.begin() + from, argv.end(), + c, d, e, f); + } // restoreCmdFromArgv + //------------------------------------------------------------------------- // Auxiliary things, should be moved somewhere because they just help @@ -155,136 +162,6 @@ namespace // End of auxiliary things } // namespace -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}, - {"UU_SPECTATOR", UU_SPECTATOR}, - {"UU_USUAL", UU_USUAL}, - {"UU_CROWNED", UU_CROWNED}, - {"UU_SINGLE", UU_SINGLE}, - {"UU_HAMMER", UU_HAMMER}, - {"UU_MANIPULATOR", UU_MANIPULATOR}, - {"UU_CONSOLE", UU_CONSOLE}, - {"PE_SPECTATOR", PE_SPECTATOR}, - {"PE_USUAL", PE_USUAL}, - {"PE_CROWNED", PE_CROWNED}, - {"PE_SINGLE", PE_SINGLE}, - {"PE_HAMMER", PE_HAMMER}, - {"PE_MANIPULATOR", PE_MANIPULATOR}, - {"PE_CONSOLE", PE_CONSOLE}, - {"UU_OWN_COMMANDS", UU_OWN_COMMANDS}, - {"UU_OTHERS_COMMANDS", UU_OTHERS_COMMANDS}, - {"PE_ALLOW_ANYONE", PE_ALLOW_ANYONE}, - {"PE_VOTED_SPECTATOR", PE_VOTED_SPECTATOR}, - {"PE_VOTED_NORMAL", PE_VOTED_NORMAL}, - {"PE_VOTED", PE_VOTED}, - {"UP_CONSOLE", UP_CONSOLE}, - {"UP_MANIPULATOR", UP_MANIPULATOR}, - {"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 = std::move(file_name); - m_interval = interval; - m_contents = ""; - m_last_invoked = 0; - read(); -} // FileResource::FileResource -// ======================================================================== - -void CommandManager::FileResource::read() -{ - if (m_file_name.empty()) // in case it is not properly initialized - return; - // 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; - if (message.is_open()) - { - for (std::string line; std::getline(message, line); ) - { - answer += line; - answer.push_back('\n'); - } - if (!answer.empty()) - answer.pop_back(); - } - m_contents = answer; - m_last_invoked = StkTime::getMonoTimeMs(); -} // FileResource::read -// ======================================================================== - -std::string CommandManager::FileResource::get() -{ - uint64_t current_time = StkTime::getMonoTimeMs(); - if (m_interval == 0 || current_time < m_interval + m_last_invoked) - return m_contents; - read(); - return m_contents; -} // FileResource::get -// ======================================================================== - -CommandManager::AuthResource::AuthResource(std::string secret, std::string server, - std::string link_format): - m_secret(secret), m_server(server), m_link_format(link_format) -{ - -} // AuthResource::AuthResource -// ======================================================================== - -std::string CommandManager::AuthResource::get(const std::string& username, int online_id) const -{ -#ifdef ENABLE_CRYPTO_OPENSSL - std::string header = "{\"alg\":\"HS256\",\"typ\":\"JWT\"}"; - uint64_t timestamp = StkTime::getTimeSinceEpoch(); - std::string payload = "{\"sub\":\"" + username + "/" + std::to_string(online_id) + "\","; - payload += "\"iat\":\"" + std::to_string(timestamp) + "\","; - payload += "\"iss\":\"" + m_server + "\"}"; - header = Crypto::base64url(StringUtils::toUInt8Vector(header)); - payload = Crypto::base64url(StringUtils::toUInt8Vector(payload)); - std::string message = header + "." + payload; - std::string signature = Crypto::base64url(Crypto::hmac_sha256_array(m_secret, message)); - std::string token = message + "." + signature; - std::string response = StringUtils::insertValues(m_link_format, token.c_str()); - return response; -#else - return "This command is currently only supported for OpenSSL"; -#endif -} // 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), - m_omit_name(false) -{ - // Handling players who are allowed to run for anyone in any case - m_permissions |= UU_OTHERS_COMMANDS; -} // Command::Command(5) -// ======================================================================== - const SetTypoFixer& CommandManager::getFixer(TypoFixerType type) { switch (type) @@ -338,124 +215,30 @@ void CommandManager::initCommandsInfo() for (unsigned int i = 0; i < current->getNumNodes(); i++) { const XMLNode *node = current->getNode(i); - std::string node_name = node->getName(); - if (node_name == "external-commands-file") + + auto c = Command::unknownTypeFromXmlNode(node); + if (!c) continue; - // 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 - - // Name is read before enabled/disabled property, because we want - // to disable commands in "default" config that are present in - // "custom" config, regardless of server name - node->get("name", &name); + + const std::string name = c->getShortName(); + if (current == root2 && command == m_root_command && used_commands.find(name) != used_commands.end()) - { continue; - } else if (current == root) - { used_commands.insert(name); - } - // 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("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); + command->addChild(c); 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); if (root2) dfs(root2, m_root_command); + delete root; delete root2; } // initCommandsInfo @@ -465,11 +248,15 @@ void CommandManager::initCommands() { using CM = CommandManager; auto& mp = m_full_name_to_command; - m_root_command = std::make_shared("", &CM::special); + m_root_command = std::make_shared(); + m_root_command->setFunction(getDefaultAction()); + // std::bind(&CM::special, this, std::placeholders::_1)); initCommandsInfo(); - auto applyFunctionIfPossible = [&](std::string&& name, void (CM::*f)(Context& context)) { + auto ptr = this; + + auto applyFunctionIfPossible = [ptr, &mp](std::string&& name, std::function f) { auto it = mp.find(name); if (it == mp.end()) return; @@ -477,16 +264,45 @@ void CommandManager::initCommands() std::shared_ptr command = it->second.lock(); if (!command) return; + + command->setFunction(std::bind(std::move(f), ptr, std::placeholders::_1)); + }; + + auto applyTextIfPossible = [ptr, &mp](std::string&& name, const std::string& value) { + auto it = mp.find(name); + if (it == mp.end()) + return; - command->changeFunction(f); + std::shared_ptr command = it->second.lock(); + if (!command) + return; + + std::shared_ptr resource = std::dynamic_pointer_cast(command); + if (!resource) + return; + + resource->setText(value); }; + // special permissions according to ServerConfig options std::shared_ptr kick_command = mp["kick"].lock(); if (kick_command) { if (getSettings()->hasKicksAllowed()) - kick_command->m_permissions |= UU_CROWNED; + { + kick_command->changePermissions( + kick_command->getPermissions() | UU_CROWNED, + kick_command->getModeScope(), + kick_command->getStateScope() + ); + } else - kick_command->m_permissions &= ~UU_CROWNED; + { + kick_command->changePermissions( + kick_command->getPermissions() & (~UU_CROWNED), + kick_command->getModeScope(), + kick_command->getStateScope() + ); + } } applyFunctionIfPossible("commands", &CM::process_commands); @@ -512,8 +328,6 @@ void CommandManager::initCommands() 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); @@ -572,8 +386,6 @@ void CommandManager::initCommands() 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); applyFunctionIfPossible("muteall", &CM::process_muteall); applyFunctionIfPossible("game", &CM::process_game); @@ -625,16 +437,24 @@ void CommandManager::initCommands() applyFunctionIfPossible("liststkaddon", &CM::special); applyFunctionIfPossible("listlocaladdon", &CM::special); - addTextResponse("description", getSettings()->getMotd()); - addTextResponse("moreinfo", getSettings()->getHelpMessage()); - std::string version = Version::version(); std::string branch = Version::branch(); if (!branch.empty()) version += ", branch " + branch; - addTextResponse("version", version); - addTextResponse("clear", std::string(30, '\n')); + applyTextIfPossible("description", getSettings()->getMotd()); + applyTextIfPossible("moreinfo", getSettings()->getHelpMessage()); + applyTextIfPossible("version", version); + applyTextIfPossible("clear", std::string(30, '\n')); + + for (auto& element: m_full_name_to_command) + { + std::shared_ptr command = element.second.lock(); + if (!command || !command->needsFunction() || command->hasFunction()) + continue; + + command->setFunction(getDefaultAction()); + }; // m_votables.emplace("replay", 1.0); m_votables.emplace("start", 0.81); @@ -711,7 +531,7 @@ void CommandManager::handleCommand(Event* event, std::shared_ptr peer) argv = StringUtils::splitQuoted(cmd, ' ', '"', '"', '\\'); if (argv.empty()) return; - CommandManager::restoreCmdByArgv(cmd, argv, ' ', '"', '"', '\\'); + StringUtils::restoreCmdFromArgv(cmd, argv, ' ', '"', '"', '\\'); permissions = getLobby()->getPermissions(peer); voting = false; @@ -734,7 +554,7 @@ void CommandManager::handleCommand(Event* event, std::shared_ptr peer) argv = StringUtils::splitQuoted(cmd, ' ', '"', '"', '\\'); if (argv.empty()) return; - CommandManager::restoreCmdByArgv(cmd, argv, ' ', '"', '"', '\\'); + StringUtils::restoreCmdFromArgv(cmd, argv, ' ', '"', '"', '\\'); voting = m_user_saved_voting[username]; target_peer = m_user_saved_acting_peer[username]; target_peer_strong = target_peer.lock(); @@ -816,20 +636,20 @@ void CommandManager::handleCommand(Event* event, std::shared_ptr peer) 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); + const auto& subcommands = current_command->getSubcommands(); + bool one_omittable_subcommand = (subcommands.size() == 1 && subcommands[0]->omittedName()); std::shared_ptr command; if (one_omittable_subcommand) { - command = current_command->m_subcommands[0]; + command = subcommands[0]; } else { - if (hasTypo(target_peer_strong, peer, voting, argv, cmd, idx, current_command->m_stf_subcommand_names, 3, false, false)) + if (hasTypo(target_peer_strong, peer, voting, argv, cmd, idx, current_command->subcommandNamesTypoFixer(), 3, false, false)) return; - auto command_iterator = current_command->m_name_to_subcommand.find(argv[idx]); - command = command_iterator->second.lock(); + + command = current_command->findChild(argv[idx]); } if (!command) @@ -846,7 +666,7 @@ void CommandManager::handleCommand(Event* event, std::shared_ptr peer) // Note that we use caller's permissions to determine if the command can be invoked, // and during its invocation, we use acting peer's permissions. - int mask = ((permissions & command->m_permissions) & (~MASK_MANIPULATION)); + int mask = ((permissions & command->getPermissions()) & (~MASK_MANIPULATION)); if (mask == 0) { context.say("You don't have permissions to " + action + " this command"); @@ -855,7 +675,7 @@ void CommandManager::handleCommand(Event* event, std::shared_ptr peer) // kimden: both might be nullptr, which means different things if (target_peer.lock() != peer) { - int mask_manip = (permissions & command->m_permissions & MASK_MANIPULATION); + int mask_manip = (permissions & command->getPermissions() & MASK_MANIPULATION); if (mask_manip == 0) { context.say("You don't have permissions to " + action @@ -870,7 +690,7 @@ void CommandManager::handleCommand(Event* event, std::shared_ptr peer) if (one_omittable_subcommand) --idx; current_command = command; - if (idx + 1 == (int)argv.size() || command->m_subcommands.empty()) { + if (idx + 1 == (int)argv.size() || command->getSubcommands().empty()) { executed_command = command; execute(command, context); break; @@ -908,7 +728,7 @@ 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, ' ', '"', '"', '\\'); + StringUtils::restoreCmdFromArgv(new_cmd, new_argv, ' ', '"', '"', '\\'); std::string msg2 = StringUtils::insertValues( "Command \"/%s\" has been successfully voted", new_cmd.c_str()); @@ -934,8 +754,8 @@ int CommandManager::getCurrentModeScope() bool CommandManager::isAvailable(std::shared_ptr c) { - return (getCurrentModeScope() & c->m_mode_scope) != 0 - && (getLobby()->getCurrentStateScope() & c->m_state_scope) != 0; + return (getCurrentModeScope() & c->getModeScope()) != 0 + && (getLobby()->getCurrentStateScope() & c->getStateScope()) != 0; } // getCurrentModeScope // ======================================================================== @@ -948,11 +768,11 @@ void CommandManager::vote(Context& context, std::string category, std::string va if (!acting_peer->hasPlayerProfiles()) return; std::string username = acting_peer->getMainName(); - auto& votable = m_votables[command->m_prefix_name]; + auto& votable = m_votables[command->getFullName()]; bool neededCheck = votable.needsCheck(); votable.castVote(username, category, value); if (votable.needsCheck() && !neededCheck) - m_triggered_votables.push(command->m_prefix_name); + m_triggered_votables.push(command->getFullName()); } // vote // ======================================================================== @@ -969,7 +789,7 @@ void CommandManager::update() { std::string new_cmd = p.first + " " + p.second; auto new_argv = StringUtils::splitQuoted(new_cmd, ' ', '"', '"', '\\'); - CommandManager::restoreCmdByArgv(new_cmd, new_argv, ' ', '"', '"', '\\'); + StringUtils::restoreCmdFromArgv(new_cmd, new_argv, ' ', '"', '"', '\\'); std::string msg = StringUtils::insertValues( "Command \"/%s\" has been successfully voted", new_cmd.c_str()); @@ -991,45 +811,18 @@ void CommandManager::update() } // update // ======================================================================== -void CommandManager::error(Context& context, bool is_error) -{ - std::string msg; - if (is_error) - Log::error("CommandManager", "An error occurred while invoking %s", context.m_cmd.c_str()); - auto command = context.m_command.lock(); - auto peer = context.m_peer.lock(); - if (!command) { - Log::error("CommandManager", "CM::error: cannot load command"); - return; - } - if (!peer) { - Log::error("CommandManager", "CM::error: cannot load peer to send error"); - return; - } - msg = command->getUsage(); - if (msg.empty()) - msg = StringUtils::insertValues("An error occurred " - "while invoking command \"%s\".", - command->getFullName().c_str()); - - if (is_error) - msg += "\n/!\\ Please report this error to the server owner"; - context.say(msg); -} // error -// ======================================================================== - void CommandManager::execute(std::shared_ptr command, Context& context) { m_current_argv = context.m_argv; context.m_command = command; try { - (this->*(command->m_action))(context); + command->execute(context); } catch (std::exception& ex) { // auto peer = context.m_peer.lock(); - error(context, true); + context.error(true); // // kimden: make error message better + add log // context.say(StringUtils::insertValues( @@ -1048,83 +841,29 @@ void CommandManager::process_help(Context& context) auto peer = context.peer(); std::shared_ptr command = m_root_command; - for (int i = 1; i < (int)argv.size(); ++i) { - if (hasTypo(acting_peer, peer, context.m_voting, context.m_argv, context.m_cmd, i, command->m_stf_subcommand_names, 3, false, false)) + for (int i = 1; i < (int)argv.size(); ++i) + { + if (hasTypo(acting_peer, peer, context.m_voting, context.m_argv, context.m_cmd, i, command->subcommandNamesTypoFixer(), 3, false, false)) return; - auto ptr = command->m_name_to_subcommand[argv[i]].lock(); + + auto ptr = command->findChild(argv[i]); if (ptr) command = ptr; else break; - if (command->m_subcommands.empty()) + + if (command->getSubcommands().empty()) break; } if (command == m_root_command) { - error(context); + context.error(); return; } context.say(command->getHelp()); } // process_help // ======================================================================== -void CommandManager::process_text(Context& context) -{ - std::string response; - auto command = context.command(); - auto it = m_text_response.find(command->getFullName()); - if (it == m_text_response.end()) - response = StringUtils::insertValues( - "Error: a text command %s is defined without text", - command->getFullName().c_str()); - else - response = it->second; - context.say(response); -} // process_text -// ======================================================================== - -void CommandManager::process_file(Context& context) -{ - std::string response; - auto command = context.command(); - - auto it = m_file_resources.find(command->getFullName()); - if (it == m_file_resources.end()) - response = StringUtils::insertValues( - "Error: file not found for a file command %s", - command->getFullName().c_str()); - else - response = it->second.get(); - context.say(response); -} // process_text -// ======================================================================== - -void CommandManager::process_auth(Context& context) -{ - std::string response; - auto acting_peer = context.actingPeer(); - auto command = context.command(); - - auto it = m_auth_resources.find(command->getFullName()); - if (it == m_auth_resources.end()) - response = StringUtils::insertValues( - "Error: auth method not found for a command %s", - command->getFullName().c_str()); - else - { - auto profile = acting_peer->getMainProfile(); - std::string username = StringUtils::wideToUtf8(profile->getName()); - int online_id = profile->getOnlineId(); - if (online_id == 0) - response = "Error: you need to join with an " - "online account to use auth methods"; - else - response = it->second.get(username, online_id); - } - context.say(response); -} // process_text -// ======================================================================== - void CommandManager::process_commands(Context& context) { std::string result; @@ -1134,13 +873,16 @@ void CommandManager::process_commands(Context& context) auto& argv = context.m_argv; std::shared_ptr command = m_root_command; bool valid_prefix = true; - for (int i = 1; i < (int)argv.size(); ++i) { - if (hasTypo(acting_peer, peer, context.m_voting, context.m_argv, context.m_cmd, i, command->m_stf_subcommand_names, 3, false, false)) + for (int i = 1; i < (int)argv.size(); ++i) + { + if (hasTypo(acting_peer, peer, context.m_voting, context.m_argv, context.m_cmd, i, command->subcommandNamesTypoFixer(), 3, false, false)) return; - auto ptr = command->m_name_to_subcommand[argv[i]].lock(); + + auto ptr = command->findChild(argv[i]); if (!ptr) break; - if ((context.m_acting_user_permissions & ptr->m_permissions) != 0 + + if ((context.m_acting_user_permissions & ptr->getPermissions()) != 0 && isAvailable(ptr)) command = ptr; else @@ -1148,7 +890,7 @@ void CommandManager::process_commands(Context& context) valid_prefix = false; break; } - if (command->m_subcommands.empty()) + if (command->getSubcommands().empty()) break; } if (!valid_prefix) @@ -1161,19 +903,19 @@ void CommandManager::process_commands(Context& context) result += ":"; bool had_any_subcommands = false; std::map res; - for (std::shared_ptr& subcommand: command->m_subcommands) + for (const std::shared_ptr& subcommand: command->getSubcommands()) { - if ((context.m_acting_user_permissions & subcommand->m_permissions) != 0 + if ((context.m_acting_user_permissions & subcommand->getPermissions()) != 0 && isAvailable(subcommand)) { bool subcommands_available = false; - for (auto& c: subcommand->m_subcommands) + for (auto& c: subcommand->getSubcommands()) { - if ((context.m_acting_user_permissions & c->m_permissions) != 0 + if ((context.m_acting_user_permissions & c->getPermissions()) != 0 && isAvailable(c)) subcommands_available = true; } - res[subcommand->m_name] = subcommands_available; + res[subcommand->getShortName()] = subcommands_available; if (subcommands_available) had_any_subcommands = true; } @@ -1353,7 +1095,7 @@ void CommandManager::process_spectate(Context& context) if (argv.size() < 2 || !StringUtils::fromString(argv[1], value) || value < 0 || value > 2) { - error(context); + context.error(); return; } if (value >= 1) @@ -1525,7 +1267,7 @@ void CommandManager::process_checkaddon(Context& context) if (argv.size() < 2) { - error(context); + context.error(); return; } if (!validate(context, 1, TFT_ADDON_MAPS, false, true)) @@ -1641,7 +1383,7 @@ void CommandManager::process_id(Context& context) if (argv.size() < 2) { - error(context); + context.error(); return; } if (!validate(context, 1, TFT_ALL_MAPS, false, true)) @@ -1669,7 +1411,7 @@ void CommandManager::process_lsa(Context& context) (argv.size() == 2 && (argv[1].size() < 3 || has_options)) || (argv.size() == 3 && (!has_options || argv[2].size() < 3))) { - error(context); + context.error(); return; } std::string type = ""; @@ -1733,7 +1475,7 @@ void CommandManager::process_pha(Context& context) auto& argv = context.m_argv; if (argv.size() < 3) { - error(context); + context.error(); return; } @@ -1750,7 +1492,7 @@ void CommandManager::process_pha(Context& context) StringUtils::utf8ToWide(player_name)); if (player_name.empty() || !player_peer || addon_id.empty()) { - error(context); + context.error(); return; } @@ -1787,7 +1529,7 @@ void CommandManager::process_kick(Context& context) auto acting_peer = context.actingPeerMaybeNull(); if (argv.size() < 2) { - error(context); + context.error(); return; } @@ -1800,7 +1542,7 @@ void CommandManager::process_kick(Context& context) StringUtils::utf8ToWide(player_name)); if (player_name.empty() || !player_peer || player_peer->isAIPeer()) { - error(context); + context.error(); return; } if (player_peer->hammerLevel() > 0) @@ -1837,13 +1579,13 @@ void CommandManager::process_unban(Context& context) if (argv.size() < 2) { - error(context); + context.error(); return; } std::string player_name = argv[1]; if (player_name.empty()) { - error(context); + context.error(); return; } Log::info("CommandManager", "%s is now unbanned", player_name.c_str()); @@ -1859,13 +1601,13 @@ void CommandManager::process_ban(Context& context) auto& argv = context.m_argv; if (argv.size() < 2) { - error(context); + context.error(); return; } player_name = argv[1]; if (player_name.empty()) { - error(context); + context.error(); return; } Log::info("CommandManager", "%s is now banned", player_name.c_str()); @@ -1887,7 +1629,7 @@ void CommandManager::process_pas(Context& context) if (acting_peer->getPlayerProfiles().empty()) { Log::warn("CommandManager", "pas: no existing player profiles??"); - error(context); + context.error(); return; } player_name = acting_peer->getMainName(); @@ -1902,7 +1644,7 @@ void CommandManager::process_pas(Context& context) StringUtils::utf8ToWide(player_name)); if (player_name.empty() || !player_peer) { - error(context); + context.error(); return; } auto& scores = player_peer->getAddonsScores(); @@ -2019,7 +1761,7 @@ void CommandManager::process_sha(Context& context) auto& argv = context.m_argv; if (argv.size() != 2) { - error(context); + context.error(); return; } std::set total_addons; @@ -2050,7 +1792,7 @@ void CommandManager::process_mute(Context& context) if (argv.size() != 2 || argv[1].empty()) { - error(context); + context.error(); return; } @@ -2072,7 +1814,7 @@ void CommandManager::process_unmute(Context& context) if (argv.size() != 2 || argv[1].empty()) { - error(context); + context.error(); return; } @@ -2186,7 +1928,7 @@ void CommandManager::process_tell(Context& context) if (argv.size() == 1) { - error(context); + context.error(); return; } std::string ans; @@ -2256,7 +1998,7 @@ void CommandManager::process_to(Context& context) if (argv.size() == 1) { - error(context); + context.error(); return; } std::vector receivers; @@ -2288,7 +2030,7 @@ void CommandManager::process_record(Context& context) #ifdef ENABLE_SQLITE3 if (argv.size() < 5) { - error(context); + context.error(); return; } bool error = false; @@ -2375,7 +2117,7 @@ void CommandManager::process_length_multi(Context& context) if (argv.size() < 3 || !StringUtils::parseString(argv[2], &temp_double)) { - error(context); + context.error(); return; } double value = std::max(0.0, temp_double); @@ -2391,7 +2133,7 @@ void CommandManager::process_length_fixed(Context& context) if (argv.size() < 3 || !StringUtils::parseString(argv[2], &temp_int)) { - error(context); + context.error(); return; } int value = std::max(0, temp_int); @@ -2418,13 +2160,13 @@ void CommandManager::process_direction_assign(Context& context) auto& argv = context.m_argv; if (argv.size() < 2) { - error(context); + context.error(); return; } int temp_int = -1; if (!StringUtils::parseString(argv[1], &temp_int) || !getSettings()->setDirection(temp_int)) { - error(context); + context.error(); return; } Comm::sendStringToAllPeers(getSettings()->getDirectionAsString(true)); @@ -2435,18 +2177,19 @@ void CommandManager::process_queue(Context& context) { std::string msg = ""; int mask = get_queue_mask(context.m_argv[0]); - for (int x = QM_START; x < QM_END; x <<= 1) + + forAllQueuesInMask(mask, [&](int x) { - if (mask & x) - { - auto& queue = get_queue(x); - msg += StringUtils::insertValues("%s (size = %d):", - get_queue_name(x), (int)queue.size()); - for (std::shared_ptr& s: queue) - msg += " " + s->toString(); - msg += "\n"; - } - } + auto& queue = get_queue(x); + + msg += StringUtils::insertValues("%s (size = %d):", + get_queue_name(x), (int)queue.size()); + + for (std::shared_ptr& s: queue) + msg += " " + s->toString(); + msg += "\n"; + }); + msg.pop_back(); context.say(msg); } // process_queue @@ -2460,7 +2203,7 @@ void CommandManager::process_queue_push(Context& context) if (argv.size() < 3) { - error(context); + context.error(); return; } int mask = get_queue_mask(argv[0]); @@ -2473,7 +2216,7 @@ void CommandManager::process_queue_push(Context& context) argv[2] = asset_manager->getRandomAddonMap(); std::string filter_text = ""; - CommandManager::restoreCmdByArgv(filter_text, argv, ' ', '"', '"', '\\', 2); + restoreCmdFromArgv(filter_text, argv, ' ', '"', '"', '\\', 2); // Fix typos only if track queues are used (majority of cases anyway) // TODO: I don't know how to fix typos for both karts and tracks @@ -2514,31 +2257,23 @@ void CommandManager::process_queue_push(Context& context) std::string msg = ""; - for (int x = QM_START; x < QM_END; x <<= 1) + forAllQueuesInMask(mask, [&](int x) { - if (mask & x) - { - if (mask & QM_ALL_KART_QUEUES) - { - // TODO Make sure to update the next branch too; unite them somehow? - add_to_queue(x, mask, to_front, filter_text); - } - else - { - // TODO Make sure to update the previous branch too; unite them somehow? - add_to_queue(x, mask, to_front, filter_text); - } + // TODO: Make sure to update both branches if at all + if (mask & QM_ALL_KART_QUEUES) + add_to_queue(x, mask, to_front, filter_text); + else + add_to_queue(x, mask, to_front, filter_text); - msg += StringUtils::insertValues( - "Pushed { %s } to the %s of %s, current queue size: %d", - filter_text.c_str(), - (to_front ? "front" : "back"), - get_queue_name(x).c_str(), - get_queue(x).size() - ); - msg += "\n"; - } - } + msg += StringUtils::insertValues( + "Pushed { %s } to the %s of %s, current queue size: %d", + filter_text.c_str(), + (to_front ? "front" : "back"), + get_queue_name(x).c_str(), + get_queue(x).size() + ); + msg += "\n"; + }); msg.pop_back(); Comm::sendStringToAllPeers(msg); @@ -2554,42 +2289,41 @@ void CommandManager::process_queue_pop(Context& context) int mask = get_queue_mask(argv[0]); bool from_back = (argv[1] == "pop_back"); - for (int x = QM_START; x < QM_END; x <<= 1) + forAllQueuesInMask(mask, [&](int x) { - if (mask & x) + int another = another_cyclic_queue(x); + + if (get_queue(x).empty()) { - int another = another_cyclic_queue(x); - if (get_queue(x).empty()) { - msg += "The " + get_queue_name(x) + " was empty before.\n"; - } - else - { - auto object = (from_back ? get_queue(x).back() : get_queue(x).front()); - msg += "Popped " + object->toString() - + " from the " + (from_back ? "back" : "front") + " of the " - + get_queue_name(x) + ","; - if (from_back) - { - get_queue(x).pop_back(); - } - else - { - get_queue(x).pop_front(); - } - if (another >= QM_START && !(mask & another)) - { - // here you have to pop from FRONT and not back because it - // was pushed to the front - see process_queue_push - auto& q = get_queue(another); - if (!q.empty() && q.front()->isPlaceholder()) - q.pop_front(); - } - msg += " current queue size: " - + std::to_string(get_queue(x).size()); - msg += "\n"; - } + msg += StringUtils::insertValues( + "The %s was empty before.\n", get_queue_name(x).c_str()); + return; } - } + + auto object = (from_back ? get_queue(x).back() : get_queue(x).front()); + msg += StringUtils::insertValues("Popped %s from the %s of the %s,", + object->toString().c_str(), + (from_back ? "back" : "front"), + get_queue_name(x).c_str() + ); + + if (from_back) + get_queue(x).pop_back(); + else + get_queue(x).pop_front(); + + if (another >= QM_START && !(mask & another)) + { + // here you have to pop from FRONT and not back because it + // was pushed to the front - see process_queue_push + auto& q = get_queue(another); + if (!q.empty() && q.front()->isPlaceholder()) + q.pop_front(); + } + msg += StringUtils::insertValues(" current queue size: %s\n", + get_queue(x).size()); + }); + msg.pop_back(); Comm::sendStringToAllPeers(msg); getLobby()->updatePlayerList(); @@ -2600,24 +2334,22 @@ void CommandManager::process_queue_clear(Context& context) { int mask = get_queue_mask(context.m_argv[0]); std::string msg = ""; - for (int x = QM_START; x < QM_END; x <<= 1) + forAllQueuesInMask(mask, [&](int x) { - if (mask & x) - { - int another = another_cyclic_queue(x); - msg += StringUtils::insertValues( - "The " + get_queue_name(x) + " is now empty (previous size: %d)", - (int)get_queue(x).size()) + "\n"; - get_queue(x).clear(); + int another = another_cyclic_queue(x); - if (another >= QM_START && !(mask & another)) - { - auto& q = get_queue(another); - while (!q.empty() && q.front()->isPlaceholder()) - q.pop_front(); - } + msg += StringUtils::insertValues( + "The " + get_queue_name(x) + " is now empty (previous size: %d)", + (int)get_queue(x).size()) + "\n"; + get_queue(x).clear(); + + if (another >= QM_START && !(mask & another)) + { + auto& q = get_queue(another); + while (!q.empty() && q.front()->isPlaceholder()) + q.pop_front(); } - } + }); msg.pop_back(); Comm::sendStringToAllPeers(msg); getLobby()->updatePlayerList(); @@ -2636,28 +2368,26 @@ void CommandManager::process_queue_shuffle(Context& context) // we don't have to do anything with placeholders for the corresponding // cyclic queue, BUT we have to not shuffle the placeholders in the // current queue itself - for (int x = QM_START; x < QM_END; x <<= 1) - { - if (mask & x) + forAllQueuesInMask(mask, [&](int x) + { + auto& queue = get_queue(x); + // As the placeholders can be only at the start of a queue, + // let's just do a binary search - in case there are many placeholders + int L = -1; + int R = queue.size(); + int mid; + while (R - L > 1) { - auto& queue = get_queue(x); - // As the placeholders can be only at the start of a queue, - // let's just do a binary search - in case there are many placeholders - int L = -1; - int R = queue.size(); - int mid; - while (R - L > 1) - { - mid = (L + R) / 2; - if (queue[mid]->isPlaceholder()) - L = mid; - else - R = mid; - } - std::shuffle(queue.begin() + R, queue.end(), g); - msg += "The " + get_queue_name(x) + " is now shuffled\n"; + mid = (L + R) / 2; + if (queue[mid]->isPlaceholder()) + L = mid; + else + R = mid; } - } + std::shuffle(queue.begin() + R, queue.end(), g); + msg += "The " + get_queue_name(x) + " is now shuffled\n"; + }); + msg.pop_back(); Comm::sendStringToAllPeers(msg); getLobby()->updatePlayerList(); @@ -2678,7 +2408,7 @@ void CommandManager::process_allowstart_assign(Context& context) // because of an extra cast. if (argv.size() == 1 || !(argv[1] == "0" || argv[1] == "1")) { - error(context); + context.error(); return; } getSettings()->setAllowedToStart(argv[1] != "0"); @@ -2698,7 +2428,7 @@ void CommandManager::process_shuffle_assign(Context& context) // Move validation to lobby settings. if (argv.size() == 1 || !(argv[1] == "0" || argv[1] == "1")) { - error(context); + context.error(); return; } getSettings()->setGPGridShuffled(argv[1] != "0"); @@ -2712,7 +2442,7 @@ void CommandManager::process_timeout(Context& context) auto& argv = context.m_argv; if (argv.size() < 2 || !StringUtils::parseString(argv[1], &seconds) || seconds <= 0) { - error(context); + context.error(); return; } getLobby()->setTimeoutFromNow(seconds); @@ -2727,7 +2457,7 @@ void CommandManager::process_team(Context& context) auto& argv = context.m_argv; if (argv.size() != 3) { - error(context); + context.error(); return; } if (!validate(context, 2, TFT_PRESENT_USERS, false, true)) @@ -2761,7 +2491,7 @@ void CommandManager::process_swapteams(Context& context) if (argv.size() != 2) { - error(context); + context.error(); return; } // todo move list of teams and checking teams to another unit later, @@ -2853,12 +2583,13 @@ void CommandManager::process_randomteams(Context& context) void CommandManager::process_resetgp(Context& context) { auto& argv = context.m_argv; - if (argv.size() >= 2) { + if (argv.size() >= 2) + { int number_of_games; if (!StringUtils::parseString(argv[1], &number_of_games) || number_of_games <= 0) { - error(context); + context.error(); return; } getGameSetupFromCtx()->setGrandPrixTrack(number_of_games); @@ -2877,7 +2608,7 @@ void CommandManager::process_cat(Context& context) { if (argv.size() != 3) { - error(context); + context.error(); return; } std::string category = argv[1]; @@ -2892,7 +2623,7 @@ void CommandManager::process_cat(Context& context) { if (argv.size() != 3) { - error(context); + context.error(); return; } std::string category = argv[1]; @@ -2913,7 +2644,7 @@ void CommandManager::process_cat(Context& context) if (argv.size() != 3 || !StringUtils::parseString(argv[2], &displayed) || displayed < -1 || displayed > 1) { - error(context); + context.error(); return; } } @@ -2934,7 +2665,7 @@ void CommandManager::process_vip(Context& context) { if (argv.size() > 2) { - error(context); + context.error(); return; } if (argv.size() == 2) @@ -2954,7 +2685,7 @@ void CommandManager::process_vip(Context& context) { if (argv.size() != 2) { - error(context); + context.error(); return; } if (!validate(context, 1, TFT_PRESENT_USERS, false, true)) @@ -2971,7 +2702,7 @@ void CommandManager::process_vip(Context& context) { if (argv.size() != 2) { - error(context); + context.error(); return; } if (!validate(context, 1, TFT_PRESENT_USERS, false, true)) @@ -3006,7 +2737,7 @@ void CommandManager::process_troll_assign(Context& context) auto& argv = context.m_argv; if (argv.size() == 1 || !(argv[1] == "0" || argv[1] == "1")) { - error(context); + context.error(); return; } auto hit_processor = getHitProcessor(); @@ -3041,7 +2772,7 @@ void CommandManager::process_hitmsg_assign(Context& context) auto& argv = context.m_argv; if (argv.size() == 1 || !(argv[1] == "0" || argv[1] == "1")) { - error(context); + context.error(); return; } auto hit_processor = getHitProcessor(); @@ -3076,7 +2807,7 @@ void CommandManager::process_teamhit_assign(Context& context) auto& argv = context.m_argv; if (argv.size() == 1 || !(argv[1] == "0" || argv[1] == "1")) { - error(context); + context.error(); return; } auto hit_processor = getHitProcessor(); @@ -3106,7 +2837,7 @@ void CommandManager::process_scoring_assign(Context& context) auto& argv = context.m_argv; std::string cmd2; - CommandManager::restoreCmdByArgv(cmd2, argv, ' ', '"', '"', '\\', 1); + StringUtils::restoreCmdFromArgv(cmd2, argv, ' ', '"', '"', '\\', 1); if (getGPManager()->trySettingGPScoring(cmd2)) Comm::sendStringToAllPeers("Scoring set to \"" + cmd2 + "\""); else @@ -3149,7 +2880,7 @@ void CommandManager::process_muteall(Context& context) auto tournament = getTournament(); if (!tournament) { - error(context, true); + context.error(true); return; } if (!acting_peer->hasPlayerProfiles()) @@ -3179,7 +2910,7 @@ void CommandManager::process_game(Context& context) auto tournament = getTournament(); if (!tournament) { - error(context, true); + context.error(true); return; } @@ -3254,12 +2985,12 @@ void CommandManager::process_role(Context& context) auto tournament = getTournament(); if (!tournament) { - error(context, true); + context.error(true); return; } if (argv.size() < 3) { - error(context); + context.error(); return; } if (argv[1].length() > argv[2].length()) @@ -3274,7 +3005,7 @@ void CommandManager::process_role(Context& context) (argv[3] == "p" || argv[3] == "permanent")); if (role.length() != 1) { - error(context); + context.error(); return; } char role_char = role[0]; @@ -3395,7 +3126,7 @@ void CommandManager::process_stop(Context& context) { if (!getTournament()) { - error(context, true); + context.error(true); return; } World* w = World::getWorld(); @@ -3412,7 +3143,7 @@ void CommandManager::process_go(Context& context) { if (!getTournament()) { - error(context, true); + context.error(true); return; } World* w = World::getWorld(); @@ -3429,7 +3160,7 @@ void CommandManager::process_lobby(Context& context) { if (!getTournament()) { - error(context, true); + context.error(true); return; } World* w = World::getWorld(); @@ -3446,7 +3177,7 @@ void CommandManager::process_init(Context& context) auto& argv = context.m_argv; if (!getTournament()) { - error(context, true); + context.error(true); return; } int red, blue; @@ -3454,7 +3185,7 @@ void CommandManager::process_init(Context& context) !StringUtils::parseString(argv[1], &red) || !StringUtils::parseString(argv[2], &blue)) { - error(context); + context.error(); return; } World* w = World::getWorld(); @@ -3496,7 +3227,7 @@ void CommandManager::process_test(Context& context) argv.resize(4, ""); if (argv[2] == "no" && argv[3] == "u") { - error(context); + context.error(); return; } if (context.m_voting) @@ -3540,7 +3271,7 @@ void CommandManager::process_slots_assign(Context& context) fail = true; if (fail) { - error(context); + context.error(); return; } if (context.m_voting) @@ -3601,7 +3332,7 @@ void CommandManager::process_preserve_assign(Context& context) std::string msg = ""; if (argv.size() != 3) { - error(context); + context.error(); return; } if (argv[2] == "0") @@ -3625,7 +3356,7 @@ void CommandManager::process_history(Context& context) auto tournament = getTournament(); if (!tournament) { - error(context, true); + context.error(true); return; } std::string msg = "Map history:"; @@ -3642,19 +3373,19 @@ void CommandManager::process_history_assign(Context& context) auto tournament = getTournament(); if (!tournament) { - error(context, true); + context.error(true); return; } std::string msg = ""; if (argv.size() != 3) { - error(context); + context.error(); return; } int index; if (!StringUtils::fromString(argv[1], index) || index < 0) { - error(context); + context.error(); return; } if (!validate(context, 2, TFT_ALL_MAPS, false, false)) @@ -3662,7 +3393,7 @@ void CommandManager::process_history_assign(Context& context) std::string id = argv[2]; if (!tournament->assignToHistory(index, id)) { - error(context); + context.error(); return; } @@ -3685,13 +3416,13 @@ void CommandManager::process_voting_assign(Context& context) std::string msg = ""; if (argv.size() < 2) { - error(context); + context.error(); return; } int value; if (!StringUtils::fromString(argv[1], value) || value < 0 || value > 1) { - error(context); + context.error(); return; } getMapVoteHandler()->setAlgorithm(value); @@ -3712,7 +3443,7 @@ void CommandManager::process_why_hourglass(Context& context) if (acting_peer->getPlayerProfiles().empty()) { Log::warn("CommandManager", "whyhourglass: no existing player profiles??"); - error(context); + context.error(); return; } player_name = acting_peer->getMainName(); @@ -3727,7 +3458,7 @@ void CommandManager::process_why_hourglass(Context& context) StringUtils::utf8ToWide(player_name)); if (player_name.empty() || !player_peer) { - error(context); + context.error(); return; } @@ -3754,7 +3485,7 @@ void CommandManager::process_available_teams_assign(Context& context) if (argv.size() < 2) { - error(context); + context.error(); return; } std::string value = ""; @@ -3805,14 +3536,14 @@ void CommandManager::process_cooldown_assign(Context& context) if (argv.size() < 2) { - error(context); + context.error(); return; } int new_cooldown = -1; // kimden: figure out what's with epsilons in STK if (!StringUtils::parseString(argv[1], &new_cooldown) || new_cooldown < 0) { - error(context); + context.error(); return; } getSettings()->setLobbyCooldown(new_cooldown); @@ -3842,7 +3573,7 @@ void CommandManager::process_net(Context& context) if (acting_peer->getPlayerProfiles().empty()) { Log::warn("CommandManager", "net: no existing player profiles??"); - error(context); + context.error(); return; } player_name = acting_peer->getMainName(); @@ -3857,7 +3588,7 @@ void CommandManager::process_net(Context& context) StringUtils::utf8ToWide(player_name)); if (player_name.empty() || !player_peer) { - error(context); + context.error(); return; } @@ -3931,7 +3662,7 @@ void CommandManager::process_temp250318(Context& context) int value = 0; if (argv.size() < 2 || !StringUtils::parseString(argv[1], &value)) { - error(context); + context.error(); return; } auto settings = getSettings(); @@ -3985,20 +3716,6 @@ void CommandManager::deleteUser(std::string& s) } // deleteUser // ======================================================================== -void CommandManager::restoreCmdByArgv(std::string& cmd, - std::vector& argv, char c, char d, char e, char f, - int from) -{ - cmd.clear(); - for (int i = from; i < (int)argv.size(); ++i) { - if (i > from) { - cmd.push_back(c); - } - cmd += StringUtils::quoteEscape(argv[i], c, d, e, f); - } -} // restoreCmdByArgv -// ======================================================================== - bool CommandManager::validate(Context& ctx, int idx, TypoFixerType fixer_type, bool case_sensitive, bool allow_as_is) { @@ -4017,13 +3734,16 @@ bool CommandManager::hasTypo(std::shared_ptr acting_peer, std::shared_p { if (!acting_peer.get()) // voted return false; + std::string username = ""; if (peer->hasPlayerProfiles()) username = peer->getMainName(); + auto it = m_user_last_correct_argument.find(username); if (it != m_user_last_correct_argument.end() && std::make_pair(idx, subidx) <= it->second) return false; + std::string text = argv[idx]; std::string prefix = ""; std::string suffix = ""; @@ -4033,6 +3753,7 @@ bool CommandManager::hasTypo(std::shared_ptr acting_peer, std::shared_p suffix = text.substr(substr_r); text = text.substr(substr_l, substr_r - substr_l); } + auto closest_commands = stf.getClosest(text, top, case_sensitive); if (closest_commands.empty()) { @@ -4069,12 +3790,12 @@ bool CommandManager::hasTypo(std::shared_ptr acting_peer, std::shared_p } for (unsigned i = 0; i < closest_commands.size(); ++i) { argv[idx] = prefix + closest_commands[i].first + suffix; - CommandManager::restoreCmdByArgv(cmd, argv, ' ', '"', '"', '\\'); + StringUtils::restoreCmdFromArgv(cmd, argv, ' ', '"', '"', '\\'); m_user_command_replacements[username].push_back(cmd); response += "\ntype /" + std::to_string(i + 1) + " to choose \"" + closest_commands[i].first + "\""; } argv[idx] = initial_argument; - CommandManager::restoreCmdByArgv(cmd, argv, ' ', '"', '"', '\\'); + StringUtils::restoreCmdFromArgv(cmd, argv, ' ', '"', '"', '\\'); Comm::sendStringToPeer(peer, response); return true; } @@ -4082,7 +3803,7 @@ bool CommandManager::hasTypo(std::shared_ptr acting_peer, std::shared_p if (!dont_replace) { argv[idx] = prefix + closest_commands[0].first + suffix; // converts case or regex - CommandManager::restoreCmdByArgv(cmd, argv, ' ', '"', '"', '\\'); + StringUtils::restoreCmdFromArgv(cmd, argv, ' ', '"', '"', '\\'); } return false; } // hasTypo @@ -4104,101 +3825,6 @@ void CommandManager::onStartSelection() 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 -// ======================================================================== - -std::shared_ptr CommandManager::Context::peer() -{ - if (m_peer.expired()) - throw std::logic_error("Peer is expired"); - - auto peer = m_peer.lock(); - if (!peer) - throw std::logic_error("Peer is invalid"); - - return peer; -} // peer -//----------------------------------------------------------------------------- - -std::shared_ptr CommandManager::Context::peerMaybeNull() -{ - // if (m_peer.expired()) - // throw std::logic_error("Peer is expired"); - - auto peer = m_peer.lock(); - return peer; -} // peerMaybeNull -//----------------------------------------------------------------------------- - -std::shared_ptr CommandManager::Context::actingPeer() -{ - if (m_target_peer.expired()) - throw std::logic_error("Target peer is expired"); - - auto acting_peer = m_target_peer.lock(); - if (!acting_peer) - throw std::logic_error("Target peer is invalid"); - - return acting_peer; -} // actingPeer -//----------------------------------------------------------------------------- - -std::shared_ptr CommandManager::Context::actingPeerMaybeNull() -{ - // if (m_target_peer.expired()) - // throw std::logic_error("Target peer is expired"); - - auto acting_peer = m_target_peer.lock(); - return acting_peer; -} // actingPeerMaybeNull -//----------------------------------------------------------------------------- - -std::shared_ptr CommandManager::Context::command() -{ - if (m_command.expired()) - throw std::logic_error("Command is expired"); - - auto command = m_command.lock(); - if (!command) - throw std::logic_error("Command is invalid"); - - return command; -} // command -//----------------------------------------------------------------------------- - -void CommandManager::Context::say(const std::string& s) -{ - if (m_peer.expired()) - throw std::logic_error("Context::say: Peer has expired"); - - auto peer = m_peer.lock(); - Comm::sendStringToPeer(peer, s); -} // say -//----------------------------------------------------------------------------- - -void CommandManager::Command::changePermissions(int permissions, - int mode_scope, int state_scope) -{ - // Handling players who are allowed to run for anyone in any case - m_permissions = permissions | UU_OTHERS_COMMANDS; - m_mode_scope = mode_scope; - m_state_scope = state_scope; -} // changePermissions -// ======================================================================== std::string CommandManager::getAddonPreferredType() const { @@ -4269,6 +3895,12 @@ void CommandManager::shift(std::string& cmd, std::vector& argv, m_user_last_correct_argument[username].first -= count; - CommandManager::restoreCmdByArgv(cmd, argv, ' ', '"', '"', '\\'); + StringUtils::restoreCmdFromArgv(cmd, argv, ' ', '"', '"', '\\'); } // shift +//----------------------------------------------------------------------------- + +std::function CommandManager::getDefaultAction() +{ + return std::bind(&CommandManager::special, this, std::placeholders::_1); +} // getDefaultAction //----------------------------------------------------------------------------- \ No newline at end of file diff --git a/src/network/protocols/command_manager.hpp b/src/network/protocols/command_manager.hpp index 7504d403278..410204ab530 100644 --- a/src/network/protocols/command_manager.hpp +++ b/src/network/protocols/command_manager.hpp @@ -40,7 +40,11 @@ #include "network/protocols/command_voting.hpp" #include "network/protocols/command_permissions.hpp" -#include "utils/enum_extended_reader.hpp" +#include "utils/command_manager/auth_resource.hpp" +#include "utils/command_manager/command.hpp" +#include "utils/command_manager/context.hpp" +#include "utils/command_manager/file_resource.hpp" +#include "utils/command_manager/text_resource.hpp" #include "utils/lobby_context.hpp" #include "utils/set_typo_fixer.hpp" #include "utils/team_utils.hpp" @@ -55,166 +59,12 @@ class STKPeer; class CommandManager: public LobbyContextComponent { -public: - enum ModeScope: int - { - MS_DEFAULT = 1, - MS_SOCCER_TOURNAMENT = 2 - // add more powers of two if needed - }; - - enum StateScope: int - { - SS_LOBBY = 1, - SS_INGAME = 2, - SS_ALWAYS = SS_LOBBY | SS_INGAME - }; - private: - struct FileResource - { - std::string m_file_name; - uint64_t m_interval; - uint64_t m_last_invoked; - std::string m_contents; - - FileResource(std::string file_name = "", uint64_t interval = 0); - - void read(); - - std::string get(); - }; - - struct AuthResource - { - std::string m_secret; - std::string m_server; - std::string m_link_format; - - AuthResource(std::string secret = "", std::string server = "", - std::string link_format = ""); - - std::string get(const std::string& username, int online_id) const; - }; - - static EnumExtendedReader permission_reader; - static EnumExtendedReader mode_scope_reader; - static EnumExtendedReader state_scope_reader; - std::deque>& get_queue(int x) const; template void add_to_queue(int x, int mask, bool to_front, std::string& s) const; - struct Command; - - struct Context - { - ServerLobby* m_lobby; - - Event* m_event; - - std::weak_ptr m_peer; - - std::weak_ptr m_target_peer; - - std::vector m_argv; - - std::string m_cmd; - - std::weak_ptr m_command; - - int m_user_permissions; - - int m_acting_user_permissions; - - bool m_voting; - - Context(ServerLobby* lobby, Event* event, std::shared_ptr peer): - m_lobby(lobby), - m_event(event), m_peer(peer), m_target_peer(peer), m_argv(), - m_cmd(""), m_user_permissions(0), m_acting_user_permissions(0), m_voting(false) {} - - Context(ServerLobby* lobby, Event* event, std::shared_ptr peer, - std::vector& argv, std::string& cmd, - int user_permissions, int acting_user_permissions, bool voting): - m_lobby(lobby), - m_event(event), m_peer(peer), m_target_peer(peer), m_argv(argv), - m_cmd(cmd), m_user_permissions(user_permissions), - m_acting_user_permissions(acting_user_permissions), m_voting(voting) {} - - std::shared_ptr peer(); - std::shared_ptr peerMaybeNull(); - std::shared_ptr actingPeer(); - std::shared_ptr actingPeerMaybeNull(); - std::shared_ptr command(); - - void say(const std::string& s); - }; - - struct CommandDescription - { - std::string m_usage; - std::string m_permissions; - std::string m_description; - CommandDescription(std::string usage = "", std::string permissions = "", - std::string description = ""): m_usage(usage), - m_permissions(permissions), m_description(description) {} - - std::string getUsage() const { return "Usage: " + m_usage; } - - std::string getHelp() const - { - return "Usage: " + m_usage - + "\nAvailable to: " + m_permissions - + "\n" + m_description; - } - }; - - struct Command - { - std::string m_name; - - std::string m_prefix_name; - - void (CommandManager::*m_action)(Context& context); - - int m_permissions; - - int m_mode_scope; - - int m_state_scope; - - bool m_omit_name; - - CommandDescription m_description; - - std::weak_ptr m_parent; - - std::map> m_name_to_subcommand; - - std::vector> m_subcommands; - - 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: std::vector> m_all_commands; @@ -225,12 +75,6 @@ class CommandManager: public LobbyContextComponent std::multiset m_users; - std::map m_text_response; - - std::map m_file_resources; - - std::map m_auth_resources; - std::map m_votables; std::queue m_triggered_votables; @@ -275,14 +119,10 @@ class CommandManager: public LobbyContextComponent void vote(Context& context, std::string category, std::string value); void update(); - void error(Context& context, bool is_error = false); void execute(std::shared_ptr command, Context& context); void process_help(Context& context); - void process_text(Context& context); - void process_file(Context& context); - void process_auth(Context& context); void process_commands(Context& context); void process_replay(Context& context); void process_start(Context& context); @@ -384,24 +224,10 @@ class CommandManager: public LobbyContextComponent void handleCommand(Event* event, std::shared_ptr peer); - template - void addTextResponse(std::string key, T&& value) - { m_text_response[key] = value; } - - void addFileResource(std::string key, std::string file, uint64_t interval) - { m_file_resources[key] = FileResource(file, interval); } - - void addAuthResource(std::string key, std::string secret, std::string server, std::string link_format) - { m_auth_resources[key] = AuthResource(secret, server, link_format); } - void addUser(std::string& s); void deleteUser(std::string& s); - static void restoreCmdByArgv(std::string& cmd, - std::vector& argv, char c, char d, char e, char f, - int from = 0); - // A simple version of hasTypo for validating simple arguments. // Returns the opposite bool value. bool validate(Context& ctx, int idx, @@ -418,16 +244,14 @@ class CommandManager: public LobbyContextComponent 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); - // Helper functions, unrelated to CommandManager inner structure std::string getAddonPreferredType() const; void shift(std::string& cmd, std::vector& argv, const std::string& username, int count); + std::function getDefaultAction(); + }; #endif // COMMAND_MANAGER_HPP diff --git a/src/network/protocols/server_lobby.cpp b/src/network/protocols/server_lobby.cpp index 3f6bf333e24..ff36ea5cd44 100644 --- a/src/network/protocols/server_lobby.cpp +++ b/src/network/protocols/server_lobby.cpp @@ -4501,8 +4501,8 @@ int ServerLobby::getCurrentStateScope() || state > RESULT_DISPLAY) return 0; if (state == WAITING_FOR_START_GAME) - return CommandManager::StateScope::SS_LOBBY; - return CommandManager::StateScope::SS_INGAME; + return StateScope::SS_LOBBY; + return StateScope::SS_INGAME; } // getCurrentStateScope //----------------------------------------------------------------------------- diff --git a/src/utils/command_manager/auth_resource.cpp b/src/utils/command_manager/auth_resource.cpp new file mode 100644 index 00000000000..fe151f3ccb6 --- /dev/null +++ b/src/utils/command_manager/auth_resource.cpp @@ -0,0 +1,73 @@ +// +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2025 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/command_manager/auth_resource.hpp" +#include "network/crypto.hpp" +#include "utils/string_utils.hpp" +#include "utils/time.hpp" +#include "network/stk_peer.hpp" +#include "network/network_player_profile.hpp" + +void AuthResource::fromXmlNode(const XMLNode* node) +{ + Command::fromXmlNode(node); + node->get("secret", &m_secret); + node->get("server", &m_server); + node->get("link-format", &m_link_format); +} // AuthResource::AuthResource +//----------------------------------------------------------------------------- + +std::string AuthResource::get(const std::string& username, int online_id) const +{ +#ifdef ENABLE_CRYPTO_OPENSSL + std::string header = "{\"alg\":\"HS256\",\"typ\":\"JWT\"}"; + uint64_t timestamp = StkTime::getTimeSinceEpoch(); + std::string payload = "{\"sub\":\"" + username + "/" + std::to_string(online_id) + "\","; + payload += "\"iat\":\"" + std::to_string(timestamp) + "\","; + payload += "\"iss\":\"" + m_server + "\"}"; + header = Crypto::base64url(StringUtils::toUInt8Vector(header)); + payload = Crypto::base64url(StringUtils::toUInt8Vector(payload)); + std::string message = header + "." + payload; + std::string signature = Crypto::base64url(Crypto::hmac_sha256_array(m_secret, message)); + std::string token = message + "." + signature; + std::string response = StringUtils::insertValues(m_link_format, token.c_str()); + return response; +#else + return "This command is currently only supported for OpenSSL"; +#endif +} // AuthResource::get +//----------------------------------------------------------------------------- + +void AuthResource::execute(Context& context) +{ + std::string response; + auto acting_peer = context.actingPeer(); + auto command = context.command(); + + auto profile = acting_peer->getMainProfile(); + std::string username = StringUtils::wideToUtf8(profile->getName()); + int online_id = profile->getOnlineId(); + if (online_id == 0) + response = "Error: you need to join with an " + "online account to use auth methods"; + else + response = get(username, online_id); + + context.say(response); +} // execute +//----------------------------------------------------------------------------- \ No newline at end of file diff --git a/src/utils/command_manager/auth_resource.hpp b/src/utils/command_manager/auth_resource.hpp new file mode 100644 index 00000000000..1c5c835cb56 --- /dev/null +++ b/src/utils/command_manager/auth_resource.hpp @@ -0,0 +1,42 @@ +// +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2025 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 AUTH_RESOURCE_HPP +#define AUTH_RESOURCE_HPP + +#include +#include "utils/command_manager/command.hpp" + +class AuthResource: public Command +{ +private: + std::string m_secret; + std::string m_server; + std::string m_link_format; + + std::string get(const std::string& username, int online_id) const; + +public: + bool needsFunction() const final { return false; } + + void fromXmlNode(const XMLNode* node) final; + + void execute(Context& context) final; +}; + +#endif // AUTH_RESOURCE_HPP \ No newline at end of file diff --git a/src/utils/command_manager/command.cpp b/src/utils/command_manager/command.cpp new file mode 100644 index 00000000000..521dc55c457 --- /dev/null +++ b/src/utils/command_manager/command.cpp @@ -0,0 +1,270 @@ +// +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2025 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/command_manager/command.hpp" +#include "utils/string_utils.hpp" +#include "network/server_config.hpp" +#include "utils/enum_extended_reader.hpp" +#include "utils/log.hpp" + +#include "utils/command_manager/auth_resource.hpp" +#include "utils/command_manager/file_resource.hpp" +#include "utils/command_manager/map_file_resource.hpp" +#include "utils/command_manager/text_resource.hpp" + +#include + +namespace +{ + static EnumExtendedReader mode_scope_reader({ + {"MS_DEFAULT", MS_DEFAULT}, + {"MS_SOCCER_TOURNAMENT", MS_SOCCER_TOURNAMENT} + }); + + static EnumExtendedReader state_scope_reader({ + {"SS_LOBBY", SS_LOBBY}, + {"SS_INGAME", SS_INGAME}, + {"SS_ALWAYS", SS_ALWAYS} + }); + + static EnumExtendedReader permission_reader({ + {"PE_NONE", PE_NONE}, + {"UU_SPECTATOR", UU_SPECTATOR}, + {"UU_USUAL", UU_USUAL}, + {"UU_CROWNED", UU_CROWNED}, + {"UU_SINGLE", UU_SINGLE}, + {"UU_HAMMER", UU_HAMMER}, + {"UU_MANIPULATOR", UU_MANIPULATOR}, + {"UU_CONSOLE", UU_CONSOLE}, + {"PE_SPECTATOR", PE_SPECTATOR}, + {"PE_USUAL", PE_USUAL}, + {"PE_CROWNED", PE_CROWNED}, + {"PE_SINGLE", PE_SINGLE}, + {"PE_HAMMER", PE_HAMMER}, + {"PE_MANIPULATOR", PE_MANIPULATOR}, + {"PE_CONSOLE", PE_CONSOLE}, + {"UU_OWN_COMMANDS", UU_OWN_COMMANDS}, + {"UU_OTHERS_COMMANDS", UU_OTHERS_COMMANDS}, + {"PE_ALLOW_ANYONE", PE_ALLOW_ANYONE}, + {"PE_VOTED_SPECTATOR", PE_VOTED_SPECTATOR}, + {"PE_VOTED_NORMAL", PE_VOTED_NORMAL}, + {"PE_VOTED", PE_VOTED}, + {"UP_CONSOLE", UP_CONSOLE}, + {"UP_MANIPULATOR", UP_MANIPULATOR}, + {"UP_HAMMER", UP_HAMMER}, + {"UP_SINGLE", UP_SINGLE}, + {"UP_CROWNED", UP_CROWNED}, + {"UP_NORMAL", UP_NORMAL}, + {"UP_EVERYONE", UP_EVERYONE} + }); + + // C++20 compile-time string to class conversion + + // template + // struct XmlNameToClass; + + // template<> struct XmlNameToClass<"command"> { using type = Command; }; + // template<> struct XmlNameToClass<"text-command"> { using type = TextResource; }; + // template<> struct XmlNameToClass<"file-command"> { using type = FileResource; }; + // template<> struct XmlNameToClass<"auth-command"> { using type = AuthResource; }; + +} // namespace +//============================================================================= + +CommandDescription::CommandDescription(std::string usage, + std::string permissions, + std::string description) + : m_usage(std::move(usage)) + , m_permissions(std::move(permissions)) + , m_description(std::move(description)) +{} +//----------------------------------------------------------------------------- + +std::string CommandDescription::getUsage() const +{ + return StringUtils::insertValues("Usage: %s", m_usage.c_str()); +} // getUsage +//----------------------------------------------------------------------------- + +std::string CommandDescription::getHelp() const +{ + return StringUtils::insertValues("Usage: %s\nAvailable to: %s\n%s", + m_usage.c_str(), + m_permissions.c_str(), + m_description.c_str() + ); +} // getHelp +//============================================================================= + +void Command::changePermissions(int permissions, + int mode_scope, int state_scope) +{ + // Handling players who are allowed to run for anyone in any case + m_permissions = permissions | UU_OTHERS_COMMANDS; + m_mode_scope = mode_scope; + m_state_scope = state_scope; +} // changePermissions +//----------------------------------------------------------------------------- + + +std::shared_ptr Command::unknownTypeFromXmlNode(const XMLNode* node) +{ + std::string node_name = node->getName(); + if (node_name == "external-commands-file") + return {}; + + std::shared_ptr res; + if (node_name == "command") + res = std::make_shared(); + else if (node_name == "text-command") + res = std::make_shared(); + else if (node_name == "auth-command") + res = std::make_shared(); + else if (node_name == "file-command") + res = std::make_shared(); + else if (node_name == "map-file-command") + res = std::make_shared(); + else + { + Log::error("Command", "Unknown node name %s, treating as normal command.", node_name.c_str()); + res = std::make_shared(); + } + + try + { + res->fromXmlNode(node); + } + catch (std::exception& ex) + { + Log::error("Command", "unknownTypeFromXmlNode: error while calling fromXmlNode: %s", ex.what()); + return {}; + } + + return res; +} + +void Command::fromXmlNode(const XMLNode* node) +{ + std::string name = ""; + 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 = ""; + + node->get("name", &name); + m_name = name; + + // 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); + bool ok; + if (!enabled.empty()) + { + std::vector enabled_split = StringUtils::split(enabled, ' '); + ok = false; + for (const std::string& s: enabled_split) + if (s == ServerConfig::m_server_uid) + ok = true; + } + else + { + std::vector disabled_split = StringUtils::split(disabled, ' '); + ok = true; + for (const std::string& s: disabled_split) + if (s == ServerConfig::m_server_uid) + ok = false; + } + + if (!ok) + { + throw std::logic_error(StringUtils::insertValues( + "The command %s is not loaded, as it was disabled in the config.", + name.c_str() + )); + } + + node->get("permissions", &permissions_s); + node->get("mode-scope", &mode_scope_s); + node->get("state-scope", &state_scope_s); + permissions = permission_reader.parse(permissions_s); + mode_scope = mode_scope_reader.parse(mode_scope_s); + state_scope = state_scope_reader.parse(state_scope_s); + changePermissions(permissions, mode_scope, state_scope); + + node->get("usage", &usage); + node->get("permissions-verbose", &permissions_str); + node->get("description", &description); + m_description = CommandDescription(usage, permissions_str, description); + + node->get("aliases", &aliases); + m_aliases = StringUtils::split(aliases, ' '); + + node->get("omit-name", &omit_name); + m_omit_name = omit_name; + + // m_action is set in CommandManager, as it knows better about implementation. + // CM::special is also set from there. +} // fromXmlNode +//----------------------------------------------------------------------------- + +void Command::addChild(const std::shared_ptr& child) +{ + child->m_parent = shared_from_this(); + const std::string name = child->m_name; + + m_subcommands.push_back(child); + child->m_prefix_name = + (m_prefix_name.empty() ? "" : m_prefix_name + " ") + name; + + auto weak = std::weak_ptr(child); + m_name_to_subcommand[name] = weak; + + m_stf_subcommand_names.add(name); + for (const std::string& alias_name: child->getAliases()) + { + m_stf_subcommand_names.add(alias_name, name); + m_name_to_subcommand[alias_name] = m_name_to_subcommand[name]; + } +} // addChild +//----------------------------------------------------------------------------- + +void Command::execute(Context& context) +{ + m_action(context); +} // execute +//----------------------------------------------------------------------------- + +std::shared_ptr Command::findChild(const std::string& name) const +{ + const auto& it = m_name_to_subcommand.find(name); + if (it == m_name_to_subcommand.end()) + return {}; + + return it->second.lock(); +} // findChild +//----------------------------------------------------------------------------- \ No newline at end of file diff --git a/src/utils/command_manager/command.hpp b/src/utils/command_manager/command.hpp new file mode 100644 index 00000000000..cec4551c896 --- /dev/null +++ b/src/utils/command_manager/command.hpp @@ -0,0 +1,128 @@ +// +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2025 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 COMMAND_HPP +#define COMMAND_HPP + +#include +#include +#include +#include +#include + +#include "network/protocols/command_permissions.hpp" +#include "utils/set_typo_fixer.hpp" +#include "io/xml_node.hpp" +#include "utils/command_manager/context.hpp" + +struct Context; + +enum ModeScope: int +{ + MS_DEFAULT = 1, + MS_SOCCER_TOURNAMENT = 2 + // add more powers of two if needed +}; + +enum StateScope: int +{ + SS_LOBBY = 1, + SS_INGAME = 2, + SS_ALWAYS = SS_LOBBY | SS_INGAME +}; + +struct CommandDescription +{ + std::string m_usage; + std::string m_permissions; + std::string m_description; + CommandDescription(std::string usage = "", + std::string permissions = "", + std::string description = ""); + + std::string getUsage() const; + + std::string getHelp() const; +}; + +struct Command: public std::enable_shared_from_this +{ +private: + std::string m_name; + std::string m_prefix_name; + std::function m_action = nullptr; + int m_permissions = UP_EVERYONE; + int m_mode_scope = MS_DEFAULT; + int m_state_scope = SS_ALWAYS; + bool m_omit_name; + std::vector m_aliases; + + CommandDescription m_description; + std::weak_ptr m_parent; + std::map> m_name_to_subcommand; + std::vector> m_subcommands; + SetTypoFixer m_stf_subcommand_names; + +protected: + virtual void fromXmlNode(const XMLNode* node); + +public: + virtual bool needsFunction() const { return true; } + + Command() {} + + // Command(std::string name, + // std::function f, + // int permissions = UP_EVERYONE, + // int mode_scope = MS_DEFAULT, int state_scope = SS_ALWAYS); + + static std::shared_ptr unknownTypeFromXmlNode(const XMLNode* node); + + void setFunction(std::function f) + { m_action = std::move(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; } + std::string getShortName() const { return m_name; } + int getPermissions() const { return m_permissions; } + int getModeScope() const { return m_mode_scope; } + int getStateScope() const { return m_state_scope; } + bool omittedName() const { return m_omit_name; } + bool hasFunction() const { return m_action != nullptr; } + + const SetTypoFixer& subcommandNamesTypoFixer() const + { return m_stf_subcommand_names; } + + std::shared_ptr findChild(const std::string& name) const; + + const std::vector>& getSubcommands() const + { return m_subcommands; } + + void addChild(const std::shared_ptr& child); + + const std::vector& getAliases() const { return m_aliases; } + + virtual void execute(Context& context); +}; + +#endif // COMMAND_HPP \ No newline at end of file diff --git a/src/utils/command_manager/context.cpp b/src/utils/command_manager/context.cpp new file mode 100644 index 00000000000..c9949ec950a --- /dev/null +++ b/src/utils/command_manager/context.cpp @@ -0,0 +1,121 @@ +// +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2025 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/command_manager/context.hpp" +#include "utils/communication.hpp" +#include "utils/command_manager/command.hpp" +#include "utils/string_utils.hpp" +#include "utils/log.hpp" + +std::shared_ptr Context::peer() +{ + if (m_peer.expired()) + throw std::logic_error("Peer is expired"); + + auto peer = m_peer.lock(); + if (!peer) + throw std::logic_error("Peer is invalid"); + + return peer; +} // peer +//----------------------------------------------------------------------------- + +std::shared_ptr Context::peerMaybeNull() +{ + // if (m_peer.expired()) + // throw std::logic_error("Peer is expired"); + + auto peer = m_peer.lock(); + return peer; +} // peerMaybeNull +//----------------------------------------------------------------------------- + +std::shared_ptr Context::actingPeer() +{ + if (m_target_peer.expired()) + throw std::logic_error("Target peer is expired"); + + auto acting_peer = m_target_peer.lock(); + if (!acting_peer) + throw std::logic_error("Target peer is invalid"); + + return acting_peer; +} // actingPeer +//----------------------------------------------------------------------------- + +std::shared_ptr Context::actingPeerMaybeNull() +{ + // if (m_target_peer.expired()) + // throw std::logic_error("Target peer is expired"); + + auto acting_peer = m_target_peer.lock(); + return acting_peer; +} // actingPeerMaybeNull +//----------------------------------------------------------------------------- + +std::shared_ptr Context::command() +{ + if (m_command.expired()) + throw std::logic_error("Command is expired"); + + auto command = m_command.lock(); + if (!command) + throw std::logic_error("Command is invalid"); + + return command; +} // command +//----------------------------------------------------------------------------- + +void Context::say(const std::string& s) +{ + if (m_peer.expired()) + throw std::logic_error("Context::say: Peer has expired"); + + auto peer = m_peer.lock(); + Comm::sendStringToPeer(peer, s); +} // say +//----------------------------------------------------------------------------- + +void Context::error(bool is_error) +{ + std::string msg; + if (is_error) + Log::error("CMContext", "An error occurred while invoking %s", m_cmd.c_str()); + + auto command = m_command.lock(); + auto peer = m_peer.lock(); + if (!command) { + Log::error("CMContext", "CM::error: cannot load command"); + return; + } + if (!peer) { + Log::error("CMContext", "CM::error: cannot load peer to send error"); + return; + } + msg = command->getUsage(); + if (msg.empty()) + msg = StringUtils::insertValues("An error occurred " + "while invoking command \"%s\".", + command->getFullName().c_str()); + + if (is_error) + msg += "\n/!\\ Please report this error to the server owner"; + + say(msg); +} // error +//----------------------------------------------------------------------------- \ No newline at end of file diff --git a/src/utils/command_manager/context.hpp b/src/utils/command_manager/context.hpp new file mode 100644 index 00000000000..bad25ca5784 --- /dev/null +++ b/src/utils/command_manager/context.hpp @@ -0,0 +1,76 @@ +// +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2025 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 CONTEXT_HPP +#define CONTEXT_HPP + +#include +#include +#include +#include "network/event.hpp" + +struct Command; +class ServerLobby; + +struct Context +{ + ServerLobby* m_lobby; + + Event* m_event; + + std::weak_ptr m_peer; + + std::weak_ptr m_target_peer; + + std::vector m_argv; + + std::string m_cmd; + + std::weak_ptr m_command; + + int m_user_permissions; + + int m_acting_user_permissions; + + bool m_voting; + + Context(ServerLobby* lobby, Event* event, std::shared_ptr peer): + m_lobby(lobby), + m_event(event), m_peer(peer), m_target_peer(peer), m_argv(), + m_cmd(""), m_user_permissions(0), m_acting_user_permissions(0), m_voting(false) {} + + Context(ServerLobby* lobby, Event* event, std::shared_ptr peer, + std::vector& argv, std::string& cmd, + int user_permissions, int acting_user_permissions, bool voting): + m_lobby(lobby), + m_event(event), m_peer(peer), m_target_peer(peer), m_argv(argv), + m_cmd(cmd), m_user_permissions(user_permissions), + m_acting_user_permissions(acting_user_permissions), m_voting(voting) {} + + std::shared_ptr peer(); + std::shared_ptr peerMaybeNull(); + std::shared_ptr actingPeer(); + std::shared_ptr actingPeerMaybeNull(); + std::shared_ptr command(); + + void say(const std::string& s); + + void error(bool is_error = false); +}; + +#endif // CONTEXT_HPP \ No newline at end of file diff --git a/src/utils/command_manager/file_resource.cpp b/src/utils/command_manager/file_resource.cpp new file mode 100644 index 00000000000..96adcdbfa17 --- /dev/null +++ b/src/utils/command_manager/file_resource.cpp @@ -0,0 +1,84 @@ +// +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2025 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/command_manager/file_resource.hpp" + +#include +#include "utils/string_utils.hpp" +#include "utils/file_utils.hpp" +#include "utils/time.hpp" + + +void FileResource::fromXmlNode(const XMLNode* node) +{ + Command::fromXmlNode(node); + node->get("file", &m_file_name); + node->get("interval", &m_interval); + + m_contents = ""; + m_last_invoked = 0; +} // FileResource +//----------------------------------------------------------------------------- + +void FileResource::read() +{ + if (m_file_name.empty()) // in case it is not properly initialized + return; + + // 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; + + if (message.is_open()) + { + for (std::string line; std::getline(message, line); ) + { + answer += line; + answer.push_back('\n'); + } + if (!answer.empty()) + answer.pop_back(); + } + + m_last_invoked = StkTime::getMonoTimeMs(); + + // Don't do anything if the file is the same + if (m_contents != answer) + { + m_contents = answer; + onContentChange(); + } +} // read +//----------------------------------------------------------------------------- + +void FileResource::tryUpdate() +{ + uint64_t current_time = StkTime::getMonoTimeMs(); + if (m_interval != 0 && current_time >= m_interval + m_last_invoked) + read(); +} // tryUpdate +//----------------------------------------------------------------------------- + +void FileResource::execute(Context& context) +{ + tryUpdate(); + + context.say(m_contents); +} // execute +//----------------------------------------------------------------------------- \ No newline at end of file diff --git a/src/utils/command_manager/file_resource.hpp b/src/utils/command_manager/file_resource.hpp new file mode 100644 index 00000000000..c9badea9716 --- /dev/null +++ b/src/utils/command_manager/file_resource.hpp @@ -0,0 +1,52 @@ +// +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2025 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 FILE_RESOURCE_HPP +#define FILE_RESOURCE_HPP + +#include "utils/types.hpp" +#include "utils/command_manager/command.hpp" +#include + +struct FileResource: public Command +{ +private: + std::string m_file_name; + uint64_t m_interval; + mutable uint64_t m_last_invoked; + + void read(); + +protected: + std::string m_contents; + + void tryUpdate(); + + virtual void onContentChange() {} + +public: + bool needsFunction() const final { return false; } + + virtual void fromXmlNode(const XMLNode* node) override; + + virtual void execute(Context& context) override; +}; + + +#endif // FILE_RESOURCE_HPP \ No newline at end of file diff --git a/src/utils/command_manager/map_file_resource.cpp b/src/utils/command_manager/map_file_resource.cpp new file mode 100644 index 00000000000..9fc5f1eccd8 --- /dev/null +++ b/src/utils/command_manager/map_file_resource.cpp @@ -0,0 +1,183 @@ +// +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2025 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/command_manager/map_file_resource.hpp" + +#include "utils/string_utils.hpp" + +namespace +{ + int readChar(const XMLNode* node, std::string&& name, char* value) + { + std::string temp(1, *value); + auto res = node->get(std::move(name), &temp); + temp = StringUtils::wideToUtf8(StringUtils::xmlDecode(temp)); + if (res == 0 || temp.empty()) + return 0; + + *value = temp[0]; + return 1; + } +} // namespace +//----------------------------------------------------------------------------- + +void MapFileResource::fromXmlNode(const XMLNode* node) +{ + FileResource::fromXmlNode(node); + node->get("print-format", &m_print_format); + readChar(node, "line-delimiter", &m_line_delimiter); + readChar(node, "field-delimiter", &m_field_delimiter); + + if (!readChar(node, "out-line-delimiter", &m_out_line_delimiter)) + m_out_line_delimiter = m_line_delimiter; + if (!readChar(node, "out-field-delimiter", &m_out_field_delimiter)) + m_out_field_delimiter = m_field_delimiter; + + readChar(node, "out-field-delimiter", &m_out_field_delimiter); + readChar(node, "left-quote", &m_left_quote); + readChar(node, "right-quote", &m_right_quote); + readChar(node, "escape", &m_escape); + node->get("index", &m_index); +} // MapFileResource +//----------------------------------------------------------------------------- + +void MapFileResource::onContentChange() +{ + auto rows = StringUtils::splitQuoted(m_contents, m_line_delimiter, m_left_quote, m_right_quote, m_escape); + m_rows.resize(rows.size()); + for (size_t i = 0; i < m_rows.size(); ++i) + { + m_rows[i] = std::make_shared(); + m_rows[i]->cells = StringUtils::splitQuoted(rows[i], m_field_delimiter, m_left_quote, m_right_quote, m_escape); + } + + m_fixer.clear(); + m_indexed.clear(); + if (m_index >= 0) + { + for (size_t i = 0; i < m_rows.size(); ++i) + { + auto& container = m_rows[i]; + if (m_index < (int)container->cells.size()) + { + const std::string key = container->cells[m_index]; + m_fixer.add(key); + m_indexed[key].push_back(container); + } + } + } +} // onContentChange +//----------------------------------------------------------------------------- + +void MapFileResource::execute(Context& context) +{ + tryUpdate(); + + auto& argv = context.m_argv; + auto acting_peer = context.actingPeer(); + auto peer = context.peer(); + + if (argv.size() < 2) + { + context.error(); + return; + } + + std::string msg = ""; + + // I'm not sure if we should search by username. + // After all, the search column might contain something else. + if (argv[1] == "search") + { + if (2 >= argv.size()) + { + context.error(); + return; + } + + std::string key = argv[2]; + // Add fixing typos later + auto it = m_indexed.find(key); + if (it == m_indexed.end()) + context.say(StringUtils::insertValues("Nothing found for %s", key.c_str())); + else + { + int sz = it->second.size(); + if (sz >= 2) + msg += StringUtils::insertValues("Found %s lines\n", sz); + for (auto& row: it->second) + { + if (row) + msg += StringUtils::quoteEscapeArray( + row->cells.begin(), row->cells.end(), m_out_field_delimiter, + m_left_quote, m_right_quote, m_escape); + + msg += "\n"; + } + msg.pop_back(); + } + } + else + { + int from, to; + bool has_from = (1 < argv.size() && StringUtils::parseString(argv[1], &from) && from != 0); + bool has_to = (2 < argv.size() && StringUtils::parseString(argv[2], &to) && to != 0); + if (!has_from || (!has_to && 2 < argv.size())) + { + context.error(); + return; + } + if (!has_to) + to = (from > 0 ? 1 : -1); + + if (from >= to) + std::swap(from, to); + + if (from >= 0) // 3 8 -> [2, 8) + --from; + else if (to < 0) // -8 -3 -> [-8, -2) + ++to; + // else nothing: -4 5 -> [-4, 5) + + int n = m_indexed.size(); + int shift = (from / n) * n + (from < 0 ? -n : 0); + from -= shift; + to -= shift; + if (to - from > 0) + to = from + (to - from - 1) % n + 1; + + int sz = to - from; + if (sz >= 2) + msg += StringUtils::insertValues("Found %s lines\n", sz); + + for (int i = from; i < to; ++i) + { + int x = (i >= n ? i - n : i); + auto& row = m_rows[x]; + if (row) + msg += StringUtils::quoteEscapeArray( + row->cells.begin(), row->cells.end(), m_out_field_delimiter, + m_left_quote, m_right_quote, m_escape); + + msg += "\n"; + } + msg.pop_back(); + } + context.say(msg); +} // execute +//----------------------------------------------------------------------------- \ No newline at end of file diff --git a/src/utils/command_manager/map_file_resource.hpp b/src/utils/command_manager/map_file_resource.hpp new file mode 100644 index 00000000000..e68088df710 --- /dev/null +++ b/src/utils/command_manager/map_file_resource.hpp @@ -0,0 +1,63 @@ +// +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2025 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 MAP_FILE_RESOURCE_HPP +#define MAP_FILE_RESOURCE_HPP + +#include "utils/types.hpp" +#include "utils/command_manager/file_resource.hpp" +#include + +struct MapFileResource: public FileResource +{ +private: + std::string m_print_format; + char m_line_delimiter = '\n'; // Line breaks should be checked on Windows + char m_field_delimiter = ' '; + char m_out_line_delimiter; + char m_out_field_delimiter; + char m_left_quote = '"'; + char m_right_quote = '"'; + char m_escape = '\\'; + + // Technically nothing prevents us from making several indices. + // Except the lack of practical need, of course. + int m_index = 0; + + struct Row + { + std::vector cells; + }; + + std::vector> m_rows; + std::map>> m_indexed; + + SetTypoFixer m_fixer; + +protected: + void onContentChange() override; + +public: + virtual void fromXmlNode(const XMLNode* node) override; + + virtual void execute(Context& context) override; +}; + + +#endif // MAP_FILE_RESOURCE_HPP \ No newline at end of file diff --git a/src/utils/command_manager/text_resource.cpp b/src/utils/command_manager/text_resource.cpp new file mode 100644 index 00000000000..80a28e9e510 --- /dev/null +++ b/src/utils/command_manager/text_resource.cpp @@ -0,0 +1,32 @@ +// +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2025 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/command_manager/text_resource.hpp" + +void TextResource::fromXmlNode(const XMLNode* node) +{ + Command::fromXmlNode(node); + node->get("text", &m_text); +} // FileResource +//----------------------------------------------------------------------------- + +void TextResource::execute(Context& context) +{ + context.say(m_text); +} // execute +//----------------------------------------------------------------------------- \ No newline at end of file diff --git a/src/utils/command_manager/text_resource.hpp b/src/utils/command_manager/text_resource.hpp new file mode 100644 index 00000000000..c5695020d41 --- /dev/null +++ b/src/utils/command_manager/text_resource.hpp @@ -0,0 +1,51 @@ +// +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2025 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 TEXT_RESOURCE_HPP +#define TEXT_RESOURCE_HPP + +#include "utils/types.hpp" +#include "utils/command_manager/command.hpp" +#include + +class TextResource: public Command +{ +private: + std::string m_text; + +public: + bool needsFunction() const final { return false; } + + TextResource(): m_text("") {} + + template + TextResource(const T& str): m_text(str) {} + + template + TextResource(T&& str): m_text(std::move(str)) {} + + void setText(const std::string& str) { m_text = str; } + + void fromXmlNode(const XMLNode* node) final; + + void execute(Context& context) final; +}; + + +#endif // TEXT_RESOURCE_HPP \ No newline at end of file diff --git a/src/utils/lobby_queues.hpp b/src/utils/lobby_queues.hpp index 7b88d2ba05f..dc94fb34ac1 100644 --- a/src/utils/lobby_queues.hpp +++ b/src/utils/lobby_queues.hpp @@ -49,11 +49,8 @@ class LobbyQueues: public LobbyContextComponent private: Queue m_onetime_tracks_queue; - Queue m_cyclic_tracks_queue; - Queue m_onetime_karts_queue; - Queue m_cyclic_karts_queue; // Temporary reference getters diff --git a/src/utils/set_typo_fixer.cpp b/src/utils/set_typo_fixer.cpp index 7f859e155f4..8d57966ba51 100644 --- a/src/utils/set_typo_fixer.cpp +++ b/src/utils/set_typo_fixer.cpp @@ -41,6 +41,13 @@ void SetTypoFixer::remove(const std::string& key) } // remove //----------------------------------------------------------------------------- +void SetTypoFixer::clear() +{ + m_set.clear(); + m_map.clear(); +} // clear +//----------------------------------------------------------------------------- + std::vector> SetTypoFixer::getClosest( const std::string& query, int count, bool case_sensitive) const { diff --git a/src/utils/set_typo_fixer.hpp b/src/utils/set_typo_fixer.hpp index 4105ce04512..8a2d14469ec 100644 --- a/src/utils/set_typo_fixer.hpp +++ b/src/utils/set_typo_fixer.hpp @@ -45,6 +45,7 @@ class SetTypoFixer void add(const std::string& key); void add(const std::string& key, const std::string& value); void remove(const std::string& key); + void clear(); std::vector> getClosest( const std::string& query, int count = 3, diff --git a/src/utils/string_utils.cpp b/src/utils/string_utils.cpp index af132c19aa6..175cb6182b1 100644 --- a/src/utils/string_utils.cpp +++ b/src/utils/string_utils.cpp @@ -254,7 +254,33 @@ namespace StringUtils return ans; } // quoteEscape + + + std::string quoteEscapeArray(const std::vector::const_iterator begin, + const std::vector::const_iterator end, + char c, char d, char e, char f) + { + std::string res = ""; + for (auto it = begin; it != end; ++it) + { + if (it != begin) { + res.push_back(c); + } + res += StringUtils::quoteEscape(*it, c, d, e, f); + } + return res; + } // quoteEscapeArray //------------------------------------------------------------------------- + + void restoreCmdFromArgv(std::string& cmd, + const std::vector& argv, + char c, char d, char e, char f, + int from) + { + cmd = quoteEscapeArray(argv.begin() + from, argv.end(), c, d, e, f); + } // restoreCmdFromArgv + //------------------------------------------------------------------------- + /** Splits a string into substrings separated by a certain character, and * returns a std::vector of all those substring. E.g.: * split("a b=c d=e",' ') --> ["a", "b=c", "d=e"] diff --git a/src/utils/string_utils.hpp b/src/utils/string_utils.hpp index d83586468e1..8d5eb791629 100644 --- a/src/utils/string_utils.hpp +++ b/src/utils/string_utils.hpp @@ -58,6 +58,13 @@ namespace StringUtils char d, char e, char f); std::string quoteEscape(const std::string& s, char c, char d, char e, char f); + std::string quoteEscapeArray(const std::vector::const_iterator begin, + const std::vector::const_iterator end, + char c, char d, char e, char f); + void restoreCmdFromArgv(std::string& cmd, + const std::vector& argv, + char c, char d, char e, char f, + int from = 0); std::vector split(const std::string& s, char c, bool keepSplitChar=false); std::vector split(const std::u32string& s, char32_t c,