Как остановить (удалить) глобальный таймер

Dragokas

Добрая душа
Сообщения
229
Реакции
213
Минимальный пример "Как остановить (удалить) глобальный таймер"
Инфа от Dragokas

Я вижу, как очень часто задают вопросы об ошибках:
  • Native "KillTimer" reported: Invalid timer handle XXX (error 3)
  • Handle XXX is invalid (error 1)
  • TIMER_FLAG_NO_MAPCHANGE не останаливает таймер, когда раунд закончен/проигран и т.д.
  • когда кто-то желает запустить одноразовый или повторяющийся таймер, а затем остановить его в round_end вручную, или с использованием флага TIMER_FLAG_NO_MAPCHANGE.
Чтобы сделать это без ошибок, вам необходимо создавать таймер правильно и обнулять глобальную переменную таймера в нужных местах.

Вот минимальные примеры с комментариями, как это грамотно сделать:

1. Единоразовый таймер с хендлом в глобальной переменной (timer_single.sp)
PHP:
#include <sourcemod>

Handle g_iTimer; // глобальная переменная нашего таймера

public void OnPluginStart()
{
    HookEvent("round_start",             Event_RoundStart,         EventHookMode_PostNoCopy);
    HookEvent("round_end",                 Event_RoundEnd,         EventHookMode_PostNoCopy);
}

// Давайте создадим таймер в начале раунда:

public void Event_RoundStart(Event event, const char[] name, bool dontBroadcast)
{
    delete g_iTimer; // удалим предыдущий таймер на случай, если событие "round_start" сработает дважды

    g_iTimer = CreateTimer(30.0, Timer_Sample);

    // не ставьте здесь TIMER_FLAG_NO_MAPCHANGE !!!
    // потому что переменная с хендлом таймера не будет обнулена автоматически в случае, если произойдёт смена карты прежде, чем сработает таймер
}

public void Event_RoundEnd(Event event, const char[] name, bool dontBroadcast)
{
    delete g_iTimer; // останавливаем таймер на случай, если он ещё не сработал, таким образом этот старый таймер не будет перенесён на новый раунд и не сработает случайно в процессе загрузки уровня

    // ключевое слово "delete" останавливает таймер и автоматически присваивает ноль переменной.
    // Заметка: "delete 0" не вызывает срабатывания исключения.
}

public void OnMapEnd() // требуется, поскольку принудительная смена карты не вызывает событие "round_end"
{
    delete g_iTimer;
}

public Action Timer_Sample(Handle timer)
{
    // делаем что-либо
    PrintToChatAll("tick");

    // не забываем обнулить здесь переменную-хендл таймера, т.к. после завершения колбека этот хендл станет недействительным
    g_iTimer = null;
}

2. Повторяющийся таймер с хендлом в глобальной переменной (timer_repeat.sp)
PHP:
#include <sourcemod>

Handle g_iTimer;

public void OnPluginStart()
{
    HookEvent("round_start",             Event_RoundStart,         EventHookMode_PostNoCopy);
    HookEvent("round_end",                 Event_RoundEnd,         EventHookMode_PostNoCopy);
}

public void Event_RoundStart(Event event, const char[] name, bool dontBroadcast)
{
    // Давайте создадим таймер в начале раунда:

    delete g_iTimer; // предотвращает двойной запуск (на всякий случай)

    // не ставьте сюда флаг TIMER_FLAG_NO_MAPCHANGE !!!
    // потому что переменная с хендлом не будет обнулена
    g_iTimer = CreateTimer(1.0, Timer_Sample, _, TIMER_REPEAT);
}

public void Event_RoundEnd(Event event, const char[] name, bool dontBroadcast)
{
    delete g_iTimer;
    // ключевое слово "delete" останавливает таймер и автоматически присваивает ноль переменной.
    // Заметка: "delete 0" не вызывает срабатывания исключения.
}

public void OnMapEnd() // требуется, поскольку принудительная смена карты не вызывает событие "round_end"
{
    delete g_iTimer;
}

