Skocz do zawartości
  • Chmurka
  • Boróweczka
  • Jabłuszko
  • Limonka
  • Czekoladka
  • Węgielek

Polecane posty

W tym poradniku chciałbym możliwie jak najkrócej przedstawić sposób, w jaki można tworzyć menu, a także pokazać mały trik jak przekazywać przez nie informacje. Wykonuję go w oparciu o moje doświadczenie, a więc nie ma w nim zawartych Paneli, czy obsługi blokowania poszczególnych pozycji.

Całość przedstawiam w oparciu o nową składnię (1.7+).

 

Menu w SourcePawnie ma swój własny typ danych (methodmapę) - i nazywa się on Menu. Na samym początku musimy utworzyć sobie do niego uchwyt:

Menu menu = new Menu(Nazwa_Naszego_Handlera);

Od teraz, kiedy będziemy chcieli budować nasze menu, posłużymy się utworzoną zmienną menu.

Jak widzimy, w kodzie powyżej widnieje Nazwa_Naszego_Handlera. Otóż kiedy player widzi menu w grze (np. głosowanie na następną mapę), po zapoznaniu się z nim wybiera interesującą go opcję (może wybrać dusta, assaulta...może też zwyczajnie menu zignorować i nie nacisnąć nic). Program musi taką informację odpowiednio przetworzyć i dowiedzieć się, co tak właściwie nacisnął gracz. Cały ten proces odbywa się w tak zwanym Handlerze (pojęcie to będzie jeszcze wykorzystywane w miejscach, gdzie odpowiedź nie pojawia się natychmiastowo, a jest przetwarzana asynchronicznie, jak to ma miejsce w T_SQL).

 

Kolejną rzeczą jest oczywiście nadanie tytułu - gracz musi wiedzieć, dlaczego dane menu widzi :). Służy do tego polecenie:

menu.SetTitle("Tutaj wpisujemy nasz tytuł");

Gdybyśmy chcieli sformatować nagłówek (np. pokazać w nim imię danego gracza), musimy zastosować sformatowanego stringa, gdyż SetTitle nie obsługuje tej funkcji:

char menuTitleBuffer[128];
Format(menuTitleBuffer, sizeof(menuTitleBuffer), "Witaj %N! Wybierz opcję:", client);

menu.SetTitle(menuTitleBuffer);

Przy okazji, jeżeli chcemy pobrać imię gracza, nie ma potrzeby korzystania z GetClientName
Hej! Skorzystałeś z linku lub pobrałeś załącznik? Uhonoruj naszą pracę poprzez rejestrację na forum i rośnij razem z nami!
- robi to za nas reguła formatująca %N - w jej miejscu pojawi się nick gracza 🙂

 

Aby dodać do menu pozycję, korzystamy z polecenia AddItem:

menu.AddIitem("id pozycji", "To, co widzi gracz");

Pierwszy parametr to ciąg znaków, służący do identyfikacji itemu

Drugi parametr to widoczna dla gracza pozycja w menu.

Identyfikator nie musi być unikalny i można wykorzystać go na masę sposobów. Przykład jego zastosowania pokażę na końcu poradnika.

 

Możemy również określić, czy menu będzie posiadało możliwość wyjścia z niego:

menu.ExitButton = false;

Domyślnie wartość ta jest ustawiona na true.

 

Po przygotowaniu menu musimy je jeszcze wyświetlić (ponieważ nigdzie tego nie zrobiliśmy - to trochę jak przygotowywanie posiłku i podanie go na końcu gościom). Polecenie:

menu.Display(client, 120);

Pierwszy parametr to id gracza.

Drugi parametr to czas, po jakim menu zniknie w razie bezczynności. Istnieje także makrodefinicja, która sprawia, że menu nigdy nie zniknie, jednak nie pamiętam go teraz. Jeśli ktoś je ma, proszę o podesłanie w komentarzu. EDIT: MENU_TIME_FOREVER (dzięki @Qesik i @Brum Brum)

 

W tym momencie menu pokazało się graczowi. Aby przechwycić jego akcję, tworzymy handler, będący funkcją, która w 90% przypadków wygląda następująco:

 

public int Menu_Handler(Menu menu, MenuAction action, int client, int item) {
  if (action == MenuAction_Select) {
    char InfoBuffer[32];
    menu.GetItem(item, InfoBuffer, sizeof(InfoBuffer));

    if (StrEqual(InfoBuffer, "id pozycji 1"))  FunkcjaZPozycji1(client);
    else if (StrEqual(InfoBuffer, "id pozycji 2"))  FunkcjaZPozycji2(client);
  }
  return 0;
}

Parametry handlera:

menu - identyfikator naszego menu

