Najlepsze Serwery Minecraft w Polsce!
   Witaj serdecznie na forum gdzie znajdziesz swój ulubiony Serwer Minecraft                                         
         

Serwery Minecraft

Witaj, Serwery Minecraft - nie przypadkiem znalazłeś największe forum internetowe Minecraft w Polsce Serwery Minecraft które tu znajdziesz pozwolą Ci miło spędzić czas, poznasz nowych wspaniałych ludzi i przeżyjesz fantastyczne przygody! Jednoczymy ludzi uwielbiających Gry i Minecraft! Zagraj z Nami i odkryj fantastyczne Serwery No Premium! Zobacz co oferuje polecana przez Nas

Lista Serwerów Minecraft



Zarejestruj się bezpłatnie na forum! Oto niektóre z przywilejów:
  • Zakładaj nowe wątki oraz aktywnie w nich uczestnicz,
  • Odblokuj możliwość pisania na Shoutboxie (czat),
  • Ogranicz ilość wyświetlanych reklam,
  • Zdobywaj odznaczenia oraz reputacje,
  • Znajdziesz tutaj darmowe poradniki Minecraft,
  • Odblokuj dostęp do ukrytych działów, tematów i linków,
  • Spersonalizuj swój prywatny profil,
  • Uczestnicz w forumowych konkursach,
  • Pamiętaj to nic nie kosztuje, MineServer.pl to darmowe forum internetowe na którym dowiesz się jak zainstalować minecraft oraz jak grać w minecraft!
Szukałeś Serwerów Minecraft? Znalazłeś! Zarejestruj się, a zagraj z nami!

               
serwery minecraft



 
Ocena wątku:
  • 0 Głosów - 0 Średnio
  • 1
  • 2
  • 3
  • 4
  • 5
Jak napisać skrypt, czyli poradnik dla opornych pseudo-programistów
Autor Wiadomość
szumielxd Offline
Murzyn MC|Nikt TS
*

Liczba postów: 850
Dołączył: Jun 2017
-1616
+
Pomogłem? Daj Diaxa!
Nick na Serwerze:
szumielxd_

Odznaczenia:

(Zobacz Odznaczenia)
Poziom:
MineGold: 2.00
Post: #1
Jak napisać skrypt, czyli poradnik dla opornych pseudo-programistów



[Obrazek: Km1SFDV.png]

Jeżeli jakimś cudem sam, z własnej nieprzymuszonej niczym woli wszedłeś w ten wątek, najprawdopodobniej chcesz się czegoś nauczyć lub po prostu pośmiać z nieudolności autora w zakresie tłumaczenia i nie tylko.


UWAGA! | ATTENTION! | ACHTUNG! | ODDAM ŚWIEŻAKA! | ATTENTION! | ¡ATENCIÓN! | ВНИМАНИЕ!
Do wyciągnięcia wniosków z tego tekstu potrzebna jest umiejętność czytania ze zrozumieniem. Jeżeli czytasz ten tekst ze względu na ogłoszenie o świeżaku wejź w ten link i już nigdy nie wracaj do tego wątku.

Poniżej znajduje się kilka nagłówków streszczających znajdujący się poniżej poniższych nagłówków poniższy niestreszczony tekst:



1. Dobra dokumentacja
W podstawówce/liceum uczysz się z podręczników, w przypadku skryptów zostaje dostępna w internecie dokumentacja.
Pora na interesujące linki:
  • Oficjalna dokumentacja Skripta:
  • Baza różnych dodatków do Skripta:
    • https://skripthub.net/docs/ - Osobiście ulubiona wyszukiwarka metod ze skripta oraz dodatków do niego.
    • https://skunity.com - "Istny kombajn", co w tłumaczeniu z języka bananowego oznacza "ma praktycznie wszystko". Posiada własne forum, wyszukiwarkę metod, parser, tutoriale, liczne dodatki do pobrania, oraz "wiele więcej!".
  • Fora dla skrypto-maniaków:
  • Translator:
    • http://translate.google.pl - Chyba najpopularniejszy dostępny na rynku translator.
    • https://pl.bab.la - Alternatywny tlumacz, w przypadku tłumaczenia poradników ze skripta różnicy nie zrobi, ale lepiej poradzi sobie z "prawdziwymi" angielskimi zwrotami w tłmaczeniu na polski.
  • Google:
    • https://google.com - Google. Tak, tu możesz szukać swojego problemu który najpewniej został już rozwiązany przez dwóch hindusów i jednego azjatę.



2. Odpowiednie wyposażenie
  • Skript - O ile każdy z poziomem inteligencji wyższym od przeciętnej małpy domyśla się że do pisania i testowania skryptów potrzebny jest Skript, tyle dobór wersji jest już bardziej problematyczny. Ostatnią wersją która wspiera silnik 1.8 jest Skript 2.2-dev37c, jednak wersja ta zawiera krytyczny błąd związany z czyszczeniem starych zmiennych tymczasowych, przez co serwerowi potrafiło zabraknąć w połowie dnia RAMu, tak więc tej wersji kategorycznie się pozbywamy. Znacznie bezpieczniejsza jest wersja Skript 2.2-dev36, która aktualnie jest wykorzystywana na większości trybów z silnikiem 1.8 na naszej serwerowni. W przypadku pozostałych wersji silnika najlepiej korzystać z najnowszej wersji Skripta (aktualnie Skript 2.5.3). W przypadku pisania skryptu na pod konkretny tryb najlepiej pierw zapytać kogoś z dostępem do plików jaka wersja Skripta jest na nim zainstalowana oraz jakie dodatki do niego są wykorzystywane. Pisanie skryptu na tej samej wersji Skripta co na serwerze docelowym powinno zminimalizować do zera prawdopodobieństwo niekompatybilności kodu.
  • Własny serwer do testów - Nie mam zamiaru opisywać tutaj procesu stawiania serwera na własnym komputerze, jest od tego multum poradników w internecie. Najlepiej skorzystać z forka Spigota o nazwie Paper.
  • Notepad++ - Jest to według mnie najwygodniejsze narzędzie do edycji skryptów, umożliwia uzupelnianie wsześniej użytych fraz, automatycznie dodaje wcięcia w nowych linijkach i pokazuje numery kolejnych linii, a dodatkowo posiada wsparcie dla znaków specjalnych (np. '\n', '\r') oraz regexa (np. '[0-9A-Za-z]*')
  • Google - Google



