whale
03.06.2017, 22:00
Доброго времени суток, уважаемые пользователи портала Pro-Pawn.
Сегодня я хотел бы представить Вам недавно написанную мной работу - телепорт для администрации.
Но, телепорт весьма необычный.
В чём особенность системы?:
— Использование динамических зон (streamer).
— Полная автоматизация работы.
— От Вас требуется только занести новые данные в массив.
— Важно: код не рассчитан на новичков. Комментарии и объяснения в данной теме будут, но разжёвывать каждую строку кода - слишком кропотливое занятие, знаете ли.
— Принцип работы системы:
1. Создаётся динамическая прямоугольная зона (CreateDynamicRectangle)
Примечание: для получения минимальных и максимальных координат рекомендую использовать данную программу (SAMP Zone Editor): Жми меня (http://pro-pawn.ru/showthread.php?7280-SAMP-Zone-Editor)
2. Когда игрок входит в динамическую зону (OnPlayerEnterDynamicArea) - с помощью foreach перебираются все зоны из итератора - соответственно инкрементируется переменная, которая хранит количество игроков в указанной зоне. После чего идёт проверка - имеет ли зона статус "зелёной" (anti-dm zone). Если зона такова - переменной игрока (p_green_zone_status[MAX_PLAYERS char]) присваивается значение 1 (т.е игрок находится в "зелёной" зоне).
С выходом из зоны всё аналогично.
3. Если игрок пытается стрелять в "зелёной" зоне - ему 2 раза выводится предупреждение и его замораживает, на 3-й раз игрока отключает от сервера.
4. А что касаемо самого диалогового окна: в первом отображаются разделы, во втором - зоны, которые относятся к указанному разделу. Помимо их наименований указывается расстояние до точки телепорта (x,y,z), количество игроков в данной зоне и является ли зона "зелёной" (anti-dm zone).
Надеюсь, что принцип работы я объяснил доступно.
Так же рекомендую посмотреть несколько скриншотов ниже, после чего мы перейдём непосредственно к коду.
https://s8.hostingkartinok.com/uploads/images/2017/06/79271fc3ed34f2bf332c82558353823c.png (https://hostingkartinok.com/show-image.php?id=79271fc3ed34f2bf332c82558353823c)
https://s8.hostingkartinok.com/uploads/images/2017/06/bc0f866707bb929420a8d2de53db0b1e.png (https://hostingkartinok.com/show-image.php?id=bc0f866707bb929420a8d2de53db0b1e)
https://s8.hostingkartinok.com/uploads/images/2017/06/e7b3ac158f4589c1fdf35b34bbff835d.png (https://hostingkartinok.com/show-image.php?id=e7b3ac158f4589c1fdf35b34bbff835d)
https://s8.hostingkartinok.com/uploads/images/2017/06/2927aabdca16379dec0fba24db946b75.png (https://hostingkartinok.com/show-image.php?id=2927aabdca16379dec0fba24db946b75)
— Перейдём непосредственно к коду.
Для начала, нам понадобится несколько констант, которые мы будем использовать в дальнейшем:
const
MAX_ADMIN_TELEPORTS = 2, // Максимальное количество телепортов. При добавлении нового телепорта инкрементируйте данное значение (добавляйте +1 к значению).
MAX_ADMIN_TELEPORT_NAME = 17, // Максимальное количество символов в названии телепорта
MAX_AT_SECTIONS_NAME = 28; // Максимальное количество символов в названии раздела телепортов
Так же рекомендую создать enum для названий диалогов, дабы не путать их. Либо же представить их как макросы:
enum e_DIALOG_IDS
{
dAdmin_TpMenu_Main,
dAdmin_TpMenu_PopularPlaces
};
#define dAdmin_TpMenu_Main 1
#define dAdmin_TpMenu_PopularPlaces 2
Далее нам потребуется создать ещё один enum, который будет хранить в себе идентификатор зоны и количество игроков в ней.
enum e_ADMIN_TELEPORT_INFO
{
atDynamicArea, // Идентификатор динамической зоны
atNumberOfPlayers // Количество игроков в динамической зоне
};
new atInfo[MAX_ADMIN_TELEPORTS][e_ADMIN_TELEPORT_INFO];
Для дальнейшего использования нам понадобится ещё один enum, который будет хранить в себе перечисление типов для разделов и, собственно говоря, массив, который мы будем заполнять данной информацией (названия "разделов"):
enum _:e_AT_SECTIONS_INFO
{
POPULAR_PLACES
};
new atSectionsInfo[e_AT_SECTIONS_INFO][MAX_AT_SECTIONS_NAME] =
{
// В данный массив, при добавлении разделов, записывайте их названия
{"Часто посещаемые места"}
};
Ну и, по сути, основная часть данного кода - enum, который будет заполняться данными о динамической зоне из массива и сам массив:
enum e_ADMIN_TELEPORT_AREAS_INFO
{
atType, // Тип телепорта (зоны). Пример: POPULAR_PLACES
atName[MAX_ADMIN_TELEPORT_NAME], // Название зоны, которые будет указано в диалоговом окне
atSecondName[MAX_ADMIN_TELEPORT_NAME], // Название зоны, которое будет указано при входе в зону
Float:atTelepPosX, // Координата X для телепорта
Float:atTelepPosY, // Координата Y для телепорта
Float:atTelepPosZ, // Координата Z для телепорта
Float:atTelepPosA, // Угол поворота для телепорта
Float:atMinPosX, // Минимальная координата X
Float:atMinPosY, // Минимальная координата Y
Float:atMaxPosX, // Максимальная координата X
Float:atMaxPosY, // Максимальная координата Y
atTelepVirtualWorld, // Виртуальный мир, который распостранится на динамическую зону
atTelepInterior, // Интерьер динамической зоны
atGreenZone // "Зелёная" (anti-dm) зона. Значения: 0 и 1. 0 - зона не является "зелёной". 1 - зона является "зелёной".
};
// В массив ниже Вы и будете заносить новые данные :)
new atData[MAX_ADMIN_TELEPORTS][e_ADMIN_TELEPORT_AREAS_INFO] =
// Желательно придерживаться табличного вида, дабы самим потом было проще ориентироваться в массиве (на форуме этот код, скорее всего, превратится в кашу, но суть в том, чтоб все данные разделить по своим колонкам)
// Ну, а если Вы видите данный код не "кашей" - значит я убил много времени, чтобы его отредактировать :)
{
// atType atName atSecondName tpX tpY tpZ tpA min_posX min_posY max_posX max_posY VW Int GZ
{POPULAR_PLACES, "Мэрия LS", "Мэрии LS", 1480.9125, -1723.8544, 13.5469, 179.5673, 1433.0, -1599.0, 1529.0, -1759.0, 0, 0, 1},
{POPULAR_PLACES, "ЖДЛС", "ЖДЛС", 1581.7778, -1618.1624, 13.3828, 192.1007, 1810.7983, -1881.51780, 1759.7397, -1942.9968, 0, 0, 1}
};
И последнее, что мы объявим в данном мануале - итератор и переменную:
new
Iterator:AdminTeleportAreas<MAX_ADMIN_TELEPORTS>, // Итератор, в котором хранятся созданые динамические зоны
p_green_zone_status[MAX_PLAYERS char];
— Паблики, стоки, команды...
Теперь, когда мы закончили объявлять всё, что нам нужно, - перейдём непосредственно к использованию "этого всего".
С данного момента комментариев будет гораздо меньше, так как разжёвывать каждую строку кода - достаточно нудное занятие. А если учесть, что система, скажем так, не рассчитана на "ньюфагов" (как было написано выше) - так тем более.
Для начала разберёмся с public OnGameModeInit(). В данный паблик мы поместим загрузку наших динамических зон.
(Под спойлером так же будет stock-функция, которую мы вызываем в public OnGameModeInit())
Сам stock с загрузкой динамических зон, собственно, Вы можете поместить в конец мода, либо в другое, удобное для Вас место.
public OnGameModeInit()
{
LoadAdminTeleports(); // Загружаем динамические зоны при запуске сервера
return 1;
}
stock LoadAdminTeleports()
{
for(new i; i < MAX_ADMIN_TELEPORTS; i++)
{
Iter_Add(AdminTeleportAreas, i);
atInfo[i][atDynamicArea] = CreateDynamicRectangle(atData[i][atMinPosX], atData[i][atMinPosY], atData[i][atMaxPosX], atData[i][atMaxPosY], 0, 0, -1);
}
printf("TOTAL_ADMIN_AREAS_LOADED: %d\n", Iter_Count(AdminTeleportAreas));
return 1;
}
Далее мы рассмотрим 2 паблика: OnPlayerEnterDynamicArea и OnPlayerLeaveDynamicArea:
public OnPlayerEnterDynamicArea(playerid, areaid) // Когда игрок входит в динамическую зону
{
foreach(new i: AdminTeleportAreas) // Перебираем все созданные нами зоны
{
if(areaid == atInfo[i][atDynamicArea]) // Если идентификатор динамической зоны совпадает с одним из идентификатором наших зон - идём дальше
{
atInfo[i][atNumberOfPlayers]++; // Инкрементируем количество игроков в указанной динамической зоне
if(atData[i][atGreenZone] == 1) // Если зона, в которую вошёл игрок, является "зелёной" (anti-dm)
{
p_green_zone_status{playerid} = 1; // Устанавливаем игроку статус "находится в зелёной зоне"
SetPVarInt(playerid, "damage_in_green_zone", 0); // Устанавливаем игроку количество попыток нанести урон в "зелёной зоне" на 0
}
return 1;
}
}
return 1;
}
public OnPlayerLeaveDynamicArea(playerid, areaid) // Когда игрок покидает динамическую зону
{
foreach(new i: AdminTeleportAreas) // Перебираем все созданные нами зоны
{
if(areaid == atInfo[i][atDynamicArea]) // Если идентификатор динамической зоны совпадает с одним из идентификатором наших зон - идём дальше
{
atInfo[i][atNumberOfPlayers]--; // Декрементируем количество игроков в указанной динамической зоне
if(atData[i][atGreenZone] == 1) // Если зона, из которой вышел игрок, является "зелёной" (anti-dm)
{
p_green_zone_status{playerid} = 0; // Устанавливаем игроку статус "не находится в зелёной зоне"
DeletePVar(playerid, "damage_in_green_zone"); // Удаляем PVar с информацией о количестве попыток нанести урон в "зелёной зоне"
}
return 1;
}
}
return 1;
}
Далее мы так же рассмотрим public OnPlayerWeaponShot - когда игрок стреляет из оружия:
public OnPlayerWeaponShot(playerid, weaponid, hittype, hitid, Float:fX, Float:fY, Float:fZ)
{
if(p_green_zone_status{playerid} == 1) // Если игрок находится в "зелёной зоне"
{
if(GetPVarInt(playerid, "damage_in_green_zone") < 2) // Если попыток нанести урон игрокам меньше 2 (0 или 1)
{
SendClientMessage(playerid, -1, !"SERVER: На данной территории запрещено использовать оружие!"); // Выводим игроку предупреждение
TogglePlayerControllable(playerid, false); // Отключаем игркоу управление персонажем
SetTimerEx("UnFreezePlayer", 3500, false, "d", playerid); // Запускаем таймер для "разморозки" игрока на 3500мс (3.5 секунд)
SetPVarInt(playerid, "damage_in_green_zone", GetPVarInt(playerid, "damage_in_green_zone")+1); // Инкрементируем количество попыток нанести урон
}
else // Если же количество попыток нанести урон == 2 (тоесть это уже будет 3 попытка)
{
SendClientMessage(playerid, -1, !"Вы были кикнуты. Причина: многократное использование оружия в зелёных зонах!"); // Выводим игроку предупреждение о том, что он был кикнут
Kick(playerid); // Кикаем игрока
}
}
return 1;
}
forward UnFreezePlayer(playerid);
public UnFreezePlayer(playerid) // Паблик "разморозки" игрока
return TogglePlayerControllable(playerid, true);
Теперь можно перейти к самой команде, ради которой, по сути, всё это и было затеяно:
CMD:teleport(playerid, params[])
{
ShowAdminTeleportDialog(playerid);
return 1;
}
stock ShowAdminTeleportDialog(playerid)
{
DeletePVar(playerid, "atSectionID");
new
dialog_string[(3+sizeof(atSectionsInfo[]))*sizeof(atSectionsInfo)+1];
for(new i; i < sizeof(atSectionsInfo); i++)
{
format(dialog_string, sizeof(dialog_string), "%s— %s\n", dialog_string, atSectionsInfo[i]);
}
ShowPlayerDialog(playerid, dAdmin_TpMenu_Main, DIALOG_STYLE_LIST, "Телепорт администрации", dialog_string, "Выбрать", "Отмена");
return 1;
}
Ну и, наконец-то мы подходим к концу. Осталось лишь разобраться с самими диалогами - public OnDialogResponse:
- Примечание: в данном мануале рассматривается вариант со switch-ветвлением, а не условиями if / else.
public OnDialogResponse(playerid, dialogid, response, listitem, inputtext[])
{
switch(dialogid)
{
case dAdmin_TpMenu_Main:
{
if(!response)
return 1;
SetPVarInt(playerid, "atSectionID", listitem);
static
dialog_string[(63+MAX_ADMIN_TELEPORT_NAME+(11+3)+3)*MAX_ADMIN_TELEPORTS+1],
header_string[30+sizeof(atSectionsInfo[])+1];
dialog_string = "Наименование\tРасстояние до точки\tИгроков\tЗелёная зона\n";//44
for(new i; i < MAX_ADMIN_TELEPORTS; i++)
{
if(atData[i][atType] != listitem)
continue;
format(dialog_string, sizeof(dialog_string), "%s%s\t%.2fм\t%d\t%s\n",
dialog_string,
atData[i][atName],
GetPlayerDistanceFromPoint(playerid, atData[i][atTelepPosX], atData[i][atTelepPosY], atData[i][atTelepPosZ]),
atInfo[i][atNumberOfPlayers], (atData[i][atGreenZone] ? "Да" : "Нет"));
}
header_string = "Телепорт администрации: ";
strcat(header_string, atSectionsInfo[listitem]);
ShowPlayerDialog(playerid, dAdmin_TpMenu_PopularPlaces, DIALOG_STYLE_TABLIST_HEADERS, header_string, dialog_string, "Выбрать", "Назад");
return 1;
}
case dAdmin_TpMenu_PopularPlaces:
{
if(!response)
return ShowAdminTeleportDialog(playerid);
for(new i, num; i < MAX_ADMIN_TELEPORTS; i++)
{
if(atData[i][atType] != GetPVarInt(playerid, "atSectionID"))
continue;
if(num++ == listitem)
{
SetPlayerPosEx(playerid, atData[i][atTelepPosX], atData[i][atTelepPosY], atData[i][atTelepPosZ]);
if(floatcmp(atData[i][atTelepPosA], 0.0) == 1)
{
SetPlayerFacingAngle(playerid, atData[i][atTelepPosA]);
}
SetCameraBehindPlayer(playerid);
break;
}
}
return 1;
}
}
return 1;
}
Полный код (pastebin): https://pastebin.com/HxUvBeiN
Всех, кто уделил своё личное время и прочёл данную тему до конца - благодарю за внимание :)
Надеюсь, что я ничего не забыл и написал всё, что хотел.
P.S Это моя первая тема на данном портале и первый мануал с подобным объёмом. Так что, очень прошу не сильно ругаться/кричать, если заметите какие-либо недочёты. Так же жду комментариев, адекватной, обоснованной критики и помощи.
Ещё раз благодарю за внимание.
Так же благодарность за помощь с некоторыми участками кода выражаю: DeIMoS`у
https://pp.userapi.com/c639116/v639116410/29f7b/qqFTi1ZLnfs.jpg
Обновление от 04.06.2017: Релиз include-версии.
Download (include): https://yadi.sk/d/fyrfBkyq3JoHUe
Сегодня я хотел бы представить Вам недавно написанную мной работу - телепорт для администрации.
Но, телепорт весьма необычный.
В чём особенность системы?:
— Использование динамических зон (streamer).
— Полная автоматизация работы.
— От Вас требуется только занести новые данные в массив.
— Важно: код не рассчитан на новичков. Комментарии и объяснения в данной теме будут, но разжёвывать каждую строку кода - слишком кропотливое занятие, знаете ли.
— Принцип работы системы:
1. Создаётся динамическая прямоугольная зона (CreateDynamicRectangle)
Примечание: для получения минимальных и максимальных координат рекомендую использовать данную программу (SAMP Zone Editor): Жми меня (http://pro-pawn.ru/showthread.php?7280-SAMP-Zone-Editor)
2. Когда игрок входит в динамическую зону (OnPlayerEnterDynamicArea) - с помощью foreach перебираются все зоны из итератора - соответственно инкрементируется переменная, которая хранит количество игроков в указанной зоне. После чего идёт проверка - имеет ли зона статус "зелёной" (anti-dm zone). Если зона такова - переменной игрока (p_green_zone_status[MAX_PLAYERS char]) присваивается значение 1 (т.е игрок находится в "зелёной" зоне).
С выходом из зоны всё аналогично.
3. Если игрок пытается стрелять в "зелёной" зоне - ему 2 раза выводится предупреждение и его замораживает, на 3-й раз игрока отключает от сервера.
4. А что касаемо самого диалогового окна: в первом отображаются разделы, во втором - зоны, которые относятся к указанному разделу. Помимо их наименований указывается расстояние до точки телепорта (x,y,z), количество игроков в данной зоне и является ли зона "зелёной" (anti-dm zone).
Надеюсь, что принцип работы я объяснил доступно.
Так же рекомендую посмотреть несколько скриншотов ниже, после чего мы перейдём непосредственно к коду.
https://s8.hostingkartinok.com/uploads/images/2017/06/79271fc3ed34f2bf332c82558353823c.png (https://hostingkartinok.com/show-image.php?id=79271fc3ed34f2bf332c82558353823c)
https://s8.hostingkartinok.com/uploads/images/2017/06/bc0f866707bb929420a8d2de53db0b1e.png (https://hostingkartinok.com/show-image.php?id=bc0f866707bb929420a8d2de53db0b1e)
https://s8.hostingkartinok.com/uploads/images/2017/06/e7b3ac158f4589c1fdf35b34bbff835d.png (https://hostingkartinok.com/show-image.php?id=e7b3ac158f4589c1fdf35b34bbff835d)
https://s8.hostingkartinok.com/uploads/images/2017/06/2927aabdca16379dec0fba24db946b75.png (https://hostingkartinok.com/show-image.php?id=2927aabdca16379dec0fba24db946b75)
— Перейдём непосредственно к коду.
Для начала, нам понадобится несколько констант, которые мы будем использовать в дальнейшем:
const
MAX_ADMIN_TELEPORTS = 2, // Максимальное количество телепортов. При добавлении нового телепорта инкрементируйте данное значение (добавляйте +1 к значению).
MAX_ADMIN_TELEPORT_NAME = 17, // Максимальное количество символов в названии телепорта
MAX_AT_SECTIONS_NAME = 28; // Максимальное количество символов в названии раздела телепортов
Так же рекомендую создать enum для названий диалогов, дабы не путать их. Либо же представить их как макросы:
enum e_DIALOG_IDS
{
dAdmin_TpMenu_Main,
dAdmin_TpMenu_PopularPlaces
};
#define dAdmin_TpMenu_Main 1
#define dAdmin_TpMenu_PopularPlaces 2
Далее нам потребуется создать ещё один enum, который будет хранить в себе идентификатор зоны и количество игроков в ней.
enum e_ADMIN_TELEPORT_INFO
{
atDynamicArea, // Идентификатор динамической зоны
atNumberOfPlayers // Количество игроков в динамической зоне
};
new atInfo[MAX_ADMIN_TELEPORTS][e_ADMIN_TELEPORT_INFO];
Для дальнейшего использования нам понадобится ещё один enum, который будет хранить в себе перечисление типов для разделов и, собственно говоря, массив, который мы будем заполнять данной информацией (названия "разделов"):
enum _:e_AT_SECTIONS_INFO
{
POPULAR_PLACES
};
new atSectionsInfo[e_AT_SECTIONS_INFO][MAX_AT_SECTIONS_NAME] =
{
// В данный массив, при добавлении разделов, записывайте их названия
{"Часто посещаемые места"}
};
Ну и, по сути, основная часть данного кода - enum, который будет заполняться данными о динамической зоне из массива и сам массив:
enum e_ADMIN_TELEPORT_AREAS_INFO
{
atType, // Тип телепорта (зоны). Пример: POPULAR_PLACES
atName[MAX_ADMIN_TELEPORT_NAME], // Название зоны, которые будет указано в диалоговом окне
atSecondName[MAX_ADMIN_TELEPORT_NAME], // Название зоны, которое будет указано при входе в зону
Float:atTelepPosX, // Координата X для телепорта
Float:atTelepPosY, // Координата Y для телепорта
Float:atTelepPosZ, // Координата Z для телепорта
Float:atTelepPosA, // Угол поворота для телепорта
Float:atMinPosX, // Минимальная координата X
Float:atMinPosY, // Минимальная координата Y
Float:atMaxPosX, // Максимальная координата X
Float:atMaxPosY, // Максимальная координата Y
atTelepVirtualWorld, // Виртуальный мир, который распостранится на динамическую зону
atTelepInterior, // Интерьер динамической зоны
atGreenZone // "Зелёная" (anti-dm) зона. Значения: 0 и 1. 0 - зона не является "зелёной". 1 - зона является "зелёной".
};
// В массив ниже Вы и будете заносить новые данные :)
new atData[MAX_ADMIN_TELEPORTS][e_ADMIN_TELEPORT_AREAS_INFO] =
// Желательно придерживаться табличного вида, дабы самим потом было проще ориентироваться в массиве (на форуме этот код, скорее всего, превратится в кашу, но суть в том, чтоб все данные разделить по своим колонкам)
// Ну, а если Вы видите данный код не "кашей" - значит я убил много времени, чтобы его отредактировать :)
{
// atType atName atSecondName tpX tpY tpZ tpA min_posX min_posY max_posX max_posY VW Int GZ
{POPULAR_PLACES, "Мэрия LS", "Мэрии LS", 1480.9125, -1723.8544, 13.5469, 179.5673, 1433.0, -1599.0, 1529.0, -1759.0, 0, 0, 1},
{POPULAR_PLACES, "ЖДЛС", "ЖДЛС", 1581.7778, -1618.1624, 13.3828, 192.1007, 1810.7983, -1881.51780, 1759.7397, -1942.9968, 0, 0, 1}
};
И последнее, что мы объявим в данном мануале - итератор и переменную:
new
Iterator:AdminTeleportAreas<MAX_ADMIN_TELEPORTS>, // Итератор, в котором хранятся созданые динамические зоны
p_green_zone_status[MAX_PLAYERS char];
— Паблики, стоки, команды...
Теперь, когда мы закончили объявлять всё, что нам нужно, - перейдём непосредственно к использованию "этого всего".
С данного момента комментариев будет гораздо меньше, так как разжёвывать каждую строку кода - достаточно нудное занятие. А если учесть, что система, скажем так, не рассчитана на "ньюфагов" (как было написано выше) - так тем более.
Для начала разберёмся с public OnGameModeInit(). В данный паблик мы поместим загрузку наших динамических зон.
(Под спойлером так же будет stock-функция, которую мы вызываем в public OnGameModeInit())
Сам stock с загрузкой динамических зон, собственно, Вы можете поместить в конец мода, либо в другое, удобное для Вас место.
public OnGameModeInit()
{
LoadAdminTeleports(); // Загружаем динамические зоны при запуске сервера
return 1;
}
stock LoadAdminTeleports()
{
for(new i; i < MAX_ADMIN_TELEPORTS; i++)
{
Iter_Add(AdminTeleportAreas, i);
atInfo[i][atDynamicArea] = CreateDynamicRectangle(atData[i][atMinPosX], atData[i][atMinPosY], atData[i][atMaxPosX], atData[i][atMaxPosY], 0, 0, -1);
}
printf("TOTAL_ADMIN_AREAS_LOADED: %d\n", Iter_Count(AdminTeleportAreas));
return 1;
}
Далее мы рассмотрим 2 паблика: OnPlayerEnterDynamicArea и OnPlayerLeaveDynamicArea:
public OnPlayerEnterDynamicArea(playerid, areaid) // Когда игрок входит в динамическую зону
{
foreach(new i: AdminTeleportAreas) // Перебираем все созданные нами зоны
{
if(areaid == atInfo[i][atDynamicArea]) // Если идентификатор динамической зоны совпадает с одним из идентификатором наших зон - идём дальше
{
atInfo[i][atNumberOfPlayers]++; // Инкрементируем количество игроков в указанной динамической зоне
if(atData[i][atGreenZone] == 1) // Если зона, в которую вошёл игрок, является "зелёной" (anti-dm)
{
p_green_zone_status{playerid} = 1; // Устанавливаем игроку статус "находится в зелёной зоне"
SetPVarInt(playerid, "damage_in_green_zone", 0); // Устанавливаем игроку количество попыток нанести урон в "зелёной зоне" на 0
}
return 1;
}
}
return 1;
}
public OnPlayerLeaveDynamicArea(playerid, areaid) // Когда игрок покидает динамическую зону
{
foreach(new i: AdminTeleportAreas) // Перебираем все созданные нами зоны
{
if(areaid == atInfo[i][atDynamicArea]) // Если идентификатор динамической зоны совпадает с одним из идентификатором наших зон - идём дальше
{
atInfo[i][atNumberOfPlayers]--; // Декрементируем количество игроков в указанной динамической зоне
if(atData[i][atGreenZone] == 1) // Если зона, из которой вышел игрок, является "зелёной" (anti-dm)
{
p_green_zone_status{playerid} = 0; // Устанавливаем игроку статус "не находится в зелёной зоне"
DeletePVar(playerid, "damage_in_green_zone"); // Удаляем PVar с информацией о количестве попыток нанести урон в "зелёной зоне"
}
return 1;
}
}
return 1;
}
Далее мы так же рассмотрим public OnPlayerWeaponShot - когда игрок стреляет из оружия:
public OnPlayerWeaponShot(playerid, weaponid, hittype, hitid, Float:fX, Float:fY, Float:fZ)
{
if(p_green_zone_status{playerid} == 1) // Если игрок находится в "зелёной зоне"
{
if(GetPVarInt(playerid, "damage_in_green_zone") < 2) // Если попыток нанести урон игрокам меньше 2 (0 или 1)
{
SendClientMessage(playerid, -1, !"SERVER: На данной территории запрещено использовать оружие!"); // Выводим игроку предупреждение
TogglePlayerControllable(playerid, false); // Отключаем игркоу управление персонажем
SetTimerEx("UnFreezePlayer", 3500, false, "d", playerid); // Запускаем таймер для "разморозки" игрока на 3500мс (3.5 секунд)
SetPVarInt(playerid, "damage_in_green_zone", GetPVarInt(playerid, "damage_in_green_zone")+1); // Инкрементируем количество попыток нанести урон
}
else // Если же количество попыток нанести урон == 2 (тоесть это уже будет 3 попытка)
{
SendClientMessage(playerid, -1, !"Вы были кикнуты. Причина: многократное использование оружия в зелёных зонах!"); // Выводим игроку предупреждение о том, что он был кикнут
Kick(playerid); // Кикаем игрока
}
}
return 1;
}
forward UnFreezePlayer(playerid);
public UnFreezePlayer(playerid) // Паблик "разморозки" игрока
return TogglePlayerControllable(playerid, true);
Теперь можно перейти к самой команде, ради которой, по сути, всё это и было затеяно:
CMD:teleport(playerid, params[])
{
ShowAdminTeleportDialog(playerid);
return 1;
}
stock ShowAdminTeleportDialog(playerid)
{
DeletePVar(playerid, "atSectionID");
new
dialog_string[(3+sizeof(atSectionsInfo[]))*sizeof(atSectionsInfo)+1];
for(new i; i < sizeof(atSectionsInfo); i++)
{
format(dialog_string, sizeof(dialog_string), "%s— %s\n", dialog_string, atSectionsInfo[i]);
}
ShowPlayerDialog(playerid, dAdmin_TpMenu_Main, DIALOG_STYLE_LIST, "Телепорт администрации", dialog_string, "Выбрать", "Отмена");
return 1;
}
Ну и, наконец-то мы подходим к концу. Осталось лишь разобраться с самими диалогами - public OnDialogResponse:
- Примечание: в данном мануале рассматривается вариант со switch-ветвлением, а не условиями if / else.
public OnDialogResponse(playerid, dialogid, response, listitem, inputtext[])
{
switch(dialogid)
{
case dAdmin_TpMenu_Main:
{
if(!response)
return 1;
SetPVarInt(playerid, "atSectionID", listitem);
static
dialog_string[(63+MAX_ADMIN_TELEPORT_NAME+(11+3)+3)*MAX_ADMIN_TELEPORTS+1],
header_string[30+sizeof(atSectionsInfo[])+1];
dialog_string = "Наименование\tРасстояние до точки\tИгроков\tЗелёная зона\n";//44
for(new i; i < MAX_ADMIN_TELEPORTS; i++)
{
if(atData[i][atType] != listitem)
continue;
format(dialog_string, sizeof(dialog_string), "%s%s\t%.2fм\t%d\t%s\n",
dialog_string,
atData[i][atName],
GetPlayerDistanceFromPoint(playerid, atData[i][atTelepPosX], atData[i][atTelepPosY], atData[i][atTelepPosZ]),
atInfo[i][atNumberOfPlayers], (atData[i][atGreenZone] ? "Да" : "Нет"));
}
header_string = "Телепорт администрации: ";
strcat(header_string, atSectionsInfo[listitem]);
ShowPlayerDialog(playerid, dAdmin_TpMenu_PopularPlaces, DIALOG_STYLE_TABLIST_HEADERS, header_string, dialog_string, "Выбрать", "Назад");
return 1;
}
case dAdmin_TpMenu_PopularPlaces:
{
if(!response)
return ShowAdminTeleportDialog(playerid);
for(new i, num; i < MAX_ADMIN_TELEPORTS; i++)
{
if(atData[i][atType] != GetPVarInt(playerid, "atSectionID"))
continue;
if(num++ == listitem)
{
SetPlayerPosEx(playerid, atData[i][atTelepPosX], atData[i][atTelepPosY], atData[i][atTelepPosZ]);
if(floatcmp(atData[i][atTelepPosA], 0.0) == 1)
{
SetPlayerFacingAngle(playerid, atData[i][atTelepPosA]);
}
SetCameraBehindPlayer(playerid);
break;
}
}
return 1;
}
}
return 1;
}
Полный код (pastebin): https://pastebin.com/HxUvBeiN
Всех, кто уделил своё личное время и прочёл данную тему до конца - благодарю за внимание :)
Надеюсь, что я ничего не забыл и написал всё, что хотел.
P.S Это моя первая тема на данном портале и первый мануал с подобным объёмом. Так что, очень прошу не сильно ругаться/кричать, если заметите какие-либо недочёты. Так же жду комментариев, адекватной, обоснованной критики и помощи.
Ещё раз благодарю за внимание.
Так же благодарность за помощь с некоторыми участками кода выражаю: DeIMoS`у
https://pp.userapi.com/c639116/v639116410/29f7b/qqFTi1ZLnfs.jpg
Обновление от 04.06.2017: Релиз include-версии.
Download (include): https://yadi.sk/d/fyrfBkyq3JoHUe