action - określa jaki rodzaj operacji został wykonany (można wybrać pozycję, wyjść z menu etc. Pełna lista: https://sm.alliedmods.net/new-api/menus/MenuAction
Hej! Skorzystałeś z linku lub pobrałeś załącznik? Uhonoruj naszą pracę poprzez rejestrację na forum i rośnij razem z nami!)

client - id gracza, któremu menu wywołaliśmy

item - pozycja w menu, którą nacisnęliśmy (numerowane od 0). Można jej użyć zamiast identyfikatora z menu,AddItem (ja dzisiaj jednak będę się posługiwał wspomnianym stringiem).

 

Pierwszą rzeczą, którą robimy, jest sprawdzenie, czy gracz wybrał którąś opcję z menu. W przeciwnym razie nie interesuje nas co dalej się stanie (choć może oczywiście - wyjście z menu może kierować do innego menu itp.)

Później identyfikujemy którą opcja została naciśnięta (przez porównanie ciagu znaków). Natrafienie na właściwą opcję przekierowuje nas do odpowiedniej funkcji.

 

 

Stworzymy teraz prostą funkcję, której zadaniem będzie wyświetlenie wszystkich żywych graczy, a po wybraniu ich z listy - wysłanie do nich powiadomienia na czacie.

Aby tego dokonać, musimy wiedzieć, który gracz został wybrany. Może nam do tego celu posłużyć identyfikator z AddItem. Wystarczy, że przekonwertujemy ID gracza na stringa i przekażemy go wraz z pozycją w menu, aby odwrócić proces i odczytać integera i wykonać na wybranym graczu daną operację.

Zwróćmy uwagę na bardzo ważną rzecz - nie wiemy, w jakim czasie w przyszłości gracz wybierze pozycję w menu - może to trwać sekundę, może dziesięć minut... . Gdybyśmy przekazali jaki ID identyfikator gracza (client), wybraniec może w czasie, od wywołania menu do wybrania opcji, zwyczajnie wyjść z serwera. Na jego miejsce może wskoczyć inny gracz, co doprowadza do wysłania informacji do niewłaściwej osoby. W naszym przykładzie nie sprawiłoby to większego kłopotu, jednak w pluginach, gdzie precyzja się liczy (np. zapis informacji o graczu w bazie danych) takie działanie może doprowadzić do bardzo niepożądanych skutków.

Aby temu zapobiec, będziemy stosować tzw. UserID - unikalny identyfikator gracza na okres trwania serwera. Po starcie pierwszy gracz otrzyma UserID = 1 (lub 0, nieistotne). Kolejny gracz - oczywiście 2. Gdyby jednak gracz nr 1 wyszedł z serwera i połączył się jeszcze raz, otrzyma UserID = 3 (pod warunkiem, że nikt inny nie wszedł na serwer w międzyczasie).

Ten właśnie identyfikator będziemy używali, by określić właściwego gracza - i zareagować, jeśli player opuścił już serwer. Do pobierania UserID na podstawie client'a, a także operacja odwrotna - pobranie client'a w oparciu o UserID, służą operacje GetClientUserId
Hej! Skorzystałeś z linku lub pobrałeś załącznik? Uhonoruj naszą pracę poprzez rejestrację na forum i rośnij razem z nami! oraz GetClientOfUserId
Hej! Skorzystałeś z linku lub pobrałeś załącznik? Uhonoruj naszą pracę poprzez rejestrację na forum i rośnij razem z nami!.

 

Po wstępie teoretycznym, zajrzyjmy na rezultat końcowy:

void MenuPowitalne(int client) {
  // tworzymy buffery na nick gracza i jego UserID
  char menuBuffer[MAX_NAME_LENGTH];
  char menuOptionBuffer[8];

  // tworzymy menu
  Menu menu = new Menu(MenuPowitalne_Handler);

  // nadajemy tytuł
  menu.SetTitle("Kogo chcesz pozdrowić:");

  // lecimy pętlą po wszystkich graczach...
  for (int i = 1; i < MAXPLAYERS; i++) {
    // odfiltrowujemy niepołączonych graczy i boty...
    if (!IsValidClient(i) || !IsClientConnected(i) || IsFakeClient(i))  continue;

    // formatujemy nick...
    Format(menuBuffer, sizeof(menuBuffer), "%N", i);
    // userID jako string...
    Format(menuOptionBuffer, sizeof(menuOptionBuffer), "%d", GetClientUserId(i));
    // i dodajemy do menu :)
    menu.AddItem(menuOptionBuffer, menuBuffer);
  }
  // na końcu wyświetlamy
  menu.Display(client, 120);
}

