[SourcePawn] Урок 15 - API (natives, forwards, functions)

R1KO

fuck society
Сообщения
9,457
Реакции
7,786
  • Команда форума
  • #1
[SourcePawn] Урок 15 - API (natives, forwards, functions)

<- К содержанию

В этом уроке разберем как создать свой inc файл.

В качестве примера будет 2 плагина:

Сам инклюд назовем base_stats.inc

Из чего состоит inc файл:
  1. Первое что следует сделать - это проверку на повторное подключение инклюда:
    PHP:
    #if defined _base_stats_included    // Если директива  _base_stats_included объявлена
        #endinput // Прекращаем чтение файла (Если компилятор встречает #endinput в файле, то он игнорирует весь ниженаписанный код)
    #endif // Окончание условия
    #define _base_stats_included    // Объявляем директиву _base_stats_included
    На всякий случай поясню:
    Когда мы подключим этот inc в плагин и при его компиляции компилятор встречает условие
    PHP:
    #if defined _base_stats_included
    То оно будет ложным т.к. _base_stats_included еще не объявлена.
    И после условия она будет объявлена:
    PHP:
    #define _base_stats_included
    Если же мы повторно подключим этот inc, либо же он будет подключен в каком-то другом inc (которым мы так же подключим), либо в одном из файлов плагина, то при подключении inc компилятор опять проверит условие:
    PHP:
    #if defined _base_stats_included
    и на этот раз оно будет истинным т.к. _base_stats_included уже была объявлена и повторное подключение будет проигнорировано.
  2. Далее обычно идет объявление директив (констант), переменных, перечислений если они есть. Давайте объявим несколько:
    PHP:
    #define API_VERSION    10 // Версия нашего API (1.0). Сделаем его целым числом для упрощения проверок
    
    // Еще несколько, просто для примера
    #define MY_CONST1 64
    #define MY_CONST2 128
    #define MY_CONST3 256
    
    // Сделаем перечисление для удобного получения данных об игроке (подробнее будет далее)
    enum
    {
        BS_Kills = 0,
        BS_Kills_HS,
        BS_Deaths,
        BS_Hits,
        BS_Hits_HS,
        BS_DatSize
    }
  3. Если у нас объемное API разумно было бы его разбить на части (например, как в shop) и если это так то сейчас самое время подключить наши части:
    PHP:
    #include <myplugin/part1>
    #include <myplugin/part2>
    Т.е. иерархия файлов будет такой:
    • include:
      • base_stats.inc
      • myplugin:
        • part1.inc
        • part2.inc
    Думаю суть ясна. Поскольку наш inc не большой то в разбиении его на отдельные файлы нет смысла.
  4. Для упрощения объяснений начнем с более простого - нативов.
    Существует средство позволяющее вызывать функции одного плагина с помощью других.
    Например один плагин создает натив AddNumbers, а другие могут его использовать в своих целях. В этом и есть суть нативов - обмен данными между несколькими плагинами.
    Нативы могут использоваться как для получения информации из плагина создащего натив так и для её передачи в него.

    Давайте решим какие нам нужны будут нативы:
    • Получение указателя (Database) базы данных, для работы непосредственно с ней.
    • Получение уникального ID игрока (По индексу - т.е. когда игрок на сервере)
    • Получение данных об игроке из базы (По индексу - т.е. когда игрок на сервере)
    • Получение позиции в топе по отношению Убийств/Смертей (По уникальному ID)

    base_stats.inc:
    PHP:
    /*
    Получение указателя (Database) базы данных, для работы непосредственно с ней.
    */
    native Database BS_GetDatabase();
    /*
    Получение уникального ID игрока из базы (По индексу - т.е. когда игрок на сервере)
    int iClient - Индекс игрока
    */
    native int BS_GetClientID(int iClient);
    /*
    Получение данных об игроке из базы (По индексу - т.е. когда игрок на сервере)
    int iClient - Индекс игрока
    GetDataCallback callback - имя обратного вызова (каллбэка)
    any iData - данные, которые хотим передать в каллбэк
    */
    native void BS_GetClientData(int iClient, GetDataCallback callback, any iData = 0);
    /*
    Получение позиции в топе по отношению Убийств/Смертей (По уникальному ID)
    int iClient - Индекс игрока
    GetTopPosCallback callback - имя обратного вызова (каллбэка)
    any iData - данные, которые хотим передать в каллбэк
    */
    native void BS_GetClientTopPos(int iClient, GetTopPosCallback callback, any iData = 0);

    Сразу же объявим прототипы каллбэков. Что это поясню позже.
    PHP:
    typedef GetDataCallback = function void (int iClientID, int iData[BS_DatSize], any iData);
    
    typedef GetTopPosCallback = function void (int iClientID, int iTopPos, float fScore, any iData);

    Создавать нативы следует в событии AskPluginLoad2.
    Вызывается перед OnPluginStart, непосредственно перед загрузкой плагина.

    base_stats.sp:
    PHP:
    public APLRes AskPluginLoad2(Handle hMyself, bool bLate, char[] sError, int iErr_max) 
    {
        // Для создания натива используем ф-ю CreateNative
        CreateNative("BS_GetDatabase", Native_GetDatabase);
        /*
            BS_GetDatabase - Это имя натива. Именно его будут использовать другие плагины.
            Native_GetDatabase - Ф-я которая будет вызвана в нашем плагине, когда какой-то плагин будет вызывать этот натив.
        */
        CreateNative("BS_GetClientID", Native_GetClientID);
        CreateNative("BS_GetClientData", Native_GetClientData);
        CreateNative("BS_GetClientTopPos", Native_GetClientTopPos);
    
        RegPluginLibrary("base_stats");
        /*
        Ф-я RegPluginLibrary регистрирует имя библиотеки.
        Это нужно чтобы другие плагины, которые для работы требуют этот плагин могли определить загружен ли он.
        */
       
        return APLRes_Success; // Для продолжения загрузки плагина нужно вернуть APLRes_Success
    }
    
    /*
    Получение указателя (Database) базы данных, для работы непосредственно с ней.
    */
    public int Native_GetDatabase(Handle hPlugin, int iNumParams)
    {
        /*
        Когда какой-то плагин будет вызывать натив BS_GetDatabase то в нашем плагине будет вызываться эта ф-я.
        Обратите внимание что она имеет тип int возвращать должна значение типа int.
        Дальше у нас 2 варианта:
            1) Вернуть указатель на соединение с бд, которое мы используем.
            2) Создать копию соединения и вернуть её. В этом случае плагин после завершения работы с базой должен будет закрыть соеденение.
        Пойдем по 2-му пути.
        Убеждаемся что тип Database можно клонировать: https://wiki.alliedmods.net/Handles_(SourceMod_Scripting)#Databases
        Cloneable:    Yes - Значит можно
        Клонируем с помощью ф-и CloneHandle.
        1-й аргумент - собственно указатель, который нужно клонировать
        2-й аргумент является опциональным. Если он указан то он будет назначен новым владельцем копии.
       
        Т.к. Возвращаемый тип должен быть int то приводим результат к типу int
        Таким образом плагин вызвавший натив BS_GetDatabase получит копию нашего соеденения с базой g_hDatabase
        */
        return view_as<int>(CloneHandle(g_hDatabase, hPlugin));
    }
    
    /*
    Получение уникального ID игрока из базы (По индексу - т.е. когда игрок на сервере)
    int iClient - Индекс игрока
    */
    public int Native_GetClientID(Handle hPlugin, int iNumParams)
    {
        /*
            hPlugin - Указатель на плагин, который вызвал натив.
            iNumParams - Количество переданных аргументов.
           
            Исходя из прототипа:
            native int BS_GetClientID(int iClient);
            В натив передается 1 аргумент.
            Нумерация аргументов начинается с 1
           
            Для получения разных значений разных типов используются разные функции:
                int GetNativeArray(int param, any[] local, int size)    - Получение массива
                any GetNativeCell(int param)                            - Получение ячейки (обычно int, bool, float)
                any GetNativeCellRef(int param)                            - Получение ячейки при передаче по адресу (обычно int, bool, float)
                function GetNativeFunction(int param)                    - Получение адреса функции (каллбека)
                int GetNativeString(int param, char[] buffer, int maxlength, int &bytes)    - Получение строки
                int GetNativeStringLength(int param, int &length)        - Получение длины передаваемой строки
           
            Во всех ф-ях: int param это номер аргумента
           
            У нас тип int, поэтому используем GetNativeCell
        */
        int iClient = GetNativeCell(1);
        /*
        Тут мы имеем индекс игрока.
        Дальше опять 2 варианта:
            1) Проверить валиден ли он (в адекватных ли пределах, есть ли он на сервере, не бот ли)
            2) Переложить эти проверки на плагин, использующий натив и надеятся что всё будет хорошо.
        Для надежности пойдем по 1-му пути.
        */
        if(iClient > 0 && iClient <= MaxClients && IsClientInGame(iClient) && g_iClientID[iClient])
        {
            // Игрок валиден
            return g_iClientID[iClient]; // Вернем его ID
        }
    
        // Во всех остальных случаях будет возвращен 0
        return 0;
    }
  5. Теперь мы подошли к функциям.
    Каждая ф-я в плагине имеет имя, адрес, список параметров и их типов. Например ф-я имеет имя OnPluginStart, а её адрес 0x4f0a0019. При каждом запуске плагина эта цифра разная.
    Внутри самого SourceMod вызов ф-й происходит по адресу. Но адрес можно получить по имени с помощью ф-и GetFunctionByName.
    Если же мы передаем ф-ю в другие ф-и или нативы как аргумент то на самом деле мы передаем её адрес.
    Таким образом зная имя ф-и и список параметров, а так же их типов мы можем вызвать любую ф-ю внутри любого плагина с помощью другого плагина.
    Чтобы вызвать ф-ю нам нужно знать Handle плагина, в котором её необходимо вызвать и её адрес либо имя.
    base_stats.sp:
    PHP:
    /*
    Получение данных об игроке из базы (По индексу - т.е. когда игрок на сервере)
    int iClient - Индекс игрока
    GetDataCallback callback - имя обратного вызова (каллбэка)
    any iData - данные, которые хотим передать в каллбэк
    */
    public int Native_GetClientData(Handle hPlugin, int iNumParams)
    {
        // Получаем клиент
        int iClient = GetNativeCell(1);
        // Проверяем валидность клиента
        if(iClient > 0 && iClient <= MaxClients && IsClientInGame(iClient) && !IsClientInGame(iClient) && g_iClientID[iClient])
        {
            // Получаем данные, которые будут переданы в каллбэк
            int iData = GetNativeCell(3);
    
            // Получаем адрес каллбека
            Function fncCallback = GetNativeFunction(2);
           
            /*
            Далее нам нужно выполнить запрос на выборку и отправить результат в каллбек.
            В каллбек запроса нужно передать iData, Handle плагина вызвавшего натив и  fncCallback. Для этого будем использовать DataPack
            */
    
            DataPack hPack = new DataPack(); // Создаем DataPack
            hPack.WriteCell(hPlugin); // Записываем в него Handle плагина вызвавшего натив
            hPack.WriteFunction(fncCallback); // Записываем в него адрес каллбека 
            hPack.WriteCell(iData); // Записываем в него данные, которые будут переданы в каллбэк
           
            char szQuery[256];
            /*
            Здесь вспоминаем наш список:
            enum StatsData
            {
                BS_Kills = 0,
                BS_Kills_HS,
                BS_Deaths,
                BS_Hits,
                BS_Hits_HS
            }
           
            Для упрощения получения данных выбирать значения из базы будем в том же порядке: убийств, убийств в голову, смертей, попаданий, попаданий в голову
    
            */
            FormatEx(szQuery, sizeof(szQuery), "SELECT `id`, `kills`, `kills_hs`, `deaths`, `hits`, `hits_hs` FROM `table_stats` WHERE `id` = %d;", g_iClientID[iClient]);    // Формируем запрос
            g_hDatabase.Query(SQL_Callback_NtvGetClientData, szQuery, hPack); // Отправляем запрос
        }
    
        return 0;
    }
    
    // Пришел ответ на запрос
    public void SQL_Callback_NtvGetClientData(Database hDatabase, DBResultSet hResults, const char[] sError, any iDataPack)
    {
        if(sError[0]) // Если произошла ошибка
        {
            LogError("SQL_Callback_NtvGetClientData: %s", sError); // Выводим в лог
            return; // Прекращаем выполнение ф-и
        }
       
        DataPack hPack = view_as<DataPack>(iDataPack); // Получаем наш DataPack
        hPack.Reset(); // Переводим указатель на начало датапака
       
        Handle hPlugin = view_as<Handle>(hPack.ReadCell());
        Function fncCallback = hPack.ReadFunction();
        int iData = hPack.ReadCell();
       
        delete hPack;
    
        int iClientID, iStatsData[BS_DatSize]; // Создаем переменные. По умолчанию они будут инициализированы нолями.
    
        if(hResults.FetchRow())    // Игрок есть в базе
        {
            // Получаем значения из результата
            iClientID = hResults.FetchInt(0);    // id
           
            for(int i = 1, j = 0; i < 6; ++i)
            {
                iStatsData[j++] = hResults.FetchInt(i);
            }
        }
       
        /*
        Вот здесь и происходит вызов ф-и
        Нужно указать Handle плагина и адрес ф-и
        */
        Call_StartFunction(hPlugin, fncCallback);
        /*
        Теперь нужно передать необходимые параметры в определенном порядке.
        Мы объявляли прототип обратного вызова
        typedef GetDataCallback = function void (int iClientID, int iStatsData[BS_DatSize], any iData);
        Это как раз и есть вид ф-и которая будет вызвана.
        */
        // Передаем int iClientID
        Call_PushCell(iClientID);
        // Передаем int iStatsData[BS_DatSize]
        Call_PushArray(iStatsData, 5);
        // Передаем any iData
        Call_PushCell(iData);
        // И собственно вызываем ф-ю
        Call_Finish();
       
        // Теперь в плагине, который вызвал натив была вызвана указанная ф-я
    }
    
    /*
    Получение позиции в топе по отношению Убийств/Смертей (По уникальному ID)
    int iClientID - Уникальный ID игрока
    GetTopPosCallback callback - имя обратного вызова (каллбэка)
    any iData - данные, которые хотим передать в каллбэк
    */
    public int Native_GetClientTopPos(Handle hPlugin, int iNumParams)
    {
        // Получаем клиент
        int iClient = GetNativeCell(1);
        // Проверяем валидность клиента
        if(iClient > 0 && iClient <= MaxClients && IsClientInGame(iClient) && !IsClientInGame(iClient) && g_iClientID[iClient])
        {
            // Получаем данные, которые будут переданы в каллбэк
            int iData = GetNativeCell(3);
    
            // Получаем адрес каллбека
            Function fncCallback = GetNativeFunction(2);
    
            DataPack hPack = new DataPack(); // Создаем DataPack
            hPack.WriteCell(hPlugin); // Записываем в него Handle плагина вызвавшего натив
            hPack.WriteFunction(fncCallback); // Записываем в него адрес каллбека 
            hPack.WriteCell(iData); // Записываем в него данные, которые будут переданы в каллбэк
           
            char szQuery[256];
            FormatEx(szQuery, sizeof(szQuery), "SELECT DISTINCT ((`kills` + `kills_hs`) / `deaths`) as `score`, `id` FROM `table_stats` WHERE ((`kills` + `kills_hs`) / `deaths`) >= %.2f ORDER BY `score` ASC;", (float(g_iKills[iClient])+float(g_iKills_hs[iClient]))/float(g_iDeaths[iClient]));    // Формируем запрос
            g_hDatabase.Query(SQL_Callback_NtvGetClientTopPos, szQuery, hPack); // Отправляем запрос
        }
    
        return 0;
    }
    
    // Пришел ответ на запрос
    public void SQL_Callback_NtvGetClientTopPos(Database hDatabase, DBResultSet hResults, const char[] sError, any iDataPack)
    {
        if(sError[0]) // Если произошла ошибка
        {
            LogError("SQL_Callback_NtvGetClientTopPos: %s", sError); // Выводим в лог
            return; // Прекращаем выполнение ф-и
        }
       
        DataPack hPack = view_as<DataPack>(iDataPack); // Получаем наш DataPack
        hPack.Reset(); // Переводим указатель на начало датапака
       
        Handle hPlugin = view_as<Handle>(hPack.ReadCell());
        Function fncCallback = hPack.ReadFunction();
        int iData = hPack.ReadCell();
       
        delete hPack;
    
        int iPos = hResults.RowCount, iClientID = 0;
        float fScore = 0.0;
       
        if(hResults.FetchRow())    // Игрок есть в базе
        {
            // Получаем значения из результата
            fScore = hResults.FetchFloat(0);    // id
            iClientID = hResults.FetchInt(1);    // id
        }
    
        Call_StartFunction(hPlugin, fncCallback);
        /*
        Теперь нужно передать необходимые параметры в определенном порядке.
        Мы объявляли прототип обратного вызова
        typedef GetTopPosCallback = function void (int iClientID, int iTopPos, float fScore, any iData);
        Это как раз и есть вид ф-и которая будет вызвана.
        */
        Call_PushCell(iClientID);
        Call_PushCell(iPos);
        Call_PushFloat(fScore);
        Call_PushCell(iData);
        Call_Finish();
    }
    Попробую описать последовательность вызовов. Имеем 2 плагина:
    • Плагин предоставляющий API (base_stats.sp)
    • Плагин использующий API первого (api_example.sp)
      PHP:
      #pragma semicolon 1
      #include <sourcemod>
      #include <base_stats>
      #pragma newdecls required
      
      void GetPlayerTopPos(int iClient)
      {
          BS_GetClientTopPos(iClient, Example_PlayerTopPos, GetClientUserId(iClient));
      }
      
      public void Example_PlayerTopPos(int iClientID, int iTopPos, float fScore, any iData);
      {
          if(iClientID)
          {
              int iClient = GetClientOfUserId(iData);
              if(iClient)
              {
                  /*
                 
                 
                  */
              }
          }
      }
    • В какой-то момент работы api_example.sp нам потребовалось получить позицию игрока в топе.
      Мы вызовем ф-ю GetPlayerTopPos либо напрямую BS_GetClientTopPos
    • В base_stats.sp вызывается Native_GetClientTopPos и выполняется запрос в базу.
    • Когда приходит ответ от базы в base_stats.sp вызывается SQL_Callback_NtvGetClientTopPos.
    • Из результата запроса получаются нужные данные и передаются в ф-ю Example_PlayerTopPos с её последующим вызовом в api_example.sp
  6. Теперь можно перейти к форвардам. По своей сути форвады это те же ф-и но с некоторыми отличиями.
    Например, мы хотим чтобы после подключения игрока и загрузки его данных из базы вызывалася какая-то ф-я, уведомляющая о том что игрок успешно загружен. Для этой цели идеально подходит форвард.

    Форвады бывают 2-х видов:
    • Глобальные - имеют постоянное имя и вызываются во всех плагинах, где используются
    • Частные - вызываются не во всех плагинах, а только в заранее указанных и могут иметь разные имена
    По своей сути форвады это те же ф-и но вызываются сразу во всех или заранее заданных плагинах и имеют постоянное имя.

    Алгоритм вызова форвардов примерно такой:
    PHP:
    for(...) // Цикл по всем плагинам
    {
        if(...) // Поиск адреса ф-и по имени. Т.е. мы проверяем отслеживает ли плагин этот форвард
        {
            // Вызов форварда
        }
    }
    base_stats.inc:
    PHP:
    forward void BS_OnClientLoaded(int iClient);
    forward void BS_OnClientRankChanged(int iClient, int iOldRank, int iNewRank);

    base_stats.sp:
    PHP:
    Handle g_hGFwd_OnClientLoaded; // Указатель для вызова глобального форварда
    Handle g_hPFwd_OnClientRankChanged; // Указатель для вызова частного форварда
    
    // Создавать форварды можно как в AskPluginLoad2 так и в OnPluginStart, хотя можно и в других событиях которые вызываются единожды (либо блокировать повторное создание)
    
    public APLRes AskPluginLoad2(Handle hMyself, bool bLate, char[] sError, int iErr_max) 
    {
        // ...
        // Здесь все нативы что мы создавали ранее
    
        CreateNative("BS_HookPlayerRankChange", Native_HookPlayerRankChange);
    
        g_hGFwd_OnClientLoaded = CreateGlobalForward("BS_OnClientLoaded", ET_Ignore, Param_Cell);
        g_hPFwd_OnClientRankChanged = CreateForward(ET_Ignore, Param_Cell, Param_Cell, Param_Cell);
        /*
        Параметры у CreateGlobalForward:
        1-м параметром указывается имя форварда. Т.е. это имя ф-и которая будет вызвана
        2-й параметр дает SourceMod понять как интерпретировать возвращаемое значение.
    
        Существует четыре предопределенных метода:
    
        ET_Ignore - Все возвращаемые значения будут игнорироваться; 0 будет возвращено в конце.
        ET_Single - Возвращается только последнее возвращаемое значение.
        ET_Event - Функция должна возвращать значение Action (core.inc). Plugin_Stop действует как Plugin_Handled. Возвращается самое высокое значение.
        ET_Hook - Функция должна вернуть значение Action. Plugin_Stop немедленно завершает прямой вызов.
       
        Нам нужно всего лишь уведомить другие плагины о некоторых событиях не обращая внимания на возвращенный результат.
        Поэтому используем ET_Ignore
       
        3-й и далее определяет количество параметров и их тип.
        Может быть до 32-х параметров.
       
        Тип параметра:
    
        Param_Any - Любой тип параметра
        Param_Cell - Целое число (bool, int, Handle и его прозводные, Function и другие)
        Param_Float - Число с плавающей точкой
        Param_String - Строка
        Param_Array - Массив
        Param_VarArgs - Параметр может быть любого типа, но будет передаваться по ссылке. Это не может быть первый тип параметра, и если он используется, он должен быть последним типом параметра.
        Param_CellByRef - Тоже что и Param_Cell но с передачей по ссылке, а не по значению (будет изменятся исходное значение, а не копия)
        Param_FloatByRef - Тоже что и Param_Float но с передачей по ссылке
    
        Строки и массивы являются неявными ссылками.
       
        У CreateForward всё точно так же за исключением 1-го параметра - имени форварда
        */
    
        RegPluginLibrary("base_stats");
    
        return APLRes_Success;
    }
    
    public int Native_HookPlayerRankChange(Handle hPlugin, int iNumParams)
    {
        /*
        Этот натив нужен для добавления плагина в список тех плагинов, в которых будет вызываться форвард, а так же указывается адрес ф-и которая будет вызывана в качестве форварда.
        */
        AddToForward(g_hPFwd_OnClientRankChanged, hPlugin, GetNativeFunction(1));
    }
    
    // Для удобства создадим несколько вспомогательных ф-й
    void FwdClientLoaded(int iClient)
    {
        //    Начинаем создавать вызов форварда
        Call_StartForward(g_hGFwd_OnClientLoaded); // Передаем Handle форварда
    
        //    Теперь передаем параметры. У нас он 1
        Call_PushCell(iClient);
    
        // Завершаем вызов форварда
        Call_Finish();
    }
    
    void FwdClientRankChanged(int iClient, int iOldRank, int iNewRank)
    {
        //    Начинаем создавать вызов форварда
        Call_StartForward(g_hPFwd_OnClientRankChanged); // Передаем Handle форварда
    
        //    Теперь передаем параметры.
        Call_PushCell(iClient);
        Call_PushCell(iOldRank);
        Call_PushCell(iNewRank);
    
        // Завершаем вызов форварда
        Call_Finish();
    }
    
    // Теперь нам остается только вызывать вспомогательные функции там где это нужно.
    // Например: FwdClientLoaded(iClient);

    С частными форвардами немного сложнее.
    Сначала с помощью созданного нами натива BS_HookPlayerRankChange необходимо добавить форвард.

    api_example.inc:
    PHP:
    public void OnPluginStart()
    {
        // Добавляем наш форвард
        BS_HookPlayerRankChange(OnClientRankChanged);
    }
    
    /*
    Теперь при вызове FwdClientRankChanged в base_stats.sp будет вызываться OnClientRankChanged этом плагине
    */
    public void OnClientRankChanged(int iClient, int iOldRank, int iNewRank)
    {
        // Ранк игрока изменился
    }