public Action Timer_Sample(Handle timer)
{
    // полезная нагрузка
    PrintToChatAll("tick");

    return Plugin_Continue;

    /*
    Замечание 1: если вы желаете принудительно остановить таймер здесь, вам необходимо сначала присвоить ноль переменной с хендлом таймера

    g_iTimer = null;
    return Plugin_Stop;

    Замечание 2: Никогда не пытайтесь применить delete или KillTimer() на хендле таймера внутри своего же колбека (т.е. здесь).
    */
}

3. Единоразовый таймер с хендлами для каждого клиента в глобальном массиве (timer_single_per-client.sp)
PHP:
#include <sourcemod>

Handle g_iTimer[MAXPLAYERS+1];

public void OnPluginStart()
{
    HookEvent("round_start",             Event_RoundStart,         EventHookMode_PostNoCopy);
    HookEvent("round_end",                 Event_RoundEnd,         EventHookMode_PostNoCopy);

    RegConsoleCmd("sm_timer", CmdTestTimer, "Re-starts timer for testing")
}

Action CmdTestTimer(int client, int args)
{
    delete g_iTimer[client];
    g_iTimer[client] = CreateTimer(15.0, Timer_Sample, GetClientUserId(client));
}

public void Event_RoundStart(Event event, const char[] name, bool dontBroadcast)
{
    for ( int i = 1; i <= MaxClients; i++ )
    {
        if ( IsClientInGame(i) )
        {
            // запускаем одиночный таймер для каждого клиента отдельно
            delete g_iTimer[i];
            g_iTimer[i] = CreateTimer(3600.0, Timer_Sample, GetClientUserId(i));
        }
    }
}

public void OnClientDisconnect(int client)
{
    // детальное объяснение см. в следующем примере
    delete g_iTimer[client];
}

void Reset()
{
    for ( int i = 1; i <= MaxClients; i++ )
    {
        delete g_iTimer[i];
    }
}

public void Event_RoundEnd(Event event, const char[] name, bool dontBroadcast)
{
    Reset();
}

public void OnMapEnd()
{
    Reset();
}

public Action Timer_Sample(Handle timer, int UserId)
{
    int client = GetClientOfUserId(UserId);

    if ( client && IsClientInGame(client) )
    {
        g_iTimer[client] = null;

        // что-нибудь делаем
        // ...
        PrintToChat(client, "tick");
    }
}

4. Повторяющийся таймер с хендлами для каждого клиента в глобальном массиве (timer_repeat_per-client.sp)
PHP:
#include <sourcemod>

Handle g_iTimer[MAXPLAYERS+1];

public void OnPluginStart()
{
    HookEvent("round_start",             Event_RoundStart,         EventHookMode_PostNoCopy);
    HookEvent("round_end",                 Event_RoundEnd,         EventHookMode_PostNoCopy);

    RegConsoleCmd("sm_timer", CmdTestTimer, "Starts timer for testing")
}

Action CmdTestTimer(int client, int args)
{
    // останавливаем таймер, если он уже был запущен
    // это также установит значение переменной в 0
    delete g_iTimer[client];
    PrintToChatAll("timer = %i", g_iTimer[client]);
    // запускаем повторяющийся таймер
    // передаём ссылку на клиента, таким образом мы сможем убедиться, что он не подменён за время, пока таймер на самом деле не сработает
    g_iTimer[client] = CreateTimer(15.0, Timer_Sample, GetClientUserId(client), TIMER_REPEAT);
}

public void Event_RoundStart(Event event, const char[] name, bool dontBroadcast)
{
    for ( int i = 1; i <= MaxClients; i++ )
    {
        if ( IsClientInGame(i) )
        {
            // запускаем повторяющийся таймер для каждого клиента
            delete g_iTimer[i];
            g_iTimer[i] = CreateTimer(15.0, Timer_Sample, GetClientUserId(i), TIMER_REPEAT);
        }
    }
}

public void OnClientDisconnect(int client)
{
    // убедимся, что убили таймер при отключении клиента
    delete g_iTimer[client];
}

void Reset()
{
    for ( int i = 1; i <= MaxClients; i++ )
    {
        delete g_iTimer[i];   // останавливаем таймер для каждого игрока
    }
}