3. Zrozumienie założeń
Zadaniem tego akapitu w żadnym wypadku nie jest wytłumaczenie kolejnych elementów Skripta, ma on jedynie pomóc w ogólnym zrozumieniu jego idei oraz sposobu działania.

Jak wiadomo każdej akcji towarzyszy reakcja. Stwierdenie to ma racje bytu zarówno w fizyce, polityce, jak i Skripcie.
Kod:
on jump:                                                          <- AKCJA (EVENT)
    send "Gdyby kózka nie skakała to by nóżki nie złamała"      \
    apply slowness 2 to player                                  |
    set {_random} to random integer between 1 and 10            | <- REAKCJA
    if {_random} is bigger than 9:                              |
        damge player by 1 hearth                                /

Tak więc nic nie dzieje się bez powodu (nie licząc przebłysków inteligencji trawy, tego nikt się nie spodziewa). Reakcję natomiast można podzielić na kolejne podkategorie: warunki oraz efekty. Myślę że nie trzeba nikomu tłumaczyć co oznacza co, jednak lepiej dmuchać na zimne (w końcu link do tego wątku będzie najbardziej prawdopodobną odpowiedzią z mojej strony). Można reagować bezmyślnie na każdą akcje lub postawić kilka warunków, które mają na celu sprecyzowanie w jakiej konkretnie sytuacji ma się wykonać dany efekt.
Kod:
    send "Gdyby kózka nie skakała to by nóżki nie złamała"        <- EFEKT (EFFECT)
    apply slowness 2 to player                                    <- EFEKT
    set {_random} to random integer between 1 and 10              <- EFEKT
    if {_random} is bigger than 9:                                <- WARUNEK (CONDITION)
        damge player by 1 hearth                                  <- EFEKT

Oczywiście głupotą byłoby gdyby efekty i warunki były statyczne, korzystają one ze zmiennych. Ale czym w takim razie jest zmienna? Można powiedzieć że to pudełko do którego można coś włożyć a następnie wykorzystać w jakimś efekcie lub warunku. Istnieje kilka rodzajów takich pudełek a różnią się one sposobem zapisu w kodzie oraz funkcjonalnością.
W przypadku zmiennej o kreatywnej nazwie ZMIENNA będą to:
  • {ZMIENNA} - jest to zmienna globalna, to znaczy że istnieje i będzie istnieć aż do momentu kiedy nie zostanie usunięta. Nie przeszkodzi jej nawet restart serwera, jest zapisywana w specjalnym pliku
  • {_ZMIENNA} - jest to zmienna tymczasowa, można powiedzieć że jest to pudełko jednorazowe, które po ukończeniu reakcji po prost się rozpadnie. Jest ona dostępna w obrębie pojedyńczego eventu, a każde kolejne jego wywołanie resetuje zmienną
  • {ZMIENNA::*} - jest to właściwie to samo co {ZMIENNA}, tyle że w tym przypadku mamy do czynienia z listą zmiennych, a więc byłoby to pudełko z wieloma przegródkami. Możemy uzyskać dostęp do pojedyńczej zmiennej zastępując * na końcu identyfikatorem danej przegródki np. {ZMIENNA::1} lub {ZMIENNA::szumielxd}
  • {_ZMIENNA::*} - jest to po prostu fuzja zmiennej tymczasowej ({_ZMIENNA}) oraz listy ({ZMIENNA::*})

Oczywiście na początku swojego istnienia pudełko (nie mylić z kapeluszem magika) jest puste, dopiero później chowamy do niego jego przyszłą zawartość, analogicznie aby zmienna nie zwracała nam pięknego <none> musimy ją "nasetować", a robi się to dośc trywialnie
Kod:
set {_zmienna} to "Jestem tekstem zapisanym do zmiennej"

Ciekawym może wydać się fakt że nazwa zmiennej również może być zmienną. W poniższym przykładzie wartość zmiennej {_nick} będzie równa "To mój nick"
Kod:
set {_var} to "NICK"
set {ZMIENNA.NICK} to "To mój nick"
set {_nick} to {ZMIENNA.%{_var}%}

Analogicznie zmiennych można używać w tekście. W tym przypadku w konsoli powinna się wyświetlić fraza "Wartość zmiennej {_nick} to: kebab"
Kod:
set {_nick} to "kebab"
send "Wartość zmiennej {_nick} to: %{_nick}%" to console

Kilka sugestii:
  • Zmienne globalne są GLOBALNE. Uważaj z ich nazewnictwem, inaczej pewnego pięknego dnia zdziwisz się kiedy Twoja zmienna o nazwie {zmienna} zwróci Ci kolor owcy zamiast wartości Twojego portfela. Dobrą prakyką jest nazywanie zmiennej w taki sposób aby mówiła nam co powinna przechowywać oraz w jakim miejscu kodu możemy się jej spodziewać. Przykładowo {BOSKIE.ROG.CZAS::*} brzmi jak idealna nazwa do przechowywania czasu ostatniego użycia rogu dusz, który jest jednym z boskich przedmiotów na WD.
  • Jeżeli planujesz w przyszłości pracować w IT nazywaj zmienne po angielsku. Nie ma sensu uczyć się złego nawyku którego z czasem będziesz musiał się pozbyć. Aktualnie nawet małe studia GameDev nie dość że piszą kod po angielsku (tyczy się to również komentarzy), to jeszcze tworzą dokuemtację w tym właśnie języku.
  • Zmiennych globalnych używaj tylko w ostateczności. Każda zmienna to dodatkowa linijka pliku do wczytania i zapisu, kilkaset tysięcy zmiennych może negatywnie wpłynąć na czas ładowania serwera oraz może powodować spadki TPS w czasie zapisywania zmiennych do pliku (backup domyślnie wykonuje się co 3 godziny)
  • Zmienne globalne zapisuj WIELKIMI LITERAMI. W ten sposób na kilometr rozpoznasz zmienną globalną, a dodatkowo jest to często przyjmowana przez programstów praktyka.

