Описание работы плагинов

Тема в разделе "Программирование / Скриптинг", создана пользователем AlmazON, 10 июн 2014.

  1. AlmazON

    AlmazON деревянный © yand3xmail

    Сообщения:
    4.540
    Симпатии:
    1.984
    Описание работы плагинов

    Тема призвана помочь новичкам, начинающим и, возможно даже, любителям освоить язык программирования SourcePawn после изучения всех основ и материалов, вроде "Введение в SourcePawn программирование". Т.е. детально показывает, как можно применять их на практике для написания своих настоящих и будущих плагинов.
    Если у кого-либо есть желание научить "строить код", то эта тема также для вас.
    Желательно оформить свой пост по такой схеме:
    Код:
    1. Краткое описание назначения и функций плагина. По возможности, прилагать с ссылкой на его оригинальную версию и полное описание.
    2. Ваш действующий код с пояснениями под [I]спойлером[/I] с тегом [B]PHP[/B] кода.
    PHP:
    Пример
    Код:
    3. Прикреплённый скрипт-файл [I].sp[/I] с комментариями работы (файл-повтор) и нормальной табуляцией.
     
    KrasPvP и Konstantin нравится это.
  2. AlmazON

    AlmazON деревянный © yand3xmail

    Сообщения:
    4.540
    Симпатии:
    1.984
    Resetscore+

    Расшифровка кода для плагина обнуления смертей и/или фрагов - Resetscore+.

    PHP:
    // Включение списков файлов всех структур, функций, вызовов и тегов, которые имеются в наличии.
    #include <sourcemod>

    // Объявляем новый дескриптор h_mode и переменную с целым значением rsm для квара "sm_resetscore_mode".
    new Handle:h_modersm,
    // Объявляем новый дескриптор h_annonce и переменную с целым значением ann для квара "sm_resetscore_annonce".
        
    Handle:h_annonceann,
    // Объявляем новую строку, хранящую 8 значений (команд) в массиве с "пустым" общим количеством занимаемого места (символов).
        
    String:ds[8][] = {"!rd""1rd""!resetdeath""!rs""1rs""!кы""1кы""!resetscore"};

    // Общественная информация о плагине.
    public Plugin:myinfo 
    {
        
    name "Resetscore+",                                    // Название плагина.
        
    author "AlmazON",                                        // Автор плагина.
        
    description "Сбрасывает ваши смерти или счёт",        // Краткое описание плагина.
        
    version "1.0.4",                                        // Версия данного плагина.
        
    url "http://www.hlmod.ru"                                // Сайт поддержки плагина (автора).
    }

    // Встроенное глобальное событие, функция которого - единождый вызов при полной инициализации плагина.
    public OnPluginStart()
    {
    // Создаём квар "sm_resetscore_mode" и приравниваем его значение к ранее созданному дескриптору h_mode. Устанавливаем ему минимум 0, максимум 3.
        
    h_mode CreateConVar("sm_resetscore_mode",            "3",    "0 - off, 1 - resetdeath, 2 - resetscore, 3 - all."0true0.0true3.0);
    // Создаём квар "sm_resetscore_annonce и приравниваем его значение к ранее созданному дескриптору h_annonce. Устанавливаем ему минимум 0, максимум 3.
        
    h_annonce CreateConVar("sm_resetscore_annonce",    "3",    "0 - disable, 1 - chat, 2 - annonce, 3 - all."0true0.0true3.0);

    // Возращаем целое значение дескриптора h_mode и приравниваем его к ранее созданной переменной rsm.
        
    rsm GetConVarInt(h_mode);
    // Возращаем целое значение дескриптора h_annonce и приравниваем его к ранее созданной переменной ann.
        
    ann GetConVarInt(h_annonce);
    // Отлавливаем изменение значения дескриптора h_mode и вызываем CVarChange.
        
    HookConVarChange(h_modeCVarChange);
    // Отлавливаем изменение значения дескриптора h_annonce и вызываем CVarChange.
        
    HookConVarChange(h_annonceCVarChange);

    // Отлавливаем встроенное глобальное событие игры "round_start" с методом: после его начала и без переменных, и вызываем RoundStart.
        
    HookEvent("round_start"RoundStartEventHookMode_PostNoCopy);
    // Прямая внутренняя функция, добавляющая обратный вызов Say при отправке команды "say" (общий чат).
        
    AddCommandListener(Say"say");
    // Прямая внутренняя функция, добавляющая обратный вызов Say при отправке команды "say_team" (коммандный чат).
        
    AddCommandListener(Say"say_team");
    }

    // Функция обратного вызова CVarChange.
    public CVarChange(Handle:convar, const String:oldValue[], const String:newValue[])
    {
    // Условие: если уникальный идентификатор convar этой функции равен h_mode, то выполнить операцию, приведённую ниже.
        
    if (convar == h_mode)
    // Преобразуем строку в целое число с новым значением для переменной rsm.
            
    rsm StringToInt(newValue);
    // Условие: если следующий уникальный идентификатор convar этой функции равен h_annonce, то выполнить операцию, приведённую ниже.
        
    else if (convar == h_annonce)
    // Преобразуем строку в целое число с новым значением для переменной rsm.
            
    ann StringToInt(newValue);
    }

    // Функция обратного вызова RoundStart.
    public Action:RoundStart(Handle:event, const String:name[], bool:dontBroadcast)
    {
    // Условие: для выполнения операций ниже, переменная rsm должна быть истиной (true), а переменная ann больше 1.
        
    if (rsm && ann 1)
        {
    // Условие: для выполнения операции ниже, переменная rsm не должна быть равной 2.
            
    if (rsm != 2)
    // Отправка всем сообщения в чат.
                
    PrintToChatAll("\x01\x04[Resetdeath]\x01 Сбросить \x05смерти\x01: \x03%s\x01, \x03%s\x01 или \x03%s\x01."ds[0], ds[1], ds[2]);
    // Условие: для выполнения операции ниже, переменная rsm не должна быть равной 1.
            
    if (rsm != 1)
    // Отправка всем сообщения в чат.
                
    PrintToChatAll("\x01\x04[Resetscore]\x01 Сбросить \x05счёт\x01: \x03%s\x01, \x03%s\x01, \x03%s\x01 или \x03%s\x01."ds[3], ds[5], ds[6], ds[7]);
        }
    }

    // Функция обратного вызова Say.
    public Action:Say(client, const String:command[], args)
    {
    // Условие: для выполнения всех операций ниже, переменная rsm должна быть истиной.
        
    if (rsm)
        {
    // Готовим строку text, которая пока что содержит хлам, к заполнению символами, ограничив её ёмкость 200 символами кодировки UTF8.
            
    decl String:text[200];
    // Получаем всю строку текущих команд, направляемых на сервер.
            
    GetCmdArgString(textsizeof(text));
    // Создаём новые переменные для количества убийств GetClientDeaths и фрагов GetClientFrags для игрока с индексом из client.
            
    new deaths GetClientDeaths(client), frags GetClientFrags(client),
    // Создаём новую переменную обнаружения нужных нам строк команд обнуления смертей rd, отсутствие которых в сумме равно 0.
                
    rd StrContains(textds[0], false) * StrContains(textds[1], false) + StrContains(textds[2], false),
    // Создаём новую переменную обнаружения нужных нам строк команд обнуления фрагов rs, отсутствие которых в сумме равно 0.
                
    rs = (StrContains(textds[3], false) + StrContains(textds[4], false)) * StrContains(textds[5], false) + StrContains(textds[6], false) +
                    
    StrContains(textds[7], false);
    // Условие: для выполнения операции ниже, переменная rsm не должна быть равной 1, переменные смертей или фрагов - обязательно истинны, как и переменная rs.
            
    if (rsm != && (deaths || frags) && rs)
            {
    // Устанавливаем нулевое количество смертей для для игрока с индексом из client.
                
    SetEntProp(clientProp_Data"m_iDeaths"0);
    // Устанавливаем нулевое количество фрагов для для игрока с индексом из client.
                
    SetEntProp(clientProp_Data"m_iFrags"0);
    // Условие: для выполнения операции ниже, переменная ann должна быть истинной и не равна 2.
                
    if (ann && ann != 2)
    // Отправляем сообщение клиенту, успешно обнулившего свои смерти и фраги.
                    
    PrintToChat(client"\x01\x04[Resetscore]\x01 Ваш \x05общий счёт \x03сброшен\x01!");
            }
    // Условие: для выполнения операции ниже, переменная rsm не должна быть равна 2, а переменные смертей deaths и обнаружения команд их сброса rd - истинны.
            
    else if (rsm != && deaths && rd)
            {
    // Устанавливаем нулевое количество смертей для для игрока с индексом из client.
                
    SetEntProp(clientProp_Data"m_iDeaths"0);
    // Условие: для выполнения операции ниже, переменная ann должна быть истинной и не равной 2.
                
    if (ann && ann != 2)
    // Отправляем сообщение клиенту, успешно обнулившего свои смерти.
                    
    PrintToChat(client"\x01\x04[Resetdeath]\x01 Ваш \x05счёт смертей \x03сброшен\x01!");
            }
    // Условие: для выполнения всех операций ниже, переменная ann должна быть истинной и не равной 2, а переменная количества смертей deaths - ложной.
            
    else if (ann && ann != && !deaths)
            {
    // Условие: для выполнения операции ниже, переменная rsm не должна быть равна 1, переменная количества фрагов frags должна быть 0, а rs - истинной.
                
    if (rsm != && !frags && rs)
    // Отправляем сообщение клиенту, что его смерти и фраги не нуждаются в сбросе.
                    
    PrintToChat(client"\x01\x04[Resetscore]\x01 Ваш \x05общий счёт\x01 равен \x030\x01!");
    // Условие: для выполнения операции ниже, переменная rsm не должна быть равна 2, а переменная обнаружения смертей rd - необходима истинной.
                
    else if (rsm != && rd)
    // Отправляем сообщение клиенту, что его смерти не нуждаются в сбросе.
                    
    PrintToChat(client"\x01\x04[Resetdeath]\x01 У вас итак \x03нет \x05смертей\x01!");
            }
        }

    Советы по улучшению кода в данном случае неуместны.
    Поправки на ощутимые ошибки в объяснениях работы принимаются.
    Может объясняю не лучшим образом, но, как я считаю, достаточно грамотным.
    Если кому-то что-то не нравится - напишите и "разложите по полочкам" свой плагин. :rtfm:
     

    Вложения:

    Drumanid, ★$$StOk$$★, Leckter Hannibal и 2 другим нравится это.
  3. AlmazON

    AlmazON деревянный © yand3xmail

    Сообщения:
    4.540
    Симпатии:
    1.984
    Auto Respawn

    Расшифровка кода для плагина воскрешения игроков (прототип DM) - Auto Respawn.

    Настройки(CVAR's):
    sm_autorespawn_time - задержка между смертью и воскрешением.
    sm_endtime_autorespawn - максимальный срок воскрешений в каждом раунде.
    PHP:
    // Включение списков файлов всех структур, функций, вызовов и тегов, которые имеются в наличии.
    #include <sourcemod>
    // Подключаемая библиотека SourceMod API - cstrike.
    #include <cstrike>

    // Объявляем новый дескриптор TimeAutoRespawn для квара "sm_endtime_autorespawn".
    new Handle:TimeAutoRespawn,
    // Объявляем новый дескриптор RetryAutoRespawn для квара "sm_autorespawn_time".
        
    Handle:RetryAutoRespawn,
    // Объявляем новый дескриптор RStimer для контроля таймера.
        
    Handle:RStimer,
    // Объявляем новую переменную для хранения одного из 2 логических значений (сейчас по умолчанию - false или 0).
        
    bool:lock;

    // Встроенное глобальное событие, функция которого - единождый вызов при полной инициализации плагина.
    public OnPluginStart()
    {
    // Создаём квар "sm_autorespawn_time" и приравниваем его значение к ранее созданному дескриптору RetryAutoRespawn. Устанавливаем ему минимум 0,1.
        
    RetryAutoRespawn CreateConVar("sm_autorespawn_time",        "0.1",        "Задержка до воскрешения после смерти (секунд)."0true0.1);
    // Создаём квар "sm_endtime_autorespawn" и приравниваем его значение к ранее созданному дескриптору TimeAutoRespawn. Устанавливаем ему минимум 0,0.
        
    TimeAutoRespawn CreateConVar("sm_endtime_autorespawn",    "60.0",        "Время окончания автореспауна от начала раунда."0true0.0);

    // Отлавливаем встроенное глобальное событие игры "round_start" с методом: после его начала и без переменных, и вызываем RoundStart.
        
    HookEvent("round_start"RoundStartEventHookMode_PostNoCopy);
    // Отлавливаем встроенное глобальное событие игры "round_end" с методом: после его начала и без переменных, и вызываем RoundEnd.
        
    HookEvent("round_end"RoundEndEventHookMode_PostNoCopy);
    }

    // Функция обратного вызова RoundStart.
    public Action:RoundStart(Handle:event, const String:name[], bool:dontBroadcast)
    {
    // Условный оператор if показывает, что для выполнения операции ниже величина переменной lock должна стать отрицанием её истинного значения.
        
    if (!lock)
        {
    // Отлавливаем встроенное глобальное событие игры "player_death" с методом после его начала и вызываем PlayerDeath.
            
    HookEvent("player_death"PlayerDeathEventHookMode_Post);
    // Приравниваем переменную lock к значению true (истина).
            
    lock true;
        }
    // Создаём таймер, равный RStimer. Счётчик устанавливает возвращаемое значение TimeAutoRespawn с плавающей запятой, истечение которого вызывает Stop.
        
    RStimer CreateTimer(GetConVarFloat(TimeAutoRespawn), Stop);
    }

    // ункция обратного вызова RoundEnd.
    public Action:RoundEnd(Handle:event, const String:name[], bool:dontBroadcast)
    {
    // Условие: если RStimer не равен INVALID_HANDLE, значит таймер этого дескриптора активен и необходимо выполнить операцию ниже.
        
    if (RStimer != INVALID_HANDLE)
        {
    // Убивает ("обнуляет" без вызова его функции) таймер RStimer.
            
    KillTimer(RStimer);
    // Сбрасываем дескриптор таймера RStimer на INVALID_HANDLE, чтобы знать, что таймер больше не активен.
            
    RStimer INVALID_HANDLE;
        }
    }

    // Функция обратного вызова PlayerDeath.
    public Action:PlayerDeath(Handle:event, const String:name[], bool:dontBroadcast)
    {
    //  Получаем индекс родившегося игрока для новой переменной client и с помощью неё же - индекс команды в переменной team.
        
    new client GetClientOfUserId(GetEventInt(event"userid")), team GetClientTeam(client);
    // Условие: если игрок в игре, индекс его команды выше 1 (не спектратор) и он мёртв, то выполнить операцию ниже.
        
    if (IsClientInGame(client) && team && !IsPlayerAlive(client))
    // Создать таймер для задержки возрождения client'а на время значения RetryAutoRespawn с плавающей запятой и после вызвать RespawnPlayer.
            
    CreateTimer(GetConVarFloat(RetryAutoRespawn), RespawnPlayerclientTIMER_FLAG_NO_MAPCHANGE);
    }

    // Функция обратного вызова RespawnPlayer для таймера из PlayerDeath.
    public Action:RespawnPlayer(Handle:Timerany:client)
    {
    // Готовим строку text, которая пока что содержит хлам, к заполнению символами, ограничив её ёмкость 40 символами кодировки UTF8.
        
    decl String:game[40];
    // Получаем каталог с названием игры.
        
    GetGameFolderName(gamesizeof(game));
    // Условие: если название идентично обозначению игры cstrike или csgo, то выполнить операцию ниже.
        
    if (StrEqual(game"cstrike") || StrEqual(game"csgo"))
    // Возродить игрока с индексом из client.
            
    CS_RespawnPlayer(client);
    // Если ни одно из предыдущих условий не соответствует действительности, то выполнить операцию ниже.
        
    else
        {
    // Прекратить отлов всех трёх используемых в плагине встроенных событий игры, фактически деактивируя его.
            
    UnhookEvent("round_start"RoundStartEventHookMode_PostNoCopy);
            
    UnhookEvent("round_end"RoundEndEventHookMode_PostNoCopy);
            
    UhEPD();
    // Занести в журнал SM сообщение об ошибке плагина: "Игра не поддерживается!".
            
    LogError("The game is not supported!");
        }
    }

    // Функция обратного вызова Stop для таймера из RoundStart.
    public Action:Stop(Handle:timer)
    {
    // Условный оператор if показывает, что для выполнения операции ниже величина переменной lock должна быть истинной.
        
    if (lock)
        {
    // Вызвать (сокращаемую) часто используемую функцию UhEPD.
            
    UhEPD();
    // Приравниваем переменную lock к значению false (ложь).
            
    lock false;
        }
    // Сбрасываем дескриптор таймера RStimer на INVALID_HANDLE, чтобы знать, что таймер итак не активен.
        
    RStimer INVALID_HANDLE;
    }

    // Функция обратного вызова UhEPD.
    UhEPD()
    {
    // Прекратить отлов встроенного глобального события игры "player_death", которое было вызвано до этого, т.е. прекратить исполнять PlayerDeath
        
    UnhookEvent("player_death"PlayerDeathEventHookMode_Post);
    }
    Это и есть оригинал.
     

    Вложения:

    Leckter Hannibal и ★$$StOk$$★ нравится это.
  4. AlmazON

    AlmazON деревянный © yand3xmail

    Сообщения:
    4.540
    Симпатии:
    1.984
    Перенос строки "\n" в CS:GO или "экстра чат" в любой игре

    Реализация функций (stock), как сделать "бесконечный чат", а также полностью рабочим символ переноса строки "\n" в CS:GO.

    Общая длина текста фактически ограничена лишь пределом буфера строк самого SourceMod'а.
    Количество символов "\n", максимума как бы и нет - конечный пользователь может делать перенос хоть через каждую букву.
    Текст для переноса - до 192 символов (это предел чата).
    Код потребляет ровно столько памяти, сколько требуется для всех переносов (но 192 символа для одного переноса - см.выше).
    Дополнительно 192 символа отведено для переменных/строк (%d, %i, %s, %x и т.п.).
    Также может работать, как обычный PrintToChat или PrintToChatAll, когда нет ни единого символа переноса "\n".
    Цвета поддерживаются самой функцией! Приблизительно полная аналогия со стандартными нативами в целом.

    В файле, с основными пояснениями, показаны действующие примеры использования для отправки сообщений игроку и всем подряд.
    Текст получен с помощью лишь 1 строчки:
    [​IMG]
    [​IMG]
    Внимание! Это не плагин, который выполняет вышеописанные функции, а лишь код, который можно использовать в любых плагинах.

    Использование:
    Копируем нужную stock функцию в свой плагин и просто подставляем ко всем "расширяемым" PrintToChat или PrintToChatAll приставку Extra.

    P.S. Можете использовать данные функции ExtraPrintToChat и ExtraPrintToChatAll в своих плагинах, с вас лишь "Спасибо". :beer:
     

    Вложения:

    Drumanid, komashchenko, dead_soul и ещё 1-му нравится это.
  5. R1KO

    R1KO Супер-модератор

    Сообщения:
    5.994
    Симпатии:
    2.989
    http://hlmod.ru/forum/novye-plaginy/31606-map-comments.html

    PHP:
    #pragma semicolon 1
    #include <sourcemod>
    #include <sdktools_stringtables>
    #pragma newdecls required

    public void OnMapStart() // Ловим старт карты
    {
        
    DirectoryListing hDirectory OpenDirectory("maps/"); // Открываем папку maps
        
    if (hDirectory != null// Если папка успешно открыта
        
    {
            
    // Создаем переменные до цикла, т.к. в цикле каждый раз создавать не оптимально
            
    File hFilehFile2;
            
    char sMap[128], sMapPath[128], sBuffer[256];
            
    FileType type;
            
    int i;
            while (
    hDirectory.GetNext(sMapsizeof(sMap), type))  // GetNext будет получать имя файла/директории в текущем каталоге
            
    {
                if (
    type == FileType_File// Если получили файл то ...
                
    {
                    
    FindCharInString(sMap'.');    // Ищем точку в имени файла чтобы определить расширение
                    
    if(!= -&& strcmp(sMap[i], ".bsp") == 0)    // Если файл имеет расширение bsp, тоесть это карта
                    
    {
                        
    sMap[i] = 0// Обрезаем расширение
                        
                        
    FormatEx(sMapPathsizeof(sMapPath), "maps/%s.txt"sMap); // Формируем новое расширение
                        
    if(FileExists(sMapPath) == false)    // Если такого файла еще нет
                        
    {
                            
    hFile OpenFile("maps/base.txt""rt");    // Открываем наш base.txt, Флаги: (r - для чтения, t - текстовый докумен)
                            
    hFile2 OpenFile(sMapPath"wt");    // Открываем файл куда будем записывать (его мы формировали), Флаги: (w - для записи, t - текстовый докумен)
                            
    while(hFile.EndOfFile() == false && hFile.ReadLine(sBuffersizeof(sBuffer)))
                            {
                                
    // EndOfFile - проверяет не дошли ли мы до конца файла
                                // ReadLine считывает строку с файла
                                // Мы построчно считываем файл maps/base.txt и так же построчно записываем его содержимое в наш новый файл.
                            //    LogMessage("ReadLine: '%s''", sBuffer);
                                
    ReplaceString(sBuffersizeof(sBuffer), "\n"""); // Удаляем перенос строки
                                
    hFile2.WriteLine(sBuffer);    // Записываем строку в файл
                            
    }

                            
    // Удаляем переменные (чтобы не было утечек)
                            
    delete hFile;
                            
    delete hFile2;
                        }
                        
                        
    AddFileToDownloadsTable(sMapPath); // Добавляем наш файл для загрузки игрокам
                        
                    
    }
                    
                }
            }

            
    delete hDirectory;
        }
    }
     
    Drumanid нравится это.
  6. Hejter

    Hejter Mapper Source Engine

    Сообщения:
    1.771
    Симпатии:
    259
    Оффтоп
     
  7. R1KO

    R1KO Супер-модератор

    Сообщения:
    5.994
    Симпатии:
    2.989
    Оффтоп
     
  8. Drumanid

    Drumanid Дилетант

    Сообщения:
    567
    Симпатии:
    194
  9. R1KO

    R1KO Супер-модератор

    Сообщения:
    5.994
    Симпатии:
    2.989
    Оффтоп
     
  10. Hejter

    Hejter Mapper Source Engine

    Сообщения:
    1.771
    Симпатии:
    259
    Оффтоп