Jump to content
  • Chmurka
  • Boróweczka
  • Jabłuszko
  • Limonka
  • Czekoladka
  • Węgielek
Vasto_Lorde

[Poradnik] Sourcemod Timers - CreateTimer / TriggerTimer / KillTimer

Recommended Posts

Zadania czasowe w Sourcemod

 

1. Wprowadzenie
SourceMod oferuje nam bardzo wygodny system Timerów. Te zadania czasowe obsługują uruchomienie danej funkcji z konkretnymi parametrami w odpowiednim czasie. Takie działania mogą być przydatne gdy na przykład odblokowujemy użycie granatów jakiś czas po rozpoczęciu rundy lub wyświetlamy HUD (wiadomość na ekranie gracza) która ma się stale wyświetlać lecz jego zawartość musimy ciągle odświeżać.

 

2. CreateTimer - Teoria
Jest to funkcja tworząca Timer i ustalająca jaka funkcja ma być uruchomiona po jakim czasie. Jej opis znajduje się tutaj https://go-code.pl/dokumentacja-sourcemod/timers/CreateTimer
Hej! Skorzystałeś z linku lub pobrałeś załącznik? Uhonoruj naszą pracę poprzez rejestrację na forum i rośnij razem z nami!
Składnia:

native Handle CreateTimer(float interval, Timer func, any data=INVALID_HANDLE, int flags=0);

Handle - w tym przypadku oznacza, że funkcja zwróci nam jakiś uchwyt (uchwyt = Handle). Konkretniej, CreateTimer zwraca uchwyt do właśnie stworzonego Timera.
float interval - czas w sekundach do wywołania (uruchomienia) danej funkcji. Są to wartości float, czyli wartości zmiennoprzecinkowe. O ile dobrze kojarzę, minimalną wartość jaką można tu wpisać jest 0.1.
Timer func - nazwa funkcji która ma zostać uruchomiona (wywołana) po podanym czasie.
any data - jakikolwiek parametr jaki chcemy przekazać funkcji którą wywołamy, by mogła z niej korzystać. Nie musimy ustawiać tego parametru i domyślnie będzie on ustawiony na INVALID_HANDLE, czyli brak przekazywanego parametru.
int flags - opcjonalne flagi które możemy nadać naszemu Timerowi które zmienią domyślne działanie Timera. Domyślnie ustawione na 0, czyli TImer działa domyślnie, tak jak jest to opisane w dokumentacji.

 

2.1. CreateTimer - Napisanie tego w pluginie
Załóżmy, że mamy przykładowy plugin:

Spoiler

public void OnPluginStart()
{
	//Tutaj rejestrujemy komende gracza.
	//Jesli gracz wpisze w konsole 'sm_stworz' to wywola sie funkcja StworzTimer
	RegAdminCmd("sm_stworz", StworzTimer, ADMFLAG_ROOT);
}

public Action StworzTimer(int client, int args)
{
	//Tworzymy Timer
	CreateTimer(1.0, FunkcjaKtoraSieWywola, GetClientUserId(client));
}

public Action FunkcjaKtoraSieWywola(Handle timer, int userid)
{
	//To co sie zrobi gdy Timer zostanie wywolany
	PrintToChatAll("Hej... To %N wywolal Timer!", GetClientOfUserId(userid));
}

 

Stworzony Timer pod komendą "sm_stworz" wykona się dokładnie sekundę po wpisaniu jej w konsolę. Zauważmy, że jako trzeci parametr w CreateTimer przekazujemy ID gracza (client). Dzięki temu FunkcjaKtoraSieWywola może go dowolnie używać.

Co z flagami? (int flags)
Aktualnie dokumentacja opisuje trzy działające flagi, które zmieniają / modyfikują działanie Timera.

TIMER_REPEAT				- Sprawia, że stworzony Timer działa w pętli. (po wywołaniu Timerowej funkcji, Timer od początku odlicza sekundy i powtarza cały proces)
TIMER_FLAG_NO_MAPCHANGE		- Sprawia, że Timer nie będzie działać po zmianie mapy. Tak, jeśli plugin utworzy CreateTimer bez tej flagi i z takim czasem że Timer nie zdąży się wykonać przed zmianą mapy, to wykona się po jej zmianie zgodnie ze swoim odliczaniem! 
TIMER_DATA_HNDL_CLOSE		- Sprawia, że uchwyt (Handle) wpisany jako trzeci parametr jest automatycznie zamykany po zakończeniu wykonywania funkcji. O tym trochę dokładniej w dalszej części poradnika

