Контакты Поиск

[ вход ]
[ последние сообщения ]

  • Страница 1 из 1
  • 1
Форум » SourceMod >> CS:Source >> CSGO » Уроки SourceMod (SourcePawn) Скриптинга » Таймер (Выполнить команду через время)
Таймер
_wS_ Дата: Среда, 30.11.2011, 18:12:36 | Сообщение # 1
Handle:CreateTimer(Float:interval, Timer:TIMER_CallBack, any:data=INVALID_HANDLE, flags=0);

Код
interval - через сколько секунд сработает таймер (если повторяющийся, то будет вызываться каждые interval сек, минимум 0.1)
TIMER_CallBack - функция, которая будет вызвана
any:data - любое число (например, индекс игрока), или Handle DataPack'а
flags - флаги (если их несколько, то через символ |

Функция возвращает Handle таймера, зная который можно его остановить с помощью KillTimer(Handle:timer); (нельзя использовать CloseHandle)

Код
TIMER_REPEAT // повторяющийся таймер, чтобы остановить его, нужно сделать return Plugin_Stop (Plugin_Continue = продолжить выполнение)
TIMER_FLAG_NO_MAPCHANGE // SM сам убьёт (остановит) таймер перед сменой карты
TIMER_HNDL_CLOSE // устарело, не используется
TIMER_DATA_HNDL_CLOSE // После остановки таймера, SM сделает CloseHandle() на том Handle:hVar, что вы передавали (если вы сами сделаете CloseHandle, будет ошибка)

Функции-обработчики таймеров могут быть такими:

Код
public Action:TIMER_CallBack(Handle:timer)
{
    return Plugin_Stop;
}

public Action:TIMER_CallBack(Handle:timer, any:data)
{
    return Plugin_Stop;
}

public Action:TIMER_CallBack(Handle:timer, Handle:hndl)
{
    return Plugin_Stop;
}

Думаю в Handle:hndl нет особого смысла, т.к. и any:data без проблем примет вашу Handle: переменную.
Если вы передали таймеру какое-то значение (any:data), то и используйте функцию с any:data, но даже если вы сделаете так:

Код
CreateTimer(1.0, TIMER_CallBack, 5);

public Action:TIMER_CallBack(Handle:timer)
{
    PrintToServer("Это сработает, но доступа к 5, что мы передавали, у нас нет");
    return Plugin_Stop;
}


Если ваш таймер повторяющийся (TIMER_REPEAT), то чтобы он продолжил выполняться, сделайте return Plugin_Continue;
Во всех остальных случаях делайте return Plugin_Stop;

Пример с TIMER_DATA_HNDL_CLOSE

Код
public OnPluginStart()
{
    new Handle:pack = CreateDataPack();
    WritePackCell(pack, 5);
    CreateTimer(3.0, Timer_Func, pack, TIMER_FLAG_NO_MAPCHANGE | TIMER_DATA_HNDL_CLOSE); // перед сменой карты будет убит и SM сделает CloseHandle(pack)
}

public Action:Timer_Func(Handle:timer, any:pack)
{
    ResetPack(pack);
    PrintToServer("-> %d", ReadPackCell(pack));

    // Делать CloseHandle(pack) здесь НЕ НУЖНО, иначе увидите ошибку.
}

Приветствие игрока через 15 сек после входа на сервер:

Код
new Handle:g_hTimer[MAXPLAYERS + 1];

public OnClientPutInServer(client)
    g_hTimer[client] = CreateTimer(15.0, Hello, client); // Сохраняем Handle таймера

public OnClientDisconnect(client) // Если игрок покидает сервер, не увидев приветствие, то убиваем таймер.
{
    if (g_hTimer[client] != INVALID_HANDLE)
    {
        KillTimer(g_hTimer[client]);
        g_hTimer[client] = INVALID_HANDLE;
    }
}

public Action:Hello(Handle:timer, any:client)
{
    // Сбрасываем Handle таймера на INVALID_HANDLE, чтобы знать, что активного таймера НЕТ.
    g_hTimer[client] = INVALID_HANDLE;

    // Приветствуем через чат
    PrintToChat(client, "Hello, %N!", client);

    return Plugin_Stop;
}

Пример повторяющегося таймера:

Код
public OnPluginStart()
    CreateTimer(1.0, Timer_Func, _, TIMER_REPEAT);

public Action:Timer_Func(Handle:timer)
{
    static x = 0;
    if (++x < 10)
    {
        PrintToServer("-> %d", x);
        return Plugin_Continue;
    }
    return Plugin_Stop;
}
 
_wS_ Дата: Воскресенье, 13.05.2012, 23:55:09 | Сообщение # 2
Ещё есть CreateDataTimer, специально для DataPack (в него можно упаковывать разные типы данных).
Но в этом случае не нужно делать CreateDataPack(), делают так:

Код
new Handle:pack;
CreateDataTimer(10.0, xz_Func, pack); // тут пак создается вместе с таймером

// и ниже можно добавлять данные в пак:
WritePackCell(pack, 5);

// А внутри функции:

public Action:xz_Func(Handle:timer, Handle:pack)
{
     ResetPack(pack);
     new x = ReadPackCell(pack);
     PrintToServer("x = %d", x);

     // Handle пака закрывается само, после остановки (убийства) таймера.
     return Plugin_Stop;
}
 
olegseton Дата: Воскресенье, 30.04.2017, 21:20:17 | Сообщение # 3
Сообщений: 8
Репутация: 0 [ +/- ]
Можете добавить таймер? (то есть что бы оверлей отображался 3 секунды)
Вот плагин

Прикрепления: OverlayRoundEnd.sp(2.3 Kb)
 
_wS_ Дата: Воскресенье, 30.04.2017, 23:35:56 | Сообщение # 4
.

Прикрепления: 1686908.sp(2.3 Kb)
 
www22 Дата: Четверг, 12.09.2019, 08:39:43 | Сообщение # 5
Сообщений: 51
Репутация: 0 [ +/- ]
_wS_, да кстати а когда таймер уже всё закончился он сам "убивается" ?
Или надо принудительно его убивать ?

Добавлено (12.09.2019, 08:43:23)
---------------------------------------------

Цитата _wS_ ()
public OnClientPutInServer(client)
g_hTimer[client] = CreateTimer(15.0, Hello, client); // Сохраняем Handle таймера

Вот здесь только 1 раз получается коннектится игрок , а зачем таймер убивать ?
Игрок 1 раз только заходить же ..Или я опять что не понял ..
 
tonline_kms65 Дата: Суббота, 04.01.2020, 05:40:05 | Сообщение # 6
Сообщений: 213
Репутация: 3 [ +/- ]
Здравствуйте все.
Такой вопрос появился у меня по таймерам.
есть обычный таймер (CreateTimer(15.0, AddInventory)), запустится через 15 сек.

Проблема вот в чем - если я до истечения 15 сек. принудительно делаю рестарт раунда, эта сволочь даже не думает останавливаться. После рестарта, вместе с новым таймером как и положено запускается(через то время, которое у него еще осталось).

Ну и вопрос - есть что нибудь попроще, типа таких фишек TIMER_FLAG_NO_MAPCHANGE и т.д. что бы таймер сам отключался при рестарте? Что бы не было постоянного контроля за ним, все-равно где нибудь да не уследишь.
Может быть есть что нибудь, что сбрасывает вообще все таймеры?


Сообщение отредактировал tonline_kms65 - Суббота, 04.01.2020, 05:42:37
 
_wS_ Дата: Суббота, 04.01.2020, 16:04:32 | Сообщение # 7
Из авто-убийств есть только TIMER_FLAG_NO_MAPCHANGE и если таймер хранился в глоб переменной, нужно её очистить в OnMapStart или OnMapEnd.
А так нужно самому следить за ним.

Рестарт раунда - просто хукаем cvar "mp_restartgame".

Код
new bool:mp_restartgame = false;

public OnPluginStart()
{
    new Handle:hCvar = FindConVar("mp_restartgame");
    if (hCvar != INVALID_HANDLE) { HookConVarChange(hCvar, cvar_mp_restartgame); }
    
    HookEvent("round_start", round_start, EventHookMode_PostNoCopy);
}

public cvar_mp_restartgame(Handle:hCvar, const String:OldValue[], const String:NewValue[])
{
    if (!mp_restartgame && StringToInt(NewValue) > 0)
    {
        // Рестарт раунда активирован впервые за раунд.
        // Нам нужен лишь 1 вызов, а не спам, но можно обойтись и без этого, зависит от кода.
        mp_restartgame = true;
        
        // Код
    }
}

public round_start(Handle:event, const String:name[], bool:silent)
{
    // В ксс го, после смены карты, round_start возможно не вызывается (ждёт игроков, если не ошибаюсь).
    mp_restartgame = false;
}

public OnMapStart()
{
    mp_restartgame = false;
}

Цитата tonline_kms65 ()
все-равно где нибудь да не уследишь

Не знаю, когда знаешь для чего нужен таймер, то сразу и понятно становится в каких событиях его нужно остановить.
Выход/смерть/рождение/смена команды/рестарт раунда/конец/начало - обычно что-то из этого.
 
tonline_kms65 Дата: Воскресенье, 05.01.2020, 09:03:39 | Сообщение # 8
Сообщений: 213
Репутация: 3 [ +/- ]
Цитата _wS_ ()
Не знаю, когда знаешь для чего нужен таймер, то сразу и понятно становится в каких событиях его нужно остановить.
Выход/смерть/рождение/смена команды/рестарт раунда/конец/начало - обычно что-то из этого.


Без проблемная остановка таймера - это на повторяющемся таймере, там все ОК, без вопросов, есть куда вставить условия для прерывания. А этот я еще видимо не до конца понял, поэтому и получается как то все ненадежно. А может быть раньше просто не замечал, и вот нечаянно заметил.

Цитата _wS_ ()
Из авто-убийств есть только TIMER_FLAG_NO_MAPCHANGE и если таймер хранился в глоб переменной, нужно её очистить в OnMapStart или OnMapEnd.


Вот это интереснее.
Таймер у меня естественно в глобале, и флаг естественно есть такой. А вот чего нет так это очистки в OnMapStart.
Обалдеть, не сообразил, куда только не пихал его. Это как мартышка и очки .
ОК. Спасибо.

Кстати, почему у меня таймер не убивается:
Код
new Handle:g_TimerAddInventory = INVALID_HANDLE;
//-----------------------------------------------------
....
HookEvent("round_start", round_start, EventHookMode_Post);
//-----------------------------------------------------
public round_start(Handle: event, const String: name[], bool: dontBroadcast) {
if (g_TimerAddInventory != INVALID_HANDLE) g_TimerAddInventory = INVALID_HANDLE;


Вот так очищается, но если сделать KillTimer(g_TimerAddInventory), до очистки, то выдаст ошибку.
Что не так?


Сообщение отредактировал tonline_kms65 - Воскресенье, 05.01.2020, 09:08:24
 
_wS_ Дата: Воскресенье, 05.01.2020, 09:50:07 | Сообщение # 9
Логика как-бы =)
KillTimer нужен только если g_TimerAddInventory != INVALID_HANDLE, и затем уже g_TimerAddInventory = INVALID_HANDLE;

Код
if (g_TimerAddInventory != INVALID_HANDLE)
{
    KillTimer(g_TimerAddInventory);
    g_TimerAddInventory = INVALID_HANDLE;
}

Всегда так. Это может вызвать ошибку только если таймер уже сработал/был убит, но ты не очистил переменную (g_TimerAddInventory = INVALID_HANDLE). В OnMapEnd мы не делаем KillTimer, т.к. таймер уже был убит SourceMod'ом (CallBack он при этом не вызывает). Так что если таймер с TIMER_FLAG_NO_MAPCHANGE флагом в глоб переменной, то нужно в OnMapStart/OnMapEnd(наверно лучше это) не забыть очистить переменную.
 
tonline_kms65 Дата: Воскресенье, 05.01.2020, 10:24:59 | Сообщение # 10
Сообщений: 213
Репутация: 3 [ +/- ]
Цитата _wS_ ()
if (g_TimerAddInventory != INVALID_HANDLE)
{
KillTimer(g_TimerAddInventory);
g_TimerAddInventory = INVALID_HANDLE;
}


Я о том же и говорю -  KillTimer(g_TimerAddInventory); не выполняется, ошибку выдает.
Но сейчас не это важно, вероятнее всего я сам что то косячу в коде.
Посмотри пожалуйста видео  https://youtu.be/Cs0lwpIfxbA что бы было представление о предмете разговора(оно короткое)

При запуске игры доп.предметы должны появляться через 15 сек(я спецально все рендомные значения убрал)
На СТ-респавне видно что я забираю аптечку, не дожидаясь появления нового предмета делаю рестарт, и новые предметы появляются практически сразу после рестарта, т.е. через то время что у таймера оставалось. Значит таймер работал.
Если же я забираю предметы(в тёмке), дожидаюсь их возрождения, делаю рестарт - после рестарта, как и положено, предметы появляются через 15 сек.
Не могу сообразить где косяк, а это явно мой косяк.

Добавлено (05.01.2020, 10:34:49)
---------------------------------------------
Принцип работы кода:
1. начало карты - очищаю все точки рождения предметов
2. через 15 сек. - запускается создание предметов на свободных точках их возрождения.
3. у каждого созданного предмета свой сканер на обнаружение игроков в заданном кваром радиусе(т.е. у каждого предмета свой таймер, понимаю что такой подход не ахти, но для меня он пока наиболее понятен и приемлем, потом переведу на один общий по всем предметам)
4. когда забираю предмет - точка очищается, на её месте создается той же функцией, что и через 15 сек., создание предметов на свободных точках. Возможно здесь и есть косяк.



Сообщение отредактировал tonline_kms65 - Воскресенье, 05.01.2020, 10:25:54
 
_wS_ Дата: Воскресенье, 05.01.2020, 10:49:13 | Сообщение # 11
По видео я только понял что аптечки красивые и звуки неплохие =)