Во вложениях прилагаються финальные версии полученных нами плагина, предоставляющего API и inc файла.

Со временем дополню и расширю урок.
 

Вложения

  • base_stats.inc
    2.7 КБ · Просмотры: 300
  • base_stats.sp
    22 КБ · Просмотры: 321
Последнее редактирование:

Boeing 767

заскамили мамонта ни за что, ни про что
Сообщения
524
Реакции
913
Давно ждал такой урок, спасибо!
 

Kruzya

Участник
Сообщения
12,970
Реакции
10,914
  • Команда форума
  • #3
Имеет смысл проверять, активно ли соединение с БД в нативах. Иначе если плагин вызовет натив до коннекта к БД, будет ошибка.
 

R1KO

fuck society
Сообщения
9,457
Реакции
7,786
  • Команда форума
  • #4
Перезалил файлы.
 
T

Tolyan

Дайте пример банального передачи new Handle:pack = CreateDataPack(); через native и его получение в другом плагине.
 

Kruzya

Участник
Сообщения
12,970
Реакции
10,914
  • Команда форума
  • #6
@Tolyan,
somecore.sp
PHP:
public APLRes AskPluginLoad2(Handle hMySelf, bool bLate, char[] szErrBuffer, int iBufferLength) {
    CreateNative("SomeNative",  API_Native);
}