// uchwyt do menu
public int MenuPowitalne_Handler(Menu menu, MenuAction action, int client, int item) {
  // jeżeli gracz wybrał pozycję z menu...
  if (action == MenuAction_Select) {
    // pobieramy identyfikator z AddItem...
    char InfoBuffer[8];
    menu.GetItem(item, InfoBuffer, sizeof(InfoBuffer));

    // zamieniamy go na integera i sprawdzamy poleceniem GetClientOfUserId, czy gracz jest ciagle w grze...
    int target = GetClientOfUserId(StringToInt(InfoBuffer));

    // jeśli gracza nie ma, GetClientOfUserId zwraca zero...
    if (!target) {
      PrintToChat(client, "Gracz wyszedł z gry...");
      MenuPowitalne(client);
      return 0;
    }
    // od tego miejsca wiemy, że gracz jest dostępny w grze. Możemy go pozdrowić
    PrintToChat(target, "Pozdrowienia od gracza %N! :)", client);
  }
  // na końcu zwracamy 0.
  return 0;
}

// Koniec :)

 

Cały proces został wyjaśniony w komentarzach. To by było na tyle. Gdyby pojawiły się pytania i potrzeba rozszerzenia poradnika, z chęcią to zrobię.

 

Zapraszam do dyskusji poniżej. Pozdrowionka! 🙂

@EDIT Po pełny poradnik zapraszam tutaj (angielski): https://wiki.alliedmods.net/Menus_Step_By_Step_(SourceMod_Scripting)
Hej! Skorzystałeś z linku lub pobrałeś załącznik? Uhonoruj naszą pracę poprzez rejestrację na forum i rośnij razem z nami!

  • Lubię to! 4
  • Dobry pomysł! 4

Udostępnij ten post


Link to postu
Udostępnij na innych stronach
2 godziny temu, MAGNET napisał:

Drugi parametr to czas, po jakim menu zniknie w razie bezczynności. Istnieje także makrodefinicja, która sprawia, że menu nigdy nie zniknie, jednak nie pamiętam go teraz. Jeśli ktoś je ma, proszę o podesłanie w komentarzu.

MENU_TIME_FOREVER

 

Możemy też switchować opcję w menu nie wkładając żadnego info np.
 

Spoiler

Menu menu = new Menu(Menu_Handler);
menu.SetTitle("Przykładowe menu");
menu.AddItem("", "Informację o serwerze");
menu.AddItem("", "Jak zakupi vipa");
menu.AddItem("", "Komendy");
menu.AddItem("", "Rekrutacja");
menu.Display(client, 30);

public int Menu_Handler(Menu menu, MenuAction action, int client, int item)
{
	if (action == MenuAction_Select)
	{
		switch (item)
		{
			case 0:Info();
			case 1:ZakupVipa();
			case 2:Komendy();
			case 3:Rekrutacja();
		}
	}
	if (action == MenuAction_End)delete menu;
}

void Info()
{
	//coś o info
}
void ZakupVipa()
{
	//coś o vipie
}
void Komendy()
{
	//coś o komendach
}
void Rekrutacja()
{
	//coś o rekrutacji
}

 

 

  • Lubię to! 1
Przez MAGNET,

Yup, dokładnie tak. Wspominałem o tym, że taka opcja jest możliwa. Dzięki za przytoczenie przykładu

Udostępnij ten post


Link to postu
Udostępnij na innych stronach

Ponieważ przerywam wykonywanie się funkcji podczas sprawdzania czy gracz jest połączony. Owszem - mógłbym zagnieździć kolejnego if'a jednak nie jestem zwolennikiem tworzenia choinek (zwłaszcza, że święta się już skończyły :P)

  • Haha! 1

Udostępnij ten post


Link to postu
Udostępnij na innych stronach
6 godzin temu, MAGNET napisał:

Ponieważ przerywam wykonywanie się funkcji podczas sprawdzania czy gracz jest połączony. Owszem - mógłbym zagnieździć kolejnego if'a jednak nie jestem zwolennikiem tworzenia choinek (zwłaszcza, że święta się już skończyły :P)

return; i tyle

Udostępnij ten post


Link to postu
Udostępnij na innych stronach
11 godzin temu, MAGNET napisał:

Ponieważ przerywam wykonywanie się funkcji podczas sprawdzania czy gracz jest połączony. Owszem - mógłbym zagnieździć kolejnego if'a jednak nie jestem zwolennikiem tworzenia choinek (zwłaszcza, że święta się już skończyły :P)

wystarczyłoby dopisać "else" xD 

 

4 godziny temu, Roberrt napisał:

return; i tyle

i nie i tyle bo zwracamy informacje 😜 

Udostępnij ten post


Link to postu
Udostępnij na innych stronach

Bądź aktywny! Zaloguj się lub utwórz konto

Tylko zarejestrowani użytkownicy mogą komentować zawartość tej strony

Utwórz konto

Zarejestruj nowe konto, to proste!

Zarejestruj nowe konto

Zaloguj się

Posiadasz własne konto? Użyj go!

Zaloguj się

  • Kto przegląda   0 użytkowników

    Brak zalogowanych użytkowników przeglądających tę stronę.

×