Istnieje też coś co z jednej strony jest zmienną z drugiej zaś jakimś dziwnym tworem, a jest to metadata. Jest ona przypisana do entity, metadata gracza jest zaś usuwana tylko przy restarcie serwera, tak więc idealnie nadaje się na zapisywanie w niej takich perełek jak czas do ponownego użycia boskiego przedmiotu, czy też drużyna w której aktualnie się znajduje gracz. W ten sposób nie obciążamy ddatkowo zmiennych globalnych, a jednocześnie możemy odczytać jej wartość z dowolnego miejsca w skrypcie.
Operacje na metadacie są dosyć proste, tradycyjnie zaprezentuje to na przykładzie metadaty o nazwie ZMIENNA:
  • set metadata value "ZMIENNA" of player to "wartość"
  • set {_m} to metadata value "ZMIENNA" of player
  • if player has metadata value "ZMIENNA":
  • clear metadata value "ZMIENNA" of player

Chyba każdy przyzna że ustawianie wartości zmiennej do statycznej liczby czy też tekstu jest nudne i bezcelowe (choć są od tego wyjątki), na szczęście skrypt daje nam wyrażenia. Czym jest wyrażenie najlepiej wytłumaczyć na przykładzie. Jest sobie Marcin, Marcin posiada imię, ma na imię Marcin, a więc fraza "Marcin" to imię Marcina. W przypadku skripta byłoby to wyrażenie player's name. Mogę to wykorzystać na 2 sposoby: albo zapisać do zmiennej, albo wysłać na chacie.
Kod:
set {_p} to player
set {_nick} to {_p}'s name    # 1
broadcast "%{_p}'s name%"     # 2

Jednym z ostatnich podstawowych tematów jest iteracja. Chciałbyś wysłać 10 wiadomości na chacie a treść każdej kolejnej do numer od 1 do 10?
Możesz to zrobić tak:
Kod:
broadcast "1"
broadcast "2"
broadcast "3"
broadcast "4"
broadcast "5"
broadcast "6"
broadcast "7"
broadcast "8"
broadcast "9"
broadcast "10"
Abo tak:
Kod:
loop 10 times:
    broadcast "%loop-number%"

Dodatkowo iterować można całe listy zmiennych, a jest to banalnie proste. Taka iteracja daje nam dwie dodatkowe zmienne: loop-index oraz loop-value. Łatwo można się domyśleć że pierwsza jest naszym indexem zmiennej w liście, a druga zmienną we własnej osobie.
Kod:
loop {LISTA_ZMIENNYCH::*}:
    broadcast "%loop-index%. %loop-value%"

Nietypowym wyzwalaczem kodu jest komenda, o ile zwykły event deklarujemy za pomocą jednej linijki kodu, o tyle komendy rejestrowane są w inny sposób:
Kod:
command /test <player>:
    aliases: t, tst
    description: To jest testowa komenda
    usage: /test <nick>
    executable by: players, console
    permission: sk.command.test
    permission-message: Nie możesz użyć tej komendy
    trigger:
        send "%sender% wysłał do Ciebie testową wiadomość"
Myślę że każdy kto zna angielski jest w stanie zrozumieć która linijka do czego służy. W razie jakichkolwiek problemów tu jest to dość szczegółowo wytłumaczone: https://dev.bukkit.org/projects/skript/p...m-commands. Ten punkt po prostu należało poruszyć i odhaczyć, warto natomiast skorzystać z okazji i zasugerować w jaki sposób w miarę "profesjonalnie" deklarować komendy. Dobrze jest każdą komendę zaopatrzyć w odpowiednią permissję, każdorazowo inną, w ten sposób można łatwo manimupować dostępem do komendy, i zablokować ją w przypadku wykrycia jakiś nieprawidłowości. W ten sposób również sprawiamy że jeżeli gracz nie będzie miał dostępu do tej komendy nie będzie w stanie jej zobaczyć wpisując '/' i klikając TAB. Pozornie TAG `executable by` zdaje się bezużyteczny, jednak jail pzekonał mnie że czasem coś musi być idiotoodporne z każdej strony również dla naszego dobra. To że stworzyłeś daną komendę nie oznacza że ktoś inny jej nie wykorzysta, a co gorsza w sposób którego się nie spodziewałeś, tak jak na przykład konsola probująca się teleportować do swojej celi notorycznie wypluwając NullPointerExcetion w logach. Tak więc jeżeli tworzysz komendę tylko dla graczy dobrze jest to zadeklarowaś dodając TAG `executable by: players` i analogicznie w przypadku komend tylko dla konsoli.
Najczęściej moje komendy mają taki format:
Kod:
command /teleport [<offline player>] [<text>]:
    aliases: tp
    permision: sk.command.teleport
    executable by: players
    trigger:
        if arg 1 is online:
            teleportPlayer(arg 1)
        else:
            send "%{prefix}% Podaj nick gracza"
Jak można zauważyć nie ustawiam informacji o sposobie użycia komendy przy jej deklaracji, zamiast tego ręcznie wysyłam wiadomość o niewłaściwym jej użyciu, jest to najpopularniejsza technika wsród developerów pluginów, sekcja usage jest raczej traktowana jako pozostałość po komendach z vanilli.