public void Event_RoundEnd(Event event, const char[] name, bool dontBroadcast)
{
    Reset();
}

public void OnMapEnd() // требуется, потому что ForceChangeLevel не вызывает события "round_end"
{
    Reset();
}

public Action Timer_Sample(Handle timer, int UserId)
{
    // проверяем, является ли тем же клиентом
    int client = GetClientOfUserId(UserId);

    // проверка, действителен ли
    if ( client && IsClientInGame(client) )
    {
        // что-нибудь делаем
        // ...
        PrintToChat(client, "tick");
        return Plugin_Continue;

        /*     или, если вам хочется остановить таймер
            но, не забудьте сперва обнулить ('null') таймер:

        g_iTimer[client] = null;
        return Plugin_Stop;
        */
    }
    // здесь вы не сможете обнулить переменную таймера, потому что вы не знаете правильный индекс клиента для доступа к массиву хендлов, а поскольку клиент уже мог выйти, то GetClientOfUserId не сработает
    // поэтому вместо этого мы используем форвард OnClientDisconnect()
    return Plugin_Stop;
}

5. (Без глобальной переменной) Приостанавливаем повторяющийся таймер
- без необходимости управлять хендлом в глобальной переменной (timer_repeat_pause.sp)
PHP:
/*
    Этот пример демонстрирует 'Шлепок игрока' через 30.0 сек. задержки от начала раунда и повторяет каждые 2 секунды, пока не закончится раунд.

    Спасибо Marttt за идею и код.
*/

#pragma semicolon 1
#pragma newdecls required

#include <sourcemod>
#include <sdktools>

#define WAIT_TIME 30.0

float g_fRoundStartTime;

public void OnPluginStart()
{
    HookEvent("round_start",             Event_RoundStart,         EventHookMode_PostNoCopy);
    HookEvent("round_end",                 Event_RoundEnd,         EventHookMode_PostNoCopy);

    // запускаем таймер, который никогда не будет уничтожен
    CreateTimer(2.0, Timer_CheckPlayerZone, _, TIMER_REPEAT);
}

public Action Event_RoundStart(Handle event, const char[] name, bool dontBroadcast)
{
    g_fRoundStartTime = GetEngineTime();
}

public Action Event_RoundEnd(Handle event, const char[] name, bool dontBroadcast)
{
    g_fRoundStartTime = 0.0;
}

public void OnMapEnd()
{
    g_fRoundStartTime = 0.0;
}

public Action Timer_CheckPlayerZone(Handle timer)
{
    if ( g_fRoundStartTime == 0.0 ) // "приостанавливаем" таймер между событиями "round_end" - "round_start"
        return Plugin_Continue;

    if ( GetEngineTime() - g_fRoundStartTime < WAIT_TIME ) // "приостанавливаем" таймер на протяжении 30.0 сек. после "round_start"
        return Plugin_Continue;

    for ( int i = 1; i <= MaxClients; i++ )
    {
        if ( IsClientInGame(i) && IsPlayerAlive(i) )
        {
            SlapPlayer(i, 20, true);
            PrintToChat(i, "You are being slapped!");
        }
    }
    return Plugin_Continue;
}

-------------------

Общие советы:
  • Не используйте флаг TIMER_FLAG_NO_MAPCHANGE, если вы присваиваете хендл таймера глобальной переменной с целью дальнейшего контроля таймера через эту переменную позднее.
    - Флаг TIMER_FLAG_NO_MAPCHANGE может быть безопасно использован, если вы создаёте таймер без назначения его хендла переменной.

  • Когда вы убиваете таймер за пределами колбека этого таймера, используйте ключевое слово 'delete'.

  • Когда вы убиваете повторяющийся таймер внутри его же колбека, используйте исключительно "return Plugin_Stop"!
    - Не используйте там "delete" или"KillTimer()", иначе можете получить ошибки вида:
    Plugin "XXX.smx" encountered error 23: Native detected error
    [SM] Invalid timer handle XXX (error 3) during timer end, displayed function is timer callback, not the stack trace

  • Вы должны всегда присваивать null глобальной переменной таймера в eго колбеке (перед return) в момент перед разрушением хендла, который наступает:
    • для повторяющегося таймера, - когда вы передаёте "return Plugin_Stop"
    • для единоразового таймера, - автоматически, как только завершается колбек таймера.

    Если у вас хендлы таймера хранятся в глобальном массиве и вы не знаете, как найти нужный хендл в массиве из-под колбека таймера, то:
    • в таймерах вида "для каждого игрока", используйте форвард OnClientDisconnect(), чтобы получить индекс массива.
    • в таймерах вида "авто-инкремент":
