[SourcePawn] Урок 6 - Таймеры

Kruzya

Участник
Сообщения
12,970
Реакции
10,914
  • Команда форума
  • #41
@MAGNAT2645, я делаю один таймер на всех.
И в нём что-то вроде:
PHP:
for (int iClient = MaxClients; iClient != 0; --iClient)
{
    if (IsClientInGame(iClient))
    {
        // код
    }
}
 

MAGNAT2645

Участник
Сообщения
63
Реакции
7
@MAGNAT2645, я делаю один таймер на всех.
И в нём что-то вроде:
PHP:
for (int iClient = MaxClients; iClient != 0; --iClient)
{
    if (IsClientInGame(iClient))
    {
        // код
    }
}
Я тоже стал так делать, потому что (как мне кажется) это немного быстрее и проще для хранения.

Но, в моём случае, иногда нужно отключать таймер и не отображать HUD на 5 секунд. И пока эти 5 секунд не прошли, отображать другой текст, а потом снова создать таймер и отображать прошлый HUD.
Можно, конечно, создать переменную, и когда она = true, не отображать HUD, но с массивами как-то проще.
--- Добавлено позже ---
Я, вроде как, делаю всё правильно, но каким-то образом таймер продолжает работать (после выхода игрока?) и спамит ошибку Entity is invalid
 

Kruzya

Участник
Сообщения
12,970
Реакции
10,914
  • Команда форума
  • #43
Покажите код. Очень сложно что-то определённое сказать, не видя его.
В частности, интересует, как именно создаёте таймер, и приблизительное его содержимое.
 

MAGNAT2645

Участник
Сообщения
63
Реакции
7
Я не могу показать весь код, приватный плагин.
Повторяющийся таймер создаётся через 0.5 сек после OnClientPutInServer (при этом используется userid для проверки уже в каллбэке). Так как таймер повторяющийся, глобальный и удаляется при выходе игрока, то в данные каллбэка передаётся индекс клиента (не userid).
Проблема заключается в том, что при выходе игрока с сервера таймер каким-то образом продолжает работать и спамит Entity is invalid (от функции GetClientButtons)

Я начинаю сомневаться в использованном мною методмапом.
C-подобный:
#if defined _timers_included
methodmap SMTimer < Handle {
   /*
   * Creates a basic timer.  Calling .Kill() on a timer will kill the timer. Do not .Kill(), .Close(),
   * or delete in a callback! Make sure to null timer in callback if global!
   *
   * @param interval           Interval from the current game time to execute the given function.
   * @param func               Function to execute once the given interval has elapsed.
   * @param data               Handle or value to pass through to the timer callback function.
   * @param flags               Flags to set (such as repeatability or auto-Handle closing).
   * @return                   Handle to the timer object.  You do not need to call CloseHandle().
   *                           If the timer could not be created, null will be returned.
   */
   public SMTimer(float interval, Timer func, any data = INVALID_HANDLE, int flags = 0) {
       return view_as<SMTimer>( CreateTimer( interval, func, data, flags ) );
   }

   /*
   * Kills a timer.  Use this instead of delete if you need more options. Do not use in a timer's callback!
   *
   * @param autoClose           If autoClose is true, the data that was passed to CreateTimer() will
   *                           be closed as a handle if TIMER_DATA_HNDL_CLOSE was not specified.
   * @error                   Invalid handles will cause a run time error.
   */
   public void Kill(bool autoClose = false) {
       KillTimer( this, autoClose );
       delete this;
   }

   /*
   * Closes a timer. Do not use in a timer's callback!
   *
   * @error                   Invalid handles will cause a run time error.
   */
   public void Close() {
       delete this;
   }

   public bool Clear(bool autoClose = false) {
       if ( this == null )
           return false;

       this.Kill( autoClose );
       return true;
   }

   /*
   * Manually triggers a timer so its function will be called.
   *
   * @param reset               If reset is true, the elapsed time counter is reset
   *                           so the full interval must pass again.
   */
   public void Trigger(bool reset=false) {
       TriggerTimer( this, reset );
   }
}
#endif