I nadeszła pora na ostatni nietypowy event, a jest to funkcja. Pisanie jednego długiego bloku kodu jest dość niewygodne, nieczytelne i często syzyfowe, słabego osobnika może nieraz doprowadzić do ciężkiej zapaści (tak, mam tu na myśli pokolenie "Cześć, mam depresje i lubie żyletki"). Dlatego takowy kod najlepiej jest podzielić na funkcje, ma ona również inną umiejętność, otóż potrafi zwracać wartości. Poniżej znajdują się 2 funkcje wraz z deklaracją, pierwsza zwraca wartość, druga natomiast ma po prostu wykonać jakiś kod. Na pierwszej funkcji można również wytłumaczyć szczegóły deklaracji funkcji. Otóż deklaracja zaczyna się frazą 'function', a następnie użytkownik deklaruje nazwę tej funkcji. Każda funkcja posiada nawias okrągły w którym deklaruje się jej argumenty oddzelone przecinkami w postaci 'nazwaZmiennej: typ'. W przypadku kiedy funkcja ma coś zwracać należy również zadeklarować typ zwracanej wartości, w tym celu po nawiasie należy dodać frazę ' :: typ'. Należy nadmienić że funkcje działają globalnie, tak więc funkcja zadeklarowana w skrypcie a.sk może zostać użyta w skrypcie b.sk
Kod:
command /test:
    permission: sk.command.test
    trigger:
        set {_rand} to random integer between 1 and 10
        set {_min} to minimum({_rand}, 5)
        sendAds({_min})

function minimum(i1: integer, i2: integer) :: integer:
    if {_i1} is bigger than {_i2}:
        return {_i2}
    else:
        return {_i1}

function sendAds(amount: integer):
    loop {_amount} times:
        broadcast "&b"
        broadcast "&bNaukowcy go nienawidzą! Wymyślił lekarstwo na głupotę!"
        broadcast "&dKliknij aby zamówić darmową próbkę!"



5. Dodatki których używam
  • PlaceholderSK - Znacznie zoptymalizowany pod kątem asynchronicznych zapytań dodatek umożliwiający stworzenie własnych placeholderów wykorzystywanych przez PlaceholderAPI.
  • SkBee - Jest to hybryda kilku innych dodatków tego samego autora, znajdziemy tu łatwą edycję NBT, tworzenie nowych receptur, scoreboard, możliwość wklejania własnych struktur.
  • Skellett - Aktualnie sporo z funkcji Skelletta zostało zaimplementowanych do Skripta, jednak nadal ma się on czym pochwalić. W przypadku wersji silnika pre-flattering (1.8-1.12.2) najlepiej używać wersji Skellett 1.9.6b.
  • SkQuery - Dość mocno rozbudowuje składnię Skripta o bardziej przyjazne dla programistów stwierdzenia, wraz ze Skelletem stanowi sporą bibliotekę przydatnych metod.
  • skript-db - Umożliwia wykonanie zapytania do zewnętrznej bazy MySQL korystając z szybkiego klienta HikariCP
  • skript-reflect - Wprowadza do Skripta dość rozbudowaną implementację Javy, tym samym pozwalając korzystać z większości dobrodziejstw API Bukkita oraz zainstalowanych pluginów.
  • skUtilities - Jak sama nazwa mówi: zbiór różnorakich narzędzi które okażą się przydatne w najmniej spodziewanym momencie.
  • ThatPacketAddon - Można powiedzieć że jest to dość kulawy adapter ProtocolLib dla Skripta, jednak ciężko postarać się o coś lepszego w tym zakresie.
  • TuSke - Głównie przydatny przy tworzeniu customowych gui, których nie będzie dało się skopiować klikając więcej niż 7cps, prócz tego można nim tworzyć własne receptury oraz enchanty, posiada również multum bliżej niezgrupowanych metod.



6. Dobre praktyki
  • Jeżeli tworzysz w skripcie coś większego niż pojedyńczą blokade dropu przedmiotów z mobów dobrze jest podzielić jeden wielki skrypt na kilka mniejszych, a najlepiej wrzucić jeszcze do oddzielnego folderu. W ten sposób znacznie zmniejszysz prawdopodobieństwo zgubienia się we własnych skryptach, a sam kod będzie łatwiej przeładować, mniejszy plik to mniej kodu do wczytania i parsowania.
    Tu przykład z WvsP:
    [Obrazek: PjwyOtE.png]
    A tu w mniejszej skali z WD:
    [Obrazek: ad3iaXz.png]
  • Niektóre tryby/skrypty wrzucane są na więcej niż jeden serwer. Na większości trybów do których mam dostęp stworzyłem plik o nazwie _main.sk w którym umieszczam podstawowe zmienne, takie jak prefix na chacie czy też adres url serwera. W ten sposób migracja skryptu jest o wiele łatwiejsza ponieważ bez względu na serwer poprawne dane będzie pobierać ze zmiennej
    [Obrazek: XZ45Pa4.png]
  • Piszesz długi i skomplikowany kod? Dodaj do niego komentarze! Tak nieidealne istoty jak członkowie naszej ukochanej społeczności mają skłonność do zapominania rzeczy które przez pewien czas nie były im potrzebne, to oznacza że kiedyś odczujesz potrzebę powrotu do swojego starego kodu, który bez żadnych opisów nie będzie niczym innym jak spaghetti.
  • Recykling do przyszłość nie tylko w ekologii. Raz napisany kod może się przydać w więcej niż jednym miejscu. Nieraz trafić można na funkcję która robi coś ciekawego i od razu wiadomo że będzie można ją wykorzystać w więcej niż jednym miejscu. Wtedy warto takową funkcję wrzucić do pliku o nazwie utils.sk lub podobnej, plik ten będzie z nami podróżować na kolejne nasze place zabaw, gdzie nieraz oszczędzi nam czasu na klepaniu kolejny raz tego samego kodu. W moim przypadku jest już cały folder.
    [Obrazek: ssmkGKv.png]
  • funkcje nazywaj intuicyjnie, działają one globalnie, tak więc w przypadku znalezienia w kodzie funkcji o nazwie kosmicznyKebab() dość ciężko może być zlokalizować miejsce w którym ta funkcja została zadeklarowana. Dobrym wyjściem wydaje się poprzedzać nazwę funkcji nazwą pliku w którym się znajduje (przykład: stats_loadPlayer()), w ten sposób wystarczy otworzyć odpowiedni plik i kliknąć ctrl+f. Wśród programistów powstał również zwyczaj zapisywania nazw zmiennych w specjalnym formacie tzw. camelCase (pierwsze litery kolejnych słów są Wielkie z wyłączeniem pierwszego słowa).