таких как:
PHP:
const int MAX = 100;

Handle g_iTimer[MAX];

int g_iTimerCount;

public void OnPluginStart()
{
    g_iTimer[g_iTimerCount++] = CreateTimer( 1.0, Timer_Sample );
    g_iTimer[g_iTimerCount++] = CreateTimer( 2.0, Timer_Sample );
}

для обнуления глобальной переменной (элемента массива) из-под колбека таймера вы можете воспользоваться вспомогательной функцией:
PHP:
void NullifyHandle(Handle timer)
{
    for( int i = 0; i <= g_iTimerCount; i++ )
    {
        if( timer == g_iTimer[i])
        {
            g_iTimer[i] = null;
            break;
        }
    }
}

Использование:
PHP:
public Action Timer_Sample(Handle timer)
{
    // some code
    // ...

    NullifyHandle(timer); // найдёт соответствие хендла в массиве и обнулит его
    return Plugin_Stop;
}

  • Удаляйте таймер перед его созданием, чтобы избежать случайного его срабатывания дважды; некоторые события, такие как "round_start" имеют привычку вызываться несколько раз (в определённых играх).

  • Чтобы остановить таймер по окончании раунда, используйте оба, и событие "round_end", и форвард "OnMapEnd()", потому что "round_end" не срабатывает, когда карта сменяется принудительно.
Всем удачи!
 

Вложения

  • Timers.zip
    4.1 КБ · Просмотры: 16
Последнее редактирование:

Kruzya

Участник
Сообщения
12,970
Реакции
10,914
  • Команда форума
  • #2
Не используйте флаг TIMER_FLAG_NO_MAPCHANGE, если вы присваиваете хендл таймера глобальной переменной с целью дальнейшего контроля таймера через эту переменную позднее.
Можно в OnMapEnd() обнулять переменную с хендлом таймера. Его закрывать там не надо, поскольку он уже сам будет закрыт на этот момент (или чуть позднее, не проверял).
 

Dragokas

Добрая душа
Сообщения
229
Реакции
213
@Kruzya, веское замечание. Да, как вариант. Позже дополню статью.
 

Nekro

Терра инкогнита
Сообщения
4,025
Реакции
2,260
Подскажите, если убить таймер до его выполнения, то калбек выполниться или нет? Вот пример, если игрок введёт команду, то он будет кикнут по истечению времени? Да, будет, но как этого не допустить?


C++:
Handle
    hTimerStart[MAXPLAYERS+1];

public void OnPluginStart()
{
    RegConsoleCmd("sm_no", CmdNo);
}

public void OnClientPostAdminCheck(int client)
{
    if(!client || IsFakeClient(client))
        return;

    hTimerStart[client] = CreateTimer(10.0, StartTimer, GetClientUserId(client));
}

public Action StartTimer(Handle timer, any Userid)
{
    int client = GetClientOfUserId(Userid);
    
    if(!client)
        return Plugin_Stop;
        
    char sSteam[32];
    GetClientAuthId(client, AuthId_Steam2, sSteam, sizeof(sSteam));
    
    ServerCommand("sm_kick %N Привет", client);
    return Plugin_Changed;
}

public Action CmdNo(int client, any args)
{
    if(client < 1)
        return Plugin_Continue;
        
    hTimerStart[client] = null;
    delete hTimerStart[client];

    return Plugin_Changed;
}
 

Dragokas

Добрая душа
Сообщения
229
Реакции
213
@Nekro, не выполнится, в том и смысл.
Досрочное выполнение колбека - это через функцию TriggerTimer().

