#include <sdktools>
#include <sdkhooks>
#undef REQUIRE_EXTENSIONS // Чтобы плагин загрузился, нужно отметить дополнения как "необязательные"
#include <cstrike>
#include <tf2_stocks>
#tryinclude <dodhooks> // Для компиляции
#define MAX_EDICTS (1 << 11)
// Для дуэльной зоны
new bool:InDuelingZone[MAXPLAYERS + 1] = {false, ...}, numDuellers[MAX_EDICTS];
public Plugin:myinfo =
{
name = "SM Custom Zones",
author = "Root",
description = "Тестовый плагин \"кастомных\" зон.",
version = "1.0",
url = "http://www.dodsplugins.com/"
}
public OnPluginStart()
{
// HookEventEx не будет выдавать ошибку, если такого события не существует (например в DoD:S)
HookEventEx("round_freeze_end", OnRoundStart);
}
public OnEntityCreated(entity, const String:classname[])
{
// Количество дуэлянтов ограничено! :D
numDuellers[entity] = 0;
}
public OnEntityDestroyed(entity)
{
// Если игрок ушел с сервера, значит он больше не в дуэльной зоне
if (1 <= entity <= MaxClients)
{
InDuelingZone[entity] = false;
}
if (numDuellers[entity])
{
// Если вдруг удалилась дуэльная зона, тогда "обезвредим" всех дуэлянтов
for (new i = 1; i <= MaxClients; i++)
InDuelingZone[i] = false;
}
}
public OnRoundStart(Handle:event, const String:name[], bool:dontBroadcast)
{
// Когда раунд начался, активируем 'no rush' зону для терроров
ServerCommand("sm_actzone norush");
CreateTimer(1.0, Timer_ActivateZone, _, TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE);
}
public Action:Timer_ActivateZone(Handle:timer)
{
// Т.к. таймер повторяется каждую секунду, создадим счетчик
static TimesRepeated = 0;
switch (++TimesRepeated)
{
case 10, 20, 30: // Каждые 10 секунд...
{
for (new i = 1; i <= MaxClients; i++)
{
if (IsClientInGame(i))
{
// ... будем показывать террористам ...
if (GetClientTeam(i) != CS_TEAM_T)
continue;
// ... через сколько секунд они смогут рашить
PrintToChat(i, "Вы сможете атаковать только через %i сек!", 40 - TimesRepeated);
}
}
}
case 40: // После 40 секунд отключим зону
{
// Обнулим счетчик таймера
TimesRepeated = 0;
// Активируем зону (по её названию) через консоль
ServerCommand("sm_diactzone norush");
PrintToChatAll("Готовьтесь к смерти!");
// Остановим повторяющийся таймер
return Plugin_Stop;
}
}
return Plugin_Continue;
}
/**
* OnEnteredProtectedZone()
*
* Когда игрок входит в зону.
*
* @param zone Индекс зоны в которую вошел игрок.
* @param client Индекс игрока, который вошел в зону.
* @param prefix Префикс, отображаемый в чате (когда игрок входит в зону).
* @noreturn
* -------------------------------------------------------------------------- */
public Action:OnEnteredProtectedZone(zone, client, const String:prefix[])
{
// Заполучим переменную sm_zones_show_messages, которая показывает сообщения
static Handle:ShowZones = INVALID_HANDLE;
if (!ShowZones) ShowZones = FindConVar("sm_zones_show_messages");
if (1 <= client <= MaxClients)
{
// Заполучим имя зоны (чтобы потом сравнивать в какую конкретно зону вошел игрок)
decl String:m_iName[MAX_NAME_LENGTH*2], String:szMsg[PLATFORM_MAX_PATH];
GetEntPropString(zone, Prop_Data, "m_iName", m_iName, sizeof(m_iName));
// Пропустим первые 8 символов чтобы игнорировать префикс зоны (sm_zone )
if (StrEqual(m_iName[8], "chat", false))
{
// Тестовая зона
Format(szMsg, sizeof(szMsg), "Вы зашли в зону под названием \"%s\".", m_iName[8]);
}
// Зона неуязвимости
else if (StrEqual(m_iName[8], "godmode", false))
{
// Сделаем игрока неуязвимым при помощи плагина, представленного ниже
// https://forums.alliedmods.net/showthread.php?p=979550
ServerCommand("sm_god #%i 1", GetClientUserId(client));
strcopy(szMsg, sizeof(szMsg), "Вы зашли в зону бессмертия!");
}
// Зона пониженной гравитации
else if (StrEqual(m_iName[8], "lowgrav", false))
{
// Применим только половинную гравитацию для игрока
SetEntityGravity(client, 0.5);
strcopy(szMsg, sizeof(szMsg), "Вы зашли в зону пониженной гравитации!");
}
// А это зона где игроки могут закупиться, но не могут стрелять
else if (StrEqual(m_iName[8], "spawnoshoot", false))
{
if (SDKHookEx(client, SDKHook_PostThinkPost, PostThinkPost))
strcopy(szMsg, sizeof(szMsg), "Вы не сможете стрелять пока находитесь в этой зоне!");
}
// Зона возвращения в респаун
else if (StrEqual(m_iName[8], "spawntele", false))
{
// Возродим (т.е. возвратим) игрока на свою базу
switch (EngineVersion:GetEngineVersion())
{
// CS:S и CS:GO использует свою функцию возрожедния
case Engine_CSS, Engine_CSGO: CS_RespawnPlayer(client);
case Engine_TF2: TF2_RespawnPlayer(client); // И TF2 тоже
#if defined _dodhooks_included
case Engine_DODS: RespawnPlayer(client); // Для DoD:S необходимо использовать дополнение DoD Hooks
#endif
default: TeleportEntity(client, Float:{123.0, 456.0, 789.0}, NULL_VECTOR, NULL_VECTOR); // Можно просто телепортируем игрока в нужные координаты
}
// Форматируем сообщение
strcopy(szMsg, sizeof(szMsg), "You shall not pass, motherfucker!");
}
// Дуэльная зона
else if (StrEqual(m_iName[8], "dueling", false))
{
// Увеличим кол-во дуэлянтов
// Если дуэлянтов меньше или равно 2, то...
if (++numDuellers[zone] <= 2)
{
// ... заблокируем получаемый урон от любых игроков, находящихся вне дуэльной зоны
InDuelingZone[client] = true;
SDKHook(client, SDKHook_TraceAttack, TraceAttack);
SDKHook(client, SDKHook_OnTakeDamage, OnTakeDamage);
strcopy(szMsg, sizeof(szMsg), "Вы зашли в зону для дуэлей!");
// Сделаем так чтобы иные игроки не смогли входить в зону
//SetEntProp(zone, Prop_Send, "m_CollisionGroup", 17);
}
}
// Проверим должно ли отображаться сообщение сразу как только игрок вошел в зону?
if (GetConVarBool(ShowZones))
{
PrintToChat(client, "%s%s", prefix, szMsg);
}
}
}
/**
* OnLeftProtectedZone()
*
* Когда игрок выходит из зоны.
*
* @param zone Индекс зоны из которой вышел игрок.
* @param client Индекс игрока, который вышел из зоны.
* @param prefix Префикс, отображаемый в чате (когда игрок выходит из зоны).
* @noreturn
* -------------------------------------------------------------------------- */
public Action:OnLeftProtectedZone(zone, client, const String:prefix[])
{
static Handle:ShowZones = INVALID_HANDLE;
// Сохраним носитель переменной (через static) чтобы в дальнейшем переменную не запрашивать
if (!ShowZones) ShowZones = FindConVar("sm_zones_show_messages");
if (1 <= client <= MaxClients)
{
// Необходимо проверять зоны по их названию, иначе "нестандартные" зоны были бы полной чушью
decl String:m_iName[MAX_NAME_LENGTH*2], String:szMsg[PLATFORM_MAX_PATH];
GetEntPropString(zone, Prop_Data, "m_iName", m_iName, sizeof(m_iName));
// И здесь пропустим префиксы 'sm_zone '
if (StrEqual(m_iName[8], "chat", false))
{
Format(szMsg, sizeof(szMsg), "Вы вышли из зоны \"%s\".", m_iName[8]);
}
// Имена зон должны строго соответствовать!
else if (StrEqual(m_iName[8], "godmode", false))
{
// Отключим бессмертие после выхода из зоны неуязвимости
ServerCommand("sm_god #%i 0", GetClientUserId(client));
strcopy(szMsg, sizeof(szMsg), "Вы снова простой смертный!");
}
else if (StrEqual(m_iName[8], "lowgrav", false))
{
// Вернем стандартную гравитацию
SetEntityGravity(client, 1.0);
strcopy(szMsg, sizeof(szMsg), "Вы вышли из зоны пониженной гравитации!");
}
else if (StrEqual(m_iName[8], "spawnoshoot", false))
{
SDKUnhook(client, SDKHook_PostThinkPost, PostThinkPost);
strcopy(szMsg, sizeof(szMsg), "Вы покинули зону и снова можете стрелять!"); // Звучит двойственно...
}
else if (StrEqual(m_iName[8], "dueling", false))
{
// Для предотвращения ошибок надо сверить что один из дуэлянтов покинул зону
if (InDuelingZone[client])
{
numDuellers[zone]--;
InDuelingZone[client] = false;
SDKUnhook(client, SDKHook_TraceAttack, TraceAttack);
SDKUnhook(client, SDKHook_OnTakeDamage, OnTakeDamage);
strcopy(szMsg, sizeof(szMsg), "Вы вышли из дуэльной зоны!");
//SetEntProp(zone, Prop_Send, "m_CollisionGroup", 11);
}
}
// И так далее...
// Когда игрок умирает, считается что он покинул зону
if (GetConVarBool(ShowZones) && IsPlayerAlive(client))
{
// Отобразим сообщение только когда живой игрок покидает зону
PrintToChat(client, "%s%s", prefix, szMsg);
}
}
}
public PostThinkPost(client)
{
SetEntPropFloat(client, Prop_Send, "m_flNextAttack", GetGameTime() + 0.5);
}
public Action:TraceAttack(victim, &attacker, &inflictor, &Float:damage, &damagetype, &ammotype, hitbox, hitgroup)
{
return (InDuelingZone[attacker] && InDuelingZone[victim]) ? Plugin_Continue : Plugin_Handled;
}
public Action:OnTakeDamage(victim, &attacker, &inflictor, &Float:damage, &damagetype)
{
return (InDuelingZone[attacker] && InDuelingZone[victim]) ? Plugin_Continue : Plugin_Handled;
}