Difference between revisions of "Ru:Commands (SourceMod Scripting)"

From AlliedModders Wiki
Jump to: navigation, search
(New page: SourceMod allows you to create console commands similar to AMX Mod X. There are two main types of console commands: *'''Server Commands''' - Fired from one of the following input ...)
 
(Добавление команд)
 
(11 intermediate revisions by 4 users not shown)
Line 1: Line 1:
[[SourceMod]] allows you to create console commands similar to [[AMX Mod X]]. There are two main types of console commands:
+
[[SourceMod]] позволяет создать вам консольные команды, похожие на [[AMX Mod X]]. Есть два главных вида консольных команд:
*'''Server Commands''' - Fired from one of the following input methods:
+
*'''Серверные команды''' - Срабатывают с одним из следующих способов ввода:
**the server console itself
+
**С помощью консоли сервера
**the remote console (RCON)
+
**С помощью удаленной консоли (RCON)
**the ServerCommand() function, either from SourceMod or the [[Half-Life 2]] engine
+
**Функция ServerCommand(), либо из SourceMod либо [[Half-Life 2]] движка
*'''Console Commands''' - Fired from one of the following input methods:
+
*'''Консольные команды''' - Срабатывают с одним из следующих способов ввода:
**a client's console
+
**С помощью консоли клиента
**any of the server command input methods
+
**Любой из методов ввода серверных команд
  
For server commands, there is no client indexFor console/client commands, there is a client index, but it may be 0 to indicate that the command is from the server.
+
Для серверных команд не существует клиентского индексаДля консольных/клиентских команд, существует клиентский индекс, но он может быть 0, чтобы показать, что команда с сервера.
  
Note that button-bound "commands," such as +attack and +duck, are not actually console commandsThey are a separate command stream sent over the network, as they need to be tied to a specific frame.   
+
Обратите внимание, что кнопка-бинд "commands", такие как +attack и +duck, они не консольные командыЭти команды отдельно передаются, посколько они отдельно привязаны.   
  
=Server Commands=
+
=Серверные команды=
As noted above, server commands are fired through the server console, whether remote, local, or through the Source engine. There is no client index associated with a server command.   
+
Как отмечалось выше, серверные команды срабатывают через консоль сервера, будь то удалённую, локальную, или через движок Source. Индекс клиента не связан с серверными командами.   
  
Server commands are registered through the <tt>RegServerCmd()</tt> function defined in <tt>console.inc</tt>. When registering a server command, you may be hooking an already existing command, and thus the return value is important.
+
Серверные команды регистрируются через <tt>RegServerCmd()</tt> функцию, расположенную в <tt>console.inc</tt>. При регистрации команды, вы можете записать поверх существующей команды, и таким образом, возвращаемое значение становится важным.
*<b><tt>Plugin_Continue</tt></b> - The original server command will be processed, if there was one. If the server command was created by a plugin, this has no effect.
+
*<b><tt>Plugin_Continue</tt></b> - The original server command will be processed, if there was one. Если серверная команда создана плагином, здесь не будет эффекта.
*<b><tt>Plugin_Handled</tt></b> - The original server command will not be processed, if there was one. If the server command was created by a plugin, this has no effect.
+
*<b><tt>Plugin_Handled</tt></b> - The original server command will not be processed, if there was one. Если серверная команда создана плагином, здесь не будет эффекта.
 
*<b><tt>Plugin_Stop</tt></b> - The original server command will not be processed, if there was one.  Additionally, no further hooks will be called for this command until it is fired again.
 
*<b><tt>Plugin_Stop</tt></b> - The original server command will not be processed, if there was one.  Additionally, no further hooks will be called for this command until it is fired again.
  
==Adding Server Commands==
+
==Добавление серверных команд==
Let's say we want to add a test command to show how Half-Life 2 breaks down command argumentsA possible implementation might be
+
Допустим, мы хотим добавить тестовую команду, чтобы показать как Half-Life 2 вычисляет аргументы командыВот возможная реализация:
 
<pawn>
 
<pawn>
public OnPluginStart()
+
public void OnPluginStart()
 
{
 
{
 
RegServerCmd("test_command", Command_Test)
 
RegServerCmd("test_command", Command_Test)
 
}
 
}
  