7. Sztuczki
  • Skrypty ładują się alfabetycznie według nazwy, jeżeli chcesz żeby jakiś skrypt załadował się przed innymi po prostu dodaj '_' na początku jego nazwy. Okazuje się to przydatne kiedy Skript zaczyna się pruć o nieistniejącą funkcję która tak naprawde istnieje, ale w pliku o którym Skript jeszcze nie wie.
  • Nowsze wersje Skripta (2.3+) mają całkowicie przebudowany system zmiennych oraz ich recyklngu, przez co są usuwane po opuszczeniu oryginalnego wątku, tak jak ma to miejsce w przypadku dodatku skript-db gdzie po wykonaniu jakiegokolwiek połączenia z bazą wszystkie nasze zmienne tymczasowe stają się puste. Problem ten można rozwiązać na 2 sposoby.
    • Pierwszy polega na stworzeniu jednoelementowej listy, którą następnie będziemy iterować, zmienna zostanie usunięta, jednak loop-value jakimś cude zostaje nienaruszone, najwiekszym problemem jest tu brak możliwości ponownego ustawienia wartości loop-value oraz fakt że każda kolejna zmienna z której chcemy korzystać to kolejna iteracja, tak więc mamy kilka różnych loop-value (loop-value-1, loop-value-2, ...).
      Kod:
          function updatePlayer(p: player, statType: text):
              add {_p} to {_list1::*}
              add {_statType} to {_list2::*}
              loop {_list1::*}:
                  loop {_list2::*}:
                      execute "SELECT `value` FROM `statystyki` WHERE `username` = %unsafe loop-value-1% AND `stat_type` = %unsafe loop-value-2%" in {STATYSTYKI.SQL} and store result in {_result::*}
                      set {STATYSTYKI.PLAYER::%loop-value-1%::%loop-value-2%} to {_result::value::1}
    • Drugi natomiast wymaga wykorzystania dodatku o nazwie skript-reflect (nie zadziała z skript-mirror), który umożliwia asynchroniczne wykonanie kodu co pozwoli nam na wykonanie zapytania do bazy w tym samym watku co reszta skryptu.
      Kod:
          function updatePlayer(p: player, statType: text):
              create new section with {_p}, {_statType} and store in {_section}:
                  execute synchronously "SELECT `value` FROM `statystyki` WHERE `username` = %unsafe {_p}% AND `stat_type` = %unsafe {_statType}%" in {STATYSTYKI.SQL} and store result in {_result::*}
                  set {STATYSTYKI.PLAYER::%{_p}%::%{_statType}%} to {_result::value::1}
              run section {_section} async with {_p}, {_statType}
  • Niektóre wersje Skripta mają problem z poprawną interpretacją typu zmiennej, co może czasem sprawić że zmienna będzie po prostu odczytywana przez funkcję/wyrażenie/efekt/chomika w betoniarce/warunek jako <none>, a tu krótki przykład:
    Kod:
    command /vegeterrorysta:
        permission: op
        trigger:
            set {_var} to 1
            add 1 to {_var}
            jedzWarzywa({_var})

    function jedzWarzywa(i: integer):
        broadcast "Zjedzono warzyw: %{_i}%"
    W niektórych wersjach funkcja otrzyma informację że zmienna {_var} jest typu number zamiast integer, a skoro typ zmiennej nie jest właściwy to argument przybierze wartość <none>. Jakie jest w takim razie rozwiązanie?
    Banalnie:
    Kod:
    command /vegeterrorysta:
        permission: op
        trigger:
            set {_var} to 1
            add 1 to {_var}
            jedzWarzywa("%{_var}%" parsed as integer)

    function jedzWarzywa(i: integer):
        broadcast "Zjedzono warzyw: %{_i}%"
  • Nie bójmy się korzystać z faktu że ustawionej zmiennej nie nadpiszemy wartością <none>, ale jednocześnie bójmy się faktu że wartość <none> nie nadpisze nam zmiennej.
    Pora na 2 krótkie przykłady:
    • Możemy to wykorzystać do dynamicznego przypisywania celu naszych działań. Poniżej znajduje się prosta komenda na sprawdzanie stanu konta, może ją wykonać zarówno gracz jak i konsola. W przypadku kiedy gracz wykona tą komendę zmienna player będzie ustawiona, jednak nie w przypadku konsoli. Pierwszy argument tej komendy nie jest obowiązkowy, jednak w momencie kiedy zostanie podany nadpisze on wartość zmiennej {_p}, w ten sposób zamiast sprawdzać stan konta gracza który wykonał komendę sprawdzony zostanie stan konta gracza którego nick został podany jako argument. W przypadku kiedy komendę wykona konsola i nie poda nicku w argumencie wartość zmiennej {_p} pozostanie niezmienna, czyli <none>.
      Kod:
          command /balance [<player>] [<text>]:
              aliases: bal
              permission: sk.command.balance
              trigger:
                  set {_p} to player
                  set {_p} to arg 1
                  if {_p} is not set:
                      send "%{prefix}% &cJako konsola musisz podać nick gracza."
                  else:
                      send "%{prefix}% &7Gracz &b%{_p}% &7posiada &b%{_p}'s balance%$&7."
    • Można się na tym również przejechać i to bardzo prosto:
      Kod:
          loop {_list::*}:
              set {_p} to loop-value parsed as player
              add 10 to {_p}'s balance
              send "%{prefix}% &7Otrzymałeś wypłatę w wysokości &b10$&7!"
      I tu pojawia się problem. Co w momencie kiedy jakiś nick z listy jest offline? wtedy 'loop-value parsed as player' zwróci none, a więc zmienna {_p} pozostanie z wartością z poprzedniej iteracji, a więc pewien szczęśliwiec dostanie co najmniej 2 wypłaty. Na szczęście rozwiązanie jest trywialne
      Kod:
          loop {_list::*}:
              delete {_p}
              set {_p} to loop-value parsed as player
              if {_p} is set:
                  add 10 to {_p}'s balance
                  send "%{prefix}% &7Otrzymałeś wypłatę w wysokości &b10$&7!"
  • Zadbajmy o wygodę użytkownika, każdemu czasem zdarzy się wpisać komendę z dodatkowym, zupełnie niepotrzebnym argumentem. Przykład? '/day y'. Rozwiązanie? dodajmy na końcu deklaracji komendy dodatkowy, nieobowiązkowy argument typu tekstowego, w ten sposób podanych przez gracza nigdy nie będzie za dużo. A więc zamiast 'command /day:' wpisujemy 'command /day [<text>]:'
  • Zmienna typu boolean (prawda/fałsz) tak naprawde może przyjąć 3 wartości: true, false oraz <none>. W przypadku Skripta jest to raczej mało istotne, ale w przypadku silnie typowanych języków jak Java jest to niezwyle przydatne dla leniwych programistów (i tu wyszedł nam wiecznie prawdziwy epitet "leniwy programista")
  • SkQuery udostępnia nam niejako możliwość ustawienia domyślnej wartości zwracanej przez zmienną w przypadku kiedy jest ona pusta. Zastosowanie jest banalnie proste: 'send "Zabiłeś %{STATYSTYKI.KILLS::%player%}?0% przeciwników" to player'. W przypadku kiedy zmienna będzie równa <none> gracz otrzyma wiadomość 'Zabiłeś 0 przeciwników', w przeciwnym wypadku zero zostanie zastąpione wartością zmiennej.
  • Ewenementem Skripta jest możliwośc operowania na pustych zmiennych. W przypadku operacji arytmetycznych jest ona po prostu uznawana za zero, to samo ma miejsce w przypadku operacji typu 'add 1 to {_var}', w tym wypadku wartość zmiennej po wykonaniu operacji będzie równa 1 (0 + 1 = 1)
  • Czasem do zapisu dużej ilości zmiennych typu boolean dobrze jest zastąpić je jedną zmienną typu integer. Jak dobrze wiadomo komputery operują na systemie dwójkowym, tak więc zmienna typu int również będzie zbiorem zer i jedynek, dla przykładu wartość true, false, true, false zapiszemy jako 10, co w systemie dwójkowym (U2, big endian) da nam 00000000000000000000000000001010. W jednej zmiennej typu int zmieścimy maksymalnie 32 zmienne boolean. Może się to doskonale nadać do zapisu dropu na survivalu, gdzie zamiast 10 oddzielnych zmiennych wystarczy jedna.



