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

Znajdź zawartość

Wyświetlanie wyników dla tagów 'poradnik' .



Więcej opcji wyszukiwania

  • Wyszukaj za pomocą tagów

    Wpisz tagi, oddzielając je przecinkami.
  • Wyszukaj przy użyciu nazwy użytkownika

Typ zawartości


Forum

  • Nasze Sprawy
    • Nowości
    • Dyskusje
    • Propozycje
    • Przywitaj się!
  • Sourcemod Scripting
    • Artykuły, poradniki, tutoriale
    • Pytania na temat kodowania
    • Problem z kodem pluginu
    • Prośby o przerobienie pluginu
    • Gotowe funkcje
    • Koduj z Magnetem
  • Konfiguracja pluginów
    • Artykuły, poradniki i tutoriale
    • Szukam pluginu
    • Duże modyfikacje
    • Zbiór pluginów
    • Extensions
    • Gotowe paczki serwerowe
  • Konfiguracja serwera
    • Artykuły, poradniki, tutoriale
    • Pytania
    • Problemy
    • Ochrona
    • Metamod
  • Counter-Strike: Global Offensive
    • Nowości
    • Artykuły, poradniki, tutoriale
    • Pytania
    • Problemy
    • Pliki
    • Wasza twórczość
    • Publikacje serwerów
  • Hostingi serwerów
    • Oferty firm
    • Opinie o hostingach
    • Pytania
  • Plac zabaw
    • Luźne rozmowy
    • Szukam ekipy
    • Rynek
    • Opinie o ludziach
    • RoundSoundy
  • Archiwum
    • Przestarzałe tematy
    • Kosz

Szukaj wyników w...

Znajdź wyniki, które zawierają...


Data utworzenia

  • Od tej daty

    Do tej daty


Ostatnia aktualizacja

  • Od tej daty

    Do tej daty


Filtruj po ilości...

Dołączył

  • Od tej daty

    Do tej daty


Grupa podstawowa


O mnie