public Action:Command_Test(args)
+
public Action Command_Test(int args)
 
{
 
{
new String:arg[128]
+
char arg[128]
new String:full[256]
+
char full[256]
  
 
GetCmdArgString(full, sizeof(full))
 
GetCmdArgString(full, sizeof(full))
  
PrintToServer("Argument string: %s", full)
+
PrintToServer("Строка аргумента: %s", full)
PrintToServer("Argument count: %d", args)
+
PrintToServer("Количество аргументов: %d", args)
for (new i=1; i<=args; i++)
+
for (int i = 1; i <= args; i++)
 
{
 
{
 
GetCmdArg(i, arg, sizeof(arg))
 
GetCmdArg(i, arg, sizeof(arg))
PrintToServer("Argument %d: %s", i, arg)
+
PrintToServer("Аргумент %d: %s", i, arg)
 
}
 
}
 
}
 
}
 
</pawn>
 
</pawn>
  
==Blocking Server Commands==
+
==Блокирование серверных команд==
Let's say we wanted to disable the "kickid" command on the server. There's no real good reason to do this, but for example's sake:
+
Допустим мы хотим отключить "kickid" команду на сервере. Нет причин делать это, но для примера сойдет:
 
<pawn>
 
<pawn>
public OnPluginStart()
+
public void OnPluginStart()
 
{
 
{
RegServerCmd("kickid", Command_KickId)
+
RegServerCmd("kickid", Command_KickId);
 
}
 
}
  
public Action:Command_Kickid(args)
+
public Action Command_Kickid(int args)
 
{
 
{
return Plugin_Handled
+
return Plugin_Handled;
 
}
 
}
 
</pawn>
 
</pawn>
  
=Console Commands=
+
=Консольные команды=
Unlike server commands, console commands can be triggered by either the server or the client, so the callback function receives a client index as well as the argument countIf the server fired the command, the client index will be 0.   
+
В отличие от серверных команд, консольные могут быть вызваны, как сервером, так и клиентом, так что callback функция получает клиентский индекс, а также количество аргументовЕсли сервер использовал команду, клиентский индекс будет 0.   
  
 
When returning the <tt>Action</tt> from the callback, the following effects will happen:
 
When returning the <tt>Action</tt> from the callback, the following effects will happen:
Line 67: Line 67:
 
*<tt>Plugin_Stop</tt>: Same as <tt>Plugin_Handled</tt>, except that this will be the last hook called.
 
*<tt>Plugin_Stop</tt>: Same as <tt>Plugin_Handled</tt>, except that this will be the last hook called.
  
Note that, unlike AMX Mod X, SourceMod does not allow you to register command filters. I.e., there is no equivalent to this notation:
+
Обратите внимание, в отличие от AMX Mod X, SourceMod не сможет зарегистрировать командные фильтры. т.е. нет эквивалента этой команды:
 
<pawn>register_clcmd("say /ff", "Command_SayFF");</pawn>
 
<pawn>register_clcmd("say /ff", "Command_SayFF");</pawn>
  
This notation was removed to make our internal code simpler and fasterWriting the same functionality is easy, and demonstrated below.
+
Это было удаленно, чтобы сделать наш код проще и быстрееНаписание такого же функционала легче и показано ниже.
  
==Adding Commands==
+
==Добавление команд==
Adding client commands is very simpleLet's port our earlier testing command to display information about the client as well.
+
Создать клиентские команды очень простоДавайте переделаем нашу тест команду, чтобы можно было отобразить информацию о клиенте.
  
<pawn>public OnPluginStart()
+
<pawn>public void OnPluginStart()
 
{
 
{
RegConsoleCmd("test_command", Command_Test)
+
RegConsoleCmd("test_command", Command_Test);
 
}
 
}
  
public Action:Command_Test(client, args)
+
public Action Command_Test(int client, int args)
 