Цитата tonline_kms65 ()
не дожидаясь появления нового предмета делаю рестарт, и новые предметы появляются практически сразу после рестарта, т.е. через то время что у таймера оставалось. Значит таймер работал.

В этом проблема да?

1. Ты поднял предмет
2. Запускаешь таймер, который должен через x сек создать аптечку на этом же месте.

Ты должен хранить все запущенные в пункте 2 таймеры и закрывать их в конце раунда/в момент рестарта раунда.
Создаешь глобальный array: new Handle:g_hTempAr;

Запуск таймера из пункта 2:
Код
if (g_hTempAr == INVALID_HANDLE) { g_hTempAr = CreateArray(1); }
PushArrayCell(g_hTempAr, hMyTimer); // Где hMyTimer твой новый созданный таймер.


Таймер сработал:
Код
if (g_hTempAr != INVALID_HANDLE)
{
    new index = FindValueInArray(g_hTempAr, hMyTimer);
    if (index > -1) {
        RemoveFromArray(g_hTempAr, index);
    }
}


Конец раунда/рестарт раунда/ [OnMapEnd, если нет флага TIMER_FLAG_NO_MAPCHANGE]
Код
if (g_hTempAr != INVALID_HANDLE)
{
    new index = GetArraySize(g_hTempAr);
    while (--index > -1) {
        KillTimer(Handle:GetArrayCell(g_hTempAr, index));
    }
    ClearArray(g_hTempAr);
}