Использую конкретно SMTimer.Clear при выходе игрока с сервера
--- Добавлено позже ---
Щас попробую найти создание таймера и удаление в коде
 

MAGNAT2645

Участник
Сообщения
63
Реакции
7
C-подобный:
public Action TMR_ShowAimHUD(Handle timer, any data) {
   if ( Math_IsBitSet( GetClientButtons( data ), IN_SCORE ) ) // ошибка вызывается здесь
       return Plugin_Continue;

   int iTarget = GetClientAimTarget( data );
   if ( iTarget == -1 )
       return Plugin_Continue;

   // дальше уже отображается худ

Таймер создаётся так:
C-подобный:
public void OnClientPutInServer(int client) {
   if ( g_bEnabled && !IsFakeClient( client ) )
       CreateTimer( 0.5, TMR_Connected, GetClientUserId( client ) );
}

static Action TMR_Connected(Handle timer, any data) {
   int iClient = GetClientOfUserId( data );
   if ( !iClient )
       return Plugin_Stop;

   float flValue = CVAR_HudRefresh.FloatValue;
   if ( flValue >= 0.1 && g_bHudEnabled[ iClient ] ) {
       ShowHUD( iClient );
       g_hTimerHUD[ iClient ] = new SMTimer( flValue, TMR_ShowHUD, iClient, TIMER_REPEAT );
   }

   flValue = CVAR_AimRefresh.FloatValue;
   if ( flValue >= 0.1 && g_bAimEnabled[ iClient ] )
       g_hTimerAim[ iClient ] = new SMTimer( flValue, TMR_ShowAimHUD, iClient, TIMER_REPEAT );

Таймер удаляется:
C-подобный:
public void OnClientDisconnect(int client) {
   if ( !( g_bEnabled && IsClientInGame( client ) ) )
       return;

   g_hTimerHUD[ client ].Clear();
   g_hTimerAim[ client ].Clear();
--- Добавлено позже ---
В начале таймера есть IsClientInGame? Если нет - вот и корень проблем.
В начале каллбэка? Нет. Я просто не вижу смысла добавлять это туда, потому что таймер по сути контролируется с помощью OnClientDisconnect.
 

Kruzya

Участник
Сообщения
12,970
Реакции
10,914
  • Команда форума
  • #47
Вот и ошибка.
В OnClientDisconnect(), IsClientInGame() уже возвращает false. Да и смысла от него нет. Мы же просто память чистим.
 

MAGNAT2645

Участник
Сообщения
63
Реакции
7
Вот и ошибка.
В OnClientDisconnect(), IsClientInGame() уже возвращает false. Да и смысла от него нет. Мы же просто память чистим.
Правда? Я думал, что IsClientInGame() возвращает false уже в OnClientDisconnect_Post, но никак не в OnClientDisconnect.
--- Добавлено позже ---
Просто OnClientDisconnect также вызывается когда игрок только зашёл на сервер (ещё не в игре) и потом отключился. И мне нужно проверять, а был ли игрок на сервере (в игре) в момент отключения.
--- Добавлено позже ---
Может тогда хукнуть player_disconnect с EventHookMode_Pre?
 

Rostu

Добрая душа
Сообщения
986
Реакции
622
@MAGNAT2645, А вам обязательно под каждого создавать таймер, ведь он для всех одинаков? Иначе вы просто можете сделать ГлобалТаймер в OnMapStart и делать цикл по всем игрокам как показал вам @Крузяра,
Оффтоп

Пример, вы можете взять из любого бхоп таймера Оффтоп
 
Последнее редактирование:

MAGNAT2645

Участник
Сообщения
63
Реакции
7
@MAGNAT2645, А вам обязательно под каждого создавать таймер, ведь он для всех одинаков? Иначе вы просто можете сделать ГлобалТаймер в OnMapStart и делать цикл по всем игрокам как показал вам @Крузяра,
Оффтоп

Пример, вы можете взять из любого бхоп таймера Оффтоп
Я уже писал выше, что мне нужен персональный таймер для каждого игрока, чтобы можно было в определённый момент отрубить его на пару секунд. Пока у всех таймеры работают и отображают HUD, у определённых игроков он отключён.

Я сам стал использовать один таймер под всех игроков, но в данном случае мне нужны массивы, чтобы отделять рабочие таймеры от отключённых.

Я не считаю, что sourcemod-misc плох. Я просто беру из него некоторые части кода, которые считаю полезными. В данном случае, мне просто нравится добавлять методмапы везде, где это возможно.
 

Kruzya

Участник
Сообщения
12,970
Реакции
10,914
  • Команда форума
  • #51
чтобы можно было в определённый момент отрубить его на пару секунд
В чём проблема в таком случае сделать массив булов под отключение таймеров для каждого конкретного игрока?

В данном случае, мне просто нравится добавлять методмапы везде, где это возможно.
Для каких целей? ООП хорош, но в меру. А тут уже явный перебор.
К тому же, в конечном счёте, Сурсмоду глубоко наплевать, с методмапами плагин написан, или без.
 

MAGNAT2645

Участник
Сообщения
63
Реакции
7
В чём проблема в таком случае сделать массив булов под отключение таймеров для каждого конкретного игрока?


Для каких целей? ООП хорош, но в меру. А тут уже явный перебор.
К тому же, в конечном счёте, Сурсмоду глубоко наплевать, с методмапами плагин написан, или без.
Вот именно, что Сурсмоду всё равно на методмапы, но мне как-то приятнее читать код с ними. (Ну не пригляделся мне синтакс SM 1.6)
Я выше уже упоминал, что можно использовать переменную и когда она = true, не отображать HUD. Я просто не уверен, как лучше отслеживать данный способ.
Попробую конечно сделать массив и уже циклом в каллбэке отбрасывать всех игроков с переменной true.
У меня даже сейчас, когда на сервере никого нет, в логи идёт спам Entity n is invalid :D
 

Kruzya

Участник
Сообщения
12,970
Реакции
10,914
  • Команда форума
  • #53
Я просто не уверен, как лучше отслеживать данный способ.
Создавайте ещё один таймер на время отключения, с телом аля:
PHP:
public Action EnableHUD(Handle hTimer, int iClient)
{
    g_bHud[iClient] = false;
}

В моём примере, я предполагаю, что массив хранит именно выключения таймера. А iClient - именно индекс игрока, не UserID. Вдруг игрок выйдет, пока HUD выключен.

А если у Вас даже на пустом сервере спамит ошибкой о невалидности энтити, Вы явно делаете что-то не так.
 

MAGNAT2645

Участник
Сообщения
63
Реакции
7
Я попробую, это должно помочь.
Кстати, IsClientInGame возвращает true в OnClientDisconnect, потому что после него у меня через PrintToChatAll выводится сообщение об отключённом игроке.
Если бы IsClientInGame возвращал false, в чат бы ничего не выводилось.
 

MAGNAT2645

Участник
Сообщения
63
Реакции
7
Как раз таки TF2.
Просто в SM есть форвард OnClientDisconnect_Post, в котором клиент уже точно не в игре.
 

Rostu

Добрая душа
Сообщения
986
Реакции
622
@MAGNAT2645, Вы можете сделать данный таймер гибким, добавив форвард, и если нужно блочить отображение вызвав return Plugin_Handled/Stop. -Можете посмотреть Influx [influx_hud_draw.sp], там именно так и сделано

Если вам приятнее читать новый синтаксис, а оно и понятно, так пишите на нем, без всяких `misc` инков, проще уже написать гибкий TimerUpdate через методмапы, а туда еще и форвард для фишек и т.п засунуть.

Можете поглядеть и на блеки 2.0 - Methodmap Style [Клик]

И именно блеки 2.0 не отошел от традиции с OnMapStart и обновлением худа игрока - Сам таймерUpdate[Клик]

Оффтоп
 
Последнее редактирование:

Kruzya

Участник
Сообщения
12,970
Реакции
10,914
  • Команда форума
  • #58
@MAGNAT2645, странно. Посмотреть надо.

Оффтоп
 

MAGNAT2645

Участник
Сообщения
63
Реакции
7
@MAGNAT2645, Вы можете сделать данный таймер гибким, добавив форвард, и если нужно блочить отображение вызвав return Plugin_Handled/Stop. -Можете посмотреть Influx [influx_hud_draw.sp], там именно так и сделано

Если вам приятнее читать новый синтаксис, а оно и понятно, так пишите на нем, без всяких `misc` инков, проще уже написать гибкий TimerUpdate через методмапы, а туда еще и форвард для фишек и т.п засунуть.

Можете поглядеть и на блеки 2.0 - Methodmap Style [Клик]

И именно блеки 2.0 не отошел от традиции с OnMapStart и обновлением худа игрока - Сам таймерUpdate[Клик]

Оффтоп
Я уже исправил это, создав единый глобальный дескриптор таймера и добавив массив из bool.
Постоянно пишу всё на новом синтаксисе, просто использую некоторые части из инков типа sourcemod-misc (а не весь инклюд), чтобы уменьшить содержимое кода.
--- Добавлено позже ---
У меня ещё вопрос. А HTML-теги для Hint работают только в CS:GO, так ведь?
--- Добавлено позже ---
Оффтоп
--- Добавлено позже ---

Тут кстати возникла другая проблема. Теперь выводит ошибку Handle is invalid
C-подобный:
L 02/25/2019 - 12:43:20: [SM] Exception reported: Handle 963805ed is invalid (error 3)
L 02/25/2019 - 12:43:20: [SM] Blaming: pls.smx
L 02/25/2019 - 12:43:20: [SM] Call stack trace:
L 02/25/2019 - 12:43:20: [SM]   [0] CloseHandle
L 02/25/2019 - 12:43:20: [SM]   [1] Line 33, other.inc::SMTimer.Kill
L 02/25/2019 - 12:43:20: [SM]   [2] Line 49, other.inc::SMTimer.Clear
L 02/25/2019 - 12:43:20: [SM]   [3] Line 1421, pls.sp::OnMapEnd

C-подобный:
    public void Kill(bool autoClose = false) {
       KillTimer( this, autoClose );
       delete this; // ошибка вроде как тут.
   }

Разве delete не эквивалент для:
C-подобный:
if ( x != null ) {
      CloseHandle( x );
      x = null;
}
?


Может можно как-то приравнять переменную к null в методмапе?
Я понимаю, что лучше уж тогда вообще не использовать методы .Kill и .Close в SMTimer, но мне бы хотелось, чтобы .Kill без ошибок уничтожал дескриптор, а получается что сперва происходит KillTimer, потом уже delete проходит проверку на null и пытается CloseHandle уже на закрытом дескрипторе.
--- Добавлено позже ---
Я даже пробовал что-то типа:
C-подобный:
    property Handle Ref {
       public get() {
           return view_as<Handle>( this );
       }
   }

    public void Kill(bool autoClose = false) {
       KillAndZeroTimer( this.Ref, autoClose ); // выводит error 035: argument type mismatch (argument 1), потому что KillAndZeroTimer принимает не значение
                                                                                  // а reference переменной
   }
 
Последнее редактирование:

Nekro

Терра инкогнита
Сообщения
4,025
Реакции
2,260
В OnTakeDamage при попадании создаю таймер
C-подобный:
hTimer[iVictim] = CreateTimer(0.05, TimerM, iVictim, TIMER_REPEAT);
Но дело в том, что мне бы хотелось добавить красоты, всяких эффектов и звуков по окончанию работы функции TimerM, что в таймере, а туда я могу передать данные только жертвы, а хотелось бы и атакера (необходимо передать данные обоих), но как это можно реализовать?
 
Сверху Снизу