{
 
{
new String:arg[128]
+
char arg[128], full[256];
new String:full[256]
 
  
 
GetCmdArgString(full, sizeof(full))
 
GetCmdArgString(full, sizeof(full))
Line 89: Line 88:
 
if (client)
 
if (client)
 
{
 
{
new String:name[32]
+
char name[32];
GetClientName(client, name, sizeof(name))
+
GetClientName(client, name, sizeof(name));
PrintToServer("Command from client: %s", name);
+
PrintToServer("Команда от клиента: %s", name);
} else {
+
}
PrintToServer("Command from server.");
+
else
 +
{
 +
PrintToServer("Команда от сервера.");
 
}
 
}
  
PrintToServer("Argument string: %s", full)
+
PrintToServer("Строка аргументов: %s", full);
PrintToServer("Argument count: %d", args)
+
PrintToServer("Количество аргументов: %d", args);
for (new i=1; i<=args; i++)
+
 +
for (int i = 1; i <= args; i++)
 
{
 
{
GetCmdArg(i, arg, sizeof(arg))
+
GetCmdArg(i, arg, sizeof(arg));
PrintToServer("Argument %d: %s", i, arg)
+
PrintToServer("Аргумент %d: %s", i, arg);
 
}
 
}
 
}</pawn>
 
}</pawn>
  
==Hooking Commands==
+
==Захват команд==
A common example is hooking the say command. Let's say we want to tell players whether FF is enabled when they say '/ff' in game.   
+
Пример: захват типичной команды say. Let's say we want to tell players whether FF is enabled when they say '/ff' in game.   
  
 
Before we implement this, a common point of confusion with the 'say' command is that Half-Life 2 (and Half-Life 1), by default, send it with the text in one big quoted string.  This means if you say "I like hot dogs," your command will be broken down as such:
 
Before we implement this, a common point of confusion with the 'say' command is that Half-Life 2 (and Half-Life 1), by default, send it with the text in one big quoted string.  This means if you say "I like hot dogs," your command will be broken down as such:
Line 122: Line 124:
 
Thus, to take into account both of these situations, we are going to use <tt>GetCmdArgString</tt> and manually parse the input.   
 
Thus, to take into account both of these situations, we are going to use <tt>GetCmdArgString</tt> and manually parse the input.   
  
<pawn>public OnPluginStart()
+
<pawn>public void OnPluginStart()
 
{
 
{
 
RegConsoleCmd("say", Command_Say)
 
RegConsoleCmd("say", Command_Say)
 
}
 
}
  
public Action:Command_Say(client, args)
+
public Action Command_Say(int client, int args)
 