Если флаг TIMER_FLAG_NO_MAPCHANGE указан, то:
Код
public OnMapEnd()
{
    if (g_hTempAr != INVALID_HANDLE) {
        ClearArray(g_hTempAr);
    }
}
 
tonline_kms65 Дата: Воскресенье, 05.01.2020, 13:58:58 | Сообщение # 12
Сообщений: 213
Репутация: 3 [ +/- ]
Цитата _wS_ ()
В этом проблема да?


Да. Проблема в этом, ею можно было бы пренебречь, по идее, но как то неуютно. Люблю когда моё для меня "прозрачно".
Я сейчас еще раз проверю весь код, саму структуру что да как, по моему все-таки неверный подход у меня изначально.
Что бы таких головняков избежать на будущее.

Кстати, такой вопрос - глобальный таймер с одинаковым именем может быть создан повторно, если первый уже работает?
Проще - возможно ли создать 2 таймера с одинаковыми именами?
Например таймер hMyTimer запущен, через 5 сек. запускаю его же, при этом первый еще работает, такое возможно?

Добавлено (05.01.2020, 14:33:34)
---------------------------------------------
Код
if (g_TimerAddInventory != INVALID_HANDLE){ KillTimer(g_TimerAddInventory); g_TimerAddInventory = INVALID_HANDLE;}


