Доброго времени суток.
В сегодняшнем уроке предлагаю немного разобрать сразу две темы - использование директив препроцессора (а конкретно - макросов) и использование таймеров. Ну и, конечно, не на абстрактном примере, а сразу в написании чего-либо полезного. Например - для запрета DM в определенных зонах.
Начнем с директив препроцессора.
Собственно, препроцессор - служебный программный инструмент, осуществляющий конвертацию кода программы в промежуточный для последующей компиляции в бинарный файл. Директива препроцессора - строка в исходном коде вида #КлючевоеСловоПрепроцессора, определяющая дальнейшее поведение препроцессора и использование им неких служебных функций.
Макрос (#define) - определенная директива препроцессора, позволяющая при компиляции кода использовать замену исходного выражения на некоторое другое. Это крайне удобно, когда необходимо часто использовать какое-либо достаточно объемное выражение, но нет желания оборачивать его в отдельную функцию.
Яркий пример - нахождение большего из двух чисел:
#define max(a,b) ((a) > (b) ? (a) : (b))Перейдем к таймерам.AMX-машина может вызывать определенные функции в объявленные программистом периоды времени. Какой из функций языка Pawn будет вызван такой таймер - зависит уже от функции:
Наверное, на этом с теорией можно покончить и перейти к практике.
Итак, поставим перед собой задачу: написать скрипт, отслеживающий нажатие игроком определенных кнопок, если он находится в определенной зоне. Если игрок в зоне и нажимает на эти кнопки - увеличить счетчик на единицу, если счетчик больше двух и четырех - вывести определенные сообщения, если больше пяти - закрыть соединение.
Подключим библиотеку 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.
Подготовка завершена.
Отслеживание нажатия кнопок проходит в автовызываемой функции (коллбэке) OnPlayerKeyStateChange. Его мы и будем рассматривать.
Для начала - переберем все возможные условия, которые нам нужны. Это:
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;
}
В сегодняшнем уроке предлагаю немного разобрать сразу две темы - использование директив препроцессора (а конкретно - макросов) и использование таймеров. Ну и, конечно, не на абстрактном примере, а сразу в написании чего-либо полезного. Например - для запрета 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,_}:...)
Наверное, на этом с теорией можно покончить и перейти к практике.
Итак, поставим перед собой задачу: написать скрипт, отслеживающий нажатие игроком определенных кнопок, если он находится в определенной зоне. Если игрок в зоне и нажимает на эти кнопки - увеличить счетчик на единицу, если счетчик больше двух и четырех - вывести определенные сообщения, если больше пяти - закрыть соединение.
Подключим библиотеку 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).
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;
}
К сожалению, табуляция нещадно съехала, кому нужно - выправите самостоятельно.
На этом все. Скрипт уже работоспособен.
Я понимаю, что это, возможно, не самое удачное решение, но оно как нельзя лучше подходит для урока.
Вопросы и пожелания принимаются в этой теме.
На этом все. Скрипт уже работоспособен.
Я понимаю, что это, возможно, не самое удачное решение, но оно как нельзя лучше подходит для урока.
Вопросы и пожелания принимаются в этой теме.