[SourcePawn] Урок 3 - События (Events)

Тема в разделе "Программирование / Скриптинг", создана пользователем R1KO, 6 сен 2016.

  1. R1KO

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

    Сообщения:
    5.988
    Симпатии:
    2.986
    [SourcePawn] Урок 3 - События (Events)

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

    В SourceMod есть встроенные события:
    • OnMapStart (старт карты)
    • OnMapEnd (конец карты)
    • OnPluginStart (запуск плагина)
    • OnPluginEnd (остановка плагина)
    • OnClientConnected (игрок подключился)
    • OnClientDisconnect (игрок отключился)
    • и много других
    Но существуют еще события самого игрового сервера, которые возможно отловить, изменить или отправить.

    • Отлов событий
      Чтобы отловить событие необходимо знать его имя. Его можно узнать здесь Game Events (Source) - AlliedModders Wiki
      Существует 3 режима отлова событий:
      1. EventHookMode_Pre - Обратный вызов происходит до того, как событие произойдет
      2. EventHookMode_Post - Обратный вызов происходит после того как, событие произошло (Используется по умолчанию)
      3. EventHookMode_PostNoCopy - Обратный вызов происходит после того как, событие произошло, но данные события не копируются. Используется для оптимизации, например, когда нужно знать что событие произошло, но не нужно получать его параметры

      Есть 2 функции для отлова событий.
      Синтаксис:
      PHP:
      void HookEvent(const char[] nameEventHook callbackEventHookMode mode);
      bool HookEventEx(const char[] nameEventHook callbackEventHookMode mode);
      • name - Имя события
      • callback - Имя функции обратного вызова
      • mode - Режим отлова (см. выше)
      Отличие функций HookEvent и HookEventEx заключается в том что если событие не существует то функция HookEvent сообщит об этом в error лог, а HookEventEx просто вернет false. При этом отлов не будет выполнен.

      Функции обратных вызовов для событий бывают 2-х видов:
      • Для EventHookMode_Post и EventHookMode_PostNoCopy:
        PHP:
        function void(Event event, const char[] namebool dontBroadcast)
        Параметры:
        • event - Объект типа Event, который содержит данные о событии (для EventHookMode_PostNoCopy равен null, но в действительности это не работает)
        • name - Имя события
        • dontBroadcast - Было ли передано событие клиентам (true если не было, противном случае - false)
      • Для EventHookMode_Pre:
        PHP:
        function Action(Event event, const char[] namebool dontBroadcast)
        С параметрами всё тоже самое, разница только в типе данных функции.
        Тип Action может иметь значения:
        • Plugin_Continue - Разрешает продолжение события без изменений
        • Plugin_Changed - Разрешает продолжение события с изменениями
        • Plugin_Handled - Запрещает выполнение события, разрешая обрабатывать остальные вызовы
        • Plugin_Stop - Запрещает выполнение события и обработку остальных вызовов
        Для Event возврат Plugin_Continue или Plugin_Changed разрешит событию произойти, а возврат Plugin_Handled запретит вывод события.
        Сами события запретить нельзя, можно лишь запретить вывод уведомлений о них (сообщения об убийстве справа вверху, сообщение в чат при подключении/отключении игроков)
      Тип Event является дочерним от типа Handle
      Его свойства и методы для работы с ним можно посмотреть здесь: Event · events · SourceMod Scripting API Reference
      Примеры работы с событиями:
      PHP:
      #pragma semicolon 1
      #include <sourcemod>
      #pragma newdecls required

      public void OnPluginStart()
      {
          
      HookEvent("player_death"Event_PlayerDeath);    // Ловим событие с именем player_death
          // Event_PlayerDeath это имя функции, которая будет вызвана когда на сервере произойдет это событие
          // Мы не указали режим отлова, это значит что по умолчанию он будет EventHookMode_Post
      }

      public 
      void Event_PlayerDeath(Event hEvent, const char[] sEvNamebool bDontBroadcast)    // Фунуция будет вызвана когда на сервере произойдет событие player_death
      {
          
      // Параметры события: https://wiki.alliedmods.net/Counter-Strike:_Source_Events#player_death
          
      int iUserID hEvent.GetInt("userid");                    // userid жертвы
          
      PrintToServer("userid: %i"iUserID);
          
      int iAttackerUserID hEvent.GetInt("attacker");        // userid убийцы
          
      PrintToServer("attacker: %i"iAttackerUserID);
          
      char szWeapon[34];
          
      hEvent.GetString("weapon"szWeaponsizeof(szWeapon));    // название оружия
          
      PrintToServer("weapon: %s"szWeapon);
          
      int bHS hEvent.GetBool("headshot");                    // В голову ли (true - да, иначе - false)
          
      PrintToServer("headshot: %b"bHS);
      }
      PHP:
      public void OnPluginStart()
      {
          
      HookEvent("player_spawn"Event_PlayerSpawn);    // Ловим событие с именем player_spawn
          // Event_PlayerSpawn это имя функции, которая будет вызвана когда на сервере произойдет это событие
          // Мы не указали режим отлова, это значит что по умолчанию он будет EventHookMode_Post
      }

      public 
      void Event_PlayerSpawn(Event hEvent, const char[] sEvNamebool bDontBroadcast)    // Фунуция будет вызвана когда на сервере произойдет событие player_spawn
      {
          
      int iUserID hEvent.GetInt("userid");        // *1
          
      int iClient GetClientOfUserId(iUserID);    // *2
          
      if(iClient)    // Аналогично if(iClient != 0)        *3
          
      {
              
      PrintToChat(iClient"Вы возродились!");    // *4
          
      }
      }
      *Номер - Указатель на строку. Нужен для объяснений.

      Находим наше событие на wiki Generic Source Events - AlliedModders Wiki
      Смотрим его параметры:
      upload_2016-9-6_0-45-4.png

      У нашего события всего 1 параметр типа short. В SourcePawn он приводится к типу int
      Параметр имеет имя userid
      userid это уникальный идентификатор игрока на сервере.
      При запуске сервера первый вошедший игрок получает userid - 1, второй - 2, третий - 3
      Если первый игрок перезайдет он получит userid - 4.
      И так будет происходить до тех пор пока не произойдет перезапус сервера и всё начнется сначала.
      У сервера userid всегда 0

      В строке *1 мы получаем параметр userid
      • iUserID - имя нашей перменной
      • hEvent - имя объекта типа Event, который был передан в функции
      • GetInt - метод для типа Event, который получает целочисленное значения параметра переданого его как аргумент
      • userid - это имя параметра
      Допустим мы получили число 1437
      Все функции для работы с игроками требуют как аргумент индекс игрока.
      Индекс - это номер игрока на сервере.
      Он может быть от 1 до MaxClients. MaxClients это константа, которая хранит количество слотов на сервере.
      Следовательно индекс игрока может быть от 1 до количества слотов.
      У сервера индекс всегда 0.
      Значит нам нужно получить индекс игрока.
      Для работы с userid есть 2 фукнции:
      • PHP:
        int GetClientUserId(int client)
        Возвращает userid игрока, из его индекса (его передаем как аргумент)
      • PHP:
        int GetClientOfUserId(int userid)
        Возвращает индекс игрока, из его userid (его передаем как аргумент)
        В случае если игрока уже нет на сервере, т. е. userid уже не существует функция вернет 0.
      У нас есть userid, а нужен индекс, значит используем вторую функцию (строка *2)
      Помним что если userid не существует функция вернет 0, поэтому проверяем получили ли мы индекс игрока (строка *3)
      Если условие истинно - наш игрок появился на карте (спавнился).
      Строки *1 и *2 можно объединить в одну:
      PHP:
      int iClient GetClientOfUserId(hEvent.GetInt("userid"));
      Далее выполним с ним какие-то действия.
      Например, сообщим ему что он возродился с помощью функции PrintToChat (строка *4)
      Пример 2:
      PHP:
      #pragma semicolon 1

      #include <sourcemod>

      #pragma newdecls required

      public void OnPluginStart()
      {
          
      HookEvent("player_death"Event_PlayerDeathEventHookMode_Pre);
      }

      public 
      Action Event_PlayerDeath(Event hEvent, const char[] sEvNamebool bDontBroadcast)
      {
          
      // Параметры: https://wiki.alliedmods.net/Counter-Strike:_Source_Events#player_death
          
      if (hEvent.GetBool("headshot"))
          {
              return 
      Plugin_Handled;
          }

          return 
      Plugin_Continue;
      }
      Событие player_death
      upload_2016-9-6_0-46-36.png

      Этот код будет блокировать вывод сообщения об убийстве (справа вверху) если убийство было произведено в голову.

      Пример 3:
      PHP:
      #pragma semicolon 1

      #include <sourcemod>

      #pragma newdecls required

      public void OnPluginStart()
      {
          
      HookEvent("player_death"Event_PlayerDeathEventHookMode_Pre);
      }

      public 
      Action Event_PlayerDeath(Event hEvent, const char[] sEvNamebool bDontBroadcast)
      {
          
      hEvent.SetBool("headshot"true);
          return 
      Plugin_Changed;
      }
      Этот код будет изменять вывод сообщений об убийстве (справа вверху), отображая что все убийства происходят в голову.
    • Отправка событий
      PHP:
      void SendDeathMessage(int iAttackerint iVictim, const char[] sWeaponbool bHeadShot)
      {
          
      Event hEvent CreateEvent("player_death");    // Имя события
          
      if (hEvent != null)
          {
              
      hEvent.SetInt("userid"GetClientUserId(iVictim));    // userid жертвы
              
      hEvent.SetInt("attacker"GetClientUserId(iAttacker));    // userid убийцы
              
      hEvent.SetString("weapon"sWeapon);    // Название оружия
              
      hEvent.SetBool("headshot"bHeadShot);    // В голову ли
              
      hEvent.Fire(true);    // true - отправит сообщение игрокам
          
      }
      }
      Эта функция отправляет событие смерти игрока.

    Завтра добавлю описание методов типа Event
     
    Последнее редактирование: 31 окт 2016
    максимка27 и GreenGame нравится это.
  2. Ice_Sochi

    Ice_Sochi

    Сообщения:
    89
    Симпатии:
    22
    Оффтоп
     
  3. AlmazON

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

    Сообщения:
    4.527
    Симпатии:
    1.974
    Не сказано, что рекомендуется использовать для оптимизации именно этот метод, если возможно. Оффтоп
    Важно отметить, что никакое событие через HookEvent или HookEventEx запретить нельзя. "Запрещается" (блокируется) лишь его отображение, информация о нём, но не более того.
     
  4. FrozDark

    FrozDark Команда сайта HLMod Модератор

    Сообщения:
    1.761
    Симпатии:
    1.915
    Как раз таки блокируется. События (Events) по сути в Source существует как уведомления для игроков и их можно блокировать, эти события исходят из тех же функции
     
  5. R1KO

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

    Сообщения:
    5.988
    Симпатии:
    2.986
    @FrozDark, а есть инфа о событиях, которые можно блокировать,а какие нет?
     
  6. AlmazON

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

    Сообщения:
    4.527
    Симпатии:
    1.974
    Берём событие подключения игрока, ставим блокировку - сообщения в чате об этом пропадают, но игрок не отключается от сервера. Берём событие убийства и блокируем - кто-то всё равно умирает, лишь только исчезают иконки справа вверху. Сами события, как таковые, их действия - не блокируются. Это SDKHook реально блокирует.
     
  7. FrozDark

    FrozDark Команда сайта HLMod Модератор

    Сообщения:
    1.761
    Симпатии:
    1.915
    я же говорю, что Events(события) - лишь уведомления, которые можно заблокировать. Есть события, от которых зависят игроки. Например те же иконки смерти, их сервер не рисует, их рисует клиентская сторона, но как он нарисует иконку смерти, если событие было заблокировано? Так же есть событие воспроизведения звуков, например DoD:S и TF2. Событие связано с broadcast_audio. Заблокировав событие, у игрока не будет воспроизводится звуки криков, потому что игра попросту не будет знать что произошло событие

    SDKHook ловит уже функции, функции смерти и т.д. которые он блокирует, впоследствии которого Event даже не создается
    --- Добавлено позже ---
    Абсолютно все Events можно заблокировать, но функции (смерти, возрождения), из которых вызываются события, блокируются через сигнатуры или Offsets
     
    Серый™ и R1KO нравится это.
  8. AlmazON

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

    Сообщения:
    4.527
    Симпатии:
    1.974
    Оффтоп Дело в том, что некоторые начинающие скриптеры именно так пытаются заблокировать саму смерть игрока, вот и подчёркиваю, что события - не для этого.