{
 
{
new String:text[192]
+
char text[192]
 
GetCmdArgString(text, sizeof(text))
 
GetCmdArgString(text, sizeof(text))
  
new startidx = 0
+
int startidx = 0
 
if (text[0] == '"')
 
if (text[0] == '"')
 
{
 
{
 
startidx = 1
 
startidx = 1
/* Strip the ending quote, if there is one */
+
/* Лишаем ковычки от окончания, если есть */
new len = strlen(text);
+
int len = strlen(text);
 
if (text[len-1] == '"')
 
if (text[len-1] == '"')
 
{
 
{
Line 146: Line 148:
 
if (StrEqual(text[startidx], "/ff"))
 
if (StrEqual(text[startidx], "/ff"))
 
{
 
{
new Handle:ff = FindConVar("mp_friendlyfire")
+
Handle ff = FindConVar("mp_friendlyfire")
 
if (GetConVarInt(ff))
 
if (GetConVarInt(ff))
 
{
 
{
PrintToConsole(client, "Friendly fire is enabled.")
+
PrintToConsole(client, "Огонь по товарищам включен.")
 
} else {
 
} else {
PrintToConsole(client, "Friendly fire is disabled.")
+
PrintToConsole(client, "Огонь по товарищам выключен.")
 
}
 
}
/* Block the client's messsage from broadcasting */
+
/* Блокирование сообщения клиента от вещания */
 
return Plugin_Handled
 
return Plugin_Handled
 
}
 
}
  
/* Let say continue normally */
+
/* Пусть работает как обычно */
 
return Plugin_Continue
 
return Plugin_Continue
 
}</pawn>
 
}</pawn>
  
==Creating Admin Commands==
+
==Создание Админской команды==
Let's create a simple admin command which kicks another player by their full name.   
+
Давайте создадим простую админскую команду, которая позволяет кикнуть игрока, по его полному имени.   
  
<pawn>public OnPluginStart()
+
<pawn>public void OnPluginStart()
 
{
 
{
 
RegAdminCmd("admin_kick",
 
RegAdminCmd("admin_kick",
 
Command_Kick,
 
Command_Kick,
 
ADMFLAG_KICK,
 
ADMFLAG_KICK,
"Kicks a player by name")
+
"Кикает игрока по нику")
 
}
 
}
  
public Action:Command_Kick(client, args)
+
public Action Command_Kick(int client, int args)
 
{
 
{
 
if (args < 1)
 
if (args < 1)
 
{
 
{
PrintToConsole(client, "Usage: admin_kick <name>")
+
PrintToConsole(client, "Использование: admin_kick <ник>")
 
return Plugin_Handled
 
return Plugin_Handled
 
}
 
}
  
new String:name[32], maxplayers, target = -1
+
char name[32]
 +
        int maxplayers, target = -1
 
GetCmdArg(1, name, sizeof(name))
 
GetCmdArg(1, name, sizeof(name))
  
 
maxplayers = GetMaxClients()
 
maxplayers = GetMaxClients()
for (new i=1; i<=maxplayers; i++)
+
for (int i = 1; i <= maxplayers; i++)
 
{
 
{
 
if (!IsClientConnected(i))
 
if (!IsClientConnected(i))
Line 190: Line 193:
 
continue
 
continue
 
}
 
}
decl String:other[32]
+
char other[32]
 
GetClientName(i, other, sizeof(other))
 
GetClientName(i, other, sizeof(other))
 
if (StrEqual(name, other))
 
if (StrEqual(name, other))
Line 200: Line 203:
 
if (target == -1)
 
if (target == -1)
 
{
 
{
PrintToConsole(client, "Could not find any player with the name: \"%s\"", name)
+
PrintToConsole(client, "Не могу найти игрока с ником: \"%s\"", name)
 
return Plugin_Handled
 
return Plugin_Handled
 
}
 
}
Line 209: Line 212:
 
}</pawn>
 
}</pawn>
  
==Immunity==
+
==Иммунитет==
In our previous example, we did not take immunity into account. Immunity is a much more complex system in SourceMod than it was in AMX Mod X, and there is no simple flag to denote its permissionsInstead, two functions are provided:
+
В нашем прошлом примере, мы не сделали иммунитет. Иммунитет является гораздно сложной системой в SourceMod, чем это было в AMX Mod X, и не существует просто флага, чтобы обозначить егоВместо, этого существует две функции:
*<tt>CanAdminTarget</tt>: Tests raw AdminId values for immunity.
+
*<tt>CanAdminTarget</tt>: Проверяет исходные значения AdminId для иммунитета.
*<tt>CanUserTarget</tt>: Tests in-game clients for immunity.
+
*<tt>CanUserTarget</tt>: Проверяет внутриигровых клиентов для иммунитета.
  
 
While immunity is generally tested ''player versus player'', it is possible you might want to check for immunity and not have a targetting client.  While there is no convenience function for this yet, a good idea might be to check for either ''default'' or ''global'' immunity on the player's groups (these can be user-defined for non-player targeted scenarios).
 
While immunity is generally tested ''player versus player'', it is possible you might want to check for immunity and not have a targetting client.  While there is no convenience function for this yet, a good idea might be to check for either ''default'' or ''global'' immunity on the player's groups (these can be user-defined for non-player targeted scenarios).
Line 228: Line 231:
 
So, how can we adapt our function about to use immunity?
 
So, how can we adapt our function about to use immunity?
  
<pawn>public Action:Command_Kick(client, args)
+
<pawn>public Action Command_Kick(int client, int args)
 
{
 
{
 
if (args < 1)
 
if (args < 1)
 
{
 
{
PrintToConsole(client, "Usage: admin_kick <name>")
+
PrintToConsole(client, "Использование: admin_kick <ник>")
 
return Plugin_Handled
 
return Plugin_Handled
 
}
 
}
  
new String:name[32], maxplayers, target = -1
+
char name[32]
 +
        int maxplayers, target = -1
 
GetCmdArg(1, name, sizeof(name))
 
GetCmdArg(1, name, sizeof(name))
  
 
maxplayers = GetMaxClients()
 
maxplayers = GetMaxClients()
for (new i=1; i<=maxplayers; i++)
+
for (int i = 1; i <= maxplayers; i++)
 
{
 
{
 
if (!IsClientConnected(i))
 
if (!IsClientConnected(i))
Line 246: Line 250:
 
continue
 
continue
 
}
 
}
decl String:other[32]
+
char other[32]
 
GetClientName(i, other, sizeof(other))
 
GetClientName(i, other, sizeof(other))
 
if (StrEqual(name, other))
 
if (StrEqual(name, other))
Line 256: Line 260:
 
if (target == -1)
 
if (target == -1)
 
{
 
{
PrintToConsole(client, "Could not find any player with the name: \"%s\"", name)
+
PrintToConsole(client, "Не могу найти игрока с ником: \"%s\"", name)
 
return Plugin_Handled
 
return Plugin_Handled
 
}
 
}
Line 262: Line 266:
 
if (!CanUserTarget(client, target))
 
if (!CanUserTarget(client, target))
 
{
 
{
PrintToConsole(client, "You cannot target this client.")
+
PrintToConsole(client, "Вы не можете использовать этого клиента как цель.")
 
return Plugin_Handled
 
return Plugin_Handled
 
}
 
}
Line 271: Line 275:
 
}</pawn>
 
}</pawn>
  
 
+
=Только клиентские команды=
=Client-Only Commands=
 
 
SourceMod exposes a forward that is called whenever a client executes any command string in their console, called <tt>OnClientCommand</tt>.  An example of this looks like:
 
SourceMod exposes a forward that is called whenever a client executes any command string in their console, called <tt>OnClientCommand</tt>.  An example of this looks like:
  
<pawn>public Action:OnClientCommand(client, args)
+
<pawn>public Action OnClientCommand(int client, int args)
 
{
 
{
new String:cmd[16]
+
char cmd[16]
GetCmdArg(0, cmd, sizeof(cmd)); /* Get command name */
+
GetCmdArg(0, cmd, sizeof(cmd)); /* Получаем название команды */
  
 
if (StrEqual(cmd, "test_command"))
 
if (StrEqual(cmd, "test_command"))
 
{
 
{
/* Got the client command! Block it... */
+
/* Получаем клиентскую команду! Блокируем её... */
 
return Plugin_Handled
 
return Plugin_Handled
 
}
 
}
Line 292: Line 295:
  
  
=Chat Triggers=
+
=Чат триггеры=
SourceMod will automatically create chat triggers for every command you make (as of revision 900).  For example, if you create a console command called "sm_megaslap," administrators will be able to type any of the following commands in <tt>say</tt>/<tt>say_team</tt> message modes:
+
SourceMod будет автоматически создавать чат триггеры для каждой команды, которую вы сделали (по ревизии 900).  Для примера, если вы создали консольную команду "sm_megaslap", администраторы смогут набрать ее и в <tt>say</tt>/<tt>say_team</tt> режимах:
 
<pre>!sm_megaslap
 
<pre>!sm_megaslap
 
!megaslap
 
!megaslap
Line 299: Line 302:
 
/megaslap</pre>
 
/megaslap</pre>
  
SourceMod then executes this command and its arguments as if it came from the client console.
+
SourceMod будет выполнять эту команду и ее аргументы, как если бы она пришла из консоли клиента.
*"<tt>!</tt>" is the default ''public'' trigger (<tt>PublicChatTrigger</tt> in <tt>configs/core.cfg</tt>) and your entry will be displayed to all clients as normal.
+
*"<tt>!</tt>" является ''публичным'' триггером по умолчанию (<tt>PublicChatTrigger</tt> в <tt>configs/core.cfg</tt>) и ваша команда будет отображена всем клиентам.
*"<tt>/</tt>" is the default ''silent'' trigger (<tt>SilentChatTrigger</tt> in <tt>configs/core.cfg</tt>) and your entry will be blocked from being displayed.
+
*"<tt>/</tt>" является ''скрытым'' триггером по умолчанию (<tt>SilentChatTrigger</tt> в <tt>configs/core.cfg</tt>) и ваша команда не будет показана остальным клиентам
  
SourceMod will only execute commands registered with <tt>RegConsoleCmd</tt> or <tt>RegAdminCmd</tt>, and only if those commands are not already provided by Half-Life 2 or the game mod. If the command is prefixed with "sm_" then the "sm_" can be omitted from the chat trigger.
+
SourceMod будет исполнять только те команды, которые зарегистрированы с <tt>RegConsoleCmd</tt> или <tt>RegAdminCmd</tt>, и только если эти команды уже не используются Half-life 2 движком или другим игровым модом. Если команда идет с префиксом "sm_", то "sm_" можно исключить, из написания в чате.
  
Console commands which wish to support usage as a chat trigger should not use <tt>PrintTo*</tt> natives. Instead, they should use <tt>ReplyToCommand()</tt>, which will automatically print your message either as a chat message or to the client's console, depending on the source of the command.
+
Консольные команды, которые хотят поддерживать использование в качестве триггера чата, не должны использовать нативы вроде <tt>PrintTo*</tt>. Вместо этого, они должны использовать <tt>ReplyToCommand()</tt>, которая будет автоматически печатать ваше сообщение либо в виде сообщения чата либо в виде консоли клиента, в зависимости от источника команды.
  
[[Category:SourceMod Scripting]]
+
[[Category:Ru:SourceMod Scripting]]
[[Category:Russian]]
 

Latest revision as of 03:36, 11 February 2021

SourceMod позволяет создать вам консольные команды, похожие на AMX Mod X. Есть два главных вида консольных команд:

  • Серверные команды - Срабатывают с одним из следующих способов ввода:
    • С помощью консоли сервера
    • С помощью удаленной консоли (RCON)
    • Функция ServerCommand(), либо из SourceMod либо Half-Life 2 движка
  • Консольные команды - Срабатывают с одним из следующих способов ввода:
    • С помощью консоли клиента
    • Любой из методов ввода серверных команд

Для серверных команд не существует клиентского индекса. Для консольных/клиентских команд, существует клиентский индекс, но он может быть 0, чтобы показать, что команда с сервера.

Обратите внимание, что кнопка-бинд "commands", такие как +attack и +duck, они не консольные команды. Эти команды отдельно передаются, посколько они отдельно привязаны.

Серверные команды

Как отмечалось выше, серверные команды срабатывают через консоль сервера, будь то удалённую, локальную, или через движок Source. Индекс клиента не связан с серверными командами.

Серверные команды регистрируются через RegServerCmd() функцию, расположенную в console.inc. При регистрации команды, вы можете записать поверх существующей команды, и таким образом, возвращаемое значение становится важным.

  • Plugin_Continue - The original server command will be processed, if there was one. Если серверная команда создана плагином, здесь не будет эффекта.
  • Plugin_Handled - The original server command will not be processed, if there was one. Если серверная команда создана плагином, здесь не будет эффекта.
  • Plugin_Stop - The original server command will not be processed, if there was one. Additionally, no further hooks will be called for this command until it is fired again.

Добавление серверных команд

Допустим, мы хотим добавить тестовую команду, чтобы показать как Half-Life 2 вычисляет аргументы команды. Вот возможная реализация:

public void OnPluginStart()
{
	RegServerCmd("test_command", Command_Test)
}
 
public Action Command_Test(int args)
{
	char arg[128]
	char full[256]
 
	GetCmdArgString(full, sizeof(full))
 
	PrintToServer("Строка аргумента: %s", full)
	PrintToServer("Количество аргументов: %d", args)
	for (int i = 1; i <= args; i++)
	{
		GetCmdArg(i, arg, sizeof(arg))
		PrintToServer("Аргумент %d: %s", i, arg)
	}
}

Блокирование серверных команд

Допустим мы хотим отключить "kickid" команду на сервере. Нет причин делать это, но для примера сойдет:

public void OnPluginStart()
{
	RegServerCmd("kickid", Command_KickId);
}
 
public Action Command_Kickid(int args)
{
	return Plugin_Handled;
}

Консольные команды

В отличие от серверных команд, консольные могут быть вызваны, как сервером, так и клиентом, так что callback функция получает клиентский индекс, а также количество аргументов. Если сервер использовал команду, клиентский индекс будет 0.

When returning the Action from the callback, the following effects will happen:

  • Plugin_Continue: The original functionality of the command (if any) will still be processed. If there was no original functionality, the client will receive "Unknown command" in their console.
  • Plugin_Handled: The original functionality of the command (if any) will be blocked. If there was no functionality originally, this prevents clients from seeing "Unknown command" in their console.
  • Plugin_Stop: Same as Plugin_Handled, except that this will be the last hook called.

Обратите внимание, в отличие от AMX Mod X, SourceMod не сможет зарегистрировать командные фильтры. т.е. нет эквивалента этой команды:

register_clcmd("say /ff", "Command_SayFF");

Это было удаленно, чтобы сделать наш код проще и быстрее. Написание такого же функционала легче и показано ниже.

Добавление команд

Создать клиентские команды очень просто. Давайте переделаем нашу тест команду, чтобы можно было отобразить информацию о клиенте.

public void OnPluginStart()
{
	RegConsoleCmd("test_command", Command_Test);
}
 
public Action Command_Test(int client, int args)
{
	char arg[128], full[256];
 
	GetCmdArgString(full, sizeof(full))
 
	if (client)
	{
		char name[32];
		GetClientName(client, name, sizeof(name));
		PrintToServer("Команда от клиента: %s", name);
	}
	else
	{
		PrintToServer("Команда от сервера.");
	}
 
	PrintToServer("Строка аргументов: %s", full);
	PrintToServer("Количество аргументов: %d", args);
 
	for (int i = 1; i <= args; i++)
	{
		GetCmdArg(i, arg, sizeof(arg));
		PrintToServer("Аргумент %d: %s", i, arg);
	}
}

Захват команд

Пример: захват типичной команды say. Let's say we want to tell players whether FF is enabled when they say '/ff' in game.

Before we implement this, a common point of confusion with the 'say' command is that Half-Life 2 (and Half-Life 1), by default, send it with the text in one big quoted string. This means if you say "I like hot dogs," your command will be broken down as such:

  • Argument string: "I like hot dogs"
  • Argument count: 1
  • Argument #1: I like hot dogs

However, if a player types this in their console: say I like yams, it will be broken up as:

  • Argument string: I like yams
  • Argument count: 3
  • Argument #1: I
  • Argument #2: like
  • Argument #3: yams

Thus, to take into account both of these situations, we are going to use GetCmdArgString and manually parse the input.

public void OnPluginStart()
{
	RegConsoleCmd("say", Command_Say)
}
 
public Action Command_Say(int client, int args)
{
	char text[192]
	GetCmdArgString(text, sizeof(text))
 
	int startidx = 0
	if (text[0] == '"')
	{
		startidx = 1
		/* Лишаем ковычки от окончания, если есть */
		int len = strlen(text);
		if (text[len-1] == '"')
		{
			text[len-1] = '\0'
		}
	}
 
	if (StrEqual(text[startidx], "/ff"))
	{
		Handle ff = FindConVar("mp_friendlyfire")
		if (GetConVarInt(ff))
		{
			PrintToConsole(client, "Огонь по товарищам включен.")
		} else {
			PrintToConsole(client, "Огонь по товарищам выключен.")
		}
		/* Блокирование сообщения клиента от вещания */
		return Plugin_Handled
	}
 
	/* Пусть работает как обычно */
	return Plugin_Continue
}

Создание Админской команды

Давайте создадим простую админскую команду, которая позволяет кикнуть игрока, по его полному имени.

public void OnPluginStart()
{
	RegAdminCmd("admin_kick",
		Command_Kick,
		ADMFLAG_KICK,
		"Кикает игрока по нику")
}
 
public Action Command_Kick(int client, int args)
{
	if (args < 1)
	{
		PrintToConsole(client, "Использование: admin_kick <ник>")
		return Plugin_Handled
	}
 
	char name[32]
        int maxplayers, target = -1
	GetCmdArg(1, name, sizeof(name))
 
	maxplayers = GetMaxClients()
	for (int i = 1; i <= maxplayers; i++)
	{
		if (!IsClientConnected(i))
		{
			continue
		}
		char other[32]
		GetClientName(i, other, sizeof(other))
		if (StrEqual(name, other))
		{
			target = i
		}
	}
 
	if (target == -1)
	{
		PrintToConsole(client, "Не могу найти игрока с ником: \"%s\"", name)
		return Plugin_Handled
	}
 
	ServerCommand("kickid %d", GetClientUserId(target));
 
	return Plugin_Handled
}

Иммунитет

В нашем прошлом примере, мы не сделали иммунитет. Иммунитет является гораздно сложной системой в SourceMod, чем это было в AMX Mod X, и не существует просто флага, чтобы обозначить его. Вместо, этого существует две функции:

  • CanAdminTarget: Проверяет исходные значения AdminId для иммунитета.
  • CanUserTarget: Проверяет внутриигровых клиентов для иммунитета.

While immunity is generally tested player versus player, it is possible you might want to check for immunity and not have a targetting client. While there is no convenience function for this yet, a good idea might be to check for either default or global immunity on the player's groups (these can be user-defined for non-player targeted scenarios).

When checking for immunity, the following heuristics are performed in this exact order:

  1. If the targeting AdminId is INVALID_ADMIN_ID, targeting fails.
  2. If the targetted AdminId is INVALID_ADMIN_ID, targeting succeeds.
  3. If the targeting admin has Admin_Root (ADMFLAG_ROOT), targeting succeeds.
  4. If the targetted admin has global immunity, targeting fails.
  5. If the targetted admin has default immunity, and the targeting admin belongs to no groups, targeting fails.
  6. If the targetted admin has specific immunity from the targeting admin via group immunities, targeting fails.
  7. If no conclusion is reached via the previous steps, targeting succeeds.

So, how can we adapt our function about to use immunity?

public Action Command_Kick(int client, int args)
{
	if (args < 1)
	{
		PrintToConsole(client, "Использование: admin_kick <ник>")
		return Plugin_Handled
	}
 
	char name[32]
        int maxplayers, target = -1
	GetCmdArg(1, name, sizeof(name))
 
	maxplayers = GetMaxClients()
	for (int i = 1; i <= maxplayers; i++)
	{
		if (!IsClientConnected(i))
		{
			continue
		}
		char other[32]
		GetClientName(i, other, sizeof(other))
		if (StrEqual(name, other))
		{
			target = i
		}
	}
 
	if (target == -1)
	{
		PrintToConsole(client, "Не могу найти игрока с ником: \"%s\"", name)
		return Plugin_Handled
	}
 
	if (!CanUserTarget(client, target))
	{
		PrintToConsole(client, "Вы не можете использовать этого клиента как цель.")
		return Plugin_Handled
	}
 
	ServerCommand("kickid %d", GetClientUserId(target))
 
	return Plugin_Handled
}

Только клиентские команды

SourceMod exposes a forward that is called whenever a client executes any command string in their console, called OnClientCommand. An example of this looks like:

public Action OnClientCommand(int client, int args)
{
	char cmd[16]
	GetCmdArg(0, cmd, sizeof(cmd));	/* Получаем название команды */
 
	if (StrEqual(cmd, "test_command"))
	{
		/* Получаем клиентскую команду! Блокируем её... */
		return Plugin_Handled
	}
 
	return Plugin_Continue
}

It is worth noting that not everything a client sends will be available through this command. Command registered via external sources in C++ may not be available, especially if they are created via CON_COMMAND in the game mod itself. For example, "say" is usually implemented this way, because it can be used by both clients and the server, and thus it does not channel through this forward.


Чат триггеры

SourceMod будет автоматически создавать чат триггеры для каждой команды, которую вы сделали (по ревизии 900). Для примера, если вы создали консольную команду "sm_megaslap", администраторы смогут набрать ее и в say/say_team режимах:

!sm_megaslap
!megaslap
/sm_megaslap
/megaslap

SourceMod будет выполнять эту команду и ее аргументы, как если бы она пришла из консоли клиента.

  • "!" является публичным триггером по умолчанию (PublicChatTrigger в configs/core.cfg) и ваша команда будет отображена всем клиентам.
  • "/" является скрытым триггером по умолчанию (SilentChatTrigger в configs/core.cfg) и ваша команда не будет показана остальным клиентам

SourceMod будет исполнять только те команды, которые зарегистрированы с RegConsoleCmd или RegAdminCmd, и только если эти команды уже не используются Half-life 2 движком или другим игровым модом. Если команда идет с префиксом "sm_", то "sm_" можно исключить, из написания в чате.

Консольные команды, которые хотят поддерживать использование в качестве триггера чата, не должны использовать нативы вроде PrintTo*. Вместо этого, они должны использовать ReplyToCommand(), которая будет автоматически печатать ваше сообщение либо в виде сообщения чата либо в виде консоли клиента, в зависимости от источника команды.