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