h1-mod/src/client/component/command.cpp

827 lines
18 KiB
C++
Raw Normal View History

2022-02-03 14:05:24 -05:00
#include <std_include.hpp>
#include "loader/component_loader.hpp"
2022-03-12 18:56:15 -05:00
2022-02-03 14:05:24 -05:00
#include "command.hpp"
#include "console.hpp"
#include "game_console.hpp"
2022-03-13 10:31:57 -04:00
#include "fastfiles.hpp"
2022-03-13 16:01:29 -04:00
#include "scheduler.hpp"
2022-05-24 15:55:53 -04:00
#include "logfile.hpp"
2022-02-03 14:05:24 -05:00
#include "game/game.hpp"
2022-03-12 18:56:15 -05:00
#include "game/dvars.hpp"
2022-02-03 14:05:24 -05:00
2022-03-13 16:01:29 -04:00
#include "game/scripting/execution.hpp"
2022-02-03 14:05:24 -05:00
#include <utils/hook.hpp>
#include <utils/string.hpp>
#include <utils/memory.hpp>
2022-03-12 18:56:15 -05:00
#include <utils/io.hpp>
2022-02-03 14:05:24 -05:00
namespace command
{
namespace
{
utils::hook::detour client_command_hook;
2022-05-18 13:00:16 -04:00
utils::hook::detour parse_commandline_hook;
2022-02-03 14:05:24 -05:00
std::unordered_map<std::string, std::function<void(params&)>> handlers;
std::unordered_map<std::string, std::function<void(int, params_sv&)>> handlers_sv;
void main_handler()
{
params params = {};
const auto command = utils::string::to_lower(params[0]);
if (handlers.find(command) != handlers.end())
{
handlers[command](params);
}
}
2022-03-12 21:07:17 -05:00
void client_command(const int client_num)
2022-02-03 14:05:24 -05:00
{
2022-05-24 15:55:53 -04:00
if (!logfile::client_command_stub(client_num))
{
return;
}
2022-02-03 14:05:24 -05:00
params_sv params = {};
const auto command = utils::string::to_lower(params[0]);
if (handlers_sv.find(command) != handlers_sv.end())
{
handlers_sv[command](client_num, params);
}
2022-03-12 21:07:17 -05:00
client_command_hook.invoke<void>(client_num);
2022-02-03 14:05:24 -05:00
}
// Shamelessly stolen from Quake3
// https://github.com/id-Software/Quake-III-Arena/blob/dbe4ddb10315479fc00086f08e25d968b4b43c49/code/qcommon/common.c#L364
void parse_command_line()
2022-02-03 14:05:24 -05:00
{
static auto parsed = false;
if (parsed)
2022-02-03 14:05:24 -05:00
{
return;
2022-02-03 14:05:24 -05:00
}
static std::string comand_line_buffer = GetCommandLineA();
auto* command_line = comand_line_buffer.data();
2022-05-18 09:39:04 -04:00
auto& com_num_console_lines = *reinterpret_cast<int*>(0x35634B8_b);
auto* com_console_lines = reinterpret_cast<char**>(0x35634C0_b);
auto inq = false;
com_console_lines[0] = command_line;
com_num_console_lines = 0;
2022-02-03 14:05:24 -05:00
while (*command_line)
2022-02-03 14:05:24 -05:00
{
if (*command_line == '"')
2022-02-03 14:05:24 -05:00
{
inq = !inq;
}
// look for a + separating character
// if commandLine came from a file, we might have real line seperators
if ((*command_line == '+' && !inq) || *command_line == '\n' || *command_line == '\r')
{
if (com_num_console_lines == 0x20) // MAX_CONSOLE_LINES
{
break;
}
com_console_lines[com_num_console_lines] = command_line + 1;
com_num_console_lines++;
*command_line = '\0';
}
command_line++;
}
parsed = true;
}
void parse_commandline_stub()
{
parse_command_line();
2022-05-18 13:00:16 -04:00
parse_commandline_hook.invoke<void>();
}
2022-02-26 15:36:29 -05:00
game::dvar_t* dvar_command_stub()
{
const params args;
if (args.size() <= 0)
{
return 0;
}
const auto dvar = game::Dvar_FindVar(args[0]);
if (dvar)
{
if (args.size() == 1)
{
2022-05-18 13:00:16 -04:00
const auto current = game::Dvar_ValueToString(dvar, true, dvar->current);
const auto reset = game::Dvar_ValueToString(dvar, true, dvar->reset);
2022-02-26 15:36:29 -05:00
2022-05-19 13:09:17 -04:00
console::info("\"%s\" is: \"%s\" default: \"%s\" hash: 0x%08lX type: %i\n",
args[0], current, reset, dvar->hash, dvar->type);
2022-02-26 15:36:29 -05:00
2022-03-12 18:56:15 -05:00
const auto dvar_info = dvars::dvar_get_description(args[0]);
console::info("%s\n", dvar_info.data());
2022-02-26 15:36:29 -05:00
console::info(" %s\n", dvars::dvar_get_domain(dvar->type, dvar->domain).data());
}
else
{
2022-05-18 13:00:16 -04:00
char command[0x1000] = {0};
game::Dvar_GetCombinedString(command, 1);
game::Dvar_SetCommand(dvar->hash, "", command);
2022-02-26 15:36:29 -05:00
}
return dvar;
}
return 0;
}
2022-03-13 16:01:29 -04:00
void client_println(int client_num, const std::string& text)
{
if (game::environment::is_sp())
{
game::CG_GameMessage(0, text.data());
}
else
{
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE,
utils::string::va("f \"%s\"", text.data()));
}
}
bool check_cheats(int client_num)
{
if (!game::Dvar_FindVar("sv_cheats")->current.enabled)
{
client_println(client_num, "Cheats are not enabled on this server");
return false;
}
return true;
}
void cmd_give_weapon(const int client_num, const std::vector<std::string>& params)
{
if (params.size() < 2)
{
client_println(client_num, "You did not specify a weapon name");
return;
}
try
{
2022-03-13 18:12:34 -04:00
const auto& arg = params[1];
const auto player = scripting::entity({static_cast<uint16_t>(client_num), 0});
2022-03-13 16:01:29 -04:00
auto ps = game::SV_GetPlayerstateForClientNum(client_num);
if (arg == "ammo")
{
const auto weapon = player.call("getcurrentweapon").as<std::string>();
player.call("givemaxammo", {weapon});
}
else if (arg == "allammo")
{
const auto weapons = player.call("getweaponslistall").as<scripting::array>();
for (auto i = 0; i < weapons.size(); i++)
{
player.call("givemaxammo", {weapons[i]});
}
}
else if (arg == "health")
{
if (params.size() > 2)
{
const auto amount = atoi(params[2].data());
const auto health = player.get("health").as<int>();
player.set("health", {health + amount});
}
else
{
const auto amount = SELECT_VALUE(
game::Dvar_FindVar("g_player_maxhealth")->current.integer,
atoi(game::Dvar_FindVar("scr_player_maxhealth")->current.string)
);
player.set("health", {amount});
}
}
else if (arg == "all")
{
const auto type = game::XAssetType::ASSET_TYPE_WEAPON;
fastfiles::enum_assets(type, [&player, type](const game::XAssetHeader header)
{
const auto asset = game::XAsset{type, header};
const auto asset_name = game::DB_GetXAssetName(&asset);
player.call("giveweapon", {asset_name});
}, true);
}
else
{
const auto wp = game::G_GetWeaponForName(arg.data());
if (wp)
{
if (game::G_GivePlayerWeapon(ps, wp, 0, 0, 0, 0, 0, 0))
{
game::G_InitializeAmmo(ps, wp, 0);
game::G_SelectWeapon(0, wp);
}
}
else
{
client_println(client_num, "Weapon does not exist");
}
}
}
catch (...)
{
}
}
void cmd_drop_weapon(int client_num)
{
try
{
2022-03-13 18:12:34 -04:00
const auto player = scripting::entity({static_cast<uint16_t>(client_num), 0});
2022-03-13 16:01:29 -04:00
const auto weapon = player.call("getcurrentweapon");
player.call("dropitem", {weapon});
}
catch (...)
{
}
}
void cmd_take_weapon(int client_num, const std::vector<std::string>& params)
{
if (params.size() < 2)
{
client_println(client_num, "You did not specify a weapon name");
return;
}
const auto& weapon = params[1];
try
{
2022-03-13 18:12:34 -04:00
const auto player = scripting::entity({static_cast<uint16_t>(client_num), 0});
2022-03-13 16:01:29 -04:00
if (weapon == "all"s)
{
player.call("takeallweapons");
}
else
{
player.call("takeweapon", {weapon});
}
}
catch (...)
{
}
}
void cmd_kill(int client_num)
{
scheduler::once([client_num]()
{
try
{
2022-03-13 18:12:34 -04:00
const auto player = scripting::entity({static_cast<uint16_t>(client_num), 0});
2022-03-13 16:01:29 -04:00
player.call(SELECT_VALUE("kill", "suicide"));
}
catch (...)
{
}
}, scheduler::pipeline::server);
}
void toggle_entity_flag(int client_num, int value, const std::string& name)
{
game::mp::g_entities[client_num].flags ^= value;
client_println(client_num, utils::string::va("%s %s",
name.data(),
game::mp::g_entities[client_num].flags & value
? "^2on"
: "^1off"));
}
void toggle_entity_flag(int value, const std::string& name)
{
game::sp::g_entities[0].flags ^= value;
client_println(0, utils::string::va("%s %s",
name.data(),
game::sp::g_entities[0].flags & value
? "^2on"
: "^1off"));
}
void toggle_client_flag(int client_num, int value, const std::string& name)
{
game::mp::g_entities[client_num].client->flags ^= value;
client_println(client_num, utils::string::va("%s %s",
name.data(),
game::mp::g_entities[client_num].client->flags & value
? "^2on"
: "^1off"));
}
void toggle_client_flag(int value, const std::string& name)
{
game::sp::g_entities[0].client->flags ^= value;
client_println(0, utils::string::va("%s %s",
name.data(),
game::sp::g_entities[0].client->flags & value
? "^2on"
: "^1off"));
}
}
2022-02-03 14:05:24 -05:00
void read_startup_variable(const std::string& dvar)
{
// parse the commandline if it's not parsed
parse_command_line();
2022-02-03 14:05:24 -05:00
2022-05-18 09:39:04 -04:00
auto& com_num_console_lines = *reinterpret_cast<int*>(0x35634B8_b);
auto* com_console_lines = reinterpret_cast<char**>(0x35634C0_b);
for (int i = 0; i < com_num_console_lines; i++)
{
2022-05-18 09:39:04 -04:00
game::Cmd_TokenizeString(com_console_lines[i]); // need to re-create this function
// only +set dvar value
if (game::Cmd_Argc() >= 3 && game::Cmd_Argv(0) == "set"s && game::Cmd_Argv(1) == dvar)
{
2022-02-26 15:36:29 -05:00
game::Dvar_SetCommand(game::generateHashValue(game::Cmd_Argv(1)), "", game::Cmd_Argv(2));
2022-02-03 14:05:24 -05:00
}
2022-05-18 09:39:04 -04:00
game::Cmd_EndTokenizeString(); // need to re-create this function
2022-02-03 14:05:24 -05:00
}
}
params::params()
: nesting_(game::cmd_args->nesting)
{
}
int params::size() const
{
return game::cmd_args->argc[this->nesting_];
}
const char* params::get(const int index) const
{
if (index >= this->size())
{
return "";
}
return game::cmd_args->argv[this->nesting_][index];
}
std::string params::join(const int index) const
{
std::string result = {};
for (auto i = index; i < this->size(); i++)
{
if (i > index) result.append(" ");
result.append(this->get(i));
}
return result;
}
2022-03-13 16:01:29 -04:00
std::vector<std::string> params::get_all() const
{
std::vector<std::string> params_;
for (auto i = 0; i < this->size(); i++)
{
params_.push_back(this->get(i));
}
return params_;
}
2022-02-03 14:05:24 -05:00
params_sv::params_sv()
: nesting_(game::sv_cmd_args->nesting)
{
}
int params_sv::size() const
{
return game::sv_cmd_args->argc[this->nesting_];
}
const char* params_sv::get(const int index) const
{
if (index >= this->size())
{
return "";
}
return game::sv_cmd_args->argv[this->nesting_][index];
}
std::string params_sv::join(const int index) const
{
std::string result = {};
for (auto i = index; i < this->size(); i++)
{
if (i > index) result.append(" ");
result.append(this->get(i));
}
return result;
}
2022-03-13 16:01:29 -04:00
std::vector<std::string> params_sv::get_all() const
{
std::vector<std::string> params_;
for (auto i = 0; i < this->size(); i++)
{
params_.push_back(this->get(i));
}
return params_;
}
2022-02-03 14:05:24 -05:00
void add_raw(const char* name, void (*callback)())
{
game::Cmd_AddCommandInternal(name, callback, utils::memory::get_allocator()->allocate<game::cmd_function_s>());
}
void add_test(const char* name, void (*callback)())
{
static game::cmd_function_s cmd_test;
return game::Cmd_AddCommandInternal(name, callback, &cmd_test);
}
2022-02-03 14:05:24 -05:00
void add(const char* name, const std::function<void(const params&)>& callback)
{
const auto command = utils::string::to_lower(name);
if (handlers.find(command) == handlers.end())
add_raw(name, main_handler);
handlers[command] = callback;
}
void add(const char* name, const std::function<void()>& callback)
{
add(name, [callback](const params&)
2022-03-12 21:07:17 -05:00
{
callback();
});
2022-02-03 14:05:24 -05:00
}
void add_sv(const char* name, std::function<void(int, const params_sv&)> callback)
{
// doing this so the sv command would show up in the console
add_raw(name, nullptr);
const auto command = utils::string::to_lower(name);
if (handlers_sv.find(command) == handlers_sv.end())
handlers_sv[command] = std::move(callback);
}
void execute(std::string command, const bool sync)
{
command += "\n";
if (sync)
{
game::Cmd_ExecuteSingleCommand(0, 0, command.data());
}
else
{
2022-05-18 07:35:39 -04:00
game::Cbuf_AddText(0, 0, command.data());
2022-02-03 14:05:24 -05:00
}
}
class component final : public component_interface
{
public:
void post_unpack() override
2022-02-03 14:05:24 -05:00
{
if (game::environment::is_sp())
2022-02-03 14:05:24 -05:00
{
add_commands_sp();
2022-02-03 14:05:24 -05:00
}
else
2022-02-03 14:05:24 -05:00
{
2022-05-21 19:52:00 -04:00
parse_commandline_hook.create(0x157D50_b, parse_commandline_stub);
2022-05-18 13:00:16 -04:00
utils::hook::jump(0x4E9F40_b, dvar_command_stub, true);
add_commands_mp();
2022-02-03 14:05:24 -05:00
}
2022-02-23 15:23:00 -05:00
add_commands_generic();
2022-02-03 14:05:24 -05:00
}
private:
static void add_commands_generic()
{
2022-02-26 16:09:23 -05:00
add("quit", game::Quit);
add("crash", []()
2022-02-23 15:23:00 -05:00
{
*reinterpret_cast<int*>(1) = 0;
});
2022-02-03 14:05:24 -05:00
add("commandDump", [](const params& argument)
2022-02-23 15:23:00 -05:00
{
console::info("================================ COMMAND DUMP =====================================\n");
game::cmd_function_s* cmd = (*game::cmd_functions);
std::string filename;
if (argument.size() == 2)
{
2022-02-23 15:23:00 -05:00
filename = "h1-mod/";
filename.append(argument[1]);
if (!filename.ends_with(".txt"))
{
2022-02-23 15:23:00 -05:00
filename.append(".txt");
}
2022-02-23 15:23:00 -05:00
}
int i = 0;
while (cmd)
{
if (cmd->name)
{
2022-02-23 15:23:00 -05:00
if (!filename.empty())
{
2022-02-23 15:23:00 -05:00
const auto line = std::format("{}\r\n", cmd->name);
utils::io::write_file(filename, line, i != 0);
}
2022-02-23 15:23:00 -05:00
console::info("%s\n", cmd->name);
i++;
}
2022-02-23 15:23:00 -05:00
cmd = cmd->next;
}
console::info("\n%i commands\n", i);
console::info("================================ END COMMAND DUMP =================================\n");
});
2022-02-17 00:11:35 -05:00
2022-03-13 08:45:20 -04:00
add("listassetpool", [](const params& params)
2022-02-23 15:23:00 -05:00
{
if (params.size() < 2)
{
2022-02-23 15:23:00 -05:00
console::info("listassetpool <poolnumber> [filter]: list all the assets in the specified pool\n");
for (auto i = 0; i < game::XAssetType::ASSET_TYPE_COUNT; i++)
{
2022-02-23 15:23:00 -05:00
console::info("%d %s\n", i, game::g_assetNames[i]);
}
}
else
{
const auto type = static_cast<game::XAssetType>(atoi(params.get(1)));
2022-02-23 15:23:00 -05:00
if (type < 0 || type >= game::XAssetType::ASSET_TYPE_COUNT)
{
console::error("Invalid pool passed must be between [%d, %d]\n", 0, game::XAssetType::ASSET_TYPE_COUNT - 1);
return;
}
2022-02-23 15:23:00 -05:00
console::info("Listing assets in pool %s\n", game::g_assetNames[type]);
const std::string filter = params.get(2);
2022-03-13 10:31:57 -04:00
fastfiles::enum_assets(type, [type, filter](const game::XAssetHeader header)
{
2022-02-23 15:23:00 -05:00
const auto asset = game::XAsset{type, header};
const auto* const asset_name = game::DB_GetXAssetName(&asset);
//const auto entry = game::DB_FindXAssetEntry(type, asset_name);
//TODO: display which zone the asset is from
2022-03-13 10:31:57 -04:00
if (!filter.empty() && !utils::string::match_compare(filter, asset_name, false))
{
return;
}
2022-02-23 15:23:00 -05:00
console::info("%s\n", asset_name);
}, true);
}
2022-03-13 08:45:20 -04:00
});
add("vstr", [](const params& params)
2022-02-23 15:23:00 -05:00
{
if (params.size() < 2)
{
2022-02-23 15:23:00 -05:00
console::info("vstr <variablename> : execute a variable command\n");
return;
}
2022-03-12 21:07:17 -05:00
const auto name = params.get(1);
const auto dvar = game::Dvar_FindVar(name);
2022-02-23 15:23:00 -05:00
if (dvar == nullptr)
{
2022-03-12 21:07:17 -05:00
console::info("%s doesn't exist\n", name);
2022-02-23 15:23:00 -05:00
return;
}
2022-02-23 15:23:00 -05:00
if (dvar->type != game::dvar_type::string
&& dvar->type != game::dvar_type::enumeration)
{
console::info("%s is not a string-based dvar\n", dvar->hash);
return;
}
2022-02-23 15:23:00 -05:00
execute(dvar->current.string);
2022-03-12 21:07:17 -05:00
});
2022-02-03 14:05:24 -05:00
}
static void add_commands_sp()
2022-02-03 14:05:24 -05:00
{
2022-05-24 14:43:19 -04:00
/*add("god", []()
2022-02-23 15:23:00 -05:00
{
if (!game::SV_Loaded())
{
2022-02-23 15:23:00 -05:00
return;
}
2022-02-03 14:05:24 -05:00
2022-03-13 16:01:29 -04:00
toggle_entity_flag(1, "godmode");
2022-02-23 15:23:00 -05:00
});
2022-02-03 14:05:24 -05:00
2022-02-11 22:21:53 -05:00
add("demigod", []()
2022-02-23 15:23:00 -05:00
{
if (!game::SV_Loaded())
{
2022-02-23 15:23:00 -05:00
return;
}
2022-02-03 14:05:24 -05:00
2022-03-13 16:01:29 -04:00
toggle_entity_flag(2, "demigod mode");
2022-02-23 15:23:00 -05:00
});
2022-02-03 14:05:24 -05:00
add("notarget", []()
2022-02-23 15:23:00 -05:00
{
if (!game::SV_Loaded())
{
2022-02-23 15:23:00 -05:00
return;
}
2022-02-03 14:05:24 -05:00
2022-03-13 16:01:29 -04:00
toggle_entity_flag(4, "notarget");
2022-02-23 15:23:00 -05:00
});
2022-02-03 14:05:24 -05:00
add("noclip", []()
2022-02-23 15:23:00 -05:00
{
if (!game::SV_Loaded())
{
2022-02-23 15:23:00 -05:00
return;
}
2022-02-03 14:05:24 -05:00
2022-03-13 16:01:29 -04:00
toggle_client_flag(1, "noclip");
2022-02-23 15:23:00 -05:00
});
2022-02-03 14:05:24 -05:00
add("ufo", []()
2022-02-23 15:23:00 -05:00
{
if (!game::SV_Loaded())
{
2022-02-23 15:23:00 -05:00
return;
}
2022-03-13 16:01:29 -04:00
toggle_client_flag(2, "ufo");
});
add("dropweapon", [](const params& params)
{
if (!game::SV_Loaded())
{
return;
}
cmd_drop_weapon(0);
});
add("take", [](const params& params)
{
if (!game::SV_Loaded())
{
return;
}
cmd_take_weapon(0, params.get_all());
});
add("kill", [](const params& params)
{
if (!game::SV_Loaded())
{
return;
}
cmd_kill(0);
2022-05-25 07:45:53 -04:00
});
2022-05-24 14:43:19 -04:00
add("give", [](const params& params)
{
if (!game::SV_Loaded())
{
return;
}
cmd_give_weapon(0, params.get_all());
2022-05-25 07:45:53 -04:00
});*/
}
2022-02-03 14:05:24 -05:00
static void add_commands_mp()
{
2022-05-18 09:39:04 -04:00
client_command_hook.create(0x4132E0_b, &client_command);
2022-02-03 14:05:24 -05:00
2022-05-25 07:45:53 -04:00
/*add_sv("god", [](const int client_num, const params_sv&)
2022-02-23 15:23:00 -05:00
{
2022-03-13 16:01:29 -04:00
if (!check_cheats(client_num))
{
2022-02-23 15:23:00 -05:00
return;
}
2022-03-13 16:01:29 -04:00
toggle_entity_flag(client_num, 1, "godmode");
2022-02-23 15:23:00 -05:00
});
add_sv("demigod", [](const int client_num, const params_sv&)
2022-02-23 15:23:00 -05:00
{
2022-03-13 16:01:29 -04:00
if (!check_cheats(client_num))
{
2022-02-23 15:23:00 -05:00
return;
}
2022-03-13 16:01:29 -04:00
toggle_entity_flag(client_num, 2, "demigod mode");
2022-02-23 15:23:00 -05:00
});
add_sv("notarget", [](const int client_num, const params_sv&)
2022-02-23 15:23:00 -05:00
{
2022-03-13 16:01:29 -04:00
if (!check_cheats(client_num))
{
2022-02-23 15:23:00 -05:00
return;
}
2022-03-13 16:01:29 -04:00
toggle_entity_flag(client_num, 4, "notarget");
2022-02-23 15:23:00 -05:00
});
add_sv("noclip", [](const int client_num, const params_sv&)
2022-02-23 15:23:00 -05:00
{
2022-03-13 16:01:29 -04:00
if (!check_cheats(client_num))
{
2022-02-23 15:23:00 -05:00
return;
}
2022-03-13 16:01:29 -04:00
toggle_client_flag(client_num, 1, "noclip");
2022-02-23 15:23:00 -05:00
});
add_sv("ufo", [](const int client_num, const params_sv&)
2022-02-23 15:23:00 -05:00
{
2022-03-13 16:01:29 -04:00
if (!check_cheats(client_num))
{
2022-02-23 15:23:00 -05:00
return;
}
2022-03-13 16:01:29 -04:00
toggle_client_flag(client_num, 2, "noclip");
2022-05-25 07:45:53 -04:00
});*/
2022-03-13 08:45:20 -04:00
add_sv("give", [](const int client_num, const params_sv& params)
2022-02-23 15:23:00 -05:00
{
2022-03-13 16:01:29 -04:00
if (!check_cheats(client_num))
{
2022-02-23 15:23:00 -05:00
return;
}
2022-03-13 16:01:29 -04:00
cmd_give_weapon(client_num, params.get_all());
});
add_sv("dropweapon", [](const int client_num, const params_sv& params)
{
if (!check_cheats(client_num))
2022-02-23 15:23:00 -05:00
{
return;
}
2022-03-13 16:01:29 -04:00
cmd_drop_weapon(client_num);
2022-02-23 15:23:00 -05:00
});
add_sv("take", [](const int client_num, const params_sv& params)
2022-02-23 15:23:00 -05:00
{
2022-03-13 16:01:29 -04:00
if (!check_cheats(client_num))
{
2022-02-23 15:23:00 -05:00
return;
}
2022-03-13 16:01:29 -04:00
cmd_take_weapon(client_num, params.get_all());
});
add_sv("kill", [](const int client_num, const params_sv& params)
{
if (!check_cheats(client_num))
2022-02-23 15:23:00 -05:00
{
return;
}
2022-03-13 16:01:29 -04:00
cmd_kill(client_num);
2022-03-13 08:45:20 -04:00
});
2022-02-03 14:05:24 -05:00
}
};
}
2022-05-18 09:39:04 -04:00
REGISTER_COMPONENT(command::component)