Dlatego możemy teraz stworzyć inną wersję naszego pluginu, który będzie co sekundę pisał na czacie kto użył komendy:

Spoiler

public void OnPluginStart()
{
	//Tutaj rejestrujemy komende gracza.
	//Jesli gracz wpisze w konsole 'sm_stworz' to wywola sie funkcja StworzTimer
	RegAdminCmd("sm_stworz", StworzTimer, ADMFLAG_ROOT);
}

public Action StworzTimer(int client, int args)
{
	//Tworzymy Timer ktory powtarza sie co sekunde
	CreateTimer(1.0, FunkcjaKtoraSieWywola, GetClientUserId(client), TIMER_REPEAT);
}

public Action FunkcjaKtoraSieWywola(Handle timer, int userid)
{
	//To co sie zrobi gdy Timer zostanie wywolany
	PrintToChatAll("%N caly czas bawi sie komenda >:(", GetClientOfUserId(userid));
}

 

Aby połączyć flagi dla danego CreateTimer, należy wstawić między nie znak |. Przykład:

CreateTimer(1.0, FunkcjaKtoraSieWywola, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE|TIMER_DATA_HNDL_CLOSE);

 

3. TriggerTimer - Teoria
Jest to funkcja, która przedwcześnie wywołuje Timer, czyli wymusza aktywację Timera. Opis znajduje się tutaj: https://go-code.pl/dokumentacja-sourcemod/timers/TriggerTimer
Hej! Skorzystałeś z linku lub pobrałeś załącznik? Uhonoruj naszą pracę poprzez rejestrację na forum i rośnij razem z nami!
Składnia:

native void TriggerTimer(Handle timer, bool reset=false);

void - Oznacza, że ta funkcja nic nie zwraca
Handle timer - Uchwyt danego Timera który chcemy uruchomić
bool reset - Jeśli true, to czas Timera zostanie zresetowany i będzie musiał odczekać pełny czas (ustawiony w CreateTimer) by ponownie się samodzielnie uruchomić. Ten parametr jest opcjonalny.

 

3.1. TriggerTimer - Napisanie tego w pluginie
Możemy napisać sobie przykładowy plugin

Spoiler

Handle UchwytNaszegoTimeru

public void OnPluginStart()
{
	//Timer który wykona się kiedyś
	UchwytNaszegoTimeru = CreateTimer(99999.9, FunkcjaKtoraSieWywola, INVALID_HANDLE, TIMER_REPEAT);
	
	//Tutaj rejestrujemy komende gracza.
	//Jesli gracz wpisze w konsole 'sm_trigger' to wywola sie funkcja PyknijTimer
	RegAdminCmd("sm_trigger", PyknijTimer, ADMFLAG_ROOT);
}

public Action PyknijTimer(int client, int args)
{
	//Triggerujemy Timer 
	TriggerTimer(UchwytNaszegoTimeru);
}

public Action FunkcjaKtoraSieWywola(Handle timer)
{
	PrintToChatAll("Ta funkcja nigdy sie nie wykona... chociaz moze?");
}

 

Zauważmy, że do skorzystania z funkcji TriggerTimer potrzebowaliśmy uchwytu naszego Timeru. Dlatego została stworzona zmienna typu Handle o nazwie UchwytNaszegoTimeru. Co więcej, przy opisywaniu CreateTimer napisałem, że funkcja zwraca uchwyt do stworzonego Timera, dlatego można ten uchwyt przypisać jakiejś zmiennej

 

4. KillTimer - Teoria
Usuwa Timer bez jego wywołania. Przydatne jeśli pojawił się jakiś warunek (na przykład jeśli gracz zginął i nie potrzeba mu już wyświetlać wiadomości na HUD), który sprawia, że Timer nie jest już potrzebny. Pełny opis tutaj https://go-code.pl/dokumentacja-sourcemod/timers/KillTimer
Hej! Skorzystałeś z linku lub pobrałeś załącznik? Uhonoruj naszą pracę poprzez rejestrację na forum i rośnij razem z nami!
Składnia:

native void KillTimer(Handle timer, bool autoClose=false);

void - Oznacza, że ta funkcja nic nie zwraca
Handle timer - Uchwyt danego Timera który chcemy usunąć
bool autoClose - Jeśli ustawione na true, to dane przekazane do Timera zostaną zamknięte jeśli w CreateTimer nie dodało się flagi TIMER_DATA_HNDL_CLOSE

 