8. Bezpieczeństwo
  • Zarówno event 'every x seconds' jak i 'wait x seconds' są rejestrowane w timerze skripta, nie opiera się on na czasie systemowym, a na ilości ticków które minęły. Jaka jest różnica? Przyjmuje się że 1 sekunda to 20 ticków, tyle że 20 ticków nie musi trwać sekundy. Każdy z nas kojarzy skrót TPS oznaczający Ticks Per Second, tak więc jeżeli na serwerze będzie 10 TPS to 'wait 1 second' będzie w rzeczywistości czekać 2 sekundy. Praktyczne na żadnym serwerze nie ma idealnie stabilnych 20 TPS, tak więc nasze 'every 12 hours' może na przykład wykonać się po 12 godzinach i 30 minutach, to natomiast może doprowadzić do sytuacji kiedy jakaś akcja zostanie wykonana w ciągu doby tylko 1 raz zamiast planowanych 2.
  • Każdy tryb na naszej serwerowni jest restartowany codziennie w godzinach najmniejszego ruchu, oznacza to że event 'every 24 hours' nigdy nie powinien się wykonać, ponieważ serwer nie zdąży tyle czasu być online.
  • Pisząc kod zakładaj że coś może pójść nie tak, postarac się go tak zabezpieczyć żeby w przypadku błędu serwer doznał jak najmniej szkód. Zapewne większość z nas pisząc kod nie zastanawia się nad sytuacją w której baza danych nagle przestanie odpowiadać, a to nie dobrze bo taka sytuacja ma miejsce codziennie w godzinach restartu serwera.
  • Operując na publicznych obiektach (gracz, ekwipunek, blok, ...) bierz pod uwagę sytuację kiedy zostanie on edytowany w trakcie wykonywanego prze Ciebie kodu. Prykładowo chcemy wykonać kilka operacji na nicku gracza, jednak w trakcie kodu zmieni on swój nick. W przypadku kiedy pierw zapiszemy jego nick do zmiennej a następniej będziemy operować na tej zmiennej wszystko będzie działać prawidłowo, gorzej bedzie się mieć sytuacja w której każde odniesienie do nicku gracza będzie dynamiczne, tak więc za każdym razem będziem korzystać z frazy 'player's name', wtedy nasz kod może zrobić coś czego absolutnie się nie spodziewamy.
  • Na pewnym etapie życia każdej osoby piszącej skrypty największą zmorą staje się gracz klikający 100cps tym śmiesznym mieczem świetlnym który pożyczył od siostry spod łóżka. Tu Skript okazuje się czasem za wolny, szczególnie w przypadku klikania w przedmioty w GUI, dobrym i niezwykle wygodnym rozwiązaniem wydaje się TuSKe z metodą 'format gui slot %integer% of %player% to run function %function%', nie należy tego jednak mylić z podobną metodą z SkQuery, którą również można zbugować.
  • POD ŻADNYM POZOREM NIE UŻYWAJ JAKIEGOKOLWIEK EFEKTU FORSUJĄCEGO GRACZA DO WYKONANIA KOMENDY JAKO OPERATOR. CZASEM GRACZ ZOSTAJE OPERATOREM DO MOMNTU KIEDY KTOŚ GO RĘCZNIE NIE ZDEGRADJE SPOWROTEM DO GRACZA.
  • Jeżeli tworzysz jakieś komendy testowe koniecznie zadeklaruj do niej permissję której nie będzie posiadał żaden gracz. Wciąż pamiętam zabawę w usuwanie światów o nieprzyzwoitych nazwach na betatestach WvsP.