Вот какая ошибка:
L 01/05/2020 - 22:27:45: [SM] Exception reported: Invalid timer handle 6c2030b (error 1)
L 01/05/2020 - 22:27:45: [SM] Blaming: dm_item_healthkit.smx
L 01/05/2020 - 22:27:45: [SM] Call stack trace:
L 01/05/2020 - 22:27:45: [SM] [0] KillTimer

Получается, после рестарта, таймер реально остается работающим, т.к. срабатывает условие g_TimerAddInventory != INVALID_HANDLE


Сообщение отредактировал tonline_kms65 - Воскресенье, 05.01.2020, 14:37:58
 
_wS_ Дата: Воскресенье, 05.01.2020, 20:45:01 | Сообщение # 13
Цитата tonline_kms65 ()
таймер hMyTimer запущен, через 5 сек. запускаю его же

Не понимаешь логику =\
Запустить уже запущенный таймер невозможно.
Это как нельзя кинуть камень, потому что ты его уже кинул и он летит, ты можешь кинуть только другой.

Таймер не может быть с одинаковым "именем" и hMyTimer это не совсем "имя".
Всегда, когда ты делаешь CreateTimer, создаётся НОВЫЙ таймер и тебе возвращают его НОВЫЙ уникальный Handle (как userid игрока например).
Как хранить этот Handle, как следить за его актуальностью, когда очищать переменную, это твоя забота.