4.1. KillTimer - Napisanie tego w pluginie
Mamy przykładowy plugin

Spoiler

Handle UchwytNaszegoTimeru

public void OnPluginStart()
{
	//Timer który wykona się kiedyś
	UchwytNaszegoTimeru = CreateTimer(99999.9, FunkcjaKtoraSieWywola, INVALID_HANDLE, TIMER_REPEAT);
	
	//Tutaj rejestrujemy komende gracza.
	//Jesli gracz wpisze w konsole 'sm_trigger' to wywola sie funkcja UsunTimer
	RegAdminCmd("sm_usun", UsunTimer, ADMFLAG_ROOT);
}

public Action UsunTimer(int client, int args)
{
	//Usuwamy timer
	KillTimer(UchwytNaszegoTimeru);
}

 

Zauważmy ciekawą rzecz, jeśli użyjemy komendy sm_usun dwukrotnie to drugie jej użycie spowoduje błąd ponieważ UchwytNaszegoTimeru wynosi INVALID_HANDLE. Dzieje się tak ponieważ funkcja KillTimer usuwa dany Timer kompletnie i ustawia wartość zmiennej przetrzymującej właśnie na INVALID_HANDLE. Co za tym idzie, zgodnie z dokumentacją, funkcja KillTimer nie radzi sobie w przypadku, gdy podawany do funkcji uchwyt nie jest prawidłowy i po prostu wyrzuca nam błąd do konsoli / logów serwera.

 

5. Ale tato, co jeśli bym chciał przekazać do Timer'a więcej niż jedną zmienną?
Możemy to zrobić na dwa sposoby. Prymitywny i nieładny lub elegancki i zgodny z zasadami dobrego programowania.

Ten pierwszy osiąga się za pomocą tworzenia zmiennych globalnych (takich które działają w każdym kawałku kodu pluginu). Przed CreateTimer przypisujemy do zmiennych globalnych jakieś wartości potrzebne nam do użycia w Timerze. A gdy Timer się wywoła, używamy właśnie tych zmiennych globalnych. Barbarzyńsko wręcz.

Natomiast drugi sposób omówię trochę dokładniej. Będziemy musieli zagłębić się w plik datapack.inc. Datapack to uporządkowany zestaw obiektów, który jest przekształcony do postaci szeregowej, czyli jednego strumienia bajtów. Dzięki temu zmienne które chcemy przekazać Timerowi możemy umieścić w jednym Datapacku i umieścić jako trzeci parametr funkcji CreateTimer, przekazując tym samym ten Datapack do funkcji którą ten Timer wywoła. Brzmi prosto, natomiast trzeba się zapoznać z kodem żeby w pełni zrozumieć działanie Datapacków.

Oto przykładowy kod:

Spoiler

public void OnPluginStart()
{
	//Tutaj rejestrujemy komende gracza.
	//Jesli gracz wpisze w konsole 'sm_stworz' to wywola sie funkcja StworzTimer
	RegAdminCmd("sm_stworz", StworzTimer, ADMFLAG_ROOT);
}

public Action StworzTimer(int client, int args)
{
	//Tworzymy Datapack
	Handle data = CreateDataPack();
	WritePackCell(data, GetClientUserId(client)); //Wstawiamy do Datapacku ID klienta
	WritePackCell(data, PobierzIloscStopniCelsjusza()); //Wstawiamy do Datapacku aktualną ilość stopni Celsjusza (To tylko wymyślona funkcja)
	
	//Tworzymy Timer razem z Datapackiem jako argument przekazany do funkcji FunkcjaKtoraSieWywola
	CreateTimer(1.0, FunkcjaKtoraSieWywola, data);
}

//Zauważmy, że zmienia się tutaj typ zmiennej w parametrze drugim - "Handle"
public Action FunkcjaKtoraSieWywola(Handle timer, Handle data)
{
	//Odczytujemy wartości z Datapack
	ResetPack(data);
	int client = GetClientOfUserId(ReadPackCell(data));
	int celsjusz = ReadPackCell(data);
	
	//Zamykanie uchwytu jest niepotrzebne jeśli w CreateTimer użyliśmy flagi TIMER_DATA_HNDL_CLOSE
	CloseHandle(data); 
	
	PrintToChatAll("Hej... u gracza %N jest %i stopni Celsjusza!", client, celsjusz);
}

 