9. Optymalizacja
Skrypt ma jeden wielki minus w porównaniu do tradycyjnych pluginów pisanych w Javie czy też Kotlinie (teoretycznie można to również zrobić w Pythonie), operuje on na jednym wątku, głównym wątku serwera. Ma to również zaletę, Skript widnieje w timingach, jednak jakim kosztem. Co właściwie oznaca jednowątkowość? Taki program nie jest w stanie wykonywać dwóch operacji jednocześnie, jest wolniejszy, a w przypadku żle napisanego kodu może na stałe zablokować cały wątek czego rezultatem będzie prawdopodobnie crash. Tak więc wszystkie operacje wykonywane w Skripcie są wykonywane naprzemiennie z operacjami rdzenia serwera. Planując jakiekolwiek opercje w Skripcie naszym priorytetem musi być czas wykonywania każdej z nich. Najczęściej patrzymy żeby nasz skrypt nie wykonywał się zbyt często, ale jednocześnie zapominamy jak ważne jest trzymanie się w ramach 50ms czasu jednego ticku.
  • Najbardziej pospolitym błędem jest korzystanie z jakichkolwiek połączeń, czyli m. in. odczytanie danych z pliku, załadowanie strony www, czy też wysłanie zapytania do MySQL. Co gorsza nie da się oszacować czasu trwania tych operacji, są one zależne od obciążenia dysku i procesora, ruchu w internecie, obciążenia bazy danych i oczywiście wielkości informacji zwrotnej (callback). Tu warto odnieść się do tematu lagów na MF'owym SkyBlock'u, Aktualnie największe obciążenie generuje asynchroniczny(?) backup. Jak to możliwe skoro jest asynchroniczny? Jest asynchroniczny w połowie. Baza wysp oraz baza UUID jest zapisywana w oddzielnych asychronicznych wątkach, jednak ostatnia, najmniejsza baza zapisywana jest z poziomu głównego wątku, sam plik jest mały, więc teoretycznie nie powinien generować aż takiego obciążenia, tyle że jednocześnie 2 asynchroniczne wątki nadpisują 2 ogromne pliki z danymi wszystkich graczy i wysp którzy odwiedzili ten tryb w przeciągu ostatnich 3 lat. Gdyby ostatni i jednocześnie najmniejszy plik był również zapisywany asynchronicznie, problem najprawdopodobniej przestałby istnieć ponieważ serwer nie musiałby czekać na zakończenie zapisu tego jednego drobnego pliku.
  • Kolejną częstą praktyką jest kilkukrotne wykonywanie tego samego obciążąjącego kodu, kiedy wystarczy go wykonać raz, ale porządnie. I tu znowu pora na przykład z życia, ale z racji RODO zmienię imię bohatera naszych wydarzeń oraz stwierdzę że Wszystkie wydarzenia i postacie przedstawione w tym przykładzie są fikcyjne i nie mają bezpośredniego odniesienia do rzeczywistości. Otóż kiedyś Koko miał świetny pomysł aby wszystko zapisywać w pliku yaml, niestety żaden dodatek do skripta nie oferował sensownego sposobu przeprowadzania operacji na tego rodzaju plikach, pozostało więc użycie tego najmniej sensownego rozwiązania. Otóż Koko wykonywał w skripcie 4 akcje: pierw sprawdzał czy w pliku istnieje dana ścieżka, następnie sprawdzał wartość danej ścieżki, póżniej usuwał daną ścieżkę tylko po to aby za chwilę ustawić jej nową wartość. Ile razy nasz Koko otworzył plik? Dokładnie 4 razy i tyle samo razy ten plik parsował. Nie dość że w tym przypadku usunięcie ścieżki zupełnie nie ma sensu to ta metoda nigdy nie powinna zostać dopuszczona do użytku na produkcyjnym serwerze. Zawartość pliku YAML musi zostać przekonwertowana w taki sposóc aby dało się z niej korzystać z poziomu kodu, w javie wystarczy to zrobić raz i zapisać jako obiekt na którym następnie można wykonywać operacje odczytu wybranych danych, w skripcie natomiast miało to miejsce przy każdej kolejnej operacji na tym pliku.
  • Niekiedy zdarza się również że ciężki kod jest wykonywany ze zbyt dużą częstotliwościa, nie dając serwerowi ani chwili wytchnienia i szansy na ponowną stabilizację. W takim wypadku nie ma co się rozpisywać, wystarczy zmniejszyć częstotliwość zasobożernych operacji.
  • Niektórym developerom zdarza się również zapomnieć że im większy plik, tym dłużej będzie odczytywany i parsowany. Tak więc jeżeli planujecie zapis danych użytkowników w miejscu innym niż skryptowe zmienne, stwórzcie folder w którym będą tworzone oddzielne pliki dla każdego użytkownika, tak jak ma to miejsce w przypadku Essentials'a lub playerdaty.
  • Skript nie jest i prawdopodobnie nigdy nie będzie całkowitą alternatywą dla pluginów, pisanie całego trybu w Skripcie jest długie, toporne i średnio optymalne. Jeżeli istnieje plugin który spełnia nasze wymagania, najlepiej z niego skorzystać, natomiast jeżeli tylko częściowo spełnia nasze wymagania najlepiej z niego skorzystać, ale brakujące funkcje dorobić w skripcie. Aktualnie WvsP jest jednym z niewielu trybów stworzonych w casach tej złocistej epoki o nazwie "Serwer przeklęty w Skripcie zamknięty". Jego czas ładowania to istna porażka, a próba jakiejkolwiek modernizacji jego silnika może skończyć się katastrofą, a i tak żeby tego dokonać do pracy musiałem zaprząc skript-mirror.
  • Stwierdzenie że wait 0 tick załatwi wszelkie problemy z synchronicznym wykonywaniem kodu to jedno wielkie kłamstwo. Kod ten nadal jest wykonywany w głównym wątku, tyle że po drodze wywoływana jest funkcja BukkitScheduler::runTaskLater, która rejestruje dalszą część kodu w wewnętrznym timerze Bukkita tylko po to żeby wykonać go w zupelnie tej samej chwili.
  • Takim samym kłamstwem jest stwierdzenie że wait 1 tick magicznie odciąży serwer. Faktycznie, serwer nie zostanie automatycznie zrestartowany z powodu timeout'u, ale tylko dlatego że będzie w stanie odpowiedzieć co 1 tick po czym spowrotem się zaciąć.
  • Niewiele osób zdaje sobie również sprawę z tego jak nieoptymalna staje się skriptowa lista zmiennych wraz ze wzrostem jej wielkości. Pętla która do pustej listy będzie dodawać kolejno 10.000 elementów może spowodować restart serwera z powodu timeoutu (60 sekund braku odpowiedzi). Jest to spowodowane charakterystyką skriptowej listy, otóż przed dodaniem kolejnego elementu do listy jest ona iterowana w poszukiwaniu jej ostatniego indeksu aby następną zmienną umieścić na indeksie o jeden większym niż ostatni. To oznacza że dodanie 6 elementów do pustej tablicy będzie wymagało wykonania 0+1+2+3+4+5=15 operacji, tak więc w przypadku 10.000 elementów będzie to 0+1+2+3+...+9997+9998+9999=(9999+0)/2*10000=49.995.000 operacji, chyba każdy przyzna że to dość sporo. Swoją drogą złożoność operacji dodania jednego elementu do listy jest opisana wzorem 0.5*(n-1)(n), a więc jest to złożoność kwadratowa.
  • Bardziej zaawansowani programiści będą w stanie korzystac z dobrodziejstw skript-reflect, który prócz implementacji podstaw Javy oferuje równiez łatwe w użyciu "sekcje" w których umieszcza się kod, następnie wystarczy daną sekcję wywołać asynchronicznie.
  • Warto nadmienić że asynchronicznośc nie jest wcale lekarstwem na wszystkie choroby świata, a jedynie jednym z wyborów. Wykonywanie kodu w głównym wątku obciąża serwer, natomiast wykonywanie kodu asynchronicznie obciążą całą maszynę i jest o wiele trudniejsze do wykrycia (patrz: Zabawa z notorycznym wywalaniem Creative MS przez scoreboard). Zycie to sztuka wyborów, wybierz mądrze.
  • Pozostaje jeszcze kwestia optymalizacji samego procesu ładowania Skripta. Jak już wspominałem im większy plik tym dłużej będzie się on ładował, więc lepiej pisać więcej małych skryptów. Co jednak dziwne czas ładowania skryptu jest również zależny od złożoności linijek, długa linijka będzie się wczytywać dłużej niż ta sama linijka podzielona na kilka mniejszych przy użyciu zmiennych.
  • Należy również zwracać uwagę aby nie generować niepotrzebnych zmienych na zapas. Fatalnym pomysłem wydaje się tworzenie kilku zmiennych z domyślnymi wartościami jak tylko gracz wejdzie na tryb, jest to nic więcej jak tworzenie śmieciowych zmiennych, które z czasem dadzą o sobie znać.