public int API_Native(Handle hPlugin) {
    DataPack hPack = GetNativeCell(1);
    hPack.Reset();
    PrintToChatAll("Received DataPack %x. First int value: %d", hPack, hPack.ReadCell());
}

somecore.inc
PHP:
native void SomeNative(DataPack hPack);

someplugin.sp
PHP:
public void OnAllPluginsLoaded() {
    DataPack hPack = new DataPack();
    hPack.WriteCell(228);

    SomeNative(hPack);

    delete hPack;
}

Выведет в чате указатель на датапак и число, записанное в нём (т.е., 228).
 
T

Tolyan

а delete hPack; обязательно делать нужно? я думал нужно делать CloseHandle(hPack); в API_Native ... если можете обьясните чутка))
--- Добавлено позже ---
Как я делаю - поячему не получается?

PHP:
new Handle:pack = CreateDataPack();
    WritePackString(pack, "bla-bla");
    StartTestRules(client, pack);

----------------------
PHP:
native StartTestRules(num1,  DataPack num2);

---------------------------------
PHP:
public Native_StartTestRules(Handle:plugin, numParams)
{
    new client = GetNativeCell(1);
    DataPack pack = GetNativeCell(2);
    if (IsValidClient(client))
    {
        ResetPack(pack); //на эту строку ругается в логах
        ReadPackString(pack, comment[client], 128);
        PrintToChatAll("%N - %s", client, comment[client]);
        return 1;
    }
   
    CloseHandle(pack);
    return 0;
}
--- Добавлено позже ---
И в чемотличие этих записей - new Handle:pack = CreateDataPack(); и DataPack hPack = new DataPack(); ? только в синтаксисе?
 