// Таймер 1
hMyTimer = CreateTimer

// Если ты сразу же создаёшь другой таймер, ну или до того как таймер 1 завершит свою работу, то нельзя так делать:
hMyTimer = CreateTimer

Потому что ты теряешь Handle таймера 1 и уже ничего с ним сделать не сможешь.
В этом случае тебе уже нужно хранить 2 переменные.
Я уже дал готовое решение, динамический массив, хранящий все актуальные Handle таймеров.
Чтобы лучше понять, можешь использовать это решение со своими debug сообщениями.
 
tonline_kms65 Дата: Воскресенье, 05.01.2020, 23:44:49 | Сообщение # 14
Сообщений: 213
Репутация: 3 [ +/- ]
Цитата _wS_ ()
// Таймер 1
hMyTimer = CreateTimer

// Если ты сразу же создаёшь другой таймер, ну или до того как таймер 1 завершит свою работу, то нельзя так делать:
hMyTimer = CreateTimer

Потому что ты теряешь Handle таймера 1 и уже ничего с ним сделать не сможешь


ВОТ! Вот это мне и нужно было. Сейчас понятно.
Примерно год назад у меня был вопрос по такому же таймеру, по моему Рико отвечал с HLmod, я тогда так ничего и не понял. С повторяющимися таймерами все проще, там есть куда условие для выхода вставить. А вот с такими у меня всегда была проблема, но вот по моему, наконец-то до меня дошло. Есть над чем пoэкcпepимeнтиpoвaть. Спасибо за полезный ответ.
Я сейчас таймеры просто убиваю в OnMapEnd, но чувствую что это было не совсем то что мне нужно. Основного я так и не понимал. Теперь понятно почему не убивался таймер при рестарте, он то есть, но кого конкретно убивать - непонятно.
Если в OnMapEnd движку худо-бедно еще понятно кого убивать, то после рестарта, уже точно, одному Богу ведомо что да как.
Нужно, просто, каждому вновь создаваемому таймеру присваивать свой ID (грубо говоря к чему нибудь "привязать") к id-игрока, к id-ent, как у тебя глобал-массиву и т.д. что бы потом можно было получить ID именно этого таймера. Сейчас и работа твоего кода мне понятна стала.

Во как. Обалдеть.
Так что спасибо за развернутый ответ! "Век живи - век учись".
/*P.S*/

И, кстати, интересно, а если просто очищать само хандле (например того-же hMyTimer)по идее все таймеры созданные в этом хандле должны будут убиться? Я с этим эксперементировать не буду, это просто как мой вывод из моего нового понимания работы таймеров такого типа.


Сообщение отредактировал tonline_kms65 - Понедельник, 06.01.2020, 00:25:03
 
_wS_ Дата: Понедельник, 06.01.2020, 01:01:55 | Сообщение # 15
Цитата tonline_kms65 ()
если просто очищать само хандле (например того-же hMyTimer)по идее все таймеры созданные в этом хандле должны будут убиться?

Нет. Убить таймер можно только командой KillTimer. Просто очистив переменную, ты его потеряешь.
Переменную чистят только после того, как таймер был убит, т.к. теперь она хранит адрес несуществующего объекта.
Когда таймер убивается:

A
Сделали KillTimer()

B
Таймер вызвал свой CallBack.
Если он с флагом TIMER_REPEAT и сделано return Plugin_Continue, то таймер всё еще жив и вызовет свой CallBack снова через 'x' сек.
В остальных случаях таймер умирает после выполнения всего кода в своём CallBack.
Делать KillTimer внутри CallBack не обязательно, а если и сделать, то ошибки не будет (sm об этом позаботился).

C
Таймер запущен с флагом TIMER_FLAG_NO_MAPCHANGE и карта меняется.

Сработало A или B или C = очищай Handle.
 
Форум » SourceMod >> CS:Source >> CSGO » Уроки SourceMod (SourcePawn) Скриптинга » Таймер (Выполнить команду через время)
  • Страница 1 из 1
  • 1
Поиск: