shape1
shape2
shape3
shape4
shape7
shape8

Создание простейшей AntiDM-системы с нуля (Таймеры, макросы)


SHOROOP

Освоившийся
Пользователь
30.01.2014
58
56
0
32
Скриптер
Доброго времени суток.

В сегодняшнем уроке предлагаю немного разобрать сразу две темы - использование директив препроцессора (а конкретно - макросов) и использование таймеров. Ну и, конечно, не на абстрактном примере, а сразу в написании чего-либо полезного. Например - для запрета DM в определенных зонах.

Начнем с директив препроцессора.

Собственно, препроцессор - служебный программный инструмент, осуществляющий конвертацию кода программы в промежуточный для последующей компиляции в бинарный файл.  Директива препроцессора - строка в исходном коде вида #КлючевоеСловоПрепроцессора, определяющая дальнейшее поведение препроцессора и использование им неких служебных функций.

Макрос (#define) - определенная директива препроцессора, позволяющая при компиляции кода использовать замену исходного выражения на некоторое другое. Это крайне удобно, когда необходимо часто использовать какое-либо достаточно объемное выражение, но нет желания оборачивать его в отдельную функцию. 

Яркий пример - нахождение большего из двух чисел:

#define max(a,b) ((a) > (b) ? (a) : (b))Перейдем к таймерам.AMX-машина может вызывать определенные функции в объявленные программистом периоды времени. Какой из функций языка Pawn будет вызван такой таймер - зависит уже от функции:

  • Если вызываемая функция не имеет параметров - используется функция SetTimer:
    SetTimer (funcname[], interval, bool:repeating)Где funcname - имя вызываемой функции, interval - время следующего запуска функции в миллисекундах, repeating - повторять ли запуск функции или нет.
  • Если вызываемая функция имеет параметры - используется функция SetTimerEx:
    Код:
    SetTimerEx (funcname[], interval, repeating, const format[], {Float,_}:...)
    Первые три параметра - аналогичны SetTimer. Далее идет строка, в которой задаются типы передаваемых переменных, после нее - все необходимые для передачи вызываемой функции параметры.
Обе эти функции возвращают целочисленный ID созданного таймера. Прибить такой таймер можно вызовом функции KillTimer.
Наверное, на этом с теорией можно покончить и перейти к практике.

Итак, поставим перед собой задачу: написать скрипт, отслеживающий нажатие игроком определенных кнопок, если он находится в определенной зоне. Если игрок в зоне и нажимает на эти кнопки - увеличить счетчик на единицу, если счетчик больше двух и четырех - вывести определенные сообщения, если больше пяти - закрыть соединение.

Подключим библиотеку a_samp, более нам ничего не нужно. И сразу же объявим следующий макрос:

#define PRESSED(%0) (((newkeys & (%0)) == (%0)) && ((oldkeys & (%0)) != (%0)))Данный макрос помогает обернуть достаточно длинное выражение, которое, по сути, означает лишь одно - проверку на нажатие кнопки. Если будет необходимо, в следующих уроках я могу разобрать, как работает обработчик нажатия клавиш OnPlayerKeyStateChange.
Далее - объявим необходимые для работы переменные. Во-первых - отдельную структуру для хранения зон, состоящую из четырех чисел с плавающей точкой - трех координат и радиуса зоны. Также нужны массивы для счетчика нажатий и для всех зон.

new DM_Counter[MAX_PLAYERS];

enum DM_Checker
{
Float:X,
Float:Y,
Float:Z,
Float:Radius
}
new NoDM_Zones[][DM_Checker] =
{
{2549.0840,-2204.8350,21.9583,45.0},// 8 БИТ
{175.3027,784.5325,12.0010,27.0},//Перекрёсток
{2130.7192,-2182.4277,21.9545,40.0},//Автошкола Южный
{2745.0464,-2294.7100,17.6124,40.0},//ШтрафСтоянка
{1908.3670654297,-2233.1806640625,10.894914627075,45.0},//Мэрия
{2343.427734375,-1809.4577636719,22.09578704834,60.0},//АвтоСалон
{2509.3679199219,-2126.5373535156,23.105073928833,20.0}//Респаун новичков
};Координаты и радиусы приведены для примера.
Во время старта скрипта нам нужно запустить таймер сброса счетчика. Для этого необходима отдельная функция, которая будет доступна из любого места вызова, то есть - с публичным доступом, или public. Ее и объявим. Алгоритм простой - пробег по всему массиву DM_Counter, если в ячейке массива не ноль - обнулить.

forward DM_Counter_reset();
public DM_Counter_reset ()
{
for (new i=0; i<MAX_PLAYERS; i++)
{
if (DM_Counter!=0) SendClientMessage (i, 0x00FF0000, "Счётчик попыток DM сброшен.");
DM_Counter=0;
}
}Важно поднять прототип функции (forward DM_Counter_reset) выше места вызова самой функции и других функций, из которых она может быть вызвана.Запускаем таймер при старте бинарника (для примера - OnFilterScriptInit)

public OnFilterScriptInit ()
{
SetTimer("DM_Counter_reset", 10*1000, true);
return 1;
}Очень важно очистить данные перед подключением игрока. Не забудем это сделать в OnPlayerConnect.
Код:
public OnPlayerConnect(playerid)
{
    DM_Counter[playerid]=0;
    return 1;
}
Подготовка завершена.
Отслеживание нажатия кнопок проходит в автовызываемой функции (коллбэке) OnPlayerKeyStateChange. Его мы и будем рассматривать.

Для начала - переберем все возможные условия, которые нам нужны. Это:

  • Нажатие ЛКМ (KEY_FIRE);
  • Нажатие ЛКМ вместе с ПКМ (KEY_FIRE | KEY_HANDBRAKE);
  • Нажатие ПКМ и F (KEY_SECONDARY_ATTACK | KEY_HANDBRAKE).
Если такие клавиши нажаты - проверяем все зоны на присутствие в них игрока. Сделаем это с помощью IsPlayerInRangeOfPoint и пробежим циклом по всем зонам. Если игрок в зоне - завершаем цикл, увеличиваем счетчик предупреждений на единицу. Больше пяти? Закрываем соединение.
public OnPlayerKeyStateChange(playerid, newkeys, oldkeys)
{
if(PRESSED (KEY_FIRE) || PRESSED (KEY_FIRE | KEY_HANDBRAKE) || PRESSED (KEY_SECONDARY_ATTACK | KEY_HANDBRAKE))
{
for(new i=0;i<sizeof(NoDM_Zones);i++)
{
if(IsPlayerInRangeOfPoint(playerid,NoDM_Zones[Radius],NoDM_Zones[X],NoDM_Zones[Y],NoDM_Zones[Z]))
{
if (DM_Counter[playerid] == 5)
{
new name[MAX_PLAYER_NAME], string[64+MAX_PLAYER_NAME];
GetPlayerName(playerid, name, sizeof(name));
format(string, sizeof(string), "Игрок %s был кикнут сервером. Причина: DM в зеленой зоне", name);
SendClientMessageToAll(0xAA3333FF, string);
Kick (playerid);
}
else
{
DM_Counter[playerid] = DM_Counter[playerid]+1;
if (DM_Counter[playerid]>=2) SendClientMessage (playerid, 0xFFFF0000, "DM в зеленой зоне запрещен!");
if (DM_Counter[playerid]>=4) SendClientMessage (playerid, 0xFFFF0000, "При дальнейших попытках DM Вы будете наказаны.");
}
}
}
}
return 1;
}
К сожалению, табуляция нещадно съехала, кому нужно - выправите самостоятельно.

На этом все. Скрипт уже работоспособен.

Я понимаю, что это, возможно, не самое удачное решение, но оно как нельзя лучше подходит для урока.

Вопросы и пожелания принимаются в этой теме.