PS. По коду несколько не-критичных замечаний, не связанных с таймером;
если есть желание, могу пояснить в какой-нибудь другой теме.
 

Nekro

Терра инкогнита
Сообщения
4,025
Реакции
2,260
@Nekro, не выполнится, в том и смысл.
Досрочное выполнение колбека - это через функцию TriggerTimer().

PS. По коду несколько не-критичных замечаний, не связанных с таймером;
если есть желание, могу пояснить в какой-нибудь другой теме.
1. Код это пример на скорую руку, но конечно выслушал бы.
2. Так выполняется же, я пишу команду !no, но меня кикает
 

Grey83

не пишу плагины с весны 2022
Сообщения
8,520
Реакции
4,979
я пишу команду !no, но меня кикает
1) delete и так обнуляет хэндл, поэтому приравнивать hTimerStart[client] к null не нужно
2) пред delete ты удаляешь ссыль на хэндл таймера, который нужно удалить
 

iLoco

Пишу плагины за печеньки 🍪🍪🍪
Сообщения
2,265
Реакции
1,323
Подскажите, если убить таймер до его выполнения, то калбек выполниться или нет? Вот пример, если игрок введёт команду, то он будет кикнут по истечению времени? Да, будет, но как этого не допустить?


C++:
Handle
    hTimerStart[MAXPLAYERS+1];

public void OnPluginStart()
{
    RegConsoleCmd("sm_no", CmdNo);
}

public void OnClientPostAdminCheck(int client)
{
    if(!client || IsFakeClient(client))
        return;

    hTimerStart[client] = CreateTimer(10.0, StartTimer, GetClientUserId(client));
}

public Action StartTimer(Handle timer, any Userid)
{
    int client = GetClientOfUserId(Userid);
   
    if(!client)
        return Plugin_Stop;
       
    char sSteam[32];
    GetClientAuthId(client, AuthId_Steam2, sSteam, sizeof(sSteam));
   
    ServerCommand("sm_kick %N Привет", client);
    return Plugin_Changed;
}

public Action CmdNo(int client, any args)
{
    if(client < 1)
        return Plugin_Continue;
       
    hTimerStart[client] = null;
    delete hTimerStart[client];

    return Plugin_Changed;
}
Тут ошибка в коде yf 36 строке, нужно сначала удалить таймер а потом ему присвоить INVALID_HANDLE или null, кто как привык.
C-подобный:
    delete hTimerStart[client];
    hTimerStart[client] = null;

А так-же при создании таймера, рекомендуется удалять его (с проверкой), для предотвращения бед.
C-подобный:
    if(hTimerStart[client]) {
        delete hTimerStart[client];
    }

    hTimerStart[client] = CreateTimer(10.0, StartTimer, GetClientUserId(client));
Сообщения автоматически склеены:

1) delete и так обнуляет хэндл, поэтому приравнивать hTimerStart[client] к null не нужно
2) пред delete ты удаляешь ссыль на хэндл таймера, который нужно удалить
Сам таймер убьёт, но вот в переменной останется его Handle, никуда он сам не удалится, это особенность таймера, не раз проверял это.
 

Nekro

Терра инкогнита
Сообщения
4,025
Реакции
2,260
Тут ошибка в коде yf 36 строке, нужно сначала удалить таймер а потом ему присвоить INVALID_HANDLE или null, кто как привык.
C-подобный:
    delete hTimerStart[client];
    hTimerStart[client] = null;

А так-же при создании таймера, рекомендуется удалять его (с проверкой), для предотвращения бед.
C-подобный:
    if(hTimerStart[client]) {
        delete hTimerStart[client];
    }

    hTimerStart[client] = CreateTimer(10.0, StartTimer, GetClientUserId(client));
Сообщения автоматически склеены:


Сам таймер убьёт, но вот в переменной останется его Handle, никуда он сам не удалится, это особенность таймера, не раз проверял это.
Так удаление должно же его и обнулять ещё, нет?
 

Grey83

не пишу плагины с весны 2022
Сообщения
8,520
Реакции
4,979
@Nekro, попробуй вот так:
C-подобный:
Handle
    hTimerStart[MAXPLAYERS+1];