Trzeba zaznaczyć, że odczytywanie danych z Datapacku MUSI się odbywać w kolejności w jakiej zostały do tego Datapacku zapisane. Co do CloseHandle lub TIMER_DATA_HNDL_CLOSE, nie ma różnicy jaka metoda zostanie użyta by usunąć uchwyt nazwany w kodzie 'data'. Ważne jest natomiast by ten uchwyt został usunięty. Nie sprzątanie po sobie utworzonych uchwytów może skutkować nieoczekiwanymi crashami serwera.

 

Bibliografia
Głównie wiedzę czerpałem z własnego doświadczenia (jak również pluginów które napisałem), ale nie omieszkałem zajrzeć na strony https://wiki.alliedmods.net/SourcePawn_Basics_-_Handles,_DataPacks,_and_Timers
Hej! Skorzystałeś z linku lub pobrałeś załącznik? Uhonoruj naszą pracę poprzez rejestrację na forum i rośnij razem z nami! oraz https://pl.wikipedia.org/wiki/Serializacja
Hej! Skorzystałeś z linku lub pobrałeś załącznik? Uhonoruj naszą pracę poprzez rejestrację na forum i rośnij razem z nami! by przypomnieć sobie niektóre rzeczy. Nie zapominajmy też o naszej dokumentacji https://go-code.pl/dokumentacja-sourcemod/
Hej! Skorzystałeś z linku lub pobrałeś załącznik? Uhonoruj naszą pracę poprzez rejestrację na forum i rośnij razem z nami!

 

*Uprasza się o nie kopiowanie poradnika bez wcześniejszego zapytania autora o zgodę

  • Lubię to! 3
  • Kocham to! 2

Share this post


Link to post
Share on other sites

Ogólnie to można DataPacka też dodać bardziej "obiektowo". Moim zdaniem krótsza wersja ?

public Action StworzTimer(int client, int args)
{
	//Tworzymy Datapack
	DataPack data = new DataPack();
	data.WriteCell(GetClientUserId(client)); //Wstawiamy do Datapacku ID klienta
	data.WriteCell(PobierzIloscStopniCelsjusza()); //Wstawiamy do Datapacku aktualną ilość stopni Celsjusza (To tylko wymyślona funkcja)
	
	//Tworzymy Timer razem z Datapackiem jako argument przekazany do funkcji FunkcjaKtoraSieWywola
	CreateTimer(1.0, FunkcjaKtoraSieWywola, data);
}

//Zauważmy, że zmienia się tutaj typ zmiennej w parametrze drugim - "Handle"
public Action FunkcjaKtoraSieWywola(Handle timer, DataPack data)
{
	//Odczytujemy wartości z Datapack
	data.Reset();
	int client = GetClientOfUserId(data.ReadCell());
	int celsjusz = data.ReadCell();
	
	//Zamykanie uchwytu jest niepotrzebne jeśli w CreateTimer użyliśmy flagi TIMER_DATA_HNDL_CLOSE
	CloseHandle(data); 
	
	PrintToChatAll("Hej... u gracza %N jest %i stopni Celsjusza!", client, celsjusz);
}

 

  • Lubię to! 1

Share this post


Link to post
Share on other sites
1 godzinę temu, Qwizi napisał:

Ogólnie to można DataPacka też dodać bardziej "obiektowo". Moim zdaniem krótsza wersja ?

+1 ? Jak komu wygodniej, a jak już się obiektowo używa czegoś to czemu całego pluginu nie napisać obiektowo? Można potrenować do przyszłej pracy

  • Lubię to! 1

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.


  • Recently Browsing   0 members

    No registered users viewing this page.

Nasza historia

Na początku byliśmy małą grupą internetowych znajomych, którzy stwierdzili, że potrzebne jest solidne forum, na którym znajdą się ludzie z dużą wiedzą programistyczną ukierunkowaną na CS:GO. Pomysł powstał na początku 2018 roku, a parę miesięcy później, 19 kwietnia, powstała ta strona internetowa. Jako alternatywna odpowiedź na inne tego typu miejsca, poważnie podeszliśmy do tematu, najpierw tłumacząc angielską dokumentację SourceMod'a na język polski, a potem pisząc rozległe poradniki i wypełniając forum najpotrzebniejszymi rzeczami dla właścicieli serwerów i programistów. Cała nasza Ekipa jest dumna z pracy jaką w to włożyliśmy i cieszymy się że zbierają się wokół nas zarówno ludzie znający tematy sourcepawn'a i konfiguracji, jak i również nowe twarze w tym "biznesie", którym z chęcią niesiemy wiedzę oraz pomoc w rozwiązywaniu problemów.

Największe modyfikacje serwerowe

×
×
  • Create New...