Skocz do zawartości

Rekomendowane odpowiedzi

Co to są KeyValues?

KeyValues to prosta struktura bazująca na organizacji "drzewkowej" która zawiera zagnieżdżone w sobie sekcje które to zawierają klucze i wartości.

Dla wzrokowców i objaśnienia sprawy, najprostsza struktura KeyValues wygląda w ten sposób:

"sekcja"
{
	"klucz"	"wartosc"
}

Jak napisałem na samym początku, sekcje mogą być w sobie zagnieżdżone, czyli do sekcji możemy dopisać kolejną sekcję, do niej kolejną, itd. Przykład:

"sekcja1"
{
	"sekcja2"
	{
		"klucz"	"wartosc"
	}
}

Przy budowaniu struktury KeyValues warto zaznaczyć, że nazwy sekcji i kluczy nie mogą się powtarzać w obrębie danej sekcji. 

Trochę zagmatwane, ale mówiąc prościej tworząc sekcję "admin" nie możemy później wewnątrz tej sekcji stworzyć np. 2 razy sekcję "Kowalski", czy 2 razy klucza "Kowalski", nie powinniśmy też tworzyć klucza "Kowalski" a potem sekcji "Kowalski" czy też odwrotnie - myślę, że zrozumiałe.

Dodatkowo do KeyValues możemy swobodnie dodawać komentarze. Komentarze pozwalają Nam dodawać tekst, który zostanie pominięty przy wczytywaniu KeyValues poprzez plugin. Dodajemy je poprzez użycie dwóch slashy (//) oraz wypisaniu za nimi jakiegoś tekstu. Komentarz jest jednolinijkowy, czyli od kolejnego wiersza będziemy mogli dalej budować naszą strukturę.
 

"sekcja"
{
	"klucz1" 	"wartosc1" //To jest komentarz. Tekst ten zostanie pominiety przy wczytywaniu KeyValues przez plugin SourceMod
	"klucz2"	"wartosc2"
}

Z KeyValues mogłeś(aś) się już spotkać wcześniej, np. przy konfigurowaniu połączenia z bazą danych w pliku addons/sourcemod/configs/databases.cfg. Dla przypomnienia taki plik wygląda mniej więcej tak:

"Databases"
{
	"driver_default"		"mysql"
	
	// When specifying "host", you may use an IP address, a hostname, or a socket file path
	"default"
	{
		"driver"			"mysql" 
		"host"				"localhost" 
		"database"			"db" 
		"user"				"root" 
		"pass"				""
		//"timeout"			"0"
		//"port"			"0"
	}

	"storage-local"
	{
		"driver"			"sqlite"
		"database"			"sourcemod-local"
	}
}

Jak pracować z KeyValues w pluginie SourceMod?

Na początku przyjmijmy pewne założenia. Mianowicie napiszemy sobie plugin, który będzie tworzył główne menu serwera. Plugin ten będzie wywoływał komendę w konsoli gracza po naciśnięciu konkretnej opcji w menu. Nasz plik KeyValues będzie w addons/sourcemod/configs, nazwiemy go main_server_menu.ini. Jego struktura będzie wyglądała w ten sposób (wiem, można by to zrobić lepiej):

"MainServerMenu"
{
	"sm_vip"
	{
		"name"		"Sprawdź opis VIP'a"
		"command"	"sm_vip"
	}

	"res"
	{
		"name"		"Menu RoundSoundów"
		"command"	"res"
	}

	"rs"
	{
		"name"		"Resetuj statystyki"
		"command"	"rs"
	}
}

Na początku zadeklarujmy zmienną globalną która będzie uchwytem dla naszego menu głównego oraz od razu dodajmy RegConsoleCmd do OnPluginStart:

Menu g_mainServerMenu;

public void OnPluginStart() {
	RegConsoleCmd("sm_menu", cmd_MainServerMenu);
}

public Action cmd_MainServerMenu(int client, int args) {
	g_mainServerMenu.Display(client, MENU_TIME_FOREVER);
	return Plugin_Handled;
}

Następnie zbudujmy funkcję, która będzie odpowiedzialna za stworzenie menu na podstawie naszych KeyValues. Niech to będzie funkcja BuildMenu

void BuildMenu() {
	// kodzik...
}

Na samym jej początku stworzymy nasze menu:

	//tworzymy menu
	g_mainServerMenu = new Menu(MainServerMenu_Handler);
	g_mainServerMenu.SetTitle("Główne menu serwera", MENU_ACTIONS_ALL);

Następnie stworzymy uchwyt do KeyValues oraz wczytamy jego strukturę z naszego pliku konfiguracyjnego:

	//budujemy sciezke do pliku addons/sourcemod/configs/main_server_menu.ini
	char path[256];
	BuildPath(Path_SM, path, sizeof(path), "configs/main_server_menu.ini");

	//tworzymy KeyValues oraz importujemy strukture z pliku
	KeyValues keyValues = new KeyValues("MainServerMenu");
	keyValues.ImportFromFile(path);

 

Warto powiedzieć. że KeyValues wcale nie muszą wykorzystywać żadnego pliku. Można operować chociażby na strukturze podanej jako ciąg znaków wczytanej z zewnętrznego źródła (np. zawartość zewnętrznej strony WWW).

Jednak najczęściej oczywiście struktura jest wczytywana z pliku, tak jak w przykładzie wyżej. Co jeszcze jest istotne, to przy deklaracji new KeyValues jako paramtetr podajemy nazwę pierwszej sekcji w naszym pliku, znaną również jako sekcja główna czy węzeł główny (więcej niżej).

Mamy już wczytany pliczek do uchwytu, teraz trzeba by się odpowiednio po tej całej strukturze poruszać, a potem wczytać odpowiednie wartości do menu.

Przy poruszaniu się po pliku należy wiedzieć, że:

  • Sekcja to też w pewnym sensie klucz, o czym warto pamiętać przy poruszaniu się po strukturze. Sekcja to taka oddzielna nazwa dla kluczy, które posiadają w sobie jeszcze inne klucze.
  • Poruszając się po pliku mamy do czynienia z pozycją. Pozycja określa nam na którym poziomie podsekcji jesteśmy (inaczej poziom zagnieżdżenia) oraz na którym kluczu w danej sekcji jesteśmy. Mówiąc o pozycji mamy zatem do czynienia z dwoma wartościami. Jak to wygląda praktyce? A no tak:
"MainServerMenu" //sekcja główna (węzeł główny)
{
	"sm_vip" //pierwszy klucz w pierwszym poziomie zagnieżdżenia
	{
		"name"		"Sprawdź opis VIP'a" //pierwszy klucz w drugim poziomie zagnieżdżenia
		"command"	"sm_vip" //drugi klucz w drugim poziomie zagnieżdżenia
	}

	"res" //drugi klucz w pierwszym poziomie zagnieżdżenia
	{
		"name"		"Menu RoundSoundów" //pierwszy klucz w drugim poziomie zagnieżdżenia
		"command"	"res" //drugi klucz w drugim poziomie zagnieżdżenia
	}

	"rs" //trzeci klucz w pierwszym poziomie zagnieżdżenia
	{
		"name"		"Resetuj statystyki" //pierwszy klucz w drugim poziomie zagnieżdżenia
		"command"	"rs" //drugi klucz w drugim poziomie zagnieżdżenia
	}
}

Podstawowe funkcje do poruszania się po KeyValues:

	GotoFirstSubKey();
	// Przesuwa pozycje do pierwszej podsekcji (poziom zagniezdzenia nizej)
	// Zwraca false w przypadku nieznalezienia podsekcji

	GotoNextKey();
	// Przesuwa pozycje do kolejnego klucza/sekcji w danej sekcji
	// Zwraca false w przypadku nieznalezienia kolejnego klucza

	GoBack();
	// Przesuwa pozycje o poziom zagniezdzenia wyzej. 
	// Zwraca false, kiedy nie da sie juz wyzej przesunac (np. jestesmy w wezle glownym)

	Rewind();
	// Ustawia pozycje wezla glownego. Podobny cel mozna osiagnac poprzez
	// uzywanie GoBack dopoki nie zwroci false, jednak ta funkcja jest optymalniejsza.

	JumpToKey(const char[] key, bool create=false);
	// Przenosi nas do klucza w danej sekcji o podanej nazwie w parametrze "key"
	// W przypadku create=true oraz jezeli dany klucz nie zostanie znaleziony, funkcja ten klucz stworzy.
	// Zwraca true w przypadku znalezienia klucza, false jezeli klucza nie znaleziono

Pominąłem parametry które mogły by być trudne do zrozumienia a mają one swoje domyślne wartości. Dla ciekawskich wszystkie i w pełni opisane funkcje można znaleźć w dokumentacji.

 

Okejj, więc co dalej po otwarciu pliku? Musimy przejść o jedno zagnieżdżenie niżej, a więc kolejny skrawek kodu będzie wyglądać tak:

	//nie znaleziono zadnej podsekcji? Plik jest nieprawidłowy, koniec :/
	if (!keyValues.GotoFirstSubKey()) { 
		PrintToServer("*** I had a problem while building a menu :/ Check main_server_menu.ini");
		delete keyValues;
		return;
	}

Po prawidłowym przejściu niżej, Nasza pozycja wygląda teraz tak:

"MainServerMenu" //przed GotoFirstSubKey bylismy tutaj
{
	"sm_vip" //po GotoFirstSubKey jestesmy tutaj
	{
		"name"		"Sprawdź opis VIP'a"
		"command"	"sm_vip"
	}

	"sm_res"
	{
		"name"		"Menu RoundSoundów"
		"command"	"sm_res"
	}

	"sm_rs"
	{
		"name"		"Resetuj statystyki"
		"command"	"sm_rs"
	}
}

Co teraz? Musimy wczytać dane z danej sekcji, dodać opcję do menu i przejść do kolejnego klucza (w Naszym wypadku kolejnej sekcji). Skorzystamy z pętli do while:

	char command[64], itemName[64];
	do {
		//wczytujemy dane z danej sekcji
		keyValues.GetString("command", command, sizeof(command));
		keyValues.GetString("name", itemName, sizeof(itemName));

		//dodajemy opcje do menu
		g_mainServerMenu.AddItem(command, itemName);
	} while (keyValues.GotoNextKey()); //przechodzimy do kolejnej sekcji, dopoki te sekcje nie skoncza sie

I to by było na tyle z wczytywanie, nasze menu jest już zbudowane ^^ Nie zapomnijmy o zamknięciu uchwytu no iii dajmy odpowiednie info serwerowi:


	//info dla serwera oraz oproznianie zmiennej kv
	delete keyValues;
	PrintToServer("*** Main server menu has been built");

Funkcję BuildMenu możemy zamknąć, a pod nią dodajmy funkcję Handler do naszego menu. Będziemy tam w momencie wybrania opcji wpisywać do konsoli gracza daną komendę zapisaną pod "info" tej właśnie opcji:

//obsluga menu
public int MainServerMenu_Handler(Menu menu, MenuAction action, int param1, int param2) {
	switch (action) {
		case MenuAction_Select: {
			int client = param1;

			//wczytujemy komende z pola "info" z danej opcji w menu oraz wywolujemy komende w konsoli 
			char info[64];
			menu.GetItem(param2, info, sizeof(info));
			ClientCommand(client, info);
		}
	}
}

W OnPluginStart należy jeszcze wywołać funkcję od budowania menu:

public void OnPluginStart() {
	RegConsoleCmd("sm_menu", cmd_MainServerMenu);
	BuildMenu();
}

Nasz plugin jest gotowy 😄 Kod *.sp oraz plik *.ini podaję w załącznikach ->  

main_server_menu.ini

server_menu.sp

Co jeszcze warto wiedzieć?

  • Do wczytywania wartości z klucza mamy kilka funkcji, które odpowiadają danemu typowi wartości. Możemy np. wartość wczytać od razu w type int, słuzy do tego funkcja GetNum która zwraca wartość klucza. Jest wiele innych ciekawych funkcji jak GetFloat, GetColor, itd. - więcej w dokumentacji
  • Strukturę można zmieniać, a zmiany potem zapisać poprzez funkcję ExportToFile. Są odpowiednie funkcje do usuwania kluczy, ustawiania ich wartości, itd. Jak wyżej - więcej info na ten temat można znaleźć w dokumentacji
  • KeyValues nie powinny być zbyt wielkie. Niedopuszczalne jest skorzystanie z KeyValues w celu zapisywania ilości zabójstw graczy. Przy tak dużej ilości danych zdecydowanie lepszym rozwiązaniem jest baza danych. Wyjątkiem może być plik w którym tych graczy jest mniej, np. plik z graczami którzy dostają specjalny bonus.
  • Plików KeyValues staraj się nie otwierać zbyt często, najlepiej zrobić to maks. raz na mapę bądź też poprzez komendę do której dostęp ma tylko opiekun serwera.

 

Zadanie domowe!

Usuń klucz "command" i zamiast wczytywania go wykorzystaj funkcję GetSectionName dzięki której wczytasz nazwę sekcji, a co za tym idzie od teraz komendę będziesz podawać właśnie w nazwie sekcji.

Rozwiązaniem możesz się pochwalić w tym temacie 😄 

Sygnatura użytkownika

Właściciel sieci 1s2k.pl

  • Lubię to!
  • Dobry pomysł!
Odnośnik do odpowiedzi
Udostępnij na innych stronach

Dobrze zrobione 🤠🤠

Sygnatura użytkownika

__________________________________________________________________________________________________________________________________________________________________________
【 Deathrun    |    Deathrun Sklep    |    Deathrun VIP    |    Deathrun AutoRespawn    |    Jackpot    |    Losowy VIP    |    Modele  |   NoScope Detector    |    C4 Timer 
 ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄

Odnośnik do odpowiedzi
Udostępnij na innych stronach
  • Moderator poziom 2

❤️ 

Sygnatura użytkownika

» Steam: https://steamcommunity.com/id/pawelsteam/\

» Discord: Paweł#8244

» PluginyCS: https://pluginycs.pl/profile/Pawel

» Przyjmuję zlecenia na paczki oraz pluginy.  «

Odnośnik do odpowiedzi
Udostępnij na innych stronach
  • 3 miesiące temu...

Ostatnio przeglądający ten temat (5 użytkowników)

Dołącz do dyskusji

Możesz dodać zawartość już teraz a zarejestrować się później. Jeśli posiadasz już konto, zaloguj się aby dodać zawartość za jego pomocą.

Gość
Dodaj odpowiedź do tematu...

×   Wklejono zawartość z formatowaniem.   Usuń formatowanie

  Dozwolonych jest tylko 75 emoji.

×   Odnośnik został automatycznie osadzony.   Przywróć wyświetlanie jako odnośnik

×   Przywrócono poprzednią zawartość.   Wyczyść edytor

×   Nie możesz bezpośrednio wkleić grafiki. Dodaj lub załącz grafiki z adresu URL.

×
×
  • Dodaj nową pozycję...