10. Poprawne numerowanie punktów



11. Podsumowanie
Na samym wstępie tego zakończenia pragnę wspomnieć że w tekscie ukryłem 40 literówek, potraktujcie to jak szukanie jajek wielkanocnych. Chciałbym również nadmienić że nienawidzę używania polskich odpowiedników angielskich określeń powiązanych z informatyką, cieszcie się grą na waszych mózgach elektronowych z oświetleniem LED.

A teraz pora przejść do właściwej części zakończenia.

Po co to? Jak to się mówi "dla potomnych". Starajcie się również sami szukać informacji, jest to dobra metoda nauki oraz zapamiętywania, a jednocześnie rozwija wasze umiejętności poznawcze. Nic nie musi się udawać za pierwszym razem, traktujcie problemy jak kolejne łamigłówki. Co najważniejsze, nie probójcie skakać na głęboką wodę, przeważnie kończy się to jedynie frustracją oraz zastaniem wszystkich chęci do rozwijania się w tym kierunku.
Oceny: MichotR (+1), misterhenriques (+1), ARCHERSKIN (+1), kubamax (+1), aksaiko (+1), Kiciok420 (+1)
25-November-2023 14:56:56
Znajdź wszystkie posty Odpisz
Hobbs
Wspieram Forum

Liczba postów: 6969
Dolaczyla: Jan 2012
Reputacja: 777

MineGold: 777.77

Serdecznie polecamy serwis który pomoze Ci zdobyc Ci polubienia, subskrybcje, followersów i rozbudowac Twoje socialmedia!



Skocz do:


Użytkownicy przeglądający ten wątek: 2 gości