Последнее редактирование модератором:

Kruzya

Участник
Сообщения
12,970
Реакции
10,914
  • Команда форума
  • #8
я думал нужно делать CloseHandle(hPack); в API_Native
Можно и так, но тогда надо передать права на датапак ядру. А это слегка сложнее.
Надо реализовывать ещё один натив, который возвращает хэндл плагина (GetMyHandle()).
А так, самая простейшая реализация "передачи прав":
PHP:
Handle UTIL_ChangeHandleOwner(Handle hPointer, Handle hNewOwner) {
  if (hPointer)
    return null;

  Handle hNewPointer = CloneHandle(hPointer, hNewOwner);
  CloseHandle(hPointer);
  return hNewPointer;
}
 
T

Tolyan

Можно и так, но тогда надо передать права на датапак ядру. А это слегка сложнее.
Надо реализовывать ещё один натив, который возвращает хэндл плагина (GetMyHandle()).
А так, самая простейшая реализация "передачи прав":
PHP:
Handle UTIL_ChangeHandleOwner(Handle hPointer, Handle hNewOwner) {
  if (hPointer)
    return null;

  Handle hNewPointer = CloneHandle(hPointer, hNewOwner);
  CloseHandle(hPointer);
  return hNewPointer;
}
подскажите чуть конкретней что я делаю не так в своем примере?
--- Добавлено позже ---
Сделал в точности как у вас... результат тот же что и у меня(((

L 02/28/2018 - 20:29:23: [SM] [0] DataPack.Reset
L 02/28/2018 - 20:29:23: [SM] [1] Line 23, C:\Users\PC\Desktop\Test.sp::Native_StartTest
 
Последнее редактирование модератором:

LEONID SPARNANSKII

Участник
Сообщения
5
Реакции
0
Много информации взято из этих источников кому интересно посмотрите сами:
1) Для начала если есть свой сервер - находим библиотеки: указываю путь на самом сервере : /left4dead/addons/sourcemod/scripting/include ( там вы найдете у каждой библиотеки свои обьяснения)
А дальше смотрите по этим сыллкам, они вам откруют глаза по всем параметрам и направлениям :
Introduction to SourceMod Plugins - AlliedModders Wiki
Writing extensions - AlliedModders Wiki
Natives (SourceMod Development) - AlliedModders Wiki
Introduction to SourcePawn 1.7 - AlliedModders Wiki
CheckCommandAccess · console · SourceMod Scripting API Reference
SourcePawn Transitional Syntax - AlliedModders Wiki
И так далее ребят, это я вам подсказки для дополнения, может кому полезно будет.
 

Kruzya

Участник
Сообщения
12,970
Реакции
10,914
  • Команда форума
  • #11
@LEONID SPARNANSKII, а зачем в перечне ссылок статья на вики по написанию расширений на C++ и ссылка на страницу функции CheckCommandAccess()?
Это никак не относится к написанию плагина, который предоставляет некий функционал.
 

LEONID SPARNANSKII

Участник
Сообщения
5
Реакции
0
Чем больше узнают, тем легче поймут потом. Я думаю такое инфо не помешает если паралельно изучать. Вся инфо для написания плагина находится в самой игре, сервер. Библиотеки, функции, события и так далее. + сайты дополнения для развития темы. А что касается этой ссылки, CheckCommandAccess · console · SourceMod Scripting API Reference то я имел в виду не саму команду а страницу которая является напрямую связано и полезно для написания плагина. Если я ошибаюсь, поправьте меня пожалуйста !
 
Последнее редактирование:

Kruzya

Участник
Сообщения
12,970
Реакции
10,914
  • Команда форума
  • #13
@LEONID SPARNANSKII, не спорю, но страница с информацией для С++ явно перегрузит их лишней информацией, т.к. там всё немного иначе устроено.
Само API, да, несомненно. Оно нужно всем. В конфуз ввело именно то, что ссылка была именно на эту функцию.
 

LEONID SPARNANSKII

Участник
Сообщения
5
Реакции
0
@LEONID SPARNANSKII, не спорю, но страница с информацией для С++ явно перегрузит их лишней информацией, т.к. там всё немного иначе устроено.
Само API, да, несомненно. Оно нужно всем. В конфуз ввело именно то, что ссылка была именно на эту функцию.
Тоже согласен, извините !
 

Banana

Участник
Сообщения
892
Реакции
113
Дайте пожалуйста легкий пример форварда с возвратом результата в плагин где он вызывался.
 

R1KO

fuck society
Сообщения
9,457
Реакции
7,786
  • Команда форума
  • #16
@Banana, Результат, который вернул форвард помещается в переменную, которая передана в Call_Finish
К тому же тип форварда должен быть НЕ ET_Ignore
 

Banana

Участник
Сообщения
892
Реакции
113
Дайте пожалуйста примитивный пример как получить плагин (имя плагина) в котором был вызван native и forward
 

R1KO

fuck society
Сообщения
9,457
Реакции
7,786
  • Команда форума
  • #18
@Banana, ну с нативом всё просто. В функции натива приходит хендл плагина (NativeCall typedef · functions · SourceMod Scripting API Reference)
По нему уже можно делать GetPluginInfo · sourcemod · SourceMod Scripting API Reference и/или GetPluginFilename · sourcemod · SourceMod Scripting API Reference

А вот с форвардом не ясно немного. Он же вызывается глобально для всех, или тебя интересует в каких плагинах он был вызван? Тогда скорее всего придется делать доп. натив и при вызове в плагине через натив сообщать основному плагину что был вызван форвард.
 

Banana

Участник
Сообщения
892
Реакции
113
Спасибо, я раз 100 наверно спрашивал но интренесно услышать это от вас возможно ли искуственно создать ошибку так чтоб она указывала еще и на строку в коде. (как это делает SM в error log)
 

fl0wer

Участник
Сообщения
19
Реакции
3
Спасибо, я раз 100 наверно спрашивал но интренесно услышать это от вас возможно ли искуственно создать ошибку так чтоб она указывала еще и на строку в коде. (как это делает SM в error log)
LogError, правда оно укажет именно на эту строку, удобно если их много, а текст ошибки один.
 
Сверху Снизу