public void OnPluginStart()
{
    RegConsoleCmd("sm_no", CmdNo);
}

public void OnClientPostAdminCheck(int client)
{
    if(client && !IsFakeClient(client)) hTimerStart[client] = CreateTimer(10.0, StartTimer, GetClientUserId(client));
}

public void OnClientDisconnect(int client)
{
    if(hTimerStart[client])
    {
        delete hTimerStart[client];
        hTimerStart[client] = null;
    }
}

public Action StartTimer(Handle timer, any client)
{
    if((client = GetClientOfUserId(client)) && !IsClientInKickQueue(client))
    {
        hTimerStart[client] = null;
        KickClient(client, "Привет");
    }
    return Plugin_Stop;
}

public Action CmdNo(int client, any args)
{
    if(client)
    {
        delete hTimerStart[client];
        hTimerStart[client] = null;
    }
    return Plugin_Handled;
}
На счёт обнуления таймера iLoco таки прав.
 
Последнее редактирование:

Dragokas

Добрая душа
Сообщения
229
Реакции
213
Ещё по мелочи:
- боты не проходят механизм админ проверки, IsFakeClient лишний там в OnClientPostAdminCheck. Также движок должен уже гарантировать, что туда придёт валидный индекс.
- а вот досрочную остановку таймера по завершению карты я бы добавил (флаг TIMER_FLAG_NO_MAPCHANGE в CreateTimer).
- IsClientInGame надо бы добавить в коллбек таймера, положительный ответ GetClientOfUserId не гарантирует, что клиент в игре. Он, к примеру может быть подключён, но уже (или ещё) не "в игре".
Сообщения автоматически склеены:

Так удаление должно же его и обнулять ещё, нет?
да, delete уже включает в себя операцию присвоения переменной null.
 
Последнее редактирование:

Grey83

не пишу плагины с весны 2022
Сообщения
8,520
Реакции
4,979
а вот досрочную остановку таймера по завершению карты я бы добавил (флаг TIMER_FLAG_NO_MAPCHANGE в CreateTimer).
Тогда таймер не обнуляется, емнип.
Хотя при смены карты должен срабатывать OnClientDisconnect().
Сообщения автоматически склеены:

@Dragokas, почему-то при декомпиле получается вот такое:
C-подобный:
        if (hTimerStart[client])
        {
                new var1 = hTimerStart[client];
                CloseHandle(var1);
                var1 = 0;
        }
Т.е. обнуляется не сам хэндл.
Или я что-то путаю?
 

Muinez

Участник
Сообщения
132
Реакции
120
Тогда таймер не обнуляется, емнип.
Хотя при смены карты должен срабатывать OnClientDisconnect().
Сообщения автоматически склеены:

@Dragokas, почему-то при декомпиле получается вот такое:
C-подобный:
        if (hTimerStart[client])
        {
                new var1 = hTimerStart[client];
                CloseHandle(var1);
                var1 = 0;
        }
Т.е. обнуляется не сам хэндл.
Или я что-то путаю?
ужос
C-подобный:
Handle Test[1337];
public void OnPluginStart()
{
   int test = GetRandomInt(0, 500);
   Test[test] = CreateTimer(123.0, bla);
   PrintToServer("%i", Test[test]);
   delete Test[test];
   PrintToServer("%i", Test[test]);
}
Action bla(Handle timer)
{
   
}
1649197058191.png
 

Grey83

не пишу плагины с весны 2022
Сообщения
8,520
Реакции
4,979
@Muinez, примерно так и проверял, кстати.
А вчера первый раз декомпил сделать попробовал.
 

Kruzya

Участник
Сообщения
12,970
Реакции
10,914
  • Команда форума
  • #15
Так удаление должно же его и обнулять ещё, нет?
Операция обнуления хендла вставляется на усмотрение компилятора в зависимости от самого выражения.
Самый надежный вариант закрывать хендлы, кмк, по старинке: Handle.Close() и занулить руками.
 

_wS_

