Nexius_Tailer
03.04.2016, 13:29
Приветствую. Сегодня представлю систему, которая будет полезна при написании каких-либо скриптов для сервера.
Суть её вот в чём: многие скрипты, например include, которые имеют в себе команды, требуют либо подключения cmd процессора, либо команды в них работают только на стандартном.
Лично моя политика написания скриптов (не модов) всегда была такой, чтобы конечный скрипт имел как можно меньше зависимостей (а по возможности не имел вообще), ибо в противном случае пользователю нужно следить не только за обновлениями моего скрипта, но и за обновлениями всех тех, подключение которых необходимо, что не очень удобно.
Либо же, если пользователь никогда бы не использовал тот скрипт, который требуется в моём, но ему приходится (хороший пример - античит на FlyHack с использованием MapAndreas).
В общем, с помощью этой системы можно делать команды внутри своих модулей (фс, инклудов), которые не будут требовать zcmd, но при его подключении всё также будет работать как и прежде.
Работает это со всеми cmd процессорами, кроме rCmd, ибо синтаксис команд там абсолютно другой.
Для этого придётся создать как бы свой cmd процессор в своём скрипте, но работать он будет немного иначе.
Если вам нужна такая фича в вашем fs/include, код ниже к вашим услугам:
#if !defined CMD
#define CMD:%0(%1) forward cmd_%0(%1); public cmd_%0(%1)
#endif
//YCMD и izcmd не блокируют вызов OnPlayerCommandText, поэтому их игнорируем, чтобы команды не дублировались
#if !defined YCMD && !defined _I_ZCMD_INCLUDED
public OnPlayerCommandText(playerid, cmdtext[])
{
new cmd[32], params[126], pos;
while(cmdtext[pos] > ' ')
{
cmd[pos] = cmdtext[pos];
pos++;
}
while(cmdtext[pos] == ' ')
{
pos++;
if(!cmdtext[pos])
{
params = "\1";
break;
}
}
strcat(params, cmdtext[pos]);
//Здесь уже команды, шапку делаем по методу стандартных
//Только вместо каких-либо действий вызываем паблик команды, в котором уже будет весь её код
//if(!strcmp(cmd, "/help", true)) return my_cmd_help(playerid, params); <- Как пример, команда "/help"
return 0;
}
#endif
Теперь объяснения, что за чем и зачем.
Дефайн "CMD:" нужен для совместимости с YCMD и прочими командными процессорами, которые не поддерживают объявление команд по типу "cmd_", но в них есть своя совместимость с командами, объявленными через "CMD:".
Выделение памяти, объяснять подробнее не вижу нужды:
new cmd[32], params[126], pos;
Единственное, что cmd в себе позже будет хранить текст "/НашейКоманды", а params - её параметры.
Далее, вот это:
while(cmdtext[pos] > ' ')
{
cmd[pos] = cmdtext[pos];
pos++;
}
Как раз и записывает весь текст команды в массив "cmd" до первого пробела либо конца строки, если команда введена без параметров. Т.е. по сути результат этого равносилен strtok, который мы пишем первый раз в начале OnPlayerCommandText, если команды на strcmp.
Вот теперь уже в:
while(cmdtext[pos] == ' ')
{
pos++;
if(!cmdtext[pos])
{
params = "\1";
break;
}
}
Идёт отделение параметров. Ну т.е. пока только мы считаем, с какого символа они начинаются, если начинаются вообще (проверка "!cmdtext[pos]" говорит о том, что если следующий символ в цикле будет равен концу строки, а параметры ещё не обнаружены, то команда введена без параметров).
И вот тут:
strcat(params, cmdtext[pos]);
Записываем в отдельную переменную params весь текст параметров, начиная с первого символа после пробела(ов).
А далее, то что под комментариями - это уже объявление своих команд.
if(!strcmp(cmd, "/help", true)) return my_cmd_help(playerid, params); //Как пример, команда "/help"
Помимо вызова функции каждой команды, эту функцию нужно ещё и объявить (а в ней ещё и код этой самой команды написать).
Опять же, на примере команды "/help". Объявляем паблик и функцию для команды (где-нибудь в начале или в конце, как вам удобнее):
CMD:help(playerid, params[]) return my_cmd_help(playerid, params);
my_cmd_help(playerid, params[])
{
#pragma unused params //Это добавлять если только команда не имеет параметров и вам выдаёт варнинг
//Проверки на залогиненность и т.д.
//И любые действия, которые должны происходить при вводе "/help"
return 1;
}
И да, русские команды добавлять можно, но название паблика команды должно быть на английском. Также работать русские команды будут только если zcmd не подключен (а если станет подключен, то такие команды уже будут вызываться только по альтернативным латинским именам, которые объявлены после "CMD:")!
Также, по моему, это не пригодится вам, если вы пишите мод, ибо там реально проще будет подключить/не подключить нужный cmd-процессор и нет надобности в гибкости.
Идею написания "независимых" команд взял у скрипта aview от автора SoNik)), но у него была реализована единственная команда и без параметров.
UPD: Другой вариант (более компактный)
Есть такая старая, но классная штука, как dcmd (https://team.sa-mp.com/wiki/Fast_Commands#dcmd). Так вот, немного её доработав, также можно достичь совместимости.. Правда только для тех zcmd-подобных процессоров, где команды можно вызывать через cmd_"cmdname" (т.е. такие реализации, как Pawn.CMD/YCMD, к сожалению, работать с этим не будут из-за того, что у каждого из них свои префиксы).
Для начала код оригинального dcmd и синтаксис его команд:
#define dcmd(%1,%2,%3) if (!strcmp((%3)[1], #%1, true, (%2)) && ((((%3)[(%2) + 1] == '\0') && (dcmd_%1(playerid, ""))) || (((%3)[(%2) + 1] == ' ') && (dcmd_%1(playerid, (%3)[(%2) + 2]))))) return 1
public OnPlayerCommandText(playerid, cmdtext[])
{
dcmd(help, 4, cmdtext); //4 = длина имени команды, равносильно strlen("help")
return 0;
}
dcmd_help(playerid, params[])
{
#pragma unused params //Избежание варнинга, если params не используется
//Проверки на залогиненность и т.д.
//И любые действия, которые должны происходить при вводе "/help"
return 1;
}
Собственно, немного изменив логику работы и пару названий мы можем сделать это средством создания почти универсальных команд:
#define dcmd2(%1,%2,%3) if(!strcmp((%3)[1], #%1, true, (%2)) && ((((%3)[(%2) + 1] == '\0') && (cmd_%1(playerid, ""))) || (((%3)[(%2) + 1] == ' ') && (cmd_%1(playerid, (%3)[(%2) + 2]))))) return 1
Теперь так будет выглядеть наш движок;
"dcmd2" для избежания путаницы, если вдруг уже имеем обычный dcmd.
Создание команд придётся сделать таким образом:
public OnPlayerCommandText(playerid, cmdtext[])
{
dcmd2(help, 4, cmdtext); //Здесь всё как у обычного dcmd, 4 = strlen("help")
return 0;
}
forward cmd_help(playerid, params[]); //А здесь уже синтаксис zcmd команды
public cmd_help(playerid, params[]) //Объявляем пабликом для того, чтобы команду видел zcmd
{
//Проверка на залогиненность и т.д.
//И любые действия, которые должны происходить при вводе "/help"
return 1;
}
Принцип работы этого варианта: когда ничего не подключено, dcmd2 вызывает нашу команду (а точнее сверяет через strcmp введённый текст с названием нужной команды, и, если таковая введена, вызывает cmd_"+введённый нами текст до пробела") из OnPlayerCommandText уже не как функцию, а как паблик.
Как только же мы подключаем zcmd, вызов паблика OnPlayerCommandText прекращается, т.е. dcmd2 перестаёт вызывать команды. Но, т.к. они объявлены синтаксисом zcmd команд и являются пабликами - zcmd их видит и уже вызывает сам.
Таким образом, это является некой золотой серединой, если вы просто хотите иметь команды, которые по скорости без zmcd работали бы как обычные, но при его подключении начинали бы выполняться им.
Надеюсь, это было полезно.
Суть её вот в чём: многие скрипты, например include, которые имеют в себе команды, требуют либо подключения cmd процессора, либо команды в них работают только на стандартном.
Лично моя политика написания скриптов (не модов) всегда была такой, чтобы конечный скрипт имел как можно меньше зависимостей (а по возможности не имел вообще), ибо в противном случае пользователю нужно следить не только за обновлениями моего скрипта, но и за обновлениями всех тех, подключение которых необходимо, что не очень удобно.
Либо же, если пользователь никогда бы не использовал тот скрипт, который требуется в моём, но ему приходится (хороший пример - античит на FlyHack с использованием MapAndreas).
В общем, с помощью этой системы можно делать команды внутри своих модулей (фс, инклудов), которые не будут требовать zcmd, но при его подключении всё также будет работать как и прежде.
Работает это со всеми cmd процессорами, кроме rCmd, ибо синтаксис команд там абсолютно другой.
Для этого придётся создать как бы свой cmd процессор в своём скрипте, но работать он будет немного иначе.
Если вам нужна такая фича в вашем fs/include, код ниже к вашим услугам:
#if !defined CMD
#define CMD:%0(%1) forward cmd_%0(%1); public cmd_%0(%1)
#endif
//YCMD и izcmd не блокируют вызов OnPlayerCommandText, поэтому их игнорируем, чтобы команды не дублировались
#if !defined YCMD && !defined _I_ZCMD_INCLUDED
public OnPlayerCommandText(playerid, cmdtext[])
{
new cmd[32], params[126], pos;
while(cmdtext[pos] > ' ')
{
cmd[pos] = cmdtext[pos];
pos++;
}
while(cmdtext[pos] == ' ')
{
pos++;
if(!cmdtext[pos])
{
params = "\1";
break;
}
}
strcat(params, cmdtext[pos]);
//Здесь уже команды, шапку делаем по методу стандартных
//Только вместо каких-либо действий вызываем паблик команды, в котором уже будет весь её код
//if(!strcmp(cmd, "/help", true)) return my_cmd_help(playerid, params); <- Как пример, команда "/help"
return 0;
}
#endif
Теперь объяснения, что за чем и зачем.
Дефайн "CMD:" нужен для совместимости с YCMD и прочими командными процессорами, которые не поддерживают объявление команд по типу "cmd_", но в них есть своя совместимость с командами, объявленными через "CMD:".
Выделение памяти, объяснять подробнее не вижу нужды:
new cmd[32], params[126], pos;
Единственное, что cmd в себе позже будет хранить текст "/НашейКоманды", а params - её параметры.
Далее, вот это:
while(cmdtext[pos] > ' ')
{
cmd[pos] = cmdtext[pos];
pos++;
}
Как раз и записывает весь текст команды в массив "cmd" до первого пробела либо конца строки, если команда введена без параметров. Т.е. по сути результат этого равносилен strtok, который мы пишем первый раз в начале OnPlayerCommandText, если команды на strcmp.
Вот теперь уже в:
while(cmdtext[pos] == ' ')
{
pos++;
if(!cmdtext[pos])
{
params = "\1";
break;
}
}
Идёт отделение параметров. Ну т.е. пока только мы считаем, с какого символа они начинаются, если начинаются вообще (проверка "!cmdtext[pos]" говорит о том, что если следующий символ в цикле будет равен концу строки, а параметры ещё не обнаружены, то команда введена без параметров).
И вот тут:
strcat(params, cmdtext[pos]);
Записываем в отдельную переменную params весь текст параметров, начиная с первого символа после пробела(ов).
А далее, то что под комментариями - это уже объявление своих команд.
if(!strcmp(cmd, "/help", true)) return my_cmd_help(playerid, params); //Как пример, команда "/help"
Помимо вызова функции каждой команды, эту функцию нужно ещё и объявить (а в ней ещё и код этой самой команды написать).
Опять же, на примере команды "/help". Объявляем паблик и функцию для команды (где-нибудь в начале или в конце, как вам удобнее):
CMD:help(playerid, params[]) return my_cmd_help(playerid, params);
my_cmd_help(playerid, params[])
{
#pragma unused params //Это добавлять если только команда не имеет параметров и вам выдаёт варнинг
//Проверки на залогиненность и т.д.
//И любые действия, которые должны происходить при вводе "/help"
return 1;
}
И да, русские команды добавлять можно, но название паблика команды должно быть на английском. Также работать русские команды будут только если zcmd не подключен (а если станет подключен, то такие команды уже будут вызываться только по альтернативным латинским именам, которые объявлены после "CMD:")!
Также, по моему, это не пригодится вам, если вы пишите мод, ибо там реально проще будет подключить/не подключить нужный cmd-процессор и нет надобности в гибкости.
Идею написания "независимых" команд взял у скрипта aview от автора SoNik)), но у него была реализована единственная команда и без параметров.
UPD: Другой вариант (более компактный)
Есть такая старая, но классная штука, как dcmd (https://team.sa-mp.com/wiki/Fast_Commands#dcmd). Так вот, немного её доработав, также можно достичь совместимости.. Правда только для тех zcmd-подобных процессоров, где команды можно вызывать через cmd_"cmdname" (т.е. такие реализации, как Pawn.CMD/YCMD, к сожалению, работать с этим не будут из-за того, что у каждого из них свои префиксы).
Для начала код оригинального dcmd и синтаксис его команд:
#define dcmd(%1,%2,%3) if (!strcmp((%3)[1], #%1, true, (%2)) && ((((%3)[(%2) + 1] == '\0') && (dcmd_%1(playerid, ""))) || (((%3)[(%2) + 1] == ' ') && (dcmd_%1(playerid, (%3)[(%2) + 2]))))) return 1
public OnPlayerCommandText(playerid, cmdtext[])
{
dcmd(help, 4, cmdtext); //4 = длина имени команды, равносильно strlen("help")
return 0;
}
dcmd_help(playerid, params[])
{
#pragma unused params //Избежание варнинга, если params не используется
//Проверки на залогиненность и т.д.
//И любые действия, которые должны происходить при вводе "/help"
return 1;
}
Собственно, немного изменив логику работы и пару названий мы можем сделать это средством создания почти универсальных команд:
#define dcmd2(%1,%2,%3) if(!strcmp((%3)[1], #%1, true, (%2)) && ((((%3)[(%2) + 1] == '\0') && (cmd_%1(playerid, ""))) || (((%3)[(%2) + 1] == ' ') && (cmd_%1(playerid, (%3)[(%2) + 2]))))) return 1
Теперь так будет выглядеть наш движок;
"dcmd2" для избежания путаницы, если вдруг уже имеем обычный dcmd.
Создание команд придётся сделать таким образом:
public OnPlayerCommandText(playerid, cmdtext[])
{
dcmd2(help, 4, cmdtext); //Здесь всё как у обычного dcmd, 4 = strlen("help")
return 0;
}
forward cmd_help(playerid, params[]); //А здесь уже синтаксис zcmd команды
public cmd_help(playerid, params[]) //Объявляем пабликом для того, чтобы команду видел zcmd
{
//Проверка на залогиненность и т.д.
//И любые действия, которые должны происходить при вводе "/help"
return 1;
}
Принцип работы этого варианта: когда ничего не подключено, dcmd2 вызывает нашу команду (а точнее сверяет через strcmp введённый текст с названием нужной команды, и, если таковая введена, вызывает cmd_"+введённый нами текст до пробела") из OnPlayerCommandText уже не как функцию, а как паблик.
Как только же мы подключаем zcmd, вызов паблика OnPlayerCommandText прекращается, т.е. dcmd2 перестаёт вызывать команды. Но, т.к. они объявлены синтаксисом zcmd команд и являются пабликами - zcmd их видит и уже вызывает сам.
Таким образом, это является некой золотой серединой, если вы просто хотите иметь команды, которые по скорости без zmcd работали бы как обычные, но при его подключении начинали бы выполняться им.
Надеюсь, это было полезно.