Znaleziono 18 wyników

  1. Sourcemod oferuje nam bardzo wygodny system językowy, dzięki któremu jeden plugin może posiadać nieskończoną ilość wersji językowych, a wyświetlany język w danej chwili jest zależny od ustawienia serwera. Jednakże to, czy plugin będzie z tego korzystał, zależny tylko i wyłącznie od programisty. Ale niezależnie, czy umiesz programować czy nie, tłumaczenie pluginów jest proste i przyjemne. Jeśli plugin posiada wyżej wymienioną opcję to w paczce w której go pobrałeś powinien znajdować się folder /translations/ a w nim plik językowy z rozszerzeniem .phrases.txt. Jego zawartość jak w każdym pluginie wygląda podobnie: "Phrases" { "Bideo_Game_Dunky" { "en" "i will see you grandpa" "jp" "おじいちゃんに会いましょう" /* tutaj dalsze linie z kodem */ } "Im_Done_With_League_Of_Legends" { "en" "not even close baby" "jp" "赤ちゃんも閉じない" /* tutaj dalsze linie z kodem */ } } Nad pierwszymi klamrami znajduje się słowo kluczowe "Phrases", które mówi pluginowi "hej, to ja jestem ten plik od tłumaczeń". W dalszych blokach możemy zauważyć takie bloki { } jak "Bideo_Game_Dunky" oraz "Im_Done_With_League_Of_Legends". Dla tłumacza, czyli w naszym wypadku nie mają one absolutnie żadnego znaczenia i nie możemy ich zmieniać, ponieważ są one zastosowane jako zmienne w pluginie i ich zmiana tylko zepsułaby tłumaczenie. Dalej, między klamrami { } są już właściwe tłumaczenia, każdy język znajduje się w osobnej linii a format tłumaczenia frazy jest następujący: "kod_kraju" "tłumaczenie" Kod naszego kraju to oczywiście "pl". A więc wystarczy że dopiszemy następujące linie do pliku w ten sposób: "Phrases" { "Bideo_Game_Dunky" { "en" "i will see you grandpa" "jp" "おじいちゃんに会いましょう" "pl" "zobaczę cię dziadku" /* tutaj dalsze linie z kodem */ } "Im_Done_With_League_Of_Legends" { "en" "not even close baby" "jp" "赤ちゃんも閉じない" "pl" "nawet nie blisko dziecko" /* tutaj dalsze linie z kodem */ } } Zapisujemy plik, wgrywamy na miejsce jego oryginału, ustawiamy język serwera na polski i mamy przetłumaczony plugin! Aby zobaczyć jak wygląda normalny plik ze spolszczeniem możecie zajrzec do pluginu runda nożowa. Miłego tłumaczenia!
  2. Opis Ten temat zawiera poradnik jak można dodać licencję na swój plugin sourcemod. Plugin będzie komunikował się z bazą danych na zewnętrznym serwerze, sprawdzał czy IP serwera, ID pluginu oraz specjalny hash pluginu są wpisane do SQL, jeśli tak, plugin będzie działał poprawnie, jeśli nie, plugin automatycznie się sam wyłączy. Aby w pełni zrozumieć poradnik powinieneś mieć podstawowe pojęcie o PHP, SourceModzie oraz bazach danych. Jeśli czegoś nie wiesz, śmiało pytaj w tym temacie, na pewno poszerzysz swoją wiedzę. Ostrzeżenie Poradnik jest w praktyce niepotrzebny, ponieważ jak dobrze pamiętamy, każda publikacja pluginu sourcemod zgodnie z licencją GNU General Public License (klik aby poczytać) włącznie ze skompilowanym plikiem .smx (którego w teorii nie da się odczytać ani zmodyfikować) musi zawierać kod źródłowy w pliku .sp. Więc nawet jeśli nałożymy na nasz plugin licencję to każdy będzie mógł ją zmodyfikować/usunąć. Ten temat znajduje się tutaj tylko i wyłącznie w celach edukacyjnych. Kod SM Pokrótce omówię poniższy kod, pomimo tego, że zawiera dość dobre komentarze*. Główną funkcję CheckValidity (); umieszczamy w kodzie innego pluginu w OnPluginStart ();. Sprawdza ona czy plugin poprawnie znajduje się w bazie danych. Jeśli się znajduje, URL zwraca odpowiedź w postaci hashu, który musi się zgadzać z tym wpisanym w plugin. Jeśli się nie zgadza, plugin się wyłącza. pluginDatabaseID - uzupełnij tą zmienną o numer ID pluginu z bazy danych (o bazie danych później) validDatabaseResponse - uzupełnij tą zmienną o unikalny ciąg znaków(hash) z bazy danych databaseURL - uzupełnij tą zmienną o URL, który będzie się komunikował z bazą danych Ten kod możemy umieścić bezpośrednio w kodzie innego pluginu lub jako plik .inc. Życzę miłego czytania komentarzy, widzimy się na dole gdzie wyjaśnię jak będzie wyglądał kod PHP po stronie URL Kod PHP Kod PHP będzie przyjmował argumenty "ip" oraz "pl" oznaczające po kolei IP serwera na którym jest plugin oraz ID pluginu. Sprawdzi czy są poprawne (pod względem składni), po czym połączy się z SQL i zapyta, czy dane znajdują się w bazie danych. Jeśli się znajdują, URL wyrzuci na ekran unikalny hash pluginu, by plugin mógł porównać go sobie ze swoim hashem wpisanym na sztywno. Sprawdza to funkcja ReturnQueryHTTP w sourcepawnie w moim kodzie. Tabela z licencjami Tabela z licencjami powinna wyglądać mniej więcej tak: | id | ip | plugin_id | plugin_hash | | | | | | |tutaj id ze | tutaj ip |tutaj id plugi-| tutaj hash | |standardową | serwera |nu | unikalny dla | |autoinkreme-| | | każdego smx | | ntacją | | | | id - zawiera standardowy integer z autoinkrementacją niepotrzebny do działania pluginu na licencję ip - ip serwera na którym ma działać dana licencja plugin_id - jest to ID pluginu (nie pojedynczego pliku) w bazie danych. Na przykład plugin nazwany "fajerwerki" będzie miał id 10 i niezależnie jaki użytkownik będzie miał na niego licencję, to plugin zawsze będzie miał id 10. Swoisty identyfikator pluginu plugin_hash - unikalny 32 znakowy ciąg znaków dla każdego osobnego pliku smx który znajduje się i w bazie i w pluginie, musisz go wpisywać ręcznie * - Pamiętajmy co do komentarzy - jeśli Twój kod zawiera ich za dużo, staje się nieczytelny. Nie papuguj kodu powyżej i nie pisz wszędzie swoich komentarzy. Staraj się pisać kod zgodnie z zasadami Clean Code ** - Regex czyli wyrażenia regularne. Jest to świetne i bardzo przydatne zagadnienie w informatyce ogółem, polecam się zagłębić
  3. DataPacki Przeważnie podczas tworzenia funkcji definiujemy pożądaną liczbę parametrów, którą chcemy użyć. Przykład: int Mnozenie(int a, int b) { return a * b; } /////////////// void JakasFunkcja() { PrintToChatAll("5x6 = %d", Mnozenie(5,6)); } Aby zrobić tutaj użytek z funkcji Mnozenie() konieczne było przekazanie dwóch argumentów. Niestety, w niektórych sytuacjach nie mamy komfortu przekazywania dowolnej ilości argumentów - jesteśmy ograniczeni tylko do...jednego! Dotyczy to chociażby asynchronicznych zapytać SQL lub timerów. Przykład: void JakasFunkcja(int client) { int a = 5; int b = 6; CreateTimer(10.0, PomnozCosPozniej, /*TUTAJ TYLKO JEDEN ARGUMENT!*/); } public Action PomnozCosPozniej(Handle timer, any data) { /// co tu zrobic ?! } Gdybyśmy chcieli wykonać operację mnożenia w tym przypadku, niezbędne jest przekazanie dwóch zmiennych. Co robić w takiej sytuacji? Z pomocą przychodzą nam DataPacki! Umożliwiają nam one "upakowanie" dowolnej liczby zmiennych o dowolnym typie do jednego ciągu danych. Cały proces zaczyna się od stworzenia uchwytu do DataPacka: DataPack data = new DataPack(); jesteśmy gotowi do załadowania go danymi. Do tego celu wykorzystuje się kolejno WriteCell, WriteFloat, WriteFunction, WriteString W naszym przykładzie poza dwiema zmiennymi przekażemy dodatkowo komunikat do wyświetlenia, aby pokazać jak posługiwać się ciągami znaków. Nasza funkcja będzie teraz wyglądać tak: void JakasFunkcja(int client) { char helloString[128]; Format(helloString, sizeof(helloString), "Siemanko %N!", client); DataPack data = new DataPack(); data.WriteCell(5); data.WriteString(helloString); data.WriteCell(6); CreateTimer(10.0, PomnozCosPozniej, data); } Celowo umieściłem stringa pomiędzy dwiema liczbami, aby pokazać, że odczyt jest odrobinę bardziej skomplikowany niż zapis. Po pierwsze, każdy DataPack posiada swój specjalny wskaźnik, który pokazuje w którym miejscu ma być przeprowadzany odczyt/zapis. Przeanalizujmy proces zapisu informacji w naszej funkcji JakasFunkcja(int client). Po stworzeniu DataPacka mamy pustą strukturę i wskaźnik znajdujący się na początku: [|] Znak '|' to właśnie nasz wskaźnik Po wywołaniu żądania umieszczenia piątki, proces zaczął się w miejscu gdzie stał nasz wskaźnik - czyli na samym początku: [5|] Po dodaniu inta, wskaźnik przesunął się (troche jak wskaźnik podczas pisania na klawiaturze) Kolejny w kolejce jest string: [5string|] I ostatnia już wartość - 6: [5string6|] W tym miejscu mamy już przygotowanego do wysyłki DataPacka. Zauważmy jednak, że nasz wskaźnik nie zmienił swojej pozycji - jest cały czas ustawiony na końcu struktury. Czy domyślacie się, jak wyglądałby odczyt tych danych? Bez przesunięcia wskaźnika na początek pliku ten proces jest niemożliwy... Na szczęście, istnieje operacja, która może tego dokonać. Nazywa się ona Reset: // zauważmy, że drugi argument funkcji zmienił się na nasz DataPack :) public Action PomnozCosPozniej(Handle timer, DataPack data) { data.Reset(); } Od tego momentu struktura DataPacka wygląda tak: [|5string6] Oznacza to, że jesteśmy gotowi do odczytu ? Istnieje jednak jeszcze jeden haczyk. Wspomniałem wcześniej, że dane w DataPacku przyjmują forme ciągu danych. Oznacza to, że SourceMod widzi je jako swoisty strumień. Właśnie do nas należy zadanie zapanowania nad tym haosem. Do odczytywania danych stosujemy kolejno ReadCell, ReadFloat, ReadFunction i ReadString. W naszym DataPacku nie możemy zacząć wyciągania od np. stringa - wszystko musimy wykonywać w takiej samej kolejności, w jakiej wkładaliśmy dane! Na pierwszy ogień pójdzie więc 5, następnie komunikat, a na samym końcu - 6. A więc pierwszą wartością jest int. Korzystamy z ReadCell: // zauważmy, że drugi argument funkcji zmienił się na nasz DataPack :) public Action PomnozCosPozniej(Handle timer, DataPack data) { data.Reset(); int a = data.ReadCell(); } // Aktualny stan DataPacka: [5|string6] Przyszła pora na stringa. W naszym przypadku nasz łańcuch składa się z 128 znaków i dokładnie tyle zapisaliśmy w DataPacku! Pamiętajmy więc o przygotowaniu odpowiednio dużej tablicy: char helloString[256]; // wielkość tablicy do której zapisujemy może się różnić - ważne, abyśmy pamiętali ile znaków zapisaliśmy WCZEŚNIEJ! data.ReadString(helloString, sizeof(helloString)); I na sam koniec ostatnia liczba: int b = data.ReadCell(); Gotowe! Wszystkie dane zostały pobrane prawidłowo. Nasz efekt końcowy: void JakasFunkcja(int client) { char helloString[128]; Format(helloString, sizeof(helloString), "Siemanko %N!", client); DataPack data = new DataPack(); data.WriteCell(5); data.WriteString(helloString); data.WriteCell(6); CreateTimer(10.0, PomnozCosPozniej, data); } public Action PomnozCosPozniej(Handle timer, DataPack data) { char helloString[256]; data.Reset(); int a = data.ReadCell(); data.ReadString(helloString, sizeof(helloString)); int b = data.ReadCell(); PrintToChatAll("Komunikat: %s", helloString); PrintToChatAll("%d x %d = %d", a, b, a*b); } // stan DataPacka: [5string6|] Oczywiście w razie pojawienia się pytań bądź wątpliwości zapraszam do dyskusji pod spodem ? PS: suplementem do tego materiału jest napisany przez @Vasto_Lorde poradnik o timerach:
  4. Jak zostać dobrym adminem? Dzisiaj wam przedstawię "Jak zostać dobrym adminem?" w grze Counter Strike Global Offensive! 1. Zacznijmy od początku, czyli blokowanie czatu i mikrofonu: /sm_gag [nick] [czas] [powód] - Blokuje czat gracza na określony czas. /sm_ungag [nick] - Usuwa blokadę na czat gracza. /sm_mute [nick] [czas] [powód] Blokuje mikrofon gracza na określony czas. /sm_unmute [nick] - Usuwa blokadę na mikrofon gracza. /sm_silence [nick] [czas] [powód] - Blokuje czat i mikrofon gracza na określony czas. /sm_unsilence [nick] - Usuwa blokadę na czat i mikrofon gracza. 2. Przeskoczmy do banowania/kickowania: /sm_kick [nick] [powód] - Wywala gracza z serwera, gracz może ponownie dołączyć do serwera. /sm_ban [nick] [czas w minutach] [powód] - Banuje gracza gracza na określony czas. /sm_addban [czas] [steamID] [powód] - Dodaje bana graczowi na określony czas. Przydatne, gdy gracz opuścił grę, a mamy jego steamID. /sm_unban [steamid] - Odbanowywuje gracza. 3. Przelećmy do wysyłania wiadomości na czacie: /sm_chat [tekst] - Wysyłanie wiadomości do admina /sm_csay [tekst] - Wyświetla tekst na środku ekranu /sm_hsay [tekst] - Ukryta wiadomość sm_msay tekst - tekst po lewej stronie /sm_psay [nick] [teks]t - Wiadomość prywatna /sm_say [tekst] - Wiadomość dla wszystkich graczy /sm_tsay [kolor] [tekst] - Wyświetla tekst w lewym górnym rogu 4. Inne komendy: /sm_admin - Przedstawia menu admina. /sm_help - Pokazuje dostępne komendy dla admina. /sm_map [mapa] - Zmienia na określoną mapę Poradnik został napisany przez @Squbany . Zakaz kopiowania na inne fora bez mojej zgody. Jeżeli uważasz, że powinienem coś dodać, napisz!
  5. Korzystanie z enum 1. Uproszczone wprowadzenie "enum" jest to struktura przez którą możemy zamiast surowych liczb wprowadzać nazwy i dzięki temu jest nam łatwiej z nich korzystać. Najprostszym przykładem będzie tutaj zbiór broni w sourcemodzie, gdzie każda broń ma swój numer i swoją enumową nazwę (zobacz też pełny temat dotyczący spisu broni): enum CSWeaponID { CSWeapon_NONE = 0, CSWeapon_P228, CSWeapon_GLOCK, CSWeapon_SCOUT, CSWeapon_HEGRENADE //(..) }; Powyższy kod jest deklaracją enuma o nazwie CSWeaponID, gdzie jego zawartością są kolejno CSWeapon_NONE, CSWeapon_P228 i tak dalej. Można zauważyć, że do pierwszej nazwy przypisane jest 0, i taką będzie przyjmować wartość CSWeapon_NONE = 0. Następne nazwy mają domyślnie wartość o jedną więcej, czyli jest to ciąg liczb naturalnych (0, 1, 2, 3, 4 i tak dalej). W taki sposób możemy korzystać z funkcji takich jak CS_GetWeaponPrice bez zapamiętywania poszczególnych numerów broni. Przykładowo osobiście nie mam pojęcia który numer w enumie ma broń AWP, ale mogę po prostu posłużyć się tym: CSWeapon_AWP. 2. Numeracja i licznik Jak już zostało wspomniane, domyślnie licznik enuma działa jak ciąg liczb naturalnych. Każda następna wartość jest większa od poprzedniej o jeden. Dlatego w tym przypadku: enum { wartosc1 = 0, wartosc2 = 10, wartosc3, wartosc4 = 30 } wartosc3 będzie równa 11. Dzieje się tak, ponieważ nawet jeśli manualnie "ustawimy" liczenie na co 10, kompilator nie będzie wiedział co mamy na myśli. Musimy użyć następującego sposobu: enum (+=10) { wartosc1 = 0, wartosc2, wartosc3 } W takim wypadku wartosc2 jest równa 10, a wartosc3 20. Możemy tak również zrobić w przypadku mnożenia na przykład: (*=2) Bibliografia https://amxx.pl/topic/1699-troche-o-enum/ https://forums.alliedmods.net/showthread.php?t=140103 i własne doświadczenie
  6. Jak działają wektory i punkty w przestrzeni 3D? 1. Wprowadzenie - Co to w ogóle jest punkt lub wektor? I. Punkt w przestrzeni trójwymiarowej (3D) posiada trzy liczby określające jego pozycję w przestrzeni. Liczba X określa odległość punktu od środka przestrzeni na osi X (oś ox). Liczba Y określa odległość punktu od środka przestrzeni na osi Y (oś oy). Liczba Z określa odległość punktu od środka przestrzeni na osi Z (oś oz). Taki układ przedstawia nam obraz: Jak widzimy, każda oś odpowiednio x, y oraz z odpowiada za przesunięcie punktu u . Działając na osi x przesuwamy punkt u od przodu do tyłu, oś y pozwala nam na przesunięcie punktu na prawo lub na lewo, natomiast oś z w górę lub w dół. Załóżmy, że chcemy napisać gdzie dokładnie znajduje się nasz punkt u w przestrzeni. Zapisujemy to w następujący sposób: Teraz żeby określić dokładną odległość, za literki x, y, z podstawiamy liczby II. Wektor jest to matematyczny obiekt posiadający własności takie jak: długość, zwrot. Podobnie jak punkt, wektor określa się za pomocą trzech wartości, które również można zapisać jako: W tym przypadku taki zapis nie oznacza punktu w przestrzeni 3D ale wektor przeniesienia w jakim kierunku i z jaką siłą coś zostało przeniesione. Także zapis może być identyczny, lecz to w jaki sposób go pobierzemy/wykorzystamy w kodzie programu definiuje jego zawartość. Kilka wzorów : - Aby obliczyć długość wektora musimy skorzystać ze wzoru √(a2^+b^2+c^2) [słownie: pierwiastek z a do kwadratu dodać b do kwadratu dodać c do kwadratu] - Wektor możemy określić również przez dwa punkty w przestrzeni. Jego początek i jego koniec. Spójrzmy jeszcze raz na obrazek W takim przypadku gdy punkt wyjściowy układu (0,0,0) jest równy początkowi wektora, łatwo możemy obliczyć czarny wektor na rysunku ponieważ wzór wynosi: (x2 - x1, y2 - y1, z2 - z1), gdzie odpowiednio (x1, y1, z1) oraz (x2, y2, z2) to punkt początkowy i punkt końcowy wektora. Musimy jednak pamiętać, że wektor w przestrzeni 3D nie zawsze ma swój początek w (0,0,0). 2. Po co nam taka wiedza w sourcemodzie? Każdy obiekt w CS:GO ma swój origin, czyli punkt w przestrzeni który określa gdzie się on znajduje. A takowymi przestrzeniami są mapy w CS:GO. Każda mapa jest osadzona w jakiejś przestrzeni 3D i każdy obiekt na tej mapie posiada origin, tablicę z trzema wartościami (x, y, z) by określić w jakim miejscu się znajduje. Jeśli chodzi o wektory, to każdy obiekt ma dwa wektory określające jego "zachowanie" w grze. Pierwszy to velocity(dosłownie prędkość) i określa on z jaką mocą poruszamy się w jakim kierunku czy to chodząc klawiszami WSAD czy spadając z wysokości czy skacząc (to samo tyczy się zwykłych obiektów takich jak bronie na ziemi). Drugi wektor to angle czyli wektor kąta/obrotu obiektu. Nie jest to typowy wektor opisany powyżej, po prostu to w którą stronę obrócony jest obiekt określają te trzy wartości (x, y, z). 3. Praktyczne zastosowanie GetClientAbsOrigin / GetClientAbsAngles / GetClientEyeAngles Zgodnie z dokumentacją dzięki tym funkcjom możemy pobrać podstawowe wartości dotyczące wektorów graczy. Wszystkie trzy wyglądają mniej więcej tak: W każdym przypadku potrzebujemy indeksu klienta oraz tablicy zawierającej trzy miejsca. Do tablicy podanej w funkcji zostaje przekazana dana wartość, wektor zawierający trzy liczby z przecinkiem. Co możemy zrobić z tymi wartościami? Pluginy na teleportacje, zatrzymywanie graczy na respawnie, efekty odurzenia, aimbota i dużo innych. Przykład praktyczny: Pamiętajmy jednak, że nie da się tak łatwo przeteleportować gracza po osiach Ox i Oy, ponieważ miejsce i zwrot środka układu jest zawsze takie samo i nie obraca się ono zgodnie z graczem. Więc jeśli od pobranej wartości origin odejmiemy -5 z [0] (oś Ox) to nie przeniesie gracza w lewo względem jego pozycji, tylko względem ustawienia środka układu! Bibliografia Tutorial powstał z pomocą https://pl.wikipedia.org/wiki/Wektor oraz moją szczątkową wiedzą i doświadczeniem w tym zakresie *Uprasza się o nie kopiowanie poradnika bez wcześniejszego zapytania autora o zgodę
  7. Łatwy sposób na testowanie pluginów 1. Przechodzimy do folderu gdzie mamy zainstalowany steam 2. Przechodzimy steamapps/sourcemods/ 3. Wrzucamy zawartość załącznika CSGO + SM.7z do /sourcemods/ 4. Wgrywamy tam najpierw Metamoda potem Sourcemoda (tutaj tutorial na ten temat) 5. Uruchamiamy ponownie Steam 6. Wchodzimy do biblioteki gier 7. Ustawiamy dla gry "CSGO + SM" w parametrach startowych -insecure 8. Końcowy układ folderów powinien wyglądać tak: Dzięki temu mamy "osobną" grę CS:GO gdzie możemy testować pluginy LOKALNIE UWAGA Byłoby miło całej ekipie GO-Code.pl jeśli skorzystasz z CSGO + SM by GO-Code.pl.7z zamiast podanego wyżej CSGO + SM. Ta paczka zawiera reklamę naszego forum i za każdym razem gdy będziesz testował pluginy dasz znać znajomym o naszym forum ? (ponieważ gra nazywa się "CSGO + SM by GO-Code.pl" zamiast samego "CSGO + SM") UWAGA2 Ten poradnik jest skopiowany słowo w słowo z tego postu napisanego przez @mastah7991 za jego pozwoleniem. Uznałem że jest to na tyle wygodny sposób że zasługuje on na osobny temat
  8. 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 - 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) 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 oraz GetClientOfUserId. 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)
  9. Źródło https://forums.alliedmods.net/showthread.php?t=312551 1. Hookujemy SDKHook_WeaponCanUse przy wejściu gracza na serwer. public void OnClientPutInServer(int client) { SDKHook(client, SDKHook_WeaponCanUse, Hook_WeaponCanUse); } 2. Następnie tworzymy funkcję do hooka Hook_WeaponCanUse public Action Hook_WeaponCanUse(int client, int weapon) { char classname[64]; GetEntityClassname(weapon, classname, sizeof classname); if (StrEqual(classname, "weapon_melee") || StrEqual(classname, "weapon_knife")) EquipPlayerWeapon(client, weapon); } 3. Teraz wystarczy dać graczowi broń. (Przykładowy plugin) 4. Pięści możemy dać graczowi takim sposobem. public void GiveFists(int client) { int fists = GivePlayerItem(client, "weapon_fists"); EquipPlayerWeapon(client, fists); }
  10. Jeżeli spędziłeś już trochę czasu na programowaniu w pawnie, na pewno natknąłeś się na funkcje, które pobierają dane z innych pluginów, np. cod_get_user_class, które zwróci ID postaci, na której aktualnie gramy. Warto zauważyć, że owe informacje wychodzą bezpośrednio z silnika cod moda - następuje więc tutaj bezpośrednia wymiana informacji pomiędzy pluginami. W tym poradniku zajmiemy się po krótce tym zagadnieniem. 1. Funkcje natywne Działanie "natywów" sprowadza się do zdalnego wywołania funkcji i ewentualnego zwrócenia rezultatu: 0 - Nasz plugin posiada funkcję natywną szarego pluginu I - zlecamy wykonanie funkcji II - szary plugin wykonuje polecenie III - szary plugin zwraca otrzymany rezultat IV - rezultat trafia do naszego pluginu Teraz pokażę jak takiego natywa z szarego pluginu stworzyć. Przede wszystkim natywy dodajemy w takiej funkcji: public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) Daje nam to pewność, że załadują się one w odpowiedniej chwili. Warto wspomnieć, że natywy możemy wczytywać pojedynczo, bądź jako biblioteka - tworzyć nasze własne pliki #include. W tym poradniku skupię się tylko na tej drugiej metodzie. Musimy więc najpierw zarejestrować naszą bibliotekę: RegPluginLibrary("NaszaNazwa"); następnie dodajemy natywy w następujący sposób: CreateNative("nazwa_natywu", Funkcja_Ktora_Ma_Sie_Wywolac); na samym końcu zwracamy: return APLRes_Success; Teraz pora na Funkcja_Ktora_Ma_Sie_Wywolac. Natywy pozwalają nam również na bezproblemowe przekazywanie argumentów, jednak funkcja, o której przed chwilą wspomniałem, nie może po prostu tych argumentów przyjmować w nawiasach () - działa to w trochę inny sposób: public int Funkcja_Ktora_Ma_Sie_Wykonac(Handle plugin, int numParams) { int liczba = GetNativeCell(1); return liczba * 2; } po pierwsze, argumenty muszą być takie, jak wyżej - Handle plugin (uchwyt to NASZEGO pluginu), a także numParams (ilość parametrów, które przekazaliśmy) Pobieranie argumentów odbywa się poprzez specjalne funkcje GetNative___ (polecam po prostu wpisać sobie "getnative" a także "setnative" w dokumentacji). Każdorazowo pierwszy argument GetNative__ to numer argumentu z naszego wywołania (wyjątkowo numerujemy tutaj od 1!) W przykładzie powyżej założyłem, że przekazany zostanie jedynie int, który zostanie przemnożony przez 2 i zwrócony. Przykładowe zastosowanie: Pamiętajmy jednak, że ciągle nie stworzyliśmy biblioteki GoCode! Będzie ona wyglądać mniej więcej tak: Tak przygotowany plik możemy zapisać jako GoCode.inc i wrzucić do naszego folderu include w kompilatorze. Koniec ? 2. Forwardy (coming soon)
  11. Wstęp Poradnik pisany na podstawie https://wiki.alliedmods.net/Building_SourceMod Ładnie jest tam to opisane, ale korzystając tylko z tamtego artykułu nie udało mi się skompilować, musiałem szukać rozwiązań w internecie, z stąd ten poradnik ? Dla tych co chcą kompilować na Windowsie zostaje jedynie powyższy artykuł. Cały proces przeprowadziłem na Antergosie x64 (Arch) Instalacja bibliotek oraz kompilatora Arch: sudo pacman -S git python clang gcc-multilib lib32-glibc lib32-libstdc++5 lib32-zlib Debian/Ubuntu: sudo apt-get install clang sudo apt-get install lib32stdc++-4.8-dev sudo apt-get install lib32z1 lib32z1-dev sudo apt-get install libc6-dev-i386 libc6-i386 Przygotowanie AMBuild Wpierw pobieramy najnowszy ambuild z repozytorium: git clone --recursive https://github.com/alliedmodders/ambuild.git Następnie przechodzimy do folderu z kodem: cd ambuild Instalujemy sudo python setup.py install Wracamy do katalogu wyżej: cd .. Kompilacja Tak samo jak w przypadku ambuild najpierw pobieramy najnowszy kod z repozytorium (--recursive jest tutaj bardzo ważne aby zassało nam biblioteki zależne takie jak amtl): git clone --recursive https://github.com/alliedmodders/sourcemod.git Pobieramy SDK które potrzebne są do interakcji sourcemod'a z srcds danego moda: source sourcemod/tools/checkout-deps.sh Przechodzimy do katalogu z kodem: cd sourcemod Tworzymy nowy folder do zbudowania sourcemod'a oraz przechodzimy do niego: mkdir build cd build Teraz konfigurujemy proces budowania: CC=clang CXX=clang++ python ../configure.py Wreszcie odpalamy kompilacje ? : ambuild Skompilowany sourcemod pojawi się w folderze package w naszym folderze do budowania Dodatkowe informacje configure.py posiada wiele przydatnych parametrów, pełną listę wraz z opisem uzyskamy wpisując: python configure.py --help Jeśli chcemy skompilować kod przy pomocy innego kompilatora wystarczy że w procesie konfiguracji ustawimy go, np. dla g++: CC=gcc CXX=g++ python ../configure.py Istnieje jednak możliwość że będziemy musieli wtedy edytować kod, bądź dodać dodatkowe flagi kompilacji, sourcemod oficjalnie jest kompilowany na clang'u
  12. Cześć ? Szukałem i znalazłem, więc przekazuję dalej ku uciesze gawiedzi ? 1. Tworzymy w pluginie globalnego boola : bool g_bIsPanorama[MAXPLAYERS + 1]; 2. Tworzymy funkcje : void PanoramaCheck(int client) { g_bIsPanorama[client] = false; QueryClientConVar(client, "@panorama_debug_overlay_opacity", ClientConVar); } public void ClientConVar(QueryCookie cookie, int client, ConVarQueryResult result, const char[] cvarName, const char[] cvarValue) { g_bIsPanorama[client] = (result == ConVarQuery_Okay); } public void OnClientPostAdminCheck(int client) { PanoramaCheck(client); } 3. Prostym ifem sprawdzamy w pożądanym miejscu, czy gracz ma panoramę, np : public void JakaśFunkcja(client, args) { if(g_bIsPanorama[client]) PrintToChat(client, "Masz panoramę"); } Źródło https://forums.alliedmods.net/showpost.php?p=2601768&amp;postcount=1
  13. Witam Dzisiaj pokaże wam jak w łatwy sposób dodać rounde paintball do arenek A więc potrzebujemy plugin paintball pobieramy go stąd https://forums.alliedmods.net/showthread.php?t=287879 I instalujemy jak normalne pluginy Następnie przechodzimy do addons/sourcemod/config/paintball/weapons I robimy by wyglądało to tak "Paintball-guns" { "weapon_mp7" { "price" "300" "auto" "1" "bullet-speed" "1600.0" "bullet-gravity" "0.2" "accuracy" "40.0" "jump-accuracy" "200.0" "running-speed" "1.0" "reload-speed" "1.6" "switch-guns-speed" "0.9" "shot-delay" "0.05" } resztę usuwamy i zapisujemy Następnie przechodzimy do pliku addons/sourcemod/config/multi1v1_customrounds.cfg I tworzymy swoją rounde "Paintball" { "name" "Paintball" "ranked" "1" "ratingFieldName" "mp7Rating" "optional" "1" "enabled" "0" "armor" "1" "helmet" "1" "health" "100" "weapons" { "weapon_knife" "" "weapon_mp7" "" } } I po zmianie mapy /resetnieciu serwera wszystko będzie śmigać w razie pytań pytajcie ?
  14. Czasami zdarza się, że nie mamy pojęcia z jakimi ilościami elementów będziemy mieli do czynienia (np. ile klas w CodMod'ie musimy załadować do pamięci). I owszem, możemy wówczas sprowadzić to do prostego ograniczenia, np.: #define MAX_ILOSC_KLAS 100 int ClassID[MAX_ILOSC_KLAS]; //... Niestety, rodzi to dwa zasadnicze problemy: 1. Jeśli aktualnie nie wykorzystujemy całej zaalokowanej pamięci, marnujemy bez potrzeby miejsce 2. Zawsze jesteśmy ograniczeni przez limit, który sami na siebie nałożyliśmy Rozwiązaniem naszego problemu są tablice dynamiczne Mimo, że działają one nieco wolniej od zwykłych tablic, pozwalają na całkowicie dynamiczną alokację pamięci - oznacza to, że w trakcie trwania programu możemy dowolnie manipulować ilością zarezerwowanej pamięci - zarówno alokując, jak i zwalniając zasoby Korzystanie z nich jest naprawdę proste i wymaga jedynie odrobinę wprawy Na samym początku tworzymy uchwyt (najczęściej globalnie): ArrayList MojUchwyt Do poprawnego działania konieczne jest jeszcze jego zainicjowanie - bez tego niemożliwe jest jego działanie, a serwer będzie sypał errorami. Najczęściej piszemy w OnPluginStart: MojUchwyt = CreateArray(block_size, init_size) Teraz omówię parametry block_size oraz init_size, posługując się strukturą tablicy dynamicznej. Możemy sobie ją przyrównać do takiej oto szafeczki: block_size mówi nam ile komórek zarezerwowanych jest dla jednego poziomu. Wartość raz tam podana pozostanie niezmienna dla danej tablicy (chyba, że użyjemy ResizeArray). Na naszym przykładzie dla każdego poziomu zarezerwowane zostaną 3 komórki. init_size określa ile poziomów zostanie zaalokowane na samym początku. Jego wartość ulega zmianie na przestrzeni trwania programu. Nasza szafeczka ma na start zaalokowane 12 poziomów, jednak w praktyce tego parametru najczęścieć nie stosuje się wogóle - wówczas na starcie nie ma żadnego poziomu i zostaną one dodane dopiero później. Jaką wartość podawać w block_size? Jeżeli wiemy, że w danej tablicy umieszczone zostaną jakieś ciągi znaków, które de facto wymagają więcej niż jednej komórki, wówczas podajemy tam wartość, która pozwoli na zapewnienie odpowiedniej ilości miejsca. Przykładowa inicjalizacja tablicy na imiona graczy może wyglądać na przykład tak: MojUchwyt = CreateArray(MAX_NAME_LENGTH+1) Z wykorzystaniem methodmap: MojUchwyt = new ArrayList(MAX_NAME_LENGTH+1); Polecenie zainicjuje pustą tablicę, która będzie posiadała na każdym poziomie ilość komórek równą wartość stałej MAX_NAME_LENGTH + 1 Niedobrze jest ograniczać się do konkretnych ilości danych (np. klas), jednak już dobrą praktyką jest ograniczanie dopuszczalnej ilości znaków, jak w przypadku limitu w nazwie gracza, zdefiniowanego przez podaną wyżej stałą. Gdyby zdarzyło się, że przekroczymy te ograniczenie, nadmiarowe znaki zostają usuwane... Skoro znamy już podstawy, możemy zapoznać się z procedurą zarządzania tablicą dynamiczną: 1/4. Dodawanie Każdorazowe dodanie elementu powoduje utworzenie się na samej górze nowego poziomu (patrz: przykład z półką) z zaalokowanymi komórkami w ilości block_size. Możemy tam wrzucić pojedyńczą komórkę, ciąg znaków, bądź tablicę. Przykład: 2/4. Modyfikowanie Każdy poziom możemy dowolnie edytować. Musimy jedynie wiedzieć na jakiej wysokości się on znajduje. Ważne jest, że poziomy numerujemy od zera! (dół - zero). Przykład: 3/4. Pobieranie Jeżeli pobieramy komórkę ( GetArrayCell ), zostanie ona zwrócona przez return. W przypadku stringa i tablicy, wartości zostaną skopiowane do podanych przez nas lokalizacji. Przykład: 4/4. Usuwanie Nic prostszego - wystarczy index poziomu. Po usunięciu wszystkie wyższe poziomy spadają o jeden w dół (troche jak ściąganie obrusu z zastawionego stołu ?) 5/4. Reszta SourceMod posiada dużo więcej funkcji obsługujących tablicy dynamiczne. Zapraszam tutaj: https://go-code.pl/dokumentacja-sourcemod/adt_array/ Przykład Poniższy kod dodaje do tablic dynamicznych nazwę i UserId każdego gracza, który wejdzie na serwer. Ponadto, komenda !show pozwala na wyświetlenie zawartości owych tablic, a po wybraniu gracza z menu plugin wypisuje jego status - czy gracz jest połączony, czy też nie (lub wykonał reconnect): To by było na tyle ? Jeżeli pojawią się jakieś pytania z radością na nie odpowiem, a w razie potrzeby zaktualizuję poradnik Pozdrawiam!
  15. 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 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: 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: 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 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 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 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 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: 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 oraz https://pl.wikipedia.org/wiki/Serializacja by przypomnieć sobie niektóre rzeczy. Nie zapominajmy też o naszej dokumentacji https://go-code.pl/dokumentacja-sourcemod/ *Uprasza się o nie kopiowanie poradnika bez wcześniejszego zapytania autora o zgodę
  16. Elo. Na screeny niestety nie liczcie, ale postaram się napisać małe kompendium wiedzy odnośnie tego, co możemy zrobić aby poprawić jakość naszego łącza. Nie będę wchodził na temat od strony technicznej (słaby komp net, zmiana łącza z wifi na kabel itp.) Do diagnozy polecam wykorzystać net_graph 1 z konsoli w cs:go albo innej grze na silniku source (na zdecydowanej wiekszości działa) Wiec co jest czym? - Loss czyli pakiety stracone (wyrażane w %, im więcej tym gorzej) - Ping aka opóźnienie wyrażane w milisekundach. - Choke czyli ilość danych które przesyła nasz komputer do serwera. Rozwiązać to możemy poprzez: 1. Skan naszego komputera w poszukiwaniu wirusów: Sprawa wygląda tak, że możemy mieć jakiś syf który żre nam łącze i trzeba się go pozbyć. Dam tu dobre do tego darmowe programy ale generalnie to i tak polecam jakimś płatnym antywirusem uruchomić ten skan (a mieć wyłaczoną ochrone stałą wiadomo dlaczego :kappapride:) 2. w cs:go jest opcja aby przydzielić przepustowość łącza. Jeżeli macie coś tam wpierdzielone to ustawicie to nieograniczone. Pomoże to głównie z choke. 3. Upewnijcie się że nie macie odpalonego jakiegoś pobierania w tle (nie tylko dosłownego ale też jakiś streamów itp, wiem że to banał ale warto napisać xd) 4. koooooomendy.. rate ten sam co tickrate (64 tick = 64000 rate, 124 tick = 124000 rate) 5. W przypadku internetu rodzinnego (jakiś router z wifi raczej jest) - wbijcie się na router i nadajcie swojemu komputerowi priorytet wyższy od pozostałych. W wypadku internetu od jakiś tam komórkowców czy jak im tam (Play, T-mobile) łacze bezprzewodowe jest zależne na ogół od sygnału i nie ma możliwości poprawienia go u Ciebie bezpośrednio. Tam im lepszy sygnał na telefonie tym lepszy internet ( ͡° ͜ʖ ͡°) enjoy i uzupełniajcie o coś jak macie po za narzekaniem na brak starannego wykonania bo tu tylko sciana tekstu pasuje ;/// Autor Acti. Źródło http://forum.arenaskilla.pl/showthread.php?tid=19582
  17. KOMPEDIUM WIEDZY O BITACH, OPERACJACH BITOWYCH I ZASTOSOWANIU W PRAKTYCE Jeśli masz już jakieś doświadczenie z programowaniem, niezależnie czy jest to pawn, C czy inny język, na pewno na trafiłeś na takie pojęcie, jak "flaga" w pluginie/skrypcie etc. Masz też zapewne świadomość do czego taka flaga służy Przykładowo, w poradniku Vasto o timerach mamy do czynienia z flagami timera, np. UchwytNaszegoTimeru = CreateTimer(99999.9, FunkcjaKtoraSieWywola, INVALID_HANDLE, TIMER_REPEAT); czwarty argument funkcji CreateTimer mówi nam, że timer będzie cyklicznie wywoływał podaną funkcję dopóki go nie 'zabijemy' (KillTimer). Idea wydaje się być prosta - funkcja działa tak, jak jej rozkażemy. Nieco wyżej pojawia się jednak takie wywołanie: CreateTimer(1.0, FunkcjaKtoraSieWywola, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE|TIMER_DATA_HNDL_CLOSE); Czy potrafisz wyjaśnić, co tutaj się dzieje? Na pewno domyślasz się, że timer wykona się wraz ze wszystkimi trzema flagami, jednak rozłożenie tego na czynniki pierwsze wydaje się być dosyć enigmatyczne. Okazuje się jednak, że nie jest to wcale takie straszne. Wymaga to dobrego zrozumienia, jednak na pewno zapunktuje w przyszłości. Postaram się wyjaśnić to tak dobrze, na ile tylko będę w stanie. Przygotuj sobie jakieś jedzenie i do roboty! Część I - jak zapisujemy liczby? Podstawą tutaj jest dwubitowa architektura komputerów. Oznacza to, że dane w pamięci reprezentowane są w postaci logicznej prawdy (1 - jedynka logiczna), lub fałszu (0 - zero logiczne). Kiedyś nieodzownym było operowanie właśnie na pamięci w takiej formie, jednak z czasem zaczęły powstawać języki programowania, które pozwalały na bardziej abstrakcyjne podejście do tematu kodowania. Jednak pomimo tego, programowanie cały czas sprowadza się do tych samych operacji, co wiele lat temu, jednak w ładniejszym wydaniu (co niekonieczne oznacza, że lepszym). Pod tym, co widzi współczesny programista kodując w C++, w dalszym ciągu kryją się skomplikowane operacje bitowe. I tutaj mamy całe clue sprawy - flagi są reprezentowane bitowo! Od razu nasuwa się pytanie - dlaczego? Po co ktoś miałby używać takiego podejścia? Odpowiedź nasunie się sama w dalszej części poradnika W komputerach najmniejszą adresowalną jednostką jest bajt, który składa się z ośmiu bitów, gdzie każdy bit może zawierać 1 lub 0 (jak wspominałem wyżej). My nie posługujemy się "bajtami" - my działamy ma zmiennych, jak int, float, czy char. Otóż każda zmienna składa się ze stałej ilości bajtów (char 1 bajt, int i float 4 bajty w pawnie). Współczesne języki są jednak na tyle elastyczne, że możemy bez problemu posługiwać się znanymi nam dobrze operacjami dodawania, odejmowania, mnożenia, potęgowania itd bez potrzeby działania na bitach Nie oznacza to jednak, że jesteśmy od tych operacji odcięci ? programiści bardzo dobrze wykorzystują je do optymalizacji działania programów, w tym znanych nam już flag. Dzisiaj również nauczymy się, jak pisać takie flagi samemu. Skoro wiemy już, że pod takim "prostym" integerem(int) kryje się zlepek bitów, zajrzyjmy sobie do niego: Jak widzimy w powyższej jakże profesjonalnie wykonanej, tabeli pokazana jest tylko połowa integera - 2 bajty. Tyle jednak w zupełności wystarczy, żeby pokazać o co tutaj chodzi... Dowolną liczbę zapisaną w jednym systemie można zapisać w innym (np. z dziesiętnego na szesnastkowy). W tym przypadku interesuje nas zamiana liczby dziesiętnej na binarną(ciąg zer i jedynek). Ktoś kiedyś słusznie zauważył, że: i tak 1 to 2^0, 2 to 2^1 i tak dalej.... można zauważyć tutaj jeszcze jedną ciekawą zależność - wartość dziesiętna każdego bitu jest równa wartości wszystkich poprzednich bitów plus jeden, np. 2^6 = 64 = 32+16+8+4+2+1+1 Płynący z tego wniosek jest taki, że każda liczba dziesiętna ma swoją unikalną reprezentację w systemie dwójkowym. Jak zapisujemy liczby dwójkowo? Oto przykłady: 15 = 1111 (2^0 + 2^1 + 2^2 + 2^3) | 20 = 10100 (2^4 + 2^2) | 1024 = 10000000000 (2^10) Działanie odwrotne (liczba binarna na dziesiętną) prezentuje się następująco: 101 = 2^0*1 + 2^1*0 + 2^2*1 = 5 | 1000 = 2^0*0 + 2^1*0 + 2^2*0 + 2^3*1 = 8 Po więcej informacji odnośnie przeliczeń zapraszam tutaj. Skoro więc wiemy, że pod każdą zmienną kryją się bity, oraz że jest ich skończona ilość dla danej zmiennej, możemy również znaleźć największą wartość, jaką dany typ zmiennej może przechowywać. Dla uproszczenia nie będę się zagłębiał w liczby zmiennoprzecinkowe - dziś skupimy się tylko na liczbach całkowitym i na integerze. Przydatnym narzędziem systemowym jest kalkulator, który oferuje tryb programisty. Po wprowadzeniu do niego 32 jedynek binarnie (bo tyle bitów posiada integer), otrzymałem nastepujący wynik: Sporo, ale...nie jest to faktyczna wartość maksymalna integera. Musimy także pamiętać o tym, że liczba może być także ujemna! Jak określić tutaj ujemność liczby? W tej sytuacji poświęca się ostatni bit, którego wartośc określa, czy liczba jest dodatnia, czy ujemna. Skoro straciliśmy jeden bit, nasza liczba "skurczyła się" do 31 miejsc, a więc teraz jest to... 2 147 483 647, a konkretniej przedział < -2 147 483 647 ; 2 147 483 647 > Na tym etapie rozumiemy jak zapisywane są liczby w komputerze, co to bit, bajt i jak przechodzimy z systemu dziesiętnego do binarnego i odwrotnie. Teraz możemy zająć się operacjami bitowymi. Część II - operacje bitowe W systemie dziesiętnym wykonujemy operacje dodawania, odejmowania, mnożenia, dzielenia, potęgowania, pierwiastkowania i tak dalej. System binarny charakteryzuje się swoim własnym zestawem operacji. Każdą operację wykonujemy parami - pierwszy bit liczby numer 1 z pierwszym bitem numer 2 - czynność powtarzamy na wszystkich bitach. Omówię teraz po kolei wszystkie operacje. 1. AND (mnożenie - koniunkcja) -> Znak & Wynik operacji AND daje 1, jeśli obydwie liczby są jedynką Przykład: 1010 & 1001 = 1000 | 1100 & 0101 = 0100 2. OR (dodawanie - alternatywa) -> Znak | Wynik operacji daje 1, jeśli przynajmniej jedna z liczb jest jedynką Przykład: 1010 | 1001 = 1011 | 1100 | 0101 = 1101 3. XOR (alternatywa wykluczająca) | Znak ^ Wynik operacji daje 1, jeśli dwie liczby różnią się, a 0, jeśli są takie same Przykład: 1010 ^ 1001 = 0011 | 1100 ^ 0101 = 1001 4. NOT (negacja) | Znak ~ (tylda) Operacja odwraca wartość bitu Przykład: ~1010 = 0101 | ~1100 = 0011 Są to cztery podstawowe operacje logiczne. Do omówienia pozostały jeszcze przesunięcia bitowe: 5. Przesunięcie bitowe w lewo | Znak << Operacja powoduje, że wszystkie bity w liczbie zostają przesunięte w lewo. Wszystkie bity, które na skutek przesunięcia 'wypadły' poza liczbę zostają utracone. Bity, które 'pojawiają się z prawej strony są zapisywane zawsze jako zera. Po lewej stronie operandu zapisujemy liczbę, natomiast po prawej o ile miejsc przesuwamy. Przykład: 1010 << 1 = 0100 | 1100 << 1 = 1000 5. Przesunięcie bitowe w prawo | Znak >> Operacja powoduje, że wszystkie bity w liczbie zostają przesunięte w prawo. Wszystkie bity, które na skutek przesunięcia 'wypadły' poza liczbę zostają utracone. Bity, które 'pojawiają się z lewej strony są zapisywane zawsze jako zera. Po lewej stronie operandu zapisujemy liczbę, natomiast po prawej o ile miejsc przesuwamy. Przykład: 1010 >> 1 = 0101 | 1100 >> 1 = 0110 Wszystko wydaje się być dziwne i bezsensowne, jednak o tym jak potężne jest to narzędzie przekonasz się w części trzeciej ? Omówiliśmy już wszystkie operacje. Teraz, kiedy jesteśmy już gruntowo przygotowani pod względem teoretycznym, możemy wreszcie przejść do praktyki. Część III - flagi Rozpatrzmy teoretyczną sytuację, w której chcemy monitorować, czy gracz (dla uproszczenia jeden) posiada w nicku frazę "[GO-Code]", aby nagradzać go dodatkową ilością złota co rundę. Aby tego dokonać, musimy wykonać szereg operacji: znaleźć id gracza, na jego podstawie pobrać nick i umieścić w tablicy, sprawdzić, czy nazwa posiada szukaną frazę za pomocą StrContains, a następnie zwrócić rezultat. Mimo, że wszystkie te operacje trwają dość szybko (dla człowieka), to komputer nie ma świadomości tego, że już raz sprawdziliśmy nick gracza - dlatego w każdej kolejnej rundzie musi mieć jasną informację, czy ma przydzielać złoto z tytułu posiadanej frazy... Co byś w tej sytuacji zrobił? Najsensowniejszym rozwiązaniem wydaje się być zastosowanie zwykłej zmiennej, która domyślnie przyjmuje wartość 0 i zmienia się na 1, jeśli powyższe warunki zostaną spełnione. Teraz wystarczy, że serwer będzie na początku rundy sprawdzał zawartość zmiennej, zamiast każdorazowo wykonywać szereg kosztownych operacji - a nie zapominajmy, że przeważnie jest dużo więcej graczy do sprawdzenia ? Załóżmy teraz, że poza sprawdzeniem frazy [GO-Code] chcielibyśmy, by serwer dodatkowo sprawdzał, czy SteamID gracza to STEAM_0:1:1234567, gdyż jest to streamer, które gra na naszym serwerze i chcemy zapewnić mu dodatkowe przywileje. Aby to sprawdzać zapewne ponownie zastosujemy dodatkową zmienną, jednak musimy być świadomi, że teraz mamy już dwie zmienne, zamiast jednej. A co, jeżeli nasze dwie zmienne są lokalne i chcielibyśmy je przekazać do innej funkcji? Sprawa wygląda w miarę prosto, gdy mamy dwie zmienne, ale co w sytuacji, gdzie potrzebujemy takich zmiennych np. dziesięć? PrzekazStatystyki(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10) Pomyślmy, jak wyglądałaby deklaracja flag regexa? Co w sytuacji, gdybyśmy chcieli skorzystać tylko z jednej opcji? Mimo wszystko musielibyśmy wysyłać wszystkie zmienne (bez wyraźnej potrzeby) Dopiero teraz na wierzch wychodzi to, jak użyteczne są operacje bitowe Pamiętamy, że każdy int to zestaw 32 bitów. Każdy bit przyjmuje wartość 1 lub 0.... czyli właśnie to, czego potrzebujemy! 1 i 0 to właśnie nasza prawda i fałsz! daje nam to niesamowicie szeroki wachlarz odogodnień. Zamiast tworzyć dziesięć zmiennych: int x1, x2, x3, x4, x5, x6, x7, x8, x9; tworzymy jedną: int flags; Zamiast przekazywać do funkcji dziesięć zmiennych: PrzekazStatystyki(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10) przekazujemy jedną: PrzekazStatystyki(flags) Teraz pozostaje nam nauczenie się, jak z tego korzystać, gdyż wszystkie operacje będą przeprowadzane na bitach. Powróćmy do naszego przykładu dwóch flag gracza. Gdybyśmy realizowali to standardową metodą, robilibyśmy to w taki sposób: Oraz funkcja przydzielająca atrybuty graczowi: Teraz napiszmy to samo z wykorzystaniem bitów  W naszym przykładzie posiadanie frazy w nicku będzie równoznaczne z ustawieniem pierwszego bitu (od prawej) w zmiennej na 1, natomiast streamer - drugiego bitu. Zauważmy tutaj ciekawą rzecz - pierwszy bit oznacza jedynkę (2^0), natomiast drugi bit dwójkę (2^1). Jeśli więc streamer będzie miał w nicku frazę "GO-Code" (obydwa przypadki spełnione), nasza flaga przyjmie wartość...3! Dlaczego? Oto wyjaśnienie (przypominam, że int ma 32 bity): int flags = 0000 0000 0000 0000 0000 0000 0000 0011 + 2^0 + 2^1 = 3 Prawda, że intuicyjne? Podobnie, gdybyśmy mieli dziesięć flag i chcielibyśmy sprawdzić, czy WSZYSTKIE są ustawione na true(1) wystarczyłoby porównanie wartości zmiennej flags do 1023 (ponieważ każdy bit to suma poprzedników plus jeden, a pod jedenastym bitem siedzi 2^10, czyli 1024. Oznacza to, że wartość 1024 to 00...100 0000 0000, a 1023 to 00...011 1111 1111) Dodatkowo, jeśli wykonujemy operacje arytmetyczne na liczbie dziesiętnej, i tak można ją przedstawić w postaci binarnej. Jeśli więc do zmiennej, która ma wartość 0, dodamy 2 (zmienna += 2) drugi bit tej zmiennej stanie się jedynką, a pozostałe będą zerami. My jednak nie będziemy wykonywać operacji arytmetycznych, gdyż operacja na np. dziesięciu zmiennych mogłaby przyprawić o zawrót głowy nawet największego wymiatacza  Aby dodać flagę do zmiennej (zmienić wartość bitu), będziemy posługiwać się logiczną operacją OR. Wiemy z części drugiej, że OR zwraca 1, jeśli przynajmniej jedna ze zmiennych jest prawdziwa. Jeśli więc wykonamy taką operację: flags |= 1 ( to samo, co flags = flags|1 ) spowodujemy, że pierwszy bit naszej zmiennej stanie się jedynką, natomiast pozostałe pozostaną bez zmian: ...101010 | ...000001(jedynka binarnie) = ...101011 prawda, że fajne? jedynym problemem jest dobre zrozumienie co tutaj się dzieje  podobnie, ustawienie drugiej flagi wiąże się z operacją OR, jednak tutaj posłużymy się już dwójką: ...010101 | ...000010(dwójka binarnie) = ...010111 Ale to jeszcze nie wszystko! Aby nie musieć poruszać się cały czas po potęgach dwójki, możemy również użyć przesunięć bitowych. Jak to działa? Zauważmy, że przesunięcie bitowe w lewo jest równoznaczne z pomnożeniem naszej liczby przez 2. Tak więc 2<<1 = 4. Widzimy też, że działa to dokładnie tak, jak nasza identyfikacja numeru bitu. Jeśli więc zrobimy coś takiego: flags |= (1<<1) efekt będzie taki sam, jak: flags |= 2 Sytuacja robi się jeszcze przyjemniejsza, gdy zdefiniujemy sobie takie makra: #define FRAZA (1<<0) #define STREAMER (1<<1) a później już tylko sama bajka: flags |= STREAMER; flags |= FRAZA; lub flags |= (STREAMER|FRAZA); I WŁAŚNIE TAKICH MAKR UŻYWA SIĘ WSZĘDZIE! Nie ważne, czy będzie to ADMIN_IMMUNITY z flag admina, czy PCRE_UTF8 z regexa - wszędzie odbywa się to w dokładnie ten sam sposób! Ostatnią rzeczą jest sprawdzenie, czy dana flaga jest aktywna, Tutaj posługujemy się logiczną operacją AND. Daje ona jedynkę tylko w sytuacji, gdy obydwie zmienne są prawdziwe. Możemy to obrócić na naszą korzyść. Aby sprawdzić, czy dany bit (tj nasza flaga) jest "włączona", robimy coś takiego: if(flags & FRAZA) jak wiemy, if "przepuszcza dalej", jeśli wartość wyrażenia jest różna od zera. A więc jeśli if otrzymał przykładowo: ...010101 & ...000001 = ...000001 = 1 (binarnie) ...musiał on przepuścić warunek sprawdzenia możemy łączyć: if(flags & FRAZA && flags & STREAMER) { // uprawnienia i kasiora prosze } pamiętajmy też o kolejności wykonywania działań! Z tego właśnie powodu stosuję nawiasy zarówno w warunku jak i przy deklaracji makr #define Tak oto przerobiliśmy cały materiał. Po zastosowaniu zdobytej wiedzy w praktyce, nasz poprzedni kod możemy napisać w następujący sposób: Oczywiście nie należy popadać w paranoję i stosować tych flag wszędzie, gdzie się tylko da xD. W małych pluginach możemy spokojnie korzystać ze zwykłych zmiennych, gdyż narzut pamięciowy jest i tak znikomy. Wierzę, że będziesz w stanie znaleźć do tego wiele zastosowań. Wooah.. ale poleciałem. Poradnik wyszedł strasznie długi, ale chciałem to zrobić w sposób rzetelny i w takiej formie, jaką sam bym chciał otrzymać lata temu, ponieważ nikt nie wytłumaczył tego w należyty sposób. Niech Wam dobrze służy Jeśli macie jeszcze jakieś pytania, czy wątpliwości, śmiało zadawajcie pytania, a jeśli okaże się to potrzebne, zaktualizuję poradnik. Oczywiście zachęcam do przeglądania innych poradników i ciągłego samodoskonalenia. Dzięki za uwagę i powodzenia w lekturze
  18. Ten temat zawiera spis wszystkich artykułów zamieszczonych w tym dziale 1. Poradniki na temat wiedzy ogólnej Składnia nowa czy stara? (#pragma newdecls required) Jak skompilować plugin sourcemod? (linux) Spis errorlogów i co one oznaczają? Najczęściej popełniane błędy Co to backdoor? 2. Poradniki o strukturach danych Co to bity i jak ich używać? Jak działają tablice dynamiczne? Struktura enum i jak tego używać Co to datapack? 3. Poradniki dotyczące funkcji w dokumentacji Sourcemod Timer's - CreateTimer / TriggerTimer / KillTimer Pobieranie wielkości magazynka Wszystko co dotyczy Menu Kompendium wiedzy na temat Natywów i Forwardów 4. Inne Jak sprawdzić czy gracz używa panoramy? Dodatki do Danger Zone (weapon_axe weapon_hammer weapon_spanner) Jak działają wektory w przestrzeni 3D? Podświetlenie składni Sourcemod w Notepad++ Jak utworzyć licencję na plugin?

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

×
×
  • Dodaj nową pozycję...