Участник
Сообщения
383
Реакции
760
боты не проходят механизм админ проверки, IsFakeClient лишний там в OnClientPostAdminCheck
PHP:
public void OnClientPostAdminCheck(int client)
{
    PrintToServer("'%N' (%s)", client, IsFakeClient(client) ? "bot" : "human");
}
C-подобный:
] bot_add
'Scott' (bot)

Вы должны всегда присваивать ноль глобальной переменной таймера в самой последней строке колбека таймера (перед return)
Почему в последней? Я наоборот делаю это максимально выше, т.к. понимаю, что код может завершиться с ошибкой и переменная останется висячей не обнулённой, ибо текущая цепь вызовов оборвётся и код ниже выполнен не будет. Кст чтобы код ниже гарантированно был выполнен, можно вызвать опасную функцию через Call_StartFunction, а свой код писать ниже за её пределами.
 

Dragokas

Добрая душа
Сообщения
229
Реакции
213
@Dragokas, почему-то при декомпиле получается вот такое:
Потому что это декомпил, он раскладывает логику на синтаксический сахар.

Тогда таймер не обнуляется, емнип.
Хотя при смены карты должен срабатывать OnClientDisconnect().
Справедливое замечание.
Кроме TIMER_FLAG_NO_MAPCHANGE, нужно сделать:
- как писал Kruzya, пост #2 - обнулить все таймеры в OnMapEnd.
- либо как в статье (код № 2) без флага + закрывать таймер в OnClientDisconnect()

Спасибо, что-то напутал. Вычеркнул ответ.

Почему в последней? Я наоборот делаю это максимально выше, т.к. понимаю, что код может завершиться с ошибкой и переменная останется висячей не обнулённой, ибо текущая цепь вызовов оборвётся и код ниже выполнен не будет.
Так и знал, что придерутся к этому предложению. Здесь стоит разделить на два вида:
- в одноразовом таймере действительно лучше обнулять в самой первой строке (исходя из логики, что дальше может попастся ошибка).
- в посторяющемся - обнуляем, как только станет известно, что мы возвращаем именно Plugin_Stop.
Вообщем, перефразирую в статье, как смогу, спасибо за замечание.
 

KiKiEEKi

🏆 🥇
Сообщения
653
Реакции
513
Привет всем, скажите а это нормально что калбек в ошибке не правильное название?
Удаляю таймер в конце раунда и карты.
SM 1.11

C-подобный:
[SM] Plugin "[OS]FightArenaV2.smx" encountered error 23: Native detected error
[SM] Invalid timer handle adc0046c (error 3) during timer end, displayed function is timer callback, not the stack trace
[SM] Unable to call function ".9576.Timer_Duration" due to above error(s).

//
//
//
//============================
//Продолжительность битвы(игры)
//============================
void OnTimerDuration()
{
    g_hGameDuration.GetValue(g_sGameName, g_fTimeDuration);
    if(g_fTimeDuration > 0.0)
    {
        delete g_hTimerDuration;
        g_hTimerDuration = CreateTimer(1.0, Timer_Duration, _, TIMER_REPEAT);
        PrintHintTextToAll("● %s ●\n==============================\n● До конца битвы %.1f сек ●\n==============================", g_sGameName, g_fTimeDuration);
    }
}

Action Timer_Duration(Handle timer)
{
    g_fTimeDuration -= 1.0;
    PrintHintTextToAll("● %s ●\n==============================\n● До конца битвы %.1f сек ●\n==============================", g_sGameName, g_fTimeDuration);

    if(g_fTimeDuration <= 0.0)
    {
        OnPlayerCheckHP();

        g_hTimerDuration = null;
        return Plugin_Stop;
    }

    return Plugin_Continue;
}
 

KiKiEEKi

🏆 🥇
Сообщения
653
Реакции
513
@Dragokas, Да все так же как и в 1 посту, в конце раунда и конце карты.

C-подобный:
public void Event_RoundStart(Event hEvent, const char[] sEvName, bool bDontBroadcast)
{
    delete g_hTimerDuration;
}


public void OnMapEnd()
{
    delete g_hTimerDuration;
}
 
Сверху Снизу