Tutorial LPC: Różnice pomiędzy wersjami

Z ArkadiaWiki
Jump to navigation Jump to search
 
(Nie pokazano 37 pośrednich wersji utworzonych przez tego samego użytkownika)
Linia 1: Linia 1:
:''Wersja tekstowa z wyszczególnionym autorstwem rozdziałów znajduje się na [http://ath.net.pl/barsawia/download/lpc.txt stronie MUD-a Barsawia]. Tu znajduje się ładnie, w założeniu, sformatowana i w wielu miejscach poprawiona wersja HTML.
+
:''Wersja tekstowa z wyszczególnionym autorstwem rozdziałów znajduje się na [http://mrozo.ayz.pl/Barsawia/download/lpc.txtstronie MUD-a Barsawia]. Tu znajduje się ładnie, w założeniu, sformatowana i w wielu miejscach poprawiona wersja HTML.
  
 
<pre>
 
<pre>
Linia 52: Linia 52:
  
 
==== Alvin ====
 
==== Alvin ====
Tłumaczenie może być miejscami niezręczne. Jest tez spora szansa, ze napotkasz jakieś błędy. W obu przypadkach wal z nimi proszę do mnie, czyli do [[Alvin]]a.
+
Tłumaczenie może być miejscami niezręczne. Jest też spora szansa, że napotkasz jakieś błędy. W obu przypadkach wal z nimi proszę do mnie, czyli do [[Alvin]]a.
  
Mam nadzieje, ze Ci się na coś zda to tłumaczenie.
+
Mam nadzieję, że Ci się na coś zda to tłumaczenie.
  
 
==== Kael ====
 
==== Kael ====
Linia 3167: Linia 3167:
  
 
:'''UWAGA! PRZECZYTAJ TO UWAŻNIE!'''
 
:'''UWAGA! PRZECZYTAJ TO UWAŻNIE!'''
:Bardzo łatwo wpaść w nawyk dzielenia programu na wiele &lsquo;alarmowych&rsquo;
+
Bardzo łatwo wpaść w nawyk dzielenia programu na wiele &lsquo;alarmowych&rsquo;
 
wywołań w małych odstępach czasowych. Jednakże '''NIE''' do tego służą alarmy.
 
wywołań w małych odstępach czasowych. Jednakże '''NIE''' do tego służą alarmy.
 
Śmiertelnym grzechem jest robienie funkcji alarmowej, która tworzy
 
Śmiertelnym grzechem jest robienie funkcji alarmowej, która tworzy
Linia 3185: Linia 3185:
 
environment, all_inventory, deep_inventory, id, present]
 
environment, all_inventory, deep_inventory, id, present]
  
Jak juz wczesniej mowielem, kazdy obiekt ma zarowno &bdquo;wnetrze&rdquo;, jak
+
Jak już wcześniej mówiłem, każdy obiekt ma zarówno &bdquo;wnętrze&rdquo;, jak
i &bdquo;otoczenie&rdquo;. Otoczenie, lub inaczej mowiac &bdquo;srodowisko&rdquo; moze byc
+
i &bdquo;otoczenie&rdquo;. Otoczenie, lub inaczej mówiąc &bdquo;środowisko&rdquo; może być
tylko jednym obiektem, podczas gdy we wnetrzu, w &bdquo;zawartosci&rdquo; moze sie
+
tylko jednym obiektem, podczas gdy we wnętrzu, w &bdquo;zawartości&rdquo; może się
znajdowac wiele obiektow.
+
znajdować wiele obiektów.
  
Swiezo sklonowany obiekt znajduje sie w swego rodzaju pustce, gdyz nie ma  
+
świeżo sklonowany obiekt znajduje się w swego rodzaju pustce, gdyż nie ma  
zadnego srodowiska. Zeby mogl znalezc sie w fizycznym swiecie gry, musi byc
+
żadnego środowiska. Żeby mógł znaleźć się w fizycznym świecie gry, musi być
tam przesuniety. Jednakze, nie wszystkie obiekty moga byc przemieszczane.  
+
tam przesunięty. Jednakże, nie wszystkie obiekty mogą być przemieszczane.  
Zeby '''DOWOLNY''' obiekt, ktory chce byc gdzies umieszczony, albo chce samemu  
+
Żeby '''DOWOLNY''' obiekt, który chce być gdzieś umieszczony, albo chce samemu  
zawierac jakies inne obiekty dzialal, '''MUSI''' dziedziczyc &lsquo;/std/object.c&rsquo;  
+
zawierać jakieś inne obiekty działał, '''MUSI''' dziedziczyć &lsquo;/std/object.c&rsquo;  
gdzies w swoim lancuchu dziedziczen. Po co to ograniczenie? Dlatego, ze
+
gdzieś w swoim łańcuchu dziedziczeń. Po co to ograniczenie? Dlatego, że
standardowy obiekt definiuje spora liczbe pomocnych funkcji i inne obiekty
+
standardowy obiekt definiuje sporą liczbę pomocnych funkcji i inne obiekty
polegaja na tym, ze beda one w twoim obiekcie.
+
polegają na tym, że będa one w twoim obiekcie.
  
Oto najwazniejsze sposrod nich:
+
Oto najważniejsze spośród nich:
  
 
* move()
 
* move()
: Przemieszcza obiekt do innego, obslugujac rachunek wagi/objetosci. Zwraca, czy przesuniecie powiodlo sie. Jest odpowiedzialna za wywolywanie nastepujacych funkcji:
+
: Przemieszcza obiekt do innego, obsługując rachunek wagi/objętości. Zwraca, czy przesunięcie powiodło się. Jest odpowiedzialna za wywoływanie następujących funkcji:
  
 
* enter_inv()
 
* enter_inv()
: Wywolywana jest w obiekcie, gdy inny obiekt wchodzi w jego zawartosc.
+
: Wywoływana jest w obiekcie, gdy inny obiekt wchodzi w jego zawartość.
  
 
* leave_inv()
 
* leave_inv()
: Wywolywana w obiekcie, gdy inny obiekt wchodzi w jego zawartosc.
+
: Wywoływana w obiekcie, gdy inny obiekt wchodzi w jego zawartość.
  
 
* enter_env()
 
* enter_env()
Linia 3216: Linia 3216:
 
: (!!!)
 
: (!!!)
  
'''UWAGA!''' Powyzsze funkcje beda wywolane '''TYLKO''' wtedy gdy to wlasnie
+
'''UWAGA!''' Powyższe funkcje będą wywołane '''TYLKO''' wtedy gdy to właśnie
&lsquo;move()&rsquo; zostanie uzyty do przesuniecia. Dlatego tak wazne jest to, zebys
+
&lsquo;move()&rsquo; zostanie użyty do przesunięcia. Dlatego tak ważne jest to, żebyś
przemieszczal w ten sposob, a nie poprzez efunkcje, ktora robi to
+
przemieszczał w ten sposób, a nie poprzez efunkcje, która robi to
bezposrednio.
+
bezpośrednio.
  
Lfunkcja &lsquo;move()&rsquo; korzysta z efunkcji &lsquo;move_object()&rsquo;. '''ALE''' pamietaj, gdy  
+
Lfunkcja &lsquo;move()&rsquo; korzysta z efunkcji &lsquo;move_object()&rsquo;. '''ALE''' pamiętaj, gdy  
odwolasz sie bezposrednio do tej drugiej, to stany obiektu takie jak  
+
odwołasz się bezpośrednio do tej drugiej, to stany obiektu takie jak  
oswietlenie, waga, czy objetosc nie zostana uaktualnione. Jak juz poprzednio  
+
oświetlenie, waga, czy objętość nie zostaną uaktualnione. Jak już poprzednio  
mowilem, fiaskiem skonczy sie proba przeniesienia obiektu do innego przy  
+
mówiłem, fiaskiem skończy się próbą przeniesienia obiektu do innego przy  
pomocy &lsquo;move_object()&rsquo;, gdy ktorys z nich nie dziedziczy &lsquo;/std/object.c&rsquo;.  
+
pomocy &lsquo;move_object()&rsquo;, gdy któryś z nich nie dziedziczy &lsquo;/std/object.c&rsquo;.  
Dodatkowo, efunkcja moze byc wywolana tylko z obiektu, ktory chce byc
+
Dodatkowo, efunkcja może być wywołana tylko z obiektu, który chce być
przesuniety. To samo dotyczy oczywiscie lfunkcji &lsquo;move()&rsquo;.
+
przesunięty. To samo dotyczy oczywiście lfunkcji &lsquo;move()&rsquo;.
  
W celu uzyskania odnosnika do obiektu srodowiska, uzywa sie efunkcji
+
W celu uzyskania odnośnika do obiektu środowiska, używa się efunkcji
&lsquo;environment()&rsquo;. Jak juz wczesniej powiedzialem, zaden obiekt nie ma  
+
&lsquo;environment()&rsquo;. Jak już wcześniej powiedziałem, żaden obiekt nie ma  
zdefiniowanego srodowiska zaraz po utworzeniu &ndash; otrzymuje je dopiero, gdy  
+
zdefiniowanego środowiska zaraz po utworzeniu &ndash; otrzymuje je dopiero, gdy  
jest gdzies przenoszony. Kiedy obiekt chociaz raz opusci &lsquo;pustke&rsquo;, w ktorej
+
jest gdzieś przenoszony. Kiedy obiekt chociaż raz opuści &lsquo;pustkę&rsquo;, w której
sie znajduje na poczatku, to juz nigdy nie moze tam wrocic, tzn. nie mozna
+
się znajduje na początku, to już nigdy nie może tam wrócić, tzn. nie można
przeniesc obiektu do &lsquo;0&rsquo;. Obiektami, po ktorych mozesz sie spodziewac, ze nie
+
przenieść obiektu do &lsquo;0&rsquo;. Obiektami, po których możesz się spodziewać, że nie
beda mialy zadnego srodowiska sa pokoje, dusze, cienie i obiekty daemon.
+
będą miały żadnego środowiska są pokoje, dusze, cienie i obiekty daemon.
  
Masz do wyboru dwie funkcje, sposrod ktorych mozesz wybierac, gdy chcesz
+
Masz do wyboru dwie funkcje, spośród których możesz wybierać, gdy chcesz
zdobyc sklad obiektu. Efunkcja &lsquo;all_inventory()&rsquo; zwraca tablice ze wszystkimi
+
zdobyć skład obiektu. Efunkcja &lsquo;all_inventory()&rsquo; zwraca tablice ze wszystkimi
obiektami bedacymi zawartoscia podanego obiektu. Efunkcja &lsquo;deep_inventory()&rsquo;  
+
obiektami będącymi zawartością podanego obiektu. Efunkcja &lsquo;deep_inventory()&rsquo;  
zwraca tablice, zawierajaca nie tylko to, co &lsquo;all_inventory()&rsquo;, ale rowniez
+
zwraca tablice, zawierającą nie tylko to, co &lsquo;all_inventory()&rsquo;, ale rownież
obiekty, ktore sa we wnetrzu obiektow, ktore sa we wnetrzu podanego obiektu,  
+
obiekty, które są we wnętrzu obiektów, które są we wnętrzu podanego obiektu,  
 
itd.
 
itd.
  
Linia 3248: Linia 3248:
 
     np.
 
     np.
 
     /*
 
     /*
       * Funkcja wyswietla ekwpipunek Fatty na ekranie. Od argumentu bedzie
+
       * Funkcja wyświetla ekwipunek Fatty na ekranie. Od argumentu będzie
       * zalezalo, czy bedzie to tylko ekwipunek widoczny na pierwszy rzut
+
       * zależało, czy będzie to tylko ekwipunek widoczny na pierwszy rzut
       * oka, czy caly.
+
       * oka, czy cały.
 
       */
 
       */
 
     void
 
     void
Linia 3259: Linia 3259:
 
         if (!objectp((fatty_ob = find_player("fatty"))))
 
         if (!objectp((fatty_ob = find_player("fatty"))))
 
         {
 
         {
             write("Przykro mi, Fatty nie ma dzis w grze.\n");
+
             write("Przykro mi, Fatty nie ma dziś w grze.\n");
 
             return 0;
 
             return 0;
 
         }
 
         }
Linia 3267: Linia 3267:
 
      
 
      
 
         write("Oto " + (wszystko ? "cala " : "") +
 
         write("Oto " + (wszystko ? "cala " : "") +
                     " zawartosc wielkiego brzucha Fatty:\n");
+
                     " zawartość wielkiego brzucha Fatty:\n");
 
         dump_array(listaob);
 
         dump_array(listaob);
 
     }
 
     }
  
Co powiesz na sprawdzenie, czy dany obiekt jest obecny w zawartosci
+
Co powiesz na sprawdzenie, czy dany obiekt jest obecny w zawartości
innego? Bazowy obiekt &lsquo;/std/object.c&rsquo; definiuje zarowno nazwe jak i opis
+
innego? Bazowy obiekt &lsquo;/std/object.c&rsquo; definiuje zarówno nazwę jak i opis
w obiektach. Rowniez w nim znajduje sie lfunkcja &lsquo;id()&rsquo;, ktora sprawdza
+
w obiektach. Również w nim znajduje się lfunkcja &lsquo;id()&rsquo;, która sprawdza
czy podany argument jest jedna z nazw danego obiektu. Jesli jest to  
+
czy podany argument jest jedną z nazw danego obiektu. Jeśli jest to  
zwraca 1(prawde). Efunkcja &lsquo;present()&rsquo; przeszukuje zawartosci obiektow,  
+
zwraca 1(prawdę). Efunkcja &lsquo;present()&rsquo; przeszukuje zawartości obiektów,  
sprawdzajac czy jest tam obiekt o podanym odnosniku lub nazwie. Gdy  
+
sprawdzając czy jest tam obiekt o podanym odnośniku lub nazwie. Gdy  
 
w argumencie wpiszesz to drugie, to &lsquo;present()&rsquo; skorzysta z poprzednio  
 
w argumencie wpiszesz to drugie, to &lsquo;present()&rsquo; skorzysta z poprzednio  
omowionej funkcji &lsquo;id()&rsquo; do sprawdzenia, czy w przeszukiwanym aktualnie  
+
omówionej funkcji &lsquo;id()&rsquo; do sprawdzenia, czy w przeszukiwanym aktualnie  
obiekcie istnieje takowa nazwa. Wykonanie funkcji skonczy sie tak szybko,  
+
obiekcie istnieje takowa nazwa. Wykonanie funkcji skończy się tak szybko,  
jak szybko znajdzie ona pierwszy pasujacy do opisu obiekt. Oznacza to, ze
+
jak szybko znajdzie ona pierwszy pasujący do opisu obiekt. Oznacza to, że
jesli jest wiecej takich obiektow, to funkcja zwroci ci tylko jeden z nich.
+
jeśli jest więcej takich obiektów, to funkcja zwróci ci tylko jeden z nich.
  
 
     object present(object ob|string odnob, object *listaob|object ob|void)
 
     object present(object ob|string odnob, object *listaob|object ob|void)
 
     np.
 
     np.
 
     /*
 
     /*
       * Szuka orzeszkow u Fatty
+
       * Szuka orzeszków u Fatty
 
       */
 
       */
 
     void
 
     void
Linia 3295: Linia 3295:
 
         fatty_ob = find_player("fatty");
 
         fatty_ob = find_player("fatty");
 
      
 
      
         // Nie mozna znalezc Fatty!
+
         // Nie można znaleźć Fatty!
 
         if (!objectp(fatty_ob))
 
         if (!objectp(fatty_ob))
 
         {
 
         {
             write("Fatty chwilowo nie ma, sprobuj pozniej.\n");
+
             write("Fatty chwilowo nie ma, spróbuj później.\n");
 
             return;
 
             return;
 
         }
 
         }
 
      
 
      
 
         if (present("orzeszek", fatty_ob))
 
         if (present("orzeszek", fatty_ob))
             write("Tak, Fatty wydaje sie byc bardzo zadowolony z zycia.\n");
+
             write("Tak, Fatty wydaje się być bardzo zadowolony z życia.\n");
 
         else
 
         else
             write("Na twoim miejscu trzymalbym sie z dala od " +
+
             write("Na twoim miejscu trzymałbym się z dala od " +
                   "Fatty, dopoki nie zaspokoi glodu.\n");
+
                   "Fatty, dopóki nie zaspokoi głodu.\n");
 
     }
 
     }
  
Jesli nie podasz drugiego argumentu w &lsquo;present()&rsquo;, to funkcja poszuka
+
Jeśli nie podasz drugiego argumentu w &lsquo;present()&rsquo;, to funkcja poszuka
obiektu w zawartosci &lsquo;this_object()&rsquo;, czyli tego obiektu, z ktorego zostala
+
obiektu w zawartości &lsquo;this_object()&rsquo;, czyli tego obiektu, z którego została
wywolana. Gdy w drugim argumencie podasz tablice, to funkcja przeszuka
+
wywołana. Gdy w drugim argumencie podasz tablice, to funkcja przeszuka
wszystkie obiekty z listy. Jesli nie znajdzie niczego, to zwroci 0.
+
wszystkie obiekty z listy. Jeśli nie znajdzie niczego, to zwróci 0.
  
==== Funkcje obsługujące łancuchy znakowe ====
+
==== Funkcje obsługujące łańcuchy znakowe ====
  
 
   [break_string, capitalize, lower_case, sprintf, strlen, wildmatch]
 
   [break_string, capitalize, lower_case, sprintf, strlen, wildmatch]
  
W srodowisku gry opartym na tekscie, naturalne jest to, ze tworcy
+
W środowisku gry opartym na tekście, naturalne jest to, ze twórcy
zadali sobie troche trudu w stworzeniu latwych i wszechstronnych funkcji  
+
zadali sobie trochę trudu w stworzeniu łatwych i wszechstronnych funkcji  
obslugujacych lancuchy znakowe. Jak juz wiesz, stringi mozna sumowac przy
+
obsługujących łańcuchy znakowe. Jak już wiesz, stringi można sumować przy
pomocy operatora &lsquo;+&rsquo;, a nawet laczyc je z liczbami calkowitymi bez zadnych
+
pomocy operatora &lsquo;+&rsquo;, a nawet łączyć je z liczbami całkowitymi bez żadnych
klopotow. Floaty i wskazniki do obiektow musza juz jednak byc konwertowane.
+
kłopotów. Floaty i wskaźniki do obiektów muszą już jednak być konwertowane.
Te pierwsze przy pomocy efunkcji &lsquo;ftoa()&rsquo; (opisanej pozniej), a te drugie
+
Te pierwsze przy pomocy efunkcji &lsquo;ftoa()&rsquo; (opisanej później), a te drugie
poprzez juz opisana efunkcje &lsquo;file_name()&rsquo;.
+
poprzez już opisaną efunkcje &lsquo;file_name()&rsquo;.
  
Jedna z najczesciej sprawdzana rzecza w stringach, poza tym co
+
Jedną z najczęściej sprawdzanych rzeczy w stringach, poza tym co
zawieraja, jest ich dlugosc. Uzyskuje sie ja przy pomocy efunkcji  
+
zawierają, jest ich długość. Uzyskuje się ją przy pomocy efunkcji  
&lsquo;strlen()&rsquo;. Jako argument mozna podac rowniez liczby calkowite (zwroci
+
&lsquo;strlen()&rsquo;. Jako argument można podać również liczby całkowite (zwróci
wtedy 0), dzieki czemu mozna ja wykorzystywac takze do sprawdzania
+
wtedy 0), dzięki czemu można ją wykorzystywać także do sprawdzania
czy zmienna typu string zostala juz zainicjalizowana.
+
czy zmienna typu string została już zainicjalizowana.
  
 
     int strlen(string str)
 
     int strlen(string str)
 
     np.
 
     np.
         string str = "Fatty jest spasionym, zatwardzialym szowinista";
+
         string str = "Fatty jest spasionym, zatwardziałym szowinistą";
         write("Dlugosc stringa '" + str + "' wynosi " + strlen(str) +
+
         write("Długość stringa '" + str + "' wynosi " + strlen(str) +
               " znakow.\n");
+
               " znaków.\n");
  
Nieraz zachodzi potrzeba takiego przeformatowania stringa, by zaczynal sie
+
Nieraz zachodzi potrzeba takiego przeformatowania stringa, by zaczynał się
z duzej litery. Sluzy do tego efunkcja &lsquo;capitalize()&rsquo;. Oprocz tego istnieje
+
z dużej litery. Służy do tego efunkcja &lsquo;capitalize()&rsquo;. Oprócz tego istnieje
efunkcja &lsquo;lower_case()&rsquo;, ktora zamienia wszystkie litery w podanym stringu
+
efunkcja &lsquo;lower_case()&rsquo;, która zamienia wszystkie litery w podanym stringu
na male.
+
na małe.
  
 
     string capitalize(string str)
 
     string capitalize(string str)
Linia 3347: Linia 3347:
 
np.
 
np.
 
     void
 
     void
     // Wyswietli podane imie, odpowiednio sformatowane.
+
     // Wyświetli podane imię, odpowiednio sformatowane.
 
     wyswietl_ladne_imie(string imie)
 
     wyswietl_ladne_imie(string imie)
 
     {
 
     {
Linia 3365: Linia 3365:
 
     }
 
     }
  
Czasem przydaloby sie polamac stringa na mniejsze kawalki(np dlugosci
+
Czasem przydałoby się połamać stringa na mniejsze kawałki(np długości
linijki ekranu), by ladniej wygladal i nadawal sie do wyswietlenia.  
+
linijki ekranu), by ładniej wyglądał i nadawał się do wyświetlenia.  
Sluzy do tego efunkcja &lsquo;break_string()&rsquo;. Dzieki niej mozesz nawet dodac
+
Służy do tego efunkcja &lsquo;break_string()&rsquo;. Dzięki niej możesz nawet dodać
spacje na poczatku polamanych lancuchow znakowych. Jej dzialanie polega  
+
spacje na początku połamanych łańcuchów znakowych. Jej działanie polega  
na wstawianiu znaku nowej linii po odpowiedniej ilosci slow, tak by jedna  
+
na wstawianiu znaku nowej linii po odpowiedniej ilości słów, tak by jedna  
czesc zmiescila sie w podanym limicie znakow. Trzeci argument mowiacy ile ma  
+
część zmieściła się w podanym limicie znaków. Trzeci argument mówiący ile ma  
byc spacji wciecia, albo podajacy string wstawiany na poczatku kazdego
+
być spacji wcięcia, albo podający string wstawiany na początku każdego
 
fragmentu jest opcjonalny.
 
fragmentu jest opcjonalny.
  
Linia 3377: Linia 3377:
 
                         int dlugosc_wciecia|string wstawiany_string|void)
 
                         int dlugosc_wciecia|string wstawiany_string|void)
 
np.
 
np.
         string str = "To jest string, ktory chce przedstawic na rozne " +
+
         string str = "To jest string, który chce przedstawić na różne " +
 
                       "sposoby.";
 
                       "sposoby.";
 
      
 
      
Linia 3384: Linia 3384:
 
         write(break_string(str, 26, "Fatty mowi: ") + "\n");
 
         write(break_string(str, 26, "Fatty mowi: ") + "\n");
 
      
 
      
         /* Efektem bedzie:
+
         /* Efektem będzie:
  
 
             To jest string, ktory chce
 
             To jest string, ktory chce
Linia 3397: Linia 3397:
 
           */
 
           */
  
Bardzo czesto bedziesz chcial przedstawic zawartosc zmiennej na ekranie.
+
Bardzo często będziesz chciał przedstawić zawartość zmiennej na ekranie.
Jak juz pokazalem, mozesz to zrobic poprzez zamienienie wartosci zmiennej
+
Jak już pokazałem, możesz to zrobić poprzez zamienienie wartości zmiennej
w stringa i wyswietlenie jej. Integerow nawet nie trzeba konwertowac -
+
w stringa i wyświetlenie jej. Integerów nawet nie trzeba konwertować -
wystarczy, ze dodasz go przy pomocy operatora &lsquo;+&rsquo;. Otrzymasz jednak cos,
+
wystarczy, że dodasz go przy pomocy operatora &lsquo;+&rsquo;. Otrzymasz jednak coś,
co nie bedzie sformatowane i byc moze bedzie wymagalo jakis przerobek.
+
co nie będzie sformatowane i być może będzie wymagało jakiś przeróbek.
Czasem mozesz chciec wyswietlic tresc zmiennych w postaci tabelki i bedziesz
+
Czasem możesz chcieć wyświetlić treść zmiennych w postaci tabelki i będziesz
wtedy musial bawic sie w rozne duperele takie jak uzaleznianie ilosci spacji
+
wtedy musiał bawić się w różne duperele takie jak uzależnianie ilości spacji
od dlugosci zmiennej itp. Zamiast tego, mozesz skorzystac z efunkcji
+
od długości zmiennej itp. Zamiast tego, możesz skorzystać z efunkcji
 
&lsquo;sprintf()&rsquo;.
 
&lsquo;sprintf()&rsquo;.
  
&lsquo;sprintf()&rsquo; pobiera dwa stringi. Pierwszy to jest ten, ktory chcesz
+
&lsquo;sprintf()&rsquo; pobiera dwa stringi. Pierwszy to jest ten, który chcesz
przeformatowac. Drugi zas zawiera wskazowki, wedlug ktorych formatowanie
+
przeformatować. Drugi zaś zawiera wskazówki, według których formatowanie
ma sie odbywac. Wynikiem jest gotowy lancuch znakow, ktory mozesz wyswietlic
+
ma się odbywać. Wynikiem jest gotowy łańcuch znaków, który możesz wyświetlić
 
np. przy pomocy &lsquo;write()&rsquo;.
 
np. przy pomocy &lsquo;write()&rsquo;.
  
Wszystkie znaki z drugiego stringa, poza specjalnymi zaczynajacymi sie
+
Wszystkie znaki z drugiego stringa, poza specjalnymi zaczynającymi się
od &lsquo;%&rsquo; beda przekopiowane do wynikowego stringa. Znaki kontrolne maja postac:
+
od &lsquo;%&rsquo; będą przekopiowane do wynikowego stringa. Znaki kontrolne mają postać:
"%<wyznacznik szerokosci><wyznacznik typu>".
+
"%<wyznacznik szerokości><wyznacznik typu>".
  
Szerokosc jest liczba calkowita, oznaczajaca dlugosc &bdquo;okienka&rdquo; w ktorym
+
Szerokość jest liczba całkowitą, oznaczającą długość &bdquo;okienka&rdquo; w którym
dane beda wyswietlane oraz czy dana ta ma byc rownana do lewej czy do
+
dane będą wyświetlane oraz czy dana ta ma być równana do lewej czy do
prawej strony. Dodatni numer oznacza, ze do prawej, zas ujemny, ze do lewej.
+
prawej strony. Dodatni numer oznacza, że do prawej, zaś ujemny, że do lewej.
Jesli nie podasz szerokosci, to zmienna zostanie wlozona w okienko
+
Jeśli nie podasz szerokości, to zmienna zostanie włożona w okienko
o dlugosci rownej dlugosci zmiennej. Wyznacznik typu sklada sie z jednej
+
o długości równej długości zmiennej. Wyznacznik typu składa się z jednej
lub wiecej liter, okreslajacych jaki typ zmiennej bedzie tu uzyty.
+
lub więcej liter, określających jaki typ zmiennej będzie tu użyty.
  
A oto lista wyznacznikow typu:
+
A oto lista wyznaczników typu:
  
 
* d
 
* d
 
* i
 
* i
: Argument jest liczba calkowita.
+
: Argument jest liczbą całkowitą.
  
 
           string str;
 
           string str;
Linia 3440: Linia 3440:
  
 
* s
 
* s
: Argument jest lancuchem znakow.
+
: Argument jest łańcuchem znaków.
  
 
* c
 
* c
: Podany argument jest numerem ASCII, znaku ktory ma byc wyswietlony.
+
: Podany argument jest numerem ASCII, znaku który ma być wyświetlony.
  
 
* o
 
* o
: Liczba ma byc przedstawiona w systemie osemkowym.
+
: Liczba ma być przedstawiona w systemie ósemkowym.
  
 
* x
 
* x
: Liczba ma byc przedstawiona w systemie szesnastkowym.
+
: Liczba ma być przedstawiona w systemie szesnastkowym.
  
 
* X
 
* X
: Liczba ma byc przedstawiona w systemie szesnastkowym (duzymi literami).
+
: Liczba ma być przedstawiona w systemie szesnastkowym (dużymi literami).
  
 
* O
 
* O
: Argument jest typem danych LPC. Jest to swietna rzecz do wyszukiwania bledow (odpluskwiania), gdyz mozesz wyswietlic dzieki niej zawartosc DOWOLNEJ zmiennej.
+
: Argument jest typem danych LPC. Jest to świetna rzecz do wyszukiwania błędów (odpluskwiania), gdyż możesz wyświetlić dzięki niej zawartość DOWOLNEJ zmiennej.
  
 
np.
 
np.
Linia 3465: Linia 3465:
 
           // 5:7eb2 6:14F5C 7:<<FUNCTION &strlen()>>
 
           // 5:7eb2 6:14F5C 7:<<FUNCTION &strlen()>>
  
Specyfikator ten jest jak na razie jedynym, w ktorym da sie wyswietlic floaty.
+
Specyfikator ten jest jak na razie jedynym, w którym da się wyświetlić floaty.
  
To byla lista wszystkich wyznacznikow typow. Do wyznacznikow szerokosci
+
To była lista wszystkich wyznaczników typów. Do wyznaczników szerokości
mozna jeszcze dodac te elementy:
+
można jeszcze dodać te elementy:
  
 
* ' '
 
* ' '
: Liczbowy argument zostanie poprzedzony jedna spacja, o ile jest dodatni. Pozwala to na robienie fajnych tabel bez zawracania sobie glowy tym, ze liczby z minusem zajmuja o jeden znak wiecej.
+
: Liczbowy argument zostanie poprzedzony jedną spacją, o ile jest dodatni. Pozwala to na robienie fajnych tabel bez zawracania sobie głowy tym, że liczby z minusem zajmują o jeden znak więcej.
  
 
* +
 
* +
: Dodatnie argumenty liczbowe zostana poprzedzone plusem.
+
: Dodatnie argumenty liczbowe zostaną poprzedzone plusem.
  
 
* 'X'
 
* 'X'
: Znak(i) w apostrofach bedzie poprzedzal argument.
+
: Znak(i) w apostrofach będzie poprzedzał argument.
  
 
* |
 
* |
Linia 3484: Linia 3484:
 
           write((sprintf(">%19|s<\n", "Fatty grubas")));
 
           write((sprintf(">%19|s<\n", "Fatty grubas")));
  
           // Efektem bedzie:
+
           // Efektem będzie:
 
           // >  Fatty grubas    <
 
           // >  Fatty grubas    <
  
  
 
* #
 
* #
: Oznacza to tryb tablicowy. Efektem bedzie lista slow oddzielonych &lsquo;\n&rsquo; w tabeli o szerokosci okienka. Oczywiscie mozna uzyc tego tylko do stringow.
+
: Oznacza to tryb tablicowy. Efektem będzie lista słów oddzielonych &lsquo;\n&rsquo; w tabeli o szerokości okienka. Oczywiście można użyć tego tylko do stringów.
 
* =
 
* =
: Ten wyznacznik jest poprawny tylko dla stringow. Wyswietla rezultat w kolumnach, o ile argument jest szerszy od okienka.
+
: Ten wyznacznik jest poprawny tylko dla stringów. Wyświetla rezultat w kolumnach, o ile argument jest szerszy od okienka.
  
 
* *
 
* *
: Argument obok gwiazdki jest rozmiarem okienka. Jesli polaczysz to z trybem tablicowym, to otrzymasz fajne tabelki.
+
: Argument obok gwiazdki jest rozmiarem okienka. Jeśli połączysz to z trybem tablicowym, to otrzymasz fajne tabelki.
  
 
* @
 
* @
: Argumentem jest tablica. Oczywiscie musisz polaczyc to z wyznacznikiem typu, zaznaczajac typ elementow.
+
: Argumentem jest tablica. Oczywiście musisz połączyć to z wyznacznikiem typu, zaznaczając typ elementów.
  
Bardzo czesto zachodzi potrzeba sprawdzenia, czy dany string jest czescia
+
Bardzo często zachodzi potrzeba sprawdzenia, czy dany string jest częścią
jakiego innego. Jesli nie jestes zainteresowany informacja gdzie on  
+
jakiegoś innego. Jeśli nie jesteś zainteresowany informacja gdzie on  
dokladnie wystapil, a tylko czy wogole, to efunkcja &lsquo;wildmatch()&rsquo; bedzie
+
dokładnie wystąpił, a tylko czy w ogóle, to efunkcja &lsquo;wildmatch()&rsquo; będzie
czyms w sam raz dla ciebie. Po prostu zwraca 1, jesli podany string wystapil
+
czymś w sam raz dla ciebie. Po prostu zwraca 1, jeśli podany string wystąpił
gdzies, w jakims innym, wiekszym stringu. Mniejszy string moze skladac sie
+
gdzieś, w jakimś innym, większym stringu. Mniejszy string może składać się
 
tez z prostych symboli-masek.
 
tez z prostych symboli-masek.
  
 
* *
 
* *
: Odpowiada dowolnej liczbie dowolnych znakow (użyteczne np. przy szukaniu stringa typu &bdquo;''obojętnie kto'' mowi: ty draniu ''obojętnie co''&rdquo;)
+
: Odpowiada dowolnej liczbie dowolnych znaków (użyteczne np. przy szukaniu stringa typu &bdquo;''obojętnie kto'' mówi: ty draniu ''obojętnie co''&rdquo;)
  
 
* ?
 
* ?
Linia 3513: Linia 3513:
  
 
* [xyz]
 
* [xyz]
: Porownuje dowolne znaki sposrod tych w nawiasach kwadratowych
+
: Porównuje dowolne znaki spośród tych w nawiasach kwadratowych
  
 
* [^xyz]
 
* [^xyz]
: Porownuje dowolne znaki nie bedace pomiedzy nawiasami kwadratowymi
+
: Porównuje dowolne znaki nie będące pomiędzy nawiasami kwadratowymi
 
* \c
 
* \c
: Porownuje c, nawet jesli jest to znak specjalny
+
: Porównuje c, nawet jeśli jest to znak specjalny
  
 
     int wildmatch(string matryca, string str);
 
     int wildmatch(string matryca, string str);
 
np.
 
np.
         // Cokolwiek, co sie konczy na .foo
+
         // Cokolwiek, co się kończy na .foo
 
         wildmatch("*.foo", "bar.foo") == 1
 
         wildmatch("*.foo", "bar.foo") == 1
         // Cokolwiek zaczynajacego sie od a, b lub c i zawierajacego
+
         // Cokolwiek zaczynającego się od a, b lub c i zawierającego
         // conajmniej jeden znak wiecej
+
         // conajmniej jeden znak więcej
 
         wildmatch("[abc]?*", "axy") == 1
 
         wildmatch("[abc]?*", "axy") == 1
 
         wildmatch("[abc]?*", "dxy") == 0
 
         wildmatch("[abc]?*", "dxy") == 0
Linia 3535: Linia 3535:
  
 
Nieraz zachodzi potrzeba przechowania sporej liczby informacji typu
 
Nieraz zachodzi potrzeba przechowania sporej liczby informacji typu
&lsquo;tak/nie&rsquo;. Bardzo prostym i przy okazji niezbyt dobrym sposobem byloby
+
&lsquo;tak/nie&rsquo;. Bardzo prostym i przy okazji niezbyt dobrym sposobem byłoby
stworzenie sporej liczby integerow, po jednym na kazda informacje
+
stworzenie sporej liczby integerów, po jednym na każdą informacje
i wstawianie w nie 0 albo 1, zeby przedstawic jakis stan. Daje to latwy
+
i wstawianie w nie 0 albo 1, żeby przedstawić jakiś stan. Daje to łatwy
dostep i jest zrozumiale, ale jak przychodzi do przechowania wiekszej ilosci
+
dostęp i jest zrozumiałe, ale jak przychodzi do przechowania większej ilości
informacji, to sie zaczynaja problemy z straszna pamieciozernoscia
+
informacji, to się zaczynają problemy z straszną pamięciożernością
 
tej metody.
 
tej metody.
  
Zamiast tego, mozna uzywac stringow, gdzie kazdy bit w znaku (jest ich
+
Zamiast tego, można używać stringów, gdzie każdy bit w znaku (jest ich
8 na znak) moze przechowywac informacje typu tak/nie. Maksymalna liczba
+
8 na znak) może przechowywać informacje typu tak/nie. Maksymalna liczba
bitow w lancuchu wynosi okolo 1200 = dlugosc stringa okolo 150 znakow.
+
bitów w łańcuchu wynosi około 1200 = długość stringa około 150 znaków.
Choc raczej watpie, ze wykorzystasz je wszystkie.
+
Choć raczej wątpię, że wykorzystasz je wszystkie.
  
Poszczegolne bity ustawia sie przy pomocy efunkcji &lsquo;set_bit()&rsquo;, ktora
+
Poszczególne bity ustawia się przy pomocy efunkcji &lsquo;set_bit()&rsquo;, która
wymaga dwoch argumentow. Pierwszym jest zmienna typu string, w ktorej
+
wymaga dwóch argumentów. Pierwszym jest zmienna typu string, w której
bit ma byc ustawiony, drugim zas numer bitu, ktory chcesz wlaczyc.
+
bit ma być ustawiony, drugim zaś numer bitu, który chcesz włączyć.
&lsquo;clear_bit()&rsquo; dziala analogicznie do &lsquo;set_bit()&rsquo;, tylko ze zeruje(wylacza)  
+
&lsquo;clear_bit()&rsquo; działa analogicznie do &lsquo;set_bit()&rsquo;, tylko że zeruje(wyłącza)  
podany bit. Jesli chcesz sprawdzic jaka wartosc zawiera dany bit, to
+
podany bit. Jeśli chcesz sprawdzić jaką wartość zawiera dany bit, to
powinienes uzyc efunkcji &lsquo;test_bit()&rsquo;.
+
powinieneś użyć efunkcji &lsquo;test_bit()&rsquo;.
  
Nie musisz inicjalizowac stringow, ktore chcesz wykorzystac do
+
Nie musisz inicjalizować stringów, które chcesz wykorzystać do
przechowywania bitow. Zarowno &lsquo;set_bit()&rsquo; jak i &lsquo;clear_bit()&rsquo; zwracaja
+
przechowywania bitów. Zarówno &lsquo;set_bit()&rsquo; jak i &lsquo;clear_bit()&rsquo; zwracają
zmodyfikowany string, a w przypadku gdy nie jest on wystarczajaco szeroki
+
zmodyfikowany string, a w przypadku gdy nie jest on wystarczająco szeroki
to zostanie rozszerzony przez &lsquo;set_bit()&rsquo;. &lsquo;clear_bit()&rsquo; jednakze nie
+
to zostanie rozszerzony przez &lsquo;set_bit()&rsquo;. &lsquo;clear_bit()&rsquo; jednakże nie
skroci stringa.
+
skróci stringa.
  
 
     string set_bit(string bitstr, int numer_bitu)
 
     string set_bit(string bitstr, int numer_bitu)
Linia 3583: Linia 3583:
 
   [time, ctime, file_time, last_reference_time, object_time]
 
   [time, ctime, file_time, last_reference_time, object_time]
  
Z jakiejs nieznanej przyczyny wszystkie pomiary czasu w UNIXe, a co za tym  
+
Z jakiejś nieznanej przyczyny wszystkie pomiary czasu w UNIXe, a co za tym  
idzie w mudzie, zaczynaja sie od 1 stycznia 1970 roku. Byc moze tworcy
+
idzie w mudzie, zaczynają się od 1 stycznia 1970 roku. Być może twórcy
tego systemu wymyslili sobie, ze z komputerowego punktu widzenia nie ma
+
tego systemu wymyślili sobie, że z komputerowego punktu widzenia nie ma
powodu, by chciec ustawic jakas wczesniejsza date. W kazdym razie tak jest
+
powodu, by chcieć ustawić jakaś wcześniejszą datę. W każdym razie tak jest
i nic na to nie mozna poradzic. Mierniki czasu sa integerami i zliczaja
+
i nic na to nie można poradzić. Mierniki czasu integerami i zliczają
czas od wyzej wymienionej daty w sekundkach.
+
czas od wyżej wymienionej daty w sekundkach.
  
Efunkcja &lsquo;time()&rsquo; zwraca aktualny czas. Mozesz wykorzystac ja
+
Efunkcja &lsquo;time()&rsquo; zwraca aktualny czas. Możesz wykorzystać ją
w tej postaci, albo przekonwertowac zwracana wartosc w jakis zrozumialy
+
w tej postaci, albo przekonwertować zwracaną wartość w jakiś zrozumiały
string przy pomocy efunkcji &lsquo;ctime()&rsquo;. W celu otrzymania czasu, w ktorym
+
string przy pomocy efunkcji &lsquo;ctime()&rsquo;. W celu otrzymania czasu, w którym
plik zostal utworzony, uzywa sie efunkcji &lsquo;file_time()&rsquo;. Istnieje
+
plik został utworzony, używa się efunkcji &lsquo;file_time()&rsquo;. Istnieje
analogiczna efunkcja odnoszaca sie do obiektow &ndash; &lsquo;object_time()&rsquo;.
+
analogiczna efunkcja odnosząca się do obiektów &ndash; &lsquo;object_time()&rsquo;.
  
Warto czasem wiedziec kiedy ostatni raz obiekt byl uzywany, tzn. kiedy
+
Warto czasem wiedzieć kiedy ostatni raz obiekt był używany, tzn. kiedy
ostatni raz byla wywolana w nim jakas funkcja. Jesli jako pierwsza
+
ostatni raz była wywołana w nim jakaś funkcja. Jeśli jako pierwszą
instrukcje wywolasz &lsquo;last_reference_time()&rsquo; to otrzymasz ten czas.
+
instrukcje wywołasz &lsquo;last_reference_time()&rsquo; to otrzymasz ten czas.
Pamietaj jednakze, ze po wykonaniu tej funkcji, czas ostatniego wywolania
+
Pamiętaj jednakże, że po wykonaniu tej funkcji, czas ostatniego wywołania
przyjmie wartosc czasu biezacego.
+
przyjmie wartość czasu bieżącego.
  
 
     int time()
 
     int time()
Linia 3607: Linia 3607:
 
     int object_time(object ob)
 
     int object_time(object ob)
 
     np.
 
     np.
         // last_reference_time() wywolujemy jako pierwsze
+
         // last_reference_time() wywołujemy jako pierwsze
         write("Ten obiekt ostatni raz byl uzyty " +  
+
         write("Ten obiekt ostatni raz był użyty " +  
 
             ctime(last_reference_time()) + "\n");
 
             ctime(last_reference_time()) + "\n");
 
         write("Aktualny czas: " + ctime(time()) + ".\n");
 
         write("Aktualny czas: " + ctime(time()) + ".\n");
Linia 3617: Linia 3617:
  
 
   [explode, implode]
 
   [explode, implode]
Istnieje mozliwosc podzielenia stringa na mniejsze kawalki na podstawie
+
Istnieje możliwość podzielenia stringa na mniejsze kawałki na podstawie
jakiegos innego lancucha, albo sklejenia roznych stringow umieszczonych
+
jakiegoś innego łańcucha, albo sklejenia rżźnych stringów umieszczonych
w tablicy w jeden. Do tego celu sluza efunkcje &lsquo;explode()&rsquo; i &lsquo;implode()&rsquo;.
+
w tablicy w jeden. Do tego celu służą efunkcje &lsquo;explode()&rsquo; i &lsquo;implode()&rsquo;.
  
Efunkcja &lsquo;explode()&rsquo; wymaga dwoch argumentow: pierwszym jest string,
+
Efunkcja &lsquo;explode()&rsquo; wymaga dwóch argumentów: pierwszym jest string,
ktory chce sie podzielic, drugim zas jakis inny string, ktorego &lsquo;explode()&rsquo;
+
który chce się podzielić, drugim zaś jakiś inny string, którego &lsquo;explode()&rsquo;
szuka w wiekszym jako znacznika, gdzie go podzielic
+
szuka w większym jako znacznika, gdzie go podzielić
(np. explode(jakis_tekst, " ") zwroci jakis_tekst podzielony na slowa;
+
(np. explode(jakis_tekst, " ") zwróci jakiś_tekst podzielony na słowa;
  znacznikiem dzielacym jest tutaj spacja).  
+
  znacznikiem dzielącym jest tutaj spacja).  
Efunckja zwraca tablice skladajaca sie z podzielonych stringow. &lsquo;implode()&rsquo;  
+
Efunkcja zwraca tablice składającą się z podzielonych stringów. &lsquo;implode()&rsquo;  
jako argumentow wymaga tablicy i stringa, a zwraca string skladajacy sie
+
jako argumentów wymaga tablicy i stringa, a zwraca string składający się
z posklejanych elementow tablicy, polaczonych stringiem z drugiego
+
z posklejanych elementów tablicy, połączonych stringiem z drugiego
 
argumentu.
 
argumentu.
  
Linia 3634: Linia 3634:
 
     string implode(string *lista_str, string str_laczacy)
 
     string implode(string *lista_str, string str_laczacy)
 
     np.
 
     np.
         string owoce = "jablko i banan i ananas " +
+
         string owoce = "jabłko i banan i ananas " +
                         "i pomarancz i fatty ktory je to wszystko";
+
                         "i pomarancz i fatty który je to wszystko";
 
         string *lista_owocow;
 
         string *lista_owocow;
 
      
 
      
         lista_owocow = explode(owoce, " i ");
+
         lista_owoców = explode(owoce, " i ");
 
         dump_array(lista_owocow);
 
         dump_array(lista_owocow);
 
      
 
      
 
         /* Efektem bedzie:
 
         /* Efektem bedzie:
 
             (Array)
 
             (Array)
             [0] = (string) "jablko"
+
             [0] = (string) "jabłko"
 
             [1] = (string) "banan"
 
             [1] = (string) "banan"
 
             [2] = (string) "ananas"
 
             [2] = (string) "ananas"
             [3] = (string) "pomarancz"
+
             [3] = (string) "pomarańcz"
             [4] = (string) "fatty ktory je to wszystko"
+
             [4] = (string) "fatty który je to wszystko"
 
           */
 
           */
 
      
 
      
Linia 3653: Linia 3653:
 
           write(owoce + "\n");
 
           write(owoce + "\n");
 
      
 
      
         // Efektem bedzie:
+
         // Efektem będzie:
         // jablko, banan, ananas, pomarancz, fatty ktory je to wszystko
+
         // jabłko, banan, ananas, pomarańcz, fatty który je to wszystko
  
 
==== Funkcje obsługujące tablice ====
 
==== Funkcje obsługujące tablice ====
Linia 3660: Linia 3660:
 
   [allocate, member_array, sizeof, pointerp]
 
   [allocate, member_array, sizeof, pointerp]
  
Zaczne od malej powtorki z tablic. Moga one zawierac dowolne typy danych,  
+
Zacznę od małej powtórki z tablic. Mogą one zawierać dowolne typy danych,  
wlaczajac w to inne tablice. Pamietaj o tym, ze tablice w przeciwienstwie
+
włączając w to inne tablice. Pamiętaj o tym, że tablice w przeciwieństwie
do typow danych (poza mappingami) sa kopiowane poprzez odnosnik, a nie  
+
do typów danych (poza mappingami) kopiowane poprzez odnośnik, a nie  
poprzez wartosc. Oznacza to, ze gdy przypisujesz tablice zmiennej, '''nie'''  
+
poprzez wartość. Oznacza to, że gdy przypisujesz tablice zmiennej, '''nie'''  
kopiujesz jej, a jedynie przechowujesz odnosnik, wskaznik do tablicy  
+
kopiujesz jej, a jedynie przechowujesz odnośnik, wskaźnik do tablicy  
 
w zmiennej.
 
w zmiennej.
  
Linia 3685: Linia 3685:
 
           */
 
           */
  
A wiec jak widzisz, zmiana zawartosci tablicy &lsquo;arr2&rsquo; zmienia rowniez
+
A więc jak widzisz, zmiana zawartości tablicy &lsquo;arr2&rsquo; zmienia również
zawartosc tablicy &lsquo;arr1&rsquo;. Zeby uczynic ja unikalna, musisz wpierw wykonac
+
zawartość tablicy &lsquo;arr1&rsquo;. Żeby uczynić ją unikalną, musisz wpierw wykonać
kopie &lsquo;arr1&rsquo;, na przyklad poprzez dodanie do niej pustej tablicy &lsquo;({ })&rsquo;.
+
kopie &lsquo;arr1&rsquo;, na przykład poprzez dodanie do niej pustej tablicy &lsquo;({ })&rsquo;.
  
Jak juz wiesz, tablice beda automatycznie zaalokowane poprzez zwykle
+
Jak już wiesz, tablice będą automatycznie zaalokowane poprzez zwykłe
wpisanie czegos w nie badz poprzez dodanie elementu lub innej tablicy.
+
wpisanie czegoś w nie bądź poprzez dodanie elementu lub innej tablicy.
Jesli jednak chcesz natychmiast zaalokowac tablice do odpowiedniego rozmiaru
+
Jeśli jednak chcesz natychmiast zaalokować tablice do odpowiedniego rozmiaru
to mozesz uzyc efunkcji &lsquo;allocate()&rsquo;. Rozmiar podaje sie jako jedyny
+
to możesz użyć efunkcji &lsquo;allocate()&rsquo;. Rozmiar podaje się jako jedyny
argument. Funkcja zainicjalizuje podana liczbe elementow i ustawi je
+
argument. Funkcja zainicjalizuje podaną liczbę elementów i ustawi je
wszystkie na 0, niezaleznie od typu tablicy.
+
wszystkie na 0, niezależnie od typu tablicy.
  
 
     mixed *allocate(int rozmiar)
 
     mixed *allocate(int rozmiar)
Linia 3701: Linia 3701:
 
      
 
      
 
         str_tabl = allocate(3);
 
         str_tabl = allocate(3);
         str_tabl[1] = "Fatty jest sflaczalym szowinista";
+
         str_tabl[1] = "Fatty jest sflaczałym szowinistą";
 
         dump_array(str_tabl);
 
         dump_array(str_tabl);
 
      
 
      
Linia 3708: Linia 3708:
 
             (Array)
 
             (Array)
 
             [0] = (int) 0
 
             [0] = (int) 0
             [1] = (string) "Fatty jest sflaczalym szowinista"
+
             [1] = (string) "Fatty jest sflaczałym szowinistą"
 
             [2] = (int) 0
 
             [2] = (int) 0
 
           */
 
           */
  
Jesli chcesz sprawdzic, czy dane wyrazenie jest elementem tablicy
+
Jeśli chcesz sprawdzić, czy dane wyrażenie jest elementem tablicy
i jesli tak, to jaki jest indeks tego elementu, to mozesz skorzystac
+
i jeśli tak, to jaki jest indeks tego elementu, to możesz skorzystać
z efunkcji &lsquo;member_array()&rsquo;, podajac jako argumenty tablice i szukany  
+
z efunkcji &lsquo;member_array()&rsquo;, podając jako argumenty tablice i szukany  
element. Funkcja zwroci numer indeksu, jesli znajdzie element, albo -1
+
element. Funkcja zwróci numer indeksu, jeśli znajdzie element, albo -1
gdy poszukiwania zakoncza sie niepowodzeniem. Jesli w tablicy bedzie
+
gdy poszukiwania zakończa się niepowodzeniem. Jeśli w tablicy będzie
wiecej odpowiadajacych elementow, to &lsquo;member_array()&rsquo; zwroci indeks  
+
więcej odpowiadających elementów, to &lsquo;member_array()&rsquo; zwróci indeks  
 
pierwszego z nich.
 
pierwszego z nich.
  
Linia 3725: Linia 3725:
 
         int indeks;
 
         int indeks;
 
      
 
      
         // Wszystkie '5' zostana zastapione przez '33'
+
         // Wszystkie '5' zostaną zastąpione przez '33'
 
         while ((indeks = member_array(5, tablica)) != -1)
 
         while ((indeks = member_array(5, tablica)) != -1)
 
             tab[indeks] = 33;
 
             tab[indeks] = 33;
  
Bardzo wazna efunkcja dotyczaca tablic jest &lsquo;sizeof()&rsquo;. Zwraca ona
+
Bardzo wazna efunkcja dotyczącą tablic jest &lsquo;sizeof()&rsquo;. Zwraca ona
rozmiar podanej tablicy, tzn. liczbe elementow znajdujacych sie w niej.
+
rozmiar podanej tablicy, tzn. liczbę elementów znajdujących się w niej.
Czesto zachodzi potrzeba napisania petli oblatujacej wszystkie elementy
+
Czesto zachodzi potrzeba napisania pętli oblatującej wszystkie elementy
 
tablicy, albo po prostu znalezienia indeksu ostatniego elementu i wtedy  
 
tablicy, albo po prostu znalezienia indeksu ostatniego elementu i wtedy  
ta efunkcja sie bardzo przydaje.
+
ta efunkcja się bardzo przydaje.
  
 
'''UWAGA!''' Indeks ostatniego elementu wynosi rozmiar_tablicy - 1 :
 
'''UWAGA!''' Indeks ostatniego elementu wynosi rozmiar_tablicy - 1 :
(sizeof(tablica) - 1), gdyz numeracja indeksow zaczyna sie od 0.
+
(sizeof(tablica) - 1), gdyż numeracja indeksów zaczyna się od 0.
  
 
     int sizeof(mixed tab)
 
     int sizeof(mixed tab)
 
     np.
 
     np.
         string *tab = ({ "Fatty", "szownita" });
+
         string *tab = ({ "Fatty", "szowinitsa" });
 
      
 
      
         write(implode(tab, " ") + " jest zle.\n");
+
         write(implode(tab, " ") + " jest źle.\n");
 
      
 
      
 
         tab[sizeof(tab) - 1] = "szowinista";
 
         tab[sizeof(tab) - 1] = "szowinista";
Linia 3748: Linia 3748:
 
         write(implode(tab, " ") + " jest poprawnie.\n");
 
         write(implode(tab, " ") + " jest poprawnie.\n");
  
Efunkcja &lsquo;pointerp()&rsquo; moze byc zastosowana do sprawdzenia, czy zmienna
+
Efunkcja &lsquo;pointerp()&rsquo; może być zastosowana do sprawdzenia, czy zmienna
zawiera tablice (dowolnego typu), czy nie. Jest bardzo przydata, jesli
+
zawiera tablice (dowolnego typu), czy nie. Jest bardzo przydatna, jeśli
masz doczynienia z funkcjami, ktore moga zwrocic 0 (wartosc NULL), gdy
+
masz do czynienia z funkcjami, które mogą zwrócić 0 (wartość NULL), gdy
cos pojdzie nie tak jak powinno.
+
coś pójdzie nie tak jak powinno.
  
 
     int pointerp(mixed tab)
 
     int pointerp(mixed tab)
Linia 3758: Linia 3758:
 
      
 
      
 
         if (pointerp((tab = find_player("zdzichu")->pobierz_gildie())))
 
         if (pointerp((tab = find_player("zdzichu")->pobierz_gildie())))
             write("Zdzichu nalezy do: " + implode(tab, ", ") + ".\n");
+
             write("Zdzichu należy do: " + implode(tab, ", ") + ".\n");
 
         else
 
         else
             write("Zdzichu nie nalezy do zadnej gildii!.\n");
+
             write("Zdzichu nie należy do żadnej gildii!.\n");
  
 
==== Funkcje obsługujące Mappingi ====
 
==== Funkcje obsługujące Mappingi ====
Linia 3767: Linia 3767:
 
   m_restore_object, m_save_object]
 
   m_restore_object, m_save_object]
  
Tak jak to wczesniej mowilem, mappingi sa listami indeksow powiazanych
+
Tak jak to wcześniej mówiłem, mappingi listami indeksów powiązanych
z wartoscami. Podajac indeks, otrzymujesz przypisana do niego wartosc.
+
z wartościami. Podając indeks, otrzymujesz przypisana do niego wartość.
Zawartosc mappingow jest ulozona w specjalnie posortowany sposob, dzieki
+
Zawartość mappingów jest ułożona w specjalnie posortowany sposób, dzięki
czemu dostep do nich jest bardzo szybki. Jednakze maja pewna wade &ndash; sa
+
czemu dostęp do nich jest bardzo szybki. Jednakże mają pewną wadę &ndash;
strasznie pamieciozerne i zuzywaja bardzo duzo miejsca w porownaniu do
+
strasznie pamięciożerne i zużywają bardzo dużo miejsca w porównaniu do
 
tablic.
 
tablic.
  
Jak sie alokuje mappingi? Jest to bardzo proste. Wystarczy, ze
+
Jak się alokuje mappingi? Jest to bardzo proste. Wystarczy, ze
zadeklarujesz go, a potem przypiszesz jedna wartosc do indeksu. Jesli
+
zadeklarujesz go, a potem przypiszesz jedna wartość do indeksu. Jeśli
indeks bedzie juz istnial, wartosc przy nim zostanie zastapiona nowa.
+
indeks będzie już istniał, wartość przy nim zostanie zastąpiona nową.
A jesli nie, to zostanie dodana nowa para. Do tworzenia mappingow mozna
+
A jeśli nie, to zostanie dodana nowa para. Do tworzenia mappingów można
tez uzyc efunkcji &lsquo;mkmapping()&rsquo; i jako argumenty podac dwie tablice, jedna
+
tez użyć efunkcji &lsquo;mkmapping()&rsquo; i jako argumenty podać dwie tablice, jedną
z samymi indeksami, a druga z wartosciami. Pamietaj tylko, ze '''musza''' miec
+
z samymi indeksami, a drugą z wartościami. Pamiętaj tylko, że '''muszą''' mieć
 
one taki sam rozmiar.
 
one taki sam rozmiar.
  
Linia 3797: Linia 3797:
 
         mp = mkmapping(ind_tab, wart_tab);
 
         mp = mkmapping(ind_tab, wart_tab);
 
      
 
      
         // Mozesz oczywiscie podac te tablice bezposrednio,
+
         // Możesz oczywiście podać te tablice bezpośrednio,
         // bez poslugiwania sie zmiennymi
+
         // bez posługiwania się zmiennymi
  
Tak jak w tablicach, tu tez jest funkcja sprawdzajaca czy dana zmienna
+
Tak jak w tablicach, tu też jest funkcja sprawdzająca czy dana zmienna
zawiera mapping, czy nie. Jest nia &lsquo;mappingp()&rsquo;. Uzywaj jej do tego samego
+
zawiera mapping, czy nie. Jest nią &lsquo;mappingp()&rsquo;. Używaj jej do tego samego
celu, tzn. gdy jakas funkcja moze, ale nie musi zwracac mappingu, a ty  
+
celu, tzn. gdy jakaś funkcja może, ale nie musi zwracać mappingu, a ty  
chcesz miec pewnosc zanim zaczniesz indeksowac zwracana wartosc.
+
chcesz mieć pewność zanim zaczniesz indeksować zwracaną wartosć.
  
Do znajdywania rozmiaru mappingu moze ci posluzyc efunkcja &lsquo;m_sizeof()&rsquo;.
+
Do znajdywania rozmiaru mappingu może ci posłużyć efunkcja &lsquo;m_sizeof()&rsquo;.
Dziala dokladnie tak samo, jak odpowiednik u tablic, zwracajac liczbe
+
Działa dokładnie tak samo, jak odpowiednik u tablic, zwracając liczbę
elementow (par) w mappingu.
+
elementów (par) w mappingu.
  
W mappingach usuwanie elementow juz nie jest takie proste, jak w tablicach.
+
W mappingach usuwanie elementów już nie jest takie proste, jak w tablicach.
Sluzy do tego efunkcja &lsquo;m_delete()&rsquo;. Jej dzialanie nie polega na
+
Służy do tego efunkcja &lsquo;m_delete()&rsquo;. Jej działanie nie polega na
bezposrednim usunieciu elementu, tylko na stworzeniu nowego mappingu
+
bezpośrednim usunięciu elementu, tylko na stworzeniu nowego mappingu
i przekopiowaniu tam zawartosci starego bez podanego elementu.
+
i przekopiowaniu tam zawartości starego bez podanego elementu.
 
     mapping m_delete(mapping map, mixed elem)
 
     mapping m_delete(mapping map, mixed elem)
 
     np.
 
     np.
Linia 3832: Linia 3832:
 
           */
 
           */
  
A jak zdobyc wszystkie elementy mappingu? Na przyklad chcielibysmy
+
A jak zdobyć wszystkie elementy mappingu? Na przykład chcielibyśmy
jakiejs odwrotnosci &lsquo;mkmapping()&rsquo;. Do tego sluza dwie funkcje:
+
jakiejś odwrotności &lsquo;mkmapping()&rsquo;. Do tego służą dwie funkcje:
&lsquo;m_indices()&rsquo; oraz &lsquo;m_values()&rsquo;, ktore zwracaja (kolejno) wszystkie
+
&lsquo;m_indices()&rsquo; oraz &lsquo;m_values()&rsquo;, które zwracają (kolejno) wszystkie
indeksy oraz wszystkie wartosci danego mappingu.
+
indeksy oraz wszystkie wartości danego mappingu.
  
Doszlismy do dosyc niestabilnej kwestii &ndash; kolejnosci elementow
+
Doszliśmy do dosyć niestabilnej kwestii &ndash; kolejności elementów
w mappingach. Jak wczesniej mowilem, mappingi nie maja jej na stale
+
w mappingach. Jak wcześniej mówiłem, mappingi nie mają jej na stałe
zdefiniowanej. Tzn. maja, ale nie jest to temat ktorym powinienes sobie
+
zdefiniowanej. Tzn. mają, ale nie jest to temat którym powinieneś sobie
zawracac glowe. Zmienia sie ona gdy dodajesz, albo usuwasz jakas pare.
+
zawracać głowę. Zmienia się ona gdy dodajesz, albo usuwasz jakąś parę.
W kazdym razie wazne jest to, ze gdy pobierzesz wszystkie indeksy i wartosci
+
W każdym razie ważne jest to, że gdy pobierzesz wszystkie indeksy i wartości
z jakiegos mappinga (za pomoca m_indices() i m_values()), to elementy  
+
z jakiegoś mappinga (za pomocą m_indices() i m_values()), to elementy  
otrzymanych tablic '''beda''' sobie odpowiadaly '''o ile''' pomiedzy pobieraniami  
+
otrzymanych tablic '''będą''' sobie odpowiadały '''o ile''' pomiędzy pobieraniami  
nie wykonywales zadnych operacji na tym mappingu.
+
nie wykonywałeś żadnych operacji na tym mappingu.
  
 
     mixed m_indices(mapping mapp);
 
     mixed m_indices(mapping mapp);
 
     mixed m_values(mapping mapp);
 
     mixed m_values(mapping mapp);
 
     np.
 
     np.
         // Funkcja wyswietla mapping i jego zawartosc
+
         // Funkcja wyświetla mapping i jego zawartość
 
         void
 
         void
 
         dump_mapping(mapping mp)
 
         dump_mapping(mapping mp)
Linia 3856: Linia 3856:
 
             mixed ind, war;
 
             mixed ind, war;
 
      
 
      
             ind = m_indices(mp); // Pomiedzy tymi dwoma intrukcjami, nie
+
             ind = m_indices(mp); // Pomiędzy tymi dwoma instrukcjami, nie
             war = m_values(mp); // powinno byc zadnych dzialan na mappingu.
+
             war = m_values(mp); // powinno być żadnych działań na mappingu.
 
      
 
      
 
             sz = sizeof(ind);
 
             sz = sizeof(ind);
Linia 3866: Linia 3866:
 
         }
 
         }
 
      
 
      
         /* Na przyklad uruchamiamy: dump_mapping(([ "fatty" : "grubas",
+
         /* Na przykład uruchamiamy: dump_mapping(([ "fatty" : "grubas",
 
           *                                          "lewy" : "wielki",
 
           *                                          "lewy" : "wielki",
 
           *                                          "alvin" : "straszny"
 
           *                                          "alvin" : "straszny"
Linia 3877: Linia 3877:
 
           */
 
           */
  
Sa, albo beda dwie funkcje, ktore zapisuja i odtwarzaja dane obiektu.
+
, albo będą dwie funkcje, które zapisują i odtwarzają dane obiektu.
Niestety, jak na razie maja one jeszcze bledy i nie dzialaja dokladnie tak,
+
Niestety, jak na razie mają one jeszcze błędy i nie działają dokładnie tak,
jakbysmy sobie tego zyczyli. Powinny one funkcjonowac w ten sposob:
+
jakbyśmy sobie tego życzyli. Powinny one funkcjonować w ten sposób:
&lsquo;m_save_object()&rsquo; stworzy mapping, zawierajacy wszystkie globalne,
+
&lsquo;m_save_object()&rsquo; stworzy mapping, zawierający wszystkie globalne,
nie-statyczne zmienne, ktorych nazwy beda indeksami. Bedziesz mogl wtedy
+
nie-statyczne zmienne, których nazwy będą indeksami. Będziesz mógł wtedy
zgrac go bezposrednio do pliku, albo przekazac go dalej, jako argument
+
zgrać go bezpośrednio do pliku, albo przekazać go dalej, jako argument
jakiejs funkcji. Odwrotnoscia tego bedzie funkcja &lsquo;m_restore_object()&rsquo;.
+
jakiejś funkcji. Odwrotnością tego będzie funkcja &lsquo;m_restore_object()&rsquo;.
Bedzie ona przyjmowala mapping jako argument, rozkladala jego elementy
+
Będzie ona przyjmowała mapping jako argument, rozkładała jego elementy
ustawiajac zmiennym globalnym odpowiadajace wartosci.
+
ustawiając zmiennym globalnym odpowiadające wartości.
  
 
==== Konwersja typów ====
 
==== Konwersja typów ====
Linia 3891: Linia 3891:
 
   [atoi, atof, ftoa, itof, ftoi, str2val, val2str, sscanf]
 
   [atoi, atof, ftoa, itof, ftoi, str2val, val2str, sscanf]
  
Wiekszosc rzeczy wpisywanych przez gracza to stringi; wpisujesz cos
+
Większość rzeczy wpisywanych przez gracza to stringi; wpisujesz coś
i gra powinna odpowiednio na to zareagowac. Wymaga to istnienia funkcji,
+
i gra powinna odpowiednio na to zareagować. Wymaga to istnienia funkcji,
ktore zanalizuja twoje komendy i przetlumacza to na wartosci, ktorych mozesz
+
które zanalizują twoje komendy i przetłumaczą to na wartości, których możesz
uzyc w swoich programach. Analiza skladni komend, jest lekko mowiac '''bardzo'''
+
użyć w swoich programach. Analiza składni komend, jest lekko mówiąc '''bardzo'''
skomplikowana i zostawiam to na trzeci rozdzial. Teraz skupmy sie tylko
+
skomplikowana i zostawiam to na trzeci rozdział. Teraz skupmy się tylko
na konwertowaniu samych wartosci. Jak mowilem, to co gracze wpisuja jest
+
na konwertowaniu samych wartości. Jak mówiłem, to co gracze wpisują jest
w formie stringow, przez co zamienianie stringow w integery oraz floaty
+
w formie stringów, przez co zamienianie stringów w integery oraz floaty
i vice versa moze byc dosyc pozyteczna umiejetnoscia.
+
i vice versa może być dosyć pożyteczną umiejętnością.
  
Zacznijmy od integerow. Zalozmy, ze otrzymales string zawierajacy
+
Zacznijmy od integerów. Załóżmy, że otrzymałeś string zawierający
jakas wartosc numeryczna i chcesz jej uzyc do jakis obliczen. Musisz
+
jakąś wartość numeryczną i chcesz jej użyć do jakichś obliczeń. Musisz
wiec przeksztalcic stringa w integera. Sluzy do tego efunkcja &lsquo;atoi()&rsquo;;
+
więc przekształcić stringa w integera. Służy do tego efunkcja &lsquo;atoi()&rsquo;;
 
podajesz string jako argument i otrzymujesz integer &ndash; bardzo proste.
 
podajesz string jako argument i otrzymujesz integer &ndash; bardzo proste.
Lancuch nie moze jednak zawierac zadnych znakow innych niz cyfry.
+
Łańcuch nie może jednak zawierać żadnych znaków innych niż cyfry.
W przeciwnym wypadku funkcja zwroci 0.
+
W przeciwnym wypadku funkcja zwróci 0.
  
 
     int atoi(string str)
 
     int atoi(string str)
Linia 3915: Linia 3915:
 
         write("23 + 3 = " + (war + 3) + "\n");
 
         write("23 + 3 = " + (war + 3) + "\n");
  
Liczby zmiennopozycyjne (floaty) maja analogiczna efunkcje, &lsquo;atof()&rsquo;,
+
Liczby zmiennopozycyjne (floaty) mają analogiczna efunkcje, &lsquo;atof()&rsquo;,
ktora przeksztalca stringa we floata. Jak juz wiesz, floaty nie moga
+
która przekształca stringa we floata. Jak już wiesz, floaty nie mogą
byc przekonwertowane w stringi w ten sam sposob, jak integery, czyli poprzez
+
być przekonwertowane w stringi w ten sam sposób, jak integery, czyli poprzez
 
dodanie ich do innego stringa. Funkcja &lsquo;ftoa()&rsquo; zamienia floata w stringa.
 
dodanie ich do innego stringa. Funkcja &lsquo;ftoa()&rsquo; zamienia floata w stringa.
Tak samo jak w przypadku &lsquo;atoi()&rsquo;, jesli w &lsquo;atof()&rsquo; podasz jakis string,  
+
Tak samo jak w przypadku &lsquo;atoi()&rsquo;, jeśli w &lsquo;atof()&rsquo; podasz jakiś string,  
w ktorym beda znaki nie-numeryczne, to zwroci 0 (wyjatkiem beda specjalne
+
w którym będą znaki nie-numeryczne, to zwróci 0 (wyjątkiem będą specjalne
 
znaki, takie jak np. &lsquo;.&rsquo;.
 
znaki, takie jak np. &lsquo;.&rsquo;.
  
Do zamiany pomiedzy integerem a floatem sluza efunkcje &lsquo;itof()&rsquo; oraz
+
Do zamiany pomiędzy integerem a floatem służą efunkcje &lsquo;itof()&rsquo; oraz
&lsquo;ftoi()&rsquo;. Pamietaj tylko, ze gdy zamieniasz floata w integera, to czesc
+
&lsquo;ftoi()&rsquo;. Pamiętaj tylko, że gdy zamieniasz floata w integera, to część
dziesietna nie jest zaokraglana, tylko obcinana.
+
dziesiętna nie jest zaokrąglana, tylko obcinana.
  
Jest wiele momentow, w ktorych mozesz chciec przechowac wartosc w stringu,
+
Jest wiele momentów, w których możesz chcieć przechować wartość w stringu,
a potem przemienic ja spowrotem. Sluza do tego efunkcje &lsquo;val2str()&rsquo; oraz
+
a potem przemienić ją z powrotem. Służą do tego efunkcje &lsquo;val2str()&rsquo; oraz
&lsquo;str2val()&rsquo;. Da sie wyswietlic zawartosc &lsquo;val2str()&rsquo;, ale nie do tego
+
&lsquo;str2val()&rsquo;. Da się wyświetlić zawartość &lsquo;val2str()&rsquo;, ale nie do tego
zostala ona stworzona. Mozesz przechowac dowolna wartosc zmiennej,
+
została ona stworzona. Możesz przechować dowolną wartość zmiennej,
korzystajac z tych funkcji
+
korzystając z tych funkcji
  
Najczesciej jednak uzywanym konwerterem danych jest efunkcja &lsquo;sscanf()&rsquo;.
+
Najczęściej jednak używanym konwerterem danych jest efunkcja &lsquo;sscanf()&rsquo;.
Mozesz sprecyzowac, gdzie ma ona szukac wartosci (podobnie jak  
+
Możesz sprecyzować, gdzie ma ona szukać wartości (podobnie jak  
w &lsquo;sprintf()&rsquo;), w celu pobrania ich i przechowania w jakiejs zmiennej.
+
w &lsquo;sprintf()&rsquo;), w celu pobrania ich i przechowania w jakiejś zmiennej.
Jest ona charakterystyczna, gdyz ustawia wartosci zmiennych podanych
+
Jest ona charakterystyczna, gdyż ustawia wartości zmiennych podanych
w argumencie, wiec niemozliwe jest pobranie jej adresu.
+
w argumencie, więc niemożliwe jest pobranie jej adresu.
Poza tym, dziala ona bardzo prosto. Podaje sie matryce, string do  
+
Poza tym, działa ona bardzo prosto. Podaje się matryce, string do  
przeszukania i zmienne, w ktorych dane powiny byc przechowane. Funkcja  
+
przeszukania i zmienne, w których dane powinny być przechowane. Funkcja  
zwraca liczbe znalezionych wartosci.
+
zwraca liczbę znalezionych wartości.
  
String, ktory podasz jako matryce, jest interpretowany doslownie,
+
String, który podasz jako matryce, jest interpretowany dosłownie,
z wyjatkiem ponizszych lancuchow kontrolnych.
+
z wyjątkiem poniższych łańcuchów kontrolnych.
  
 
* %d
 
* %d
: porownuje liczbe calkowita.
+
: porównuje liczbę całkowitą.
  
 
* %s
 
* %s
: porownuje lancuch znakow.
+
: porównuje łańcuch znaków.
  
 
* %f
 
* %f
: porownuje liczbe zmiennopozycyjna.
+
: porównuje liczbę zmiennopozycyjną.
  
 
* %%
 
* %%
: porownuje znak `%'.
+
: porównuje znak `%'.
  
 
     int sscanf(string str, string matryca, <zmienne>...);
 
     int sscanf(string str, string matryca, <zmienne>...);
Linia 3965: Linia 3965:
 
      
 
      
 
         /*
 
         /*
           * Zalozmy, ze zadawane jest pytanie :
+
           * Załóżmy, że zadawane jest pytanie :
           * "Jak ci sie wydaje, jak ciezki i szeroki jest Fatty?".
+
           * "Jak ci się wydaje, jak ciężki i szeroki jest Fatty?".
           * Oprocz tego zalozmy, ze odpowiedz jest dana w formie
+
           * Oprócz tego załóżmy, że odpowiedź jest dana w formie
           * '<ilosc> <rodzaj> i <ilosc> <rodzaj>', na przyklad:
+
           * '<ilość> <rodzaj> i <ilość> <rodzaj>', na przykład:
           * '4 metry i 3.2 tony'. Przyjmijmy, ze pierwsza wartosc moze byc
+
           * '4 metry i 3.2 tony'. Przyjmijmy, że pierwsza wartość może być
 
           * tylko integerem, a trzecia tylko floatem.
 
           * tylko integerem, a trzecia tylko floatem.
 
           *
 
           *
           * Zalozmy jeszcze, ze odpowiedz jest podana w zmiennej `orgstr'
+
           * Załóżmy jeszcze, że odpowiedź jest podana w zmiennej `orgstr'
 
           */
 
           */
 
      
 
      
Linia 3978: Linia 3978:
 
                     waga, typ_wagi) != 4)
 
                     waga, typ_wagi) != 4)
 
         {
 
         {
             write("Podaj pelna odpowiedz!\n");
+
             write("Podaj pełną odpowiedź!\n");
 
             return;
 
             return;
 
         }
 
         }
 
      
 
      
         write("Aha, uwazasz, ze Fatty jest szeroki na " + szer + " " +
+
         write("Aha, uważasz, że Fatty jest szeroki na " + szer + " " +
 
               typ_szer + " i wazy " + ftoa(waga) + " " + typ_wagi ".\n");
 
               typ_szer + " i wazy " + ftoa(waga) + " " + typ_wagi ".\n");
  
Linia 3990: Linia 3990:
 
sinh, cosh, tanh, asinh, acosh, atanh, abs, fact, sqrt]
 
sinh, cosh, tanh, asinh, acosh, atanh, abs, fact, sqrt]
  
Efunkcja &lsquo;random()&rsquo; zwraca dowolna liczbe calkowita z zakresu od 0,
+
Efunkcja &lsquo;random()&rsquo; zwraca dowolną liczbę całkowitą z zakresu od 0,
do podanej w argumencie liczby minus jeden. Na przyklad &lsquo;random(8)&rsquo;
+
do podanej w argumencie liczby minus jeden. Na przykład &lsquo;random(8)&rsquo;
zwroci losowa liczbe z zakresu 0-7.
+
zwróci losową liczbę z zakresu 0-7.
  
Reszta funkcji matematycznych pobiera jako argumenty i zwraca floaty. Funkcje trygonometryczne uzywaja radianow, nie stopni. Pamietaj o tym.
+
Reszta funkcji matematycznych pobiera jako argumenty i zwraca floaty. Funkcje trygonometryczne używają radianów, nie stopni. Pamiętaj o tym.
  
Oto pelna lista funkcji matematycznych:
+
Oto pełna lista funkcji matematycznych:
  
 
* float rnd()
 
* float rnd()
: Zwraca losowa liczbe z zakresu od 0 do 1
+
: Zwraca losową liczbę z zakresu od 0 do 1
  
 
* float sin(float)
 
* float sin(float)
: Oblicza sinus podanego kata.
+
: Oblicza sinus podanego kąta.
  
 
* float cos(float)
 
* float cos(float)
: Oblicza cosinus podanego kata.
+
: Oblicza cosinus podanego kąta.
  
 
* float tan(float)
 
* float tan(float)
: Oblicza tangens podanego kata.
+
: Oblicza tangens podanego kąta.
  
 
* float asin(float)
 
* float asin(float)
Linia 4021: Linia 4021:
 
* float atan2(float x, float y)
 
* float atan2(float x, float y)
 
: Compute the argument (phase) of a rectangular coordinate in the range -p to p.
 
: Compute the argument (phase) of a rectangular coordinate in the range -p to p.
: '''(!!!)''' &ndash; Hmm, ktos wie co to moze znaczyc?
+
: '''(!!!)''' &ndash; Hmm, ktoś wie co to może znaczyć?
  
 
* float exp(float)
 
* float exp(float)
 
: Compute the exponential function using the natural logarithm e as base
 
: Compute the exponential function using the natural logarithm e as base
: '''(!!!)''' &ndash; Rety, nastepny kwiatek... ln do potegi ?
+
: '''(!!!)''' &ndash; Rety, następny kwiatek... ln do potęgi ?
  
 
* float log(float)
 
* float log(float)
Linia 4031: Linia 4031:
  
 
* float sinh(float)
 
* float sinh(float)
: Oblicza wartosc sinusa hiperbolicznego.
+
: Oblicza wartość sinusa hiperbolicznego.
  
 
* float cosh(float)
 
* float cosh(float)
: Oblicza wartosc cosinusa hiperbolicznego.
+
: Oblicza wartość cosinusa hiperbolicznego.
  
 
* float tanh(float)
 
* float tanh(float)
: Oblicza wartosc tangensa hiperbolicznego.
+
: Oblicza wartość tangensa hiperbolicznego.
  
 
* float asinh(float)
 
* float asinh(float)
: Oblicza wartosc arcusa sinusa hiperbolicznego.
+
: Oblicza wartość arcus sinusa hiperbolicznego.
  
 
* float acosh(float)
 
* float acosh(float)
: Oblicza wartosc arcus cosinusa hiperbolicznego.
+
: Oblicza wartość arcus cosinusa hiperbolicznego.
  
 
* float atanh(float)
 
* float atanh(float)
: Oblicza wartosc arcus tangensa hiperbolicznego.
+
: Oblicza wartość arcus tangensa hiperbolicznego.
  
 
* float abs(float)
 
* float abs(float)
: Oblicza wartosc absolutna argumentu (modul).
+
: Oblicza wartość absolutną argumentu (moduł).
  
 
* float fact(float)
 
* float fact(float)
: Oblicza silnie (funkcja gamma) podanego argumentu.
+
: Oblicza silnię (funkcja gamma) podanego argumentu.
  
 
* float sqrt(float)
 
* float sqrt(float)
Linia 4062: Linia 4062:
 
read_bytes, write_file, read_file, file_size, file_time, rename, rm, ed]
 
read_bytes, write_file, read_file, file_size, file_time, rename, rm, ed]
  
Przechowywania informacji w plikach jest bardzo wazne. Zaraz pokaze ci  
+
Przechowywanie informacji w plikach jest bardzo ważne. Zaraz pokaże ci  
kilka funkcji, ktore ci w tym pomoga. Pozwol mi jednak strzelic male kazanie,  
+
kilka funkcji, które ci w tym pomogą. Pozwól mi jednak strzelić małe kazanie,  
na temat zuzycia CPU:
+
na temat zużycia CPU:
  
Odczyt i zapis plikow jest sprawa bardzo obciazajaca CPU. Moze nie
+
Odczyt i zapis plików jest sprawą bardzo obciążającą CPU. Może nie
w porownaniu do innych procesow, ale podczas operacji dyskowej CPU nie moze
+
w porównaniu do innych procesów, ale podczas operacji dyskowej CPU nie może
robic nic innego. Innymi slowy, wczytywanie i zapisywanie duzych porcji  
+
robić nic innego. Innymi słowy, wczytywanie i zapisywanie dużych porcji  
danych znacznie spowalnia gre. Zeby ograniczyc zbyt duze obciazanie pamieci,
+
danych znacznie spowalnia grę. Żeby ograniczyć zbyt duże obciążanie pamięci,
dysku i CPU, narzucono limit nie pozwalajacy obluzyc wiecej niz 50 kb
+
dysku i CPU, narzucono limit nie pozwalający obsłużyć więcej niż 50 kb
danych na raz. Pliki moga byc wieksze, ale ty nie mozesz zapisywac ni
+
danych na raz. Pliki mogą być większe, ale ty nie możesz zapisywać ani
wczytywac fragmentow wiekszych niz ten limit. Oznacza to, ze musisz  
+
wczytywać fragmentów większych niż ten limit. Oznacza to, że musisz  
podzielic prace nad wiekszymi plikami na kawalki wykonywane sekwencyjnie,
+
podzielic pracę nad większymi plikami na kawałki wykonywane sekwencyjnie,
z ewentualna przerwa pomiedzy nimi, zeby reszta gry tez miala czas na
+
z ewentualną przerwą pomiędzy nimi, żeby reszta gry też miała czas na
zrobienie czegos. Wiec, prosze pamietaj o tym, ze to ograniczenie nie
+
zrobienie czegoś. Więc, proszę pamietaj o tym, że to ograniczenie nie
zostalo stworzone, by ci utrudnic zycie i zeby bylo obchodzone roznymi
+
zostało stworzone, by ci utrudnić życie i żeby było obchodzone różnymi
kretymi sposobami, tylko aby ci przypominac, ze obciazasz zasoby muda
+
krętymi sposobami, tylko aby ci przypominać, że obciążasz zasoby muda
i ze powinienes innym tez dac zyc. Amen.
+
i że powinieneś innym też dać żyć. Amen.
  
Zacznijmy od bardzo prostej sprawy: nagrywania i odtwarzania obiektow.
+
Zacznijmy od bardzo prostej sprawy: nagrywania i odtwarzania obiektów.
Zazwyczaj, chce sie przechowac zmienne globalne obiektu w pliku, po to
+
Zazwyczaj, chce się przechować zmienne globalne obiektu w pliku, po to
by je pozniej odtworzyc. Do tego celu sluza efunkcje &lsquo;save_object()&rsquo; oraz
+
by je później odtworzyć. Do tego celu służą efunkcje &lsquo;save_object()&rsquo; oraz
&lsquo;restore_object()&rsquo;. W obu musisz podac sciezke wraz z nazwa pliku jako
+
&lsquo;restore_object()&rsquo;. W obu musisz podać ścieżkę wraz z nazwą pliku jako
argument i oczywiscie miec prawa do zapisu lub do odczytu. Wynikiem
+
argument i oczywiście mieć prawa do zapisu lub do odczytu. Wynikiem
&lsquo;save_object()&rsquo; bedzie plik, ktorego nazwa bedzie miala koncowke &lsquo;.o&rsquo;.
+
&lsquo;save_object()&rsquo; będzie plik, którego nazwa bedzie miala końcówkę &lsquo;.o&rsquo;.
Przy odtwarzaniu musisz pamietac o tym, by ja podac. Przy nagrywaniu to nie
+
Przy odtwarzaniu musisz pamiętać o tym, by ją podać. Przy nagrywaniu to nie
 
jest konieczne &ndash; funkcja sama doda rozszerzenie &lsquo;.o&rsquo;, gdy ty o tym zapomnisz.
 
jest konieczne &ndash; funkcja sama doda rozszerzenie &lsquo;.o&rsquo;, gdy ty o tym zapomnisz.
&lsquo;restore_object()&rsquo; zwraca 1, gdy odtwarzanie zakonczylo sie sukcesem, zas
+
&lsquo;restore_object()&rsquo; zwraca 1, gdy odtwarzanie zakończyło się sukcesem, zaś
w przeciwnym wypadku zwraca 0. Zawartoscia nagranego pliku jest lista
+
w przeciwnym wypadku zwraca 0. Zawartością nagranego pliku jest lista
wszystkich zmiennych globalnych z ich wartosciami, oddzielonymi spacjami.
+
wszystkich zmiennych globalnych z ich wartościami, oddzielonymi spacjami.
Format zapisu przechowanej zmiennej jest taki sam, jak w przypadku juz
+
Format zapisu przechowanej zmiennej jest taki sam, jak w przypadku już
omowionej funkcji &lsquo;val2str()&rsquo;.
+
omówionej funkcji &lsquo;val2str()&rsquo;.
  
Mappingi sa najbardziej wygodnym typem do przechowywania zmiennych.
+
Mappingi najbardziej wygodnym typem do przechowywania zmiennych.
Wystarczy umiescic dane w mappingu, gdzie indeksami sa nazwy zmiennych,
+
Wystarczy umieścić dane w mappingu, gdzie indeksami nazwy zmiennych,
a potem nagrac mapping przy pomocy efunkcji &lsquo;save_map()&rsquo;, by pozniej
+
a potem nagrać mapping przy pomocy efunkcji &lsquo;save_map()&rsquo;, by później
odtworzyc go poprzez &lsquo;restore_map()&rsquo;. Przewaga tej metody nad  
+
odtworzyć go poprzez &lsquo;restore_map()&rsquo;. Przewaga tej metody nad  
&lsquo;save/restore_object()&rsquo; jest to, ze nie jestes ograniczony wylacznie do
+
&lsquo;save/restore_object()&rsquo; jest to, że nie jesteś ograniczony wyłącznie do
nie-statycznych zmiennych i mozesz przechowac co ci sie zywnie podoba.
+
nie-statycznych zmiennych i możesz przechować co ci się żywnie podoba.
Minusem jest to, ze odtwarzanie danych z mappingu jest juz ciut bardziej
+
Minusem jest to, że odtwarzanie danych z mappingu jest już ciut bardziej
 
skomplikowane.
 
skomplikowane.
  
Linia 4109: Linia 4109:
 
     np.
 
     np.
 
         /*
 
         /*
           * Zalozmy, ze mamy takie definicje zmiennych globalnych:
+
           * Załóżmy, że mamy takie definicje zmiennych globalnych:
 
           *
 
           *
 
           * string imie, *opis;
 
           * string imie, *opis;
Linia 4115: Linia 4115:
 
           * mapping dane_map, smap;
 
           * mapping dane_map, smap;
 
           *
 
           *
           * Zalozmy, ze interesuje nas przechowanie zmiennych imie, opis,  
+
           * Załóżmy, że interesuje nas przechowanie zmiennych imię, opis,  
 
           *                                        flip i dane_map
 
           *                                        flip i dane_map
 
           */
 
           */
 
      
 
      
         // Ustawienie dziedzicznych przywilejow poprzez nadanie obiektowi  
+
         // Ustawienie dziedzicznych przywilejów poprzez nadanie obiektowi  
 
         // euid tworcy pliku
 
         // euid tworcy pliku
 
         setuid();
 
         setuid();
Linia 4152: Linia 4152:
 
             write("Nieeee..\n");
 
             write("Nieeee..\n");
  
Musisz zapamietac, ze format zapisu uzywany przez &lsquo;save_object()&rsquo; i
+
Musisz zapamiętać, że format zapisu używany przez &lsquo;save_object()&rsquo; i
przez &lsquo;save_map()&rsquo; jest taki sam, dzieki czemu mozna odtworzyc plik
+
przez &lsquo;save_map()&rsquo; jest taki sam, dzięki czemu mozna odtworzyć plik
 
zapisany przez &lsquo;save_object()&rsquo; przy pomocy &lsquo;restore_map()&rsquo; i wybrac
 
zapisany przez &lsquo;save_object()&rsquo; przy pomocy &lsquo;restore_map()&rsquo; i wybrac
z otrzymanego mappinga, tylko te elementy, na ktorych nam zalezy.
+
z otrzymanego mappinga, tylko te elementy, na których nam zależy.
Zalozmy, ze bys chcial odtworzyc tylko zmienna &lsquo;opis&rsquo; w powyzszym
+
Załóżmy, że byś chciał odtworzyć tylko zmienną &lsquo;opis&rsquo; w powyższym
przykladzie.  Nie martwil bys sie wtedy innymi zmiennymi i uzylbys
+
przykładzie.  Nie martwił byś się wtedy innymi zmiennymi i użyłbyś
wtedy okrojonej drugiej metody odtwarzania. Uwazaj na odczytywanie danych,
+
wtedy okrojonej drugiej metody odtwarzania. Uważaj na odczytywanie danych,
ktore zostaly nagrane przez &lsquo;save_map()&rsquo; instrukcja &lsquo;restore_object()&rsquo;.
+
które zostały nagrane przez &lsquo;save_map()&rsquo; instrukcja &lsquo;restore_object()&rsquo;.
Zeby wszystko bylo poprawnie, indeksy nagranego mappingu musialy by miec
+
Żeby wszystko było poprawnie, indeksy nagranego mappingu musiały by mieć
 
takie same nazwy, jak zadeklarowane w obiekcie zmienne globalne.
 
takie same nazwy, jak zadeklarowane w obiekcie zmienne globalne.
W powyzszym przykladzie sa roznice pomiedzy nimi, wiec odtworzenie metoda 1,
+
W powyższym przykładzie są różnice pomiędzy nimi, więc odtworzenie metodą 1,
danych zgranych metoda 2 skonczyloby sie bledem. W odwrotnej sytuacji,
+
danych zgranych metodą 2 skończyłoby się błędem. W odwrotnej sytuacji,
czyli przy wczytywaniu metoda 2 danych przechowanych metoda 1 nie wyskoczy
+
czyli przy wczytywaniu metodą 2 danych przechowanych metodą 1 nie wyskoczy
zaden blad, ale nie uda sie odtworzyc zmiennej &lsquo;dane_map&rsquo;.
+
żaden błąd, ale nie uda sie odtworzyć zmiennej &lsquo;dane_map&rsquo;.
  
To byly wszystkie metody przechowywania zmiennych w plikach. Nieraz
+
To były wszystkie metody przechowywania zmiennych w plikach. Nieraz
moze zajsc potrzeba nagrania danych w wolnej formie, a nie tylko w zmiennych.
+
może zajść potrzeba nagrania danych w wolnej formie, a nie tylko w zmiennych.
Do tego celu sluza efunkcje &lsquo;write_bytes()&rsquo; i &lsquo;read_bytes()&rsquo;, albo  
+
Do tego celu służą efunkcje &lsquo;write_bytes()&rsquo; i &lsquo;read_bytes()&rsquo;, albo  
&lsquo;write_file()&rsquo; i &lsquo;read_file()&rsquo;. W zasadzie obie pary funkcji dzialaja bardzo
+
&lsquo;write_file()&rsquo; i &lsquo;read_file()&rsquo;. W zasadzie obie pary funkcji działają bardzo
podobnie, tzn wczytuja i nagrywana stringi o okreslonej dlugosci.
+
podobnie, tzn wczytują i nagrywają stringi o określonej długości.
Jedyna roznica jest to, ze &lsquo;write_bytes()&rsquo; moze byc uzyte do nagrywania
+
Jedyną różnicą jest to, że &lsquo;write_bytes()&rsquo; może być użyte do nagrywania
 
na stare dane w pliku (overwriting), podczas gdy &lsquo;write_file()&rsquo; jest w stanie
 
na stare dane w pliku (overwriting), podczas gdy &lsquo;write_file()&rsquo; jest w stanie
zapisywac tylko na koncu. No i &lsquo;read_bytes()&rsquo; operuje na bajtach,
+
zapisywać tylko na końcu. No i &lsquo;read_bytes()&rsquo; operuje na bajtach,
a &lsquo;read_file()&rsquo; na pelnych liniach. Obie zapisujace funkcje zwracaja 1
+
a &lsquo;read_file()&rsquo; na pełnych liniach. Obie zapisujące funkcje zwracają 1
w przypadku powodzenia, zas 0 w gdy cos sie nie uda. Obie wczytujace funkcje
+
w przypadku powodzenia, zaś 0 gdy coś się nie uda. Obie wczytujące funkcje
zwracaja string, zawierajacy odczytana porcje danych w przypadku sukcesu,
+
zwracają string, zawierający odczytaną porcje danych w przypadku sukcesu,
a 0 w przypadku niepowodzenia, wiec sprawdzaj rezultat przy pomocy  
+
a 0 w przypadku niepowodzenia, więc sprawdzaj rezultat przy pomocy  
&lsquo;stringp()&rsquo;, zeby sie upewnic, ze wszystko bylo ok.
+
&lsquo;stringp()&rsquo;, żeby się upewnić, że wszystko było ok.
  
     int write_bytes(string sciezka, int poz, string tekst);
+
     int write_bytes(string ścieżka, int poz, string tekst);
     string read_bytes(string sciezka, void|int poz, void|int num);
+
     string read_bytes(string ścieżka, void|int poz, void|int num);
     int write_file(string sciezka, string tekst);
+
     int write_file(string ścieżka, string tekst);
     string read_file(string sciezka, void|int poz, void|int num);
+
     string read_file(string ścieżka, void|int poz, void|int num);
  
O pliku rowniez da sie uzyskac informacje. W poznaniu rozmiaru pliku moze
+
Z pliku również da się uzyskać informacje. W poznaniu rozmiaru pliku może
dopomoc efunkcja &lsquo;file_size()&rsquo;. Mozna ja tez wykorzystac do sprawdzenia
+
dopomóc efunkcja &lsquo;file_size()&rsquo;. Można ją też wykorzystać do sprawdzenia
obecnosci pliku. Jesli liczba zwrocona przez funkcje jest dodatnia, to
+
obecności pliku. Jeśli liczba zwrócona przez funkcje jest dodatnia, to
oznacza to, ze jest to rozmiar. Gdy nie ma pliku, funkcja zwraca -1,
+
oznacza to, że jest to rozmiar. Gdy nie ma pliku, funkcja zwraca -1,
a gdy plik jest katalogiem, -2. Aby poznac czas ostatniej modyfikacji
+
a gdy plik jest katalogiem, -2. Aby poznać czas ostatniej modyfikacji
mozna uzyc efunkcji &lsquo;file_time()&rsquo;.
+
można użyć efunkcji &lsquo;file_time()&rsquo;.
  
     int file_size(string sciezka);
+
     int file_size(string ścieżka);
 
     np.
 
     np.
         void plik_info(string sciezka)
+
         void plik_info(string ścieżka)
 
         {
 
         {
 
             int typ, tm;
 
             int typ, tm;
 
      
 
      
             rozm = file_size(sciezka);
+
             rozm = file_size(ścieżka);
             tm = file_time(sciezka);
+
             tm = file_time(ścieżka);
 
      
 
      
             write("Plik '" + sciezka + "' ");
+
             write("Plik '" + ścieżka + "' ");
 
             switch (rozm)
 
             switch (rozm)
 
             {
 
             {
Linia 4215: Linia 4215:
 
      
 
      
 
             default:
 
             default:
                 write("ma rozmiar " + rozmiar + " bajtow, ostatni raz " +
+
                 write("ma rozmiar " + rozmiar + " bajtów, ostatni raz " +
                     "byl zmodyfikowany " + ctime(tm) + ".\n");
+
                     "był zmodyfikowany " + ctime(tm) + ".\n");
 
                 break;
 
                 break;
 
             }
 
             }
 
         }
 
         }
  
Jesli chcesz zmienic nazwe albo przemiescic plik, to efunkcja &lsquo;rename()&rsquo;
+
Jeśli chcesz zmienić nazwę albo przemieścić plik, to efunkcja &lsquo;rename()&rsquo;
jest w sam raz dla ciebie. Ale uwazaj, gdyz jej dzialanie polega na
+
jest w sam raz dla ciebie. Ale uważaj, gdyż jej działanie polega na
skopiowaniu pliku, a potem skasowaniu starej wersji. Moze byc rowniez uzyta
+
skopiowaniu pliku, a potem skasowaniu starej wersji. Może być również użyta
do przesuniecia katalogow. Jesli zyczysz sobie usunac caly plik, to
+
do przesunięcia katalogów. Jeśli życzysz sobie usunąć cały plik, to
mozesz skorzystac z efunkcji &lsquo;rm()&rsquo;. Zwraca ona 1 w przypadku sukcesu, a 0
+
możesz skorzystać z efunkcji &lsquo;rm()&rsquo;. Zwraca ona 1 w przypadku sukcesu, a 0
w przypadku niepowodzenia. Uwazaj, bo &lsquo;rename()&rsquo; dziala dokladnie odwrotnie,
+
w przypadku niepowodzenia. Uważaj, bo &lsquo;rename()&rsquo; działa dokładnie odwrotnie,
gdyz zwraca 1 w przypadku '''niepowodzenia''', a 0 jak wszystko jest ok.
+
gdyż zwraca 1 w przypadku '''niepowodzenia''', a 0 jak wszystko jest ok.
  
 
     int rename(string stara_sciezka, string nowa_sciezka);
 
     int rename(string stara_sciezka, string nowa_sciezka);
     int rm(string sciezka);
+
     int rm(string ścieżka);
 
     np.
 
     np.
 
         if (rm("mojplik"))
 
         if (rm("mojplik"))
 
             write("Ok, skasowane.\n");
 
             write("Ok, skasowane.\n");
 
         else
 
         else
             write("Sorki, cos nie idzie.\n");
+
             write("Sorki, coś nie idzie.\n");
 
      
 
      
 
         if (rename("plik_stary", "plik_nowy"))
 
         if (rename("plik_stary", "plik_nowy"))
             write("Niestety, wciaz po staremu...\n");
+
             write("Niestety, wciąż po staremu...\n");
 
         else
 
         else
 
             write("Ok!\n");
 
             write("Ok!\n");
  
Wewnetrzny edytor &lsquo;ed&rsquo; jest efunkcja, ktora operuje na plikach. Mozesz
+
Wewnętrzny edytor &lsquo;ed&rsquo; jest efunkcją, która operuje na plikach. Możesz
go uzywac do czegokolwiek, lecz pamietaj, ze wiekszosc ludzi nie wie jak
+
go uźywac do czegokolwiek, lecz pamiętaj, że większość ludzi nie wie jak
z niego korzystac. Pamietaj takze, ze efunkcja &lsquo;ed()&rsquo; moze byc uzyta
+
z niego korzystać. Pamiętaj także, że efunkcja &lsquo;ed()&rsquo; może być użyta
 
do stworzenia nowego pliku, albo edycji starego, na podstawie praw obiektu,
 
do stworzenia nowego pliku, albo edycji starego, na podstawie praw obiektu,
ktory ja wywolal. Jesli podasz wskaznik do funkcji, to przy wyjsciu
+
który ją wywołał. Jeśli podasz wskaźnik do funkcji, to przy wyjściu
z &lsquo;ed()&rsquo; zostanie ona wywolana. Jesli nie podasz sciezki, uzytkownik
+
z &lsquo;ed()&rsquo; zostanie ona wywołana. Jeśli nie podasz ścieżki, użytkownik
bedzie musial sam wpisac sciezke i nazwe pliku wewnatrz edytora.
+
będzie musiał sam wpisać ścieżkę i nazwę pliku wewnątrz edytora.
  
 
     void ed(void|string sciezka, void|function fun_wyjsciowa);
 
     void ed(void|string sciezka, void|function fun_wyjsciowa);
Linia 4256: Linia 4256:
 
   [mkdir, rename, rmdir, get_dir]
 
   [mkdir, rename, rmdir, get_dir]
  
Tworzenie, zmienianie nazwy i usuwanie katalogow robi sie przy uzyciu
+
Tworzenie, zmienianie nazwy i usuwanie katalogów robi się przy użyciu
efunkcji (kolejno) &lsquo;mkdir()&rsquo;, &lsquo;rename()&rsquo;, &lsquo;rmdir()&rsquo;. Aby je wykonac musisz
+
efunkcji (kolejno) &lsquo;mkdir()&rsquo;, &lsquo;rename()&rsquo;, &lsquo;rmdir()&rsquo;. Aby je wykonać musisz
oczywiscie miec prawa do zapisu w katalogu, w ktorym to robisz. &lsquo;mkdir()&lsquo;
+
oczywiście mieć prawa do zapisu w katalogu, w którym to robisz. &lsquo;mkdir()&lsquo;
i &lsquo;rmdir()&rsquo; zwracaja 1 w przypadku sukcesu, a 0 w razie niepowodzenia.
+
i &lsquo;rmdir()&rsquo; zwracają 1 w przypadku sukcesu, a 0 w razie niepowodzenia.
&lsquo;rename()&rsquo; jak juz mowilem dziala odwrotnie i zwraca 1 w przypadku
+
&lsquo;rename()&rsquo; jak już mówiłem działa odwrotnie i zwraca 1 w przypadku
niepowodzenia. &lsquo;rmdir()&rsquo; kasuje tylko puste katalogi, tzn takie, ktore nie
+
niepowodzenia. &lsquo;rmdir()&rsquo; kasuje tylko puste katalogi, tzn takie, które nie
zawieraja zadnych plikow ani katalogow.
+
zawierają żadnych plików ani katalogów.
  
     int mkdir(string sciezka);
+
     int mkdir(string ścieżka);
 
     int rename(string stara_sciezka, string nowa_sciezka);
 
     int rename(string stara_sciezka, string nowa_sciezka);
     int rmdir(string sciezka);
+
     int rmdir(string ścieżka);
  
W celu pobrania zawartosci katalogu uzywa sie efunkcji &lsquo;get_dir()&rsquo;.
+
W celu pobrania zawartości katalogu używa się efunkcji &lsquo;get_dir()&rsquo;.
Zwraca ona tablice albo zawierajaca wszystkie pliki w podanym katalogu
+
Zwraca ona tablicę albo zawierającą wszystkie pliki w podanym katalogu
w przypadku sukcesu, albo pusta w przypadku niepowodzenia.
+
w przypadku sukcesu, albo pustą w przypadku niepowodzenia.
  
     string *get_dir(string sciezka);
+
     string *get_dir(string ścieżka);
 
     np.
 
     np.
 
         string *zawartosc_kat;
 
         string *zawartosc_kat;
Linia 4281: Linia 4281:
 
         for (i = 0, sz = sizeof(zawartosc_kat) ; i < sz ; i++)
 
         for (i = 0, sz = sizeof(zawartosc_kat) ; i < sz ; i++)
 
         {
 
         {
             // Obejrzyj kod funkcji plik_info w poprzednim przykladzie
+
             // Obejrzyj kod funkcji plik_info w poprzednim przykładzie
 
             file_info(zawartosc_kat[i]);
 
             file_info(zawartosc_kat[i]);
 
         }
 
         }
Linia 4289: Linia 4289:
 
   [write, write_socket, cat, tail, input_to]
 
   [write, write_socket, cat, tail, input_to]
  
Juz zapewne jestes za pan brat z efunkcja &lsquo;write()&rsquo;, ktora wyswietla
+
Już zapewne jesteś za pan brat z efunkcją &lsquo;write()&rsquo;, która wyświetla
dane na ekranie osoby zdefiniowanej jako sluchacz w danym obiekcie. Moze
+
dane na ekranie osoby zdefiniowanej jako słuchacz w danym obiekcie. Może
nim byc gracz, ale moze rowniez byc nim jakis inny obiekt. Zazwyczaj funkcja  
+
nim być gracz, ale może również być nim jakiś inny obiekt. Zazwyczaj funkcja  
wystarcza, gdy masz pelna kontrole nad tym co i do kogo piszesz. Jednakze
+
wystarcza, gdy masz pełną kontrole nad tym co i do kogo piszesz. Jednakże
istnieje pewna podobna funkcja, ktora czasem staje sie potrzebna. Nazywa sie
+
istnieje pewna podobna funkcja, która czasem staje się potrzebna. Nazywa się
ona &lsquo;write_socket()&rsquo;. Dziala ona prawie tak jak &lsquo;write()&rsquo;, tylko ze wypisuje
+
ona &lsquo;write_socket()&rsquo;. Działa ona prawie tak jak &lsquo;write()&rsquo;, tylko że wypisuje
teksty '''wylacznie''' na ekranie interakcyjnego uzytkownika. Jesli nie ma
+
teksty '''wyłącznie''' na ekranie interakcyjnego użytkownika. Jeśli nie ma
takowego, to zamiast do niego, pisze w glownym logu bledow. W czasie
+
takowego, to zamiast do niego, pisze w głównym logu błędów. W czasie
kodowania zwyklych obiektow nie bedziesz musial jej uzywac. Przewaznie,
+
kodowania zwykłych obiektów nie będziesz musiał jej używać. Przeważnie,
 
lub nawet zawsze jest wykorzystywana w konkretnych obiektach mudliba,
 
lub nawet zawsze jest wykorzystywana w konkretnych obiektach mudliba,
czesciowo tych, ktore maja doczynienia z logujacymi sie graczami.
+
częściowo tych, które mają do czynienia z logującymi się graczami.
  
Wypisywanie write'm jest niezle, ale czasem mozesz miec chrapke na  
+
Wypisywanie write'm jest niezłe, ale czasem możesz mieć chrapkę na  
blyskawiczne wyswietlenie plikow, lub przynajmniej ich czesci. Do tego sluzy
+
błyskawiczne wyświetlenie plików, lub przynajmniej ich części. Do tego służy
efunkcja &lsquo;cat()&rsquo;. Ukaze ona na ekranie okreslona porcje podanego pliku
+
efunkcja &lsquo;cat()&rsquo;. Ukaże ona na ekranie określoną porcje podanego pliku
na ekranie. Istnieje tez inna efunkcja zwana &lsquo;tail()&rsquo;, ktora wyswietli
+
na ekranie. Istnieje też inna efunkcja zwana &lsquo;tail()&rsquo;, która wyświetli
ostatnie 1080 bajtow pliku. &lsquo;cat()&rsquo; zwraca liczbe ukazanych linii, zas
+
ostatnie 1080 bajtów pliku. &lsquo;cat()&rsquo; zwraca liczbę ukazanych linii, zaś
 
&lsquo;tail()&rsquo; zwraca 1 w przypadku sukcesu, a 0 w przypadku niepowodzenia.
 
&lsquo;tail()&rsquo; zwraca 1 w przypadku sukcesu, a 0 w przypadku niepowodzenia.
  
     int cat(string sciezka, int start, int dlug);
+
     int cat(string ścieżka, int start, int dlug);
     int tail(string sciezka);
+
     int tail(string ścieżka);
 
np.
 
np.
         // Wyswietla PLIKTEST, od 20 do 100 linii
+
         // Wyświetla PLIKTEST, od 20 do 100 linii
 
         cat("PLIKTEST", 20, 80)
 
         cat("PLIKTEST", 20, 80)
 
      
 
      
         // Wyswietla koncowke tego samego pliku
+
         // Wyświetla końcówkę tego samego pliku
 
         tail("PLIKTEST");
 
         tail("PLIKTEST");
  
Wiekszosc rzeczy wpisywanych w grze przez graczy przychodzi w formie
+
Większość rzeczy wpisywanych w grze przez graczy przychodzi w formie
argumentow do funkcji. Czasem jednak potrzeba zapytac gracza o cos, a on
+
argumentów do funkcji. Czasem jednak potrzeba zapytać gracza o coś, a on
musi odpowiedziec. Pojawia sie tu problem, gdyz wykonanie obiektow
+
musi odpowiedzieć. Pojawia się tu problem, gdyż wykonanie obiektów
w gamedriverze jest sekwencyjne; jesli sie zatrzymasz z wykonywaniem
+
w gamedriverze jest sekwencyjne; jeśli się zatrzymasz z wykonywaniem
czekajac na odpowiedz, cala gra zatrzyma sie wraz z toba, do czasu az
+
czekając na odpowiedź, cała gra zatrzyma się wraz z tobą, do czasu
leniwy gracz sie namysli (o ile kiedykolwiek) i odpowie. Z pewnoscia taka
+
leniwy gracz się namyśli (o ile kiedykolwiek) i odpowie. Z pewnością taka
sytuacja nie jest dobrym rozwiazaniem. Zamiast tego, mozna wywolac efunkcje
+
sytuacja nie jest dobrym rozwiązaniem. Zamiast tego, można wywołać efunkcje
&lsquo;input_to()&rsquo;, ktora pozwala ci ustalic ktora funkcja zostanie wywolana
+
&lsquo;input_to()&rsquo;, która pozwala ci ustalić którą funkcja zostanie wywołana
majac za argument jakakolwiek odpowiedz gracza, po ukonczeniu wykonywania
+
mając za argument jakakolwiek odpowiedź gracza, po ukończeniu wykonywania
aktualnej funkcji. Brzmi to troche skomplikowanie, ale na prawde wcale takie
+
aktualnej funkcji. Brzmi to trochę skomplikowanie, ale naprawdę wcale takie
nie jest. Spojrz tylko na przyklad:
+
nie jest. Spójrz tylko na przykład:
  
 
     void input_to(string funkcja, void|int bezecha, void|mixed arg);
 
     void input_to(string funkcja, void|int bezecha, void|mixed arg);
 
np.
 
np.
         string imie, plec, zajecie;
+
         string imię, płeć, zajęcie;
 
      
 
      
 
         pytajaca_fun()
 
         pytajaca_fun()
Linia 4342: Linia 4342:
 
         {
 
         {
 
             imie = wpis;
 
             imie = wpis;
             write("\nWpisz swoja plec (Facet/Kobitka): ");
+
             write("\nWpisz swoją płeć (Facet/Kobitka): ");
 
             input_to("fun_3");
 
             input_to("fun_3");
 
         }
 
         }
Linia 4349: Linia 4349:
 
         {
 
         {
 
             plec = wpis;
 
             plec = wpis;
             write("\nCzym sie zajmujesz?: ");
+
             write("\nCzym się zajmujesz?: ");
 
             input_to("fun_4");
 
             input_to("fun_4");
 
         }
 
         }
Linia 4355: Linia 4355:
 
         fun_4(string wpis)
 
         fun_4(string wpis)
 
         {
 
         {
             zajecie = wpis;
+
             zajęcie = wpis;
             write("\n\nDzieki za wspolprace!\n");
+
             write("\n\nDzięki za współpracę!\n");
 
         }
 
         }
  
Jesli jako drugi argument do &lsquo;input_to&rsquo; podasz 1, to cokolwiek gracz
+
Jeśli jako drugi argument do &lsquo;input_to&rsquo; podasz 1, to cokolwiek gracz
wpisze, nie zostanie wyswietlone na ekranie. Uzywa sie tego przy haslach
+
wpisze, nie zostanie wyświetlone na ekranie. Używa się tego przy hasłach
i innch waznych informacjach. Trzeci, opcjonalny argument jest przekazywany
+
i innych ważnych informacjach. Trzeci, opcjonalny argument jest przekazywany
do funkcji, ktora podales jako odbiorce.
+
do funkcji, którą podałeś jako odbiorcę.
  
 
=== Niektóre komendy MUD-a ===
 
=== Niektóre komendy MUD-a ===
  
Moze to wygladac nieco dziwnie, ze rozdzial ten umiescilem tak nisko. Problem ten podobny jest do zagadnienia : co bylo pierwsze jajko czy kura. Uzywanie tych komend jest niemozliwe, zanim nie zrozumie sie na czym to wsystko naprawde polega. By to zrozumiec zawsze mozna empirycznie eksperymentowac z komendami... Jesli rozpoczales lekture tego manualu przed eksperymentowaniem, to rozdzial ten na pewno ci sie przyda.
+
Może to wyglądać nieco dziwnie, że rozdział ten umieściłem tak nisko. Problem ten podobny jest do zagadnienia : co było pierwsze jajko czy kura. Używanie tych komend jest niemożliwe, zanim nie zrozumie się na czym to wszystko naprawdę polega. By to zrozumieć zawsze można empirycznie eksperymentować z komendami... Jeśli rozpocząłeś lekturę tego manualu przed eksperymentowaniem, to rozdział ten na pewno ci się przyda.
  
Prosilbym przede wszystkim bys skupil sie na swoich umiejetnosciach edytorskich. Naucz sie obslugiwac ed, ktory jest jedynym wewnatrzmudowym edytorem, lub uzywaj zewnetrznego edytora i pliki slij przez ftp. Polecam edytor emacs i ftp. Pamietaj, ze pliki mozesz tworzyc takze pod Windowsem np. w Notatniku, ale zanim wyslesz pliki na serwer musisz je konwertowac na format UNIXa.
+
Prosiłbym przede wszystkim byś skupił się na swoich umiejętnościach edytorskich. Naucz się obsługiwać ed, który jest jedynym wewnątrzmudowym edytorem, lub używaj zewnętrznego edytora i pliki ślij przez ftp. Polecam edytor emacs i ftp. Pamiętaj, że pliki możesz tworzyć także pod Windowsem np. w Notatniku, ale zanim wyślesz pliki na serwer musisz je konwertować na format UNIXa.
  
Ponizsze komendy sa dobrze opisane w mudzie, po prostu wpisz ?<komenda>, by uzyskac pelen opis. To co ja napisalem to zwykle podsumowanie i streszczenie tego.  
+
Poniższe komendy dobrze opisane w mudzie, po prostu wpisz ?<komenda>, by uzyskać pełen opis. To co ja napisałem to zwykłe podsumowanie i streszczenie tego.  
  
   load: Ladowanie obiektu do pamieci
+
   load: Ładowanie obiektu do pamięci
   clone: Ladowanie i klonowanie obiektu do gry  
+
   clone: Ładowanie i klonowanie obiektu do gry  
 
   destruct: Niszczenie sklonowanego obiektu  
 
   destruct: Niszczenie sklonowanego obiektu  
 
   update: Aktualizacja obiektu  
 
   update: Aktualizacja obiektu  
Linia 4380: Linia 4380:
 
==== Kompilacja i ładowanie obiektów do pamięci gamedrivera ====
 
==== Kompilacja i ładowanie obiektów do pamięci gamedrivera ====
  
Komenda load nakazujesz gamedriverowi, by sprobowal skompilowac i zaladowac dany plik do pamieci. Komendy tej uzywa sie zazwyczaj, by sprawdzic czy dany plik w ogole sie zaladuje. Istnieja takze obiekty, ktore nie sa przeznaczone do klonowania (np. pokoje), a dzieki load udostepniamy je dla innych obiektow.
+
Komendą load nakazujesz gamedriverowi, by spróbował skompilować i załadować dany plik do pamięci. Komendy tej używa się zazwyczaj, by sprawdzić czy dany plik w ogóle się załaduje. Istnieją także obiekty, które nie przeznaczone do klonowania (np. pokoje), a dzięki load udostępniamy je dla innych obiektów.
  
Jezeli plik sie nie zaladuje, komunikat o bledzie laduje w dwoch miejscach: glownym /lplog oraz w logu bledow nalezacym do domeny/czarodzieja, ktory stworzyl tenze obiekt. Dla domen sciezka do ostatniego ma ksztalt /d/<Domena>/log/errors, zas dla czarodziei /d/<Domena>/<czarodziej>/log/errors.  
+
Jeżeli plik się nie załaduje, komunikat o błędzie ładuje w dwóch miejscach: głównym /lplog oraz w logu błędów należącym do domeny/czarodzieja, który stworzył tenże obiekt. Dla domen ścieżka do ostatniego ma kształt /d/<Domena>/log/errors, zaś dla czarodziei /d/<Domena>/<czarodziej>/log/errors.  
  
Niestety komunikaty o bledach sa nierzadko trudne do zrozumienia. Nie moge zatem w tej materii sluzyc pomoca, bo po prostu jest ogromna ilosc roznych typow takich komunikatow, zreszta zapewne z wiekszoscia sie spotkacie :) Wiele komunikatow latwo jest zrozumiec, a z czasem kazdy koder nauczy sie rozpoznawac i zapobiegac tym ciekawszym...
+
Niestety komunikaty o błędach są nierzadko trudne do zrozumienia. Nie mogę zatem w tej materii służyć pomocą, bo po prostu jest ogromna ilość różnych typów takich komunikatów, zresztą zapewne z większością się spotkacie :) Wiele komunikatów łatwo jest zrozumieć, a z czasem każdy koder nauczy się rozpoznawać i zapobiegać tym ciekawszym...
  
Komenda load moze obslugiwac takze wieksza ilosc plikow jednoczesnie w porcjach w odstepach czasowych. Ladowanie jednakze, to raczej obciazajaca proces operacja, wiec lepiej unikac masowego ladowania plikow.
+
Komenda load może obsługiwać także większą ilość plików jednocześnie w porcjach w odstępach czasowych. Ładowanie jednakże, to raczej obciążająca proces operacja, więc lepiej unikać masowego ładowania plików.
  
 
CIEKAWOSTKA:
 
CIEKAWOSTKA:
  
Uzywajac ls z opcja F (tzn. ls -F) widzisz w liscie plikow i katalogow , ktore zostaly juz zaladowane do pamieci (sa one oznaczone gwiazdka). Warto zatem uzywajac komendy <alias ls ls -F> nadpisac te komende.
+
Używając ls z opcją F (tzn. ls -F) widzisz w liście plików i katalogów , które zostały już załadowane do pamięci (one oznaczone gwiazdką). Warto zatem używając komendy <alias ls ls -F> nadpisać tę komendę.
  
 
==== Kompilacja, ładowanie i klonowanie obiektów do gry ====
 
==== Kompilacja, ładowanie i klonowanie obiektów do gry ====
  
Jezeli dany obiekt zostal zaladowany do pamieci, mozna go sklonowac. Zreszta jesli potrzeba komenda clone laduje i klonuje jednoczesnie. Jesli natkniesz sie na problemy pamietaj by sprawdzic logi bledow.
+
Jeżeli dany obiekt został załadowany do pamięci, można go sklonować. Zresztą jeśli potrzeba komenda clone ładuje i klonuje jednocześnie. Jeśli natkniesz się na problemy pamiętaj by sprawdzić logi błędów.
  
 
==== Niszczenie sklonowanego obiektu ====
 
==== Niszczenie sklonowanego obiektu ====
  
Komendy destruct uzywamy, by usunac sklonowany obiekt z pamieci. Komenda ta niszczy jedynie dany obiekt nic wiecej.
+
Komendy destruct używamy, by usunąć sklonowany obiekt z pamięci. Komenda ta niszczy jedynie dany obiekt nic więcej.
  
 
==== Aktualizacja załadowanego obiektu ====
 
==== Aktualizacja załadowanego obiektu ====
  
Komendy update uzywamy w przypadku gdy w pliku zaszla zmiana i chcemy, aby gamediver ponownie go skompilowal.
+
Komendy update używamy w przypadku gdy w pliku zaszlł zmiana i chcemy, aby gamedriver ponownie go skompilował.
  
==== Odnawianie obieku ====
+
==== Odnawianie obiektu ====
 
 
Warto takze zwrocic uwage na komende odnow. Komenda ta laczy w sobie cztery inne &ndash; niszczy stary klon (destruct), przeladowuje obiekt na nowa wersje (update, load) i klonuje z powrotem nowa wersje obiektu (clone) w miejsce, gdzie byl stary klon. W przypadku, gdy poda sie sciezke zamiast nazwy obiektu, komenda przeladuje podany obiekt i sklonuje jego nowa wersje.
+
Warto także zwrócić uwagę na komendę odnów. Komenda ta łączy w sobie cztery inne &ndash; niszczy stary klon (destruct), przeładowuje obiekt na nową wersję (update, load) i klonuje z powrotem nową wersję obiektu (clone) w miejsce, gdzie był stary klon. W przypadku, gdy poda się ścieżkę zamiast nazwy obiektu, komenda przeładuje podany obiekt i sklonuje jego nową wersję.
  
 
=== Narzędzie Tracer ===
 
=== Narzędzie Tracer ===
  
Wbudowany zestaw narzedzi czarodzieja, zwany Tracerem, moze okazac sie nieodlaczny przy oddzialywaniu na wnetrze obiektu, ktory juz zaladowano do gry. Pozwala bowiem wywolywac przerozne funkcje w dowolnym obiekcie, bez wzgledu na to gdze sie znajduje, pozwala takze na wyswietlenie inwentazra obiektu, a nawet przenoszenie obiektow z roznych miejsc w inne. Warto poznac ten zestaw narzedzi doglebnie.
+
Wbudowany zestaw narzędzi czarodzieja, zwany Tracerem, może okazać się nieodłączny przy oddziaływaniu na wnętrze obiektu, który już załadowano do gry. Pozwala bowiem wywoływać przeróżne funkcje w dowolnym obiekcie, bez względu na to gdzie się znajduje, pozwala także na wyświetlenie inwentarza obiektu, a nawet przenoszenie obiektów z różnych miejsc w inne. Warto poznać ten zestaw narzędzi dogłębnie.
  
Niektorzy skarza sie, ze Tracer jest dosyc ciezki w uzyciu. Jest tak dlatego, bo nie chcialo im sie poczytac wiecej o Tracerze w manualu gry. Wewnetrzy manual gry jest napisany trudnym jezykiem, wiec postanowilem blizej zajac sie Tracerem.
+
Niektórzy skarżą się, że Tracer jest dosyć ciężki w użyciu. Jest tak dlatego, bo nie chciało im się poczytać więcej o Tracerze w manualu gry. Wewnętrzny manual gry jest napisany trudnym językiem, więc postanowiłem bliżej zająć się Tracerem.
  
Warto zwrocic uwage, ze kazda komenda z Tracera zaczyna sie wielka litera. Sluzy to odroznieniu od zwyklych komend, z ktorych niektore nazywaja sie tozsamo. Komendy Tracera sa dostepne jedynie dla pelnych wizardow (Praktykant i dalej).
+
Warto zwrócić uwagę, że każda komenda z Tracera zaczyna się wielką literą. Służy to odróżnieniu od zwykłych komend, z których niektóre nazywają się tożsamo. Komendy Tracera są dostępne jedynie dla pełnych wizardów (Praktykant i dalej).
  
Na poczatek przyjrzyjmy sie temu, jak tracer widzi obiekty. Podam jak odwolywac sie do danego obiektu w danej sytuacji.
+
Na początek przyjrzyjmy się temu, jak tracer widzi obiekty. Podam jak odwoływać się do danego obiektu w danej sytuacji.
 
* nazwa
 
* nazwa
W ten sposob mozna odwolywac sie do obiektow znajdujacych sie w ekwipunku lub w twoim srodowisku (np. na tej samej lokacji). Uwaga! Wiele obiektow moze dzielic te same nazwy (np. &lsquo;czlowiek&rsquo;, &lsquo;zbroja&rsquo;, &lsquo;miecz&rsquo;).  
+
W ten sposób można odwoływać się do obiektów znajdujących się w ekwipunku lub w twoim środowisku (np. na tej samej lokacji). Uwaga! Wiele obiektów może dzielić te same nazwy (np. &lsquo;czlowiek&rsquo;, &lsquo;zbroja&rsquo;, &lsquo;miecz&rsquo;).  
(uzycie np. In czlowiek ...)
+
(użycie np. In człowiek ...)
 
* "opis"  
 
* "opis"  
Tu uzywamy krotkiego opisu obiektu, znajdujacego sie w ekwipunku lub w tym samym srodowisku (np. tej samej lokacji). Zazwyczaj podaje sie dokladniejszy opis, nie tylko sama nazwe.  
+
Tu używamy krótkiego opisu obiektu, znajdującego się w ekwipunku lub w tym samym środowisku (np. tej samej lokacji). Zazwyczaj podaje się dokładniejszy opis, nie tylko sama nazwę.  
(uzycie np. Destruct "wysoki elf")
+
(użycie np. Destruct "wysoki elf")
* sciezka
+
* ścieżka
Tu podajemy pelna sciezke Jezeli chcesz operowac na okreslonym pojedynczym obiekcie, dodajesz numer klonowania. Np. sama sciezka ~Ansalon/guild/society/obj/nametag, albo ~Ansalon/guild/society/obj/nametag#22144 dla okreslonego obiektu.
+
Tu podajemy pełną ścieżkę Jeżeli chcesz operować na określonym pojedynczym obiekcie, dodajesz numer klonowania. Np. sama ścieżka ~Ansalon/guild/society/obj/nametag, albo ~Ansalon/guild/society/obj/nametag#22144 dla określonego obiektu.
 
* @nazwa
 
* @nazwa
W tym przypadku operujemy na istocie zywej (potworze NPC), ktora znajduje sie gdziekolwiek w grze. Zauwazmy, ze tracer znajdzie nazwe ustawiona poprzez wywolanie efunkcji set_living_name() (opisana wczesniej w manualu), a nie przez nazwe okreslona przez set_name(), czy tez ustaw_nazwe(), ustaw_imie(), itp. Istota zywa (gracz) jest automatycznie dodawana do listy livingow.
+
W tym przypadku operujemy na istocie żywej (potworze NPC), która znajduje się gdziekolwiek w grze. Zauważmy, że tracer znajdzie nazwę ustawioną poprzez wywołanie efunkcji set_living_name() (opisaną wcześniej w manualu), a nie przez nazwę określoną przez set_name(), czy tez ustaw_nazwe(), ustaw_imie(), itp. Istota żywa (gracz) jest automatycznie dodawana do listy livingów.
 
* *nazwa
 
* *nazwa
 
This specifies the name of a player, and nothing else.  
 
This specifies the name of a player, and nothing else.  
 
* here
 
* here
W tym przypadku dzialamy na srodowisku, w ktorym sie znalezlismy, np. na pokoju gdzie stoimy.
+
W tym przypadku działamy na środowisku, w którym się znaleźliśmy, np. na pokoju gdzie stoimy.
 
* me
 
* me
 
Tutaj natomiast na samym sobie, swoim obiekcie gracza.
 
Tutaj natomiast na samym sobie, swoim obiekcie gracza.
 
* #numer
 
* #numer
Tutaj okreslamy numer obiektu. Jezeli np. wiesz, ze miecz, ktory masz przy sobie jest trzecim obiektem, wowczas odwolanie ma ksztalt #3. Nalezy pamietac, ze kolejnosc obiektow moze zmienic sie bez ostrzezenia, wowczas obiekt #3 bedzie zupelnie innym niz chcielismy. Zamiast tego nalezy uzywac zmiennych tracera (opisanych przy komendzie Set), by problemu uniknac..  
+
Tutaj określamy numer obiektu. Jeżeli np. wiesz, że miecz, który masz przy sobie jest trzecim obiektem, wówczas odwołanie ma kształt #3. Należy pamiętać, że kolejność obiektów może zmienić się bez ostrzeżenia, wówczas obiekt #3 będzie zupełnie innym niż chcieliśmy. Zamiast tego należy używać zmiennych tracera (opisanych przy komendzie Set), by problemu uniknąć..  
 
* $zmienna
 
* $zmienna
Okresla zawartosc zmiennej tracera (opisane przy komendzie Set).
+
Określa zawartość zmiennej tracera (opisane przy komendzie Set).
Obiekty czesto istnieja w roznych rodzajach srodowisk. Czasem w tym samym miejscu co inne, czasem wewnatrz innych, itp. W celu okreslenia relacji typu &bdquo;drugi obiekt wewnatrz misia w tym samym pokoju, w ktorym stoje&rdquo; podaje sie liste specyfikacji obiektow rozdzielona dwukropkiem (:). Srodowisko danego obiektu jest wowczas okreslone przez :^.  
+
Obiekty często istnieją w różnych rodzajach środowisk. Czasem w tym samym miejscu co inne, czasem wewnątrz innych, itp. W celu określenia relacji typu &bdquo;drugi obiekt wewnątrz misia w tym samym pokoju, w którym stoję&rdquo; podaje się listę specyfikacji obiektów rozdzielona dwukropkiem (:). Środowisko danego obiektu jest wówczas określone przez :^.  
  
Przykladowo, poprzedni opis o misiu wygladalby tak:^mis:#2. To naprawde nie jest az tak skomplikowane jak wyglada.
+
Przykładowo, poprzedni opis o misiu wyglądałby tak:^mis:#2. To naprawdę nie jest tak skomplikowane jak wygląda.
  
Inny przyklad: &bdquo;miecz w torbie trzymanej przez Adama&rdquo;: *adam:torba:miecz.  
+
Inny przykład: &bdquo;miecz w torbie trzymanej przez Adama&rdquo;: *adam:torba:miecz.  
  
  At: Wykonanie polecenia w srodowisku gracza
+
  At: Wykonanie polecenia w środowisku gracza
  Call: Wywolanie funkcji obiektu/na obiekcie
+
  Call: Wywołanie funkcji obiektu/na obiekcie
  Cat: Podglad pliku zwiazanego z danym obiektem  
+
  Cat: Podgląd pliku związanego z danym obiektem  
  Clean: Niszczenie wszystkich nie zywych przedmioto w obiekcie  
+
  Clean: Niszczenie wszystkich nie żywych przedmiotów w obiekcie  
  Destruct: Niszczenie okreslonego obiektu
+
  Destruct: Niszczenie określonego obiektu
  Dump: Wyswietlenie przeroznych informacji o obiekcie
+
  Dump: Wyświetlenie przeróżnych informacji o obiekcie
  Ed: Edycja pliku zwiazanego z obiektem
+
  Ed: Edycja pliku związanego z obiektem
  Goto: Wejscie do srodowiska obiektu  
+
  Goto: Wejście do środowiska obiektu  
 
  In: Wykonanie komendy w innym obiekcie
 
  In: Wykonanie komendy w innym obiekcie
  Inventory: Wyswietlenie inwentarza obiektu
+
  Inventory: Wyświetlenie inwentarza obiektu
  Items: Przegladanie itemow zawartych w danym obiekcie/srodowisku
+
  Items: Przeglądanie itemów zawartych w danym obiekcie/środowisku
  Light: Wyswietlenie listy obiektow danego srodowiska
+
  Light: Wyświetlenie listy obiektów danego środowiska
  More: Przegladanie pliku zwiazanego z obiektem w porcjach
+
  More: Przeglądanie pliku związanego z obiektem w porcjach
 
  Move: Przemieszczenie obiektu do miejsca docelowego
 
  Move: Przemieszczenie obiektu do miejsca docelowego
 
  Set: Ustawienie zmiennej tracera
 
  Set: Ustawienie zmiennej tracera
  Tail: Przegladanie porcjami pliku zwiazanego z obiektem, od konca
+
  Tail: Przeglądanie porcjami pliku związanego z obiektem, od końca
  
 
==== Wykonywanie komend w środowisku gracza ====
 
==== Wykonywanie komend w środowisku gracza ====
  
Komenda ta tak naprawde przenosi wywolujacego do srodowiska w ktorym przebywa dany gracz w celu wykonania danej komendy. Przeniesienie to odbywa sie w sposob bezwzgledny, tzn. zadne sprawdzenia mozliwosci przeniesienia nie sa sprawdzane.  
+
Komenda ta tak naprawdę przenosi wywołującego do środowiska w którym przebywa dany gracz w celu wykonania danej komendy. Przeniesienie to odbywa się w sposób bezwzględny, tzn. żadne sprawdzenia możliwości przeniesienia nie sprawdzane.  
  
Skladnia: At <imie_gracza_w_mianowniku> <polecenie>
+
Składnia: At <imie_gracza_w_mianowniku> <polecenie>
  
Zazwyczaj istnieja lepsze sposoby do osiagniecia podobnego celu, jednak dla pewnych kwestii komenda At okazuje sie nieodzowna. Dobrym na to przykladem jest sytuacja, gdy chcemy spojrzec na pokoj, w ktorym znajduje sie dany gracz, np. Adam. W tym celu wykonujemy:
+
Zazwyczaj istnieją lepsze sposoby do osiągnięcia podobnego celu, jednak dla pewnych kwestii komenda At okazuje się nieodzowna. Dobrym na to przykładem jest sytuacja, gdy chcemy spojrzeć na pokój, w którym znajduje się dany gracz, np. Adam. W tym celu wykonujemy:
  
 
At adam sp
 
At adam sp
  
UWAGA! Warto zachowac ostroznosc przy wykonywaniu tego wobec smiertelnikow, istnieja sytuacje, w ktorych wywolujacy moze okazac sie widoczny dla gracza. Warto przeto uzyc &lsquo;invis&rsquo; nim wywolamy podobna komende.
+
UWAGA! Warto zachować ostrożność przy wykonywaniu tego wobec śmiertelników, istnieją sytuacje, w których wywołujący może okazać się widoczny dla gracza. Warto przeto użyć &lsquo;invis&rsquo; nim wywołamy podobną komendę.
  
 
==== Wywoływanie funkcji na obiekcie ====
 
==== Wywoływanie funkcji na obiekcie ====
  
Komenda Call jest szczegolnie uzyteczna. Dzieki niej mozesz wyolywac funckje na obiekcie podajac dowolne parametry [w praktyce jednak nie kazdy typ argumentu da sie ta droga bezproblemowo przekazac].
+
Komenda Call jest szczególnie użyteczna. Dzięki niej możesz wywoływać funkcję na obiekcie podając dowolne parametry [w praktyce jednak nie każdy typ argumentu da się tą drogą bezproblemowo przekazać].
  
Skladnia: Call <dany_obiekt> arg1%%arg2%%...
+
Składnia: Call <dany_obiekt> arg1%%arg2%%...
  
Przykladowo, aby ustawic wlasnosc OBJ_I_VOLUME na 55 w drugim obiekcie z ekwipunku Adama wykonujemy:
+
Przykładowo, aby ustawić własność OBJ_I_VOLUME na 55 w drugim obiekcie z ekwipunku Adama wykonujemy:
  
 
Call *adam:#2 add_prop _obj_i_volume%%55
 
Call *adam:#2 add_prop _obj_i_volume%%55
  
Warto zauwazyc, ze odwolano sie bezposrednio do stringu "_obj_i_volume", albowiem jest on dopiero na poziomie <stdproperties.h> definiowany jako zamiennik dla OBJ_I_VOLUME, ale tego Call niestety nie uwzglednia.
+
Warto zauważyć, że odwołano się bezpośrednio do stringu "_obj_i_volume", albowiem jest on dopiero na poziomie <stdproperties.h> definiowany jako zamiennik dla OBJ_I_VOLUME, ale tego Call niestety nie uwzględnia.
  
==== Przegladanie zawartości pliku związanego z danym obiektem ====
+
==== Przeglądanie zawartości pliku związanego z danym obiektem ====
  
Komenda Cat, wspolnie z More jest dosyc ciekawa i czesto uzyteczna komenda. Zwraca ona zrodlo pliku zwiazanego z danym obiektem, o ile mamy prawa odczytu dla niego. Oczywiscie Cat zwraca jedynie 100 pierwszych linijek kodu, by przejrzec calosc nalezy uzyc stronnicowania [zobacz komende More].
+
Komenda Cat, wspólnie z More jest dosyć ciekawą i często użyteczną komendą. Zwraca ona źródło pliku związanego z danym obiektem, o ile mamy prawa odczytu dla niego. Oczywiście Cat zwraca jedynie 100 pierwszych linijek kodu, by przejrzeć całość należy użyć stronnicowania [zobacz komendę More].
  
Skladnia: Cat <dany_obiekt>  
+
Składnia: Cat <dany_obiekt>
  
 
==== Zniszczenie wszystkich nieinteraktywnych zawartych w obiekcie docelowym ====
 
==== Zniszczenie wszystkich nieinteraktywnych zawartych w obiekcie docelowym ====
  
Clear jest uzyteczna komenda dla czyszczenia zawartosci danego obiektu. Czesto uzywa sie jej, gdy np. mamy duzo obiektow w danej lokacji i chcemy szybkim ruchem je usunac albo gdy nierozwazny czarodziej sklonuje obiekt, ktorego dzialania nie do konca przewidzial i przykladowo sparalizuje go on, wowczas dowolny inny czarodziej moze go szybko uratowac uzywajac tej komendy ;)
+
Clear jest użyteczną komendą dla czyszczenia zawartości danego obiektu. Często używa się jej, gdy np. mamy dużo obiektów w danej lokacji i chcemy szybkim ruchem je usunąć albo gdy nierozważny czarodziej sklonuje obiekt, którego działania nie do końca przewidział i przykładowo sparaliżuje go on, wówczas dowolny inny czarodziej może go szybko uratować używając tej komendy ;)
  
Skladnia: Clean <dany_obiekt>
+
Składnia: Clean <dany_obiekt>
  
UWAGA! Wszystkie nieinteraktywne obiekty, jak np przedmioty i npce, ulegna zniszczeniu.
+
UWAGA! Wszystkie nieinteraktywne obiekty, jak np przedmioty i npce, ulegną zniszczeniu.
  
 
==== Zniszczenie określonego obiektu ====
 
==== Zniszczenie określonego obiektu ====
  
W celu zniszczenia konkretnego obiektu, uzywamy komendy Destruct. Czasami np. gdy funkcja leave_inv() obiektu zawiera wadliwy kod, dany obiekt moze nie ulec zniszczeniu od razu. Nalezy wtedy uzyc opcji -D, czyli np. Destruct -D miecz, by wymusic usuniecie danego obiektu.
+
W celu zniszczenia konkretnego obiektu, używamy komendy Destruct. Czasami np. gdy funkcja leave_inv() obiektu zawiera wadliwy kod, dany obiekt może nie ulec zniszczeniu od razu. Należy wtedy użyć opcji -D, czyli np. Destruct -D miecz, by wymusić usunięcie danego obiektu.
  
Skladnia: Destruct [-D] <dany_obiekt>
+
Składnia: Destruct [-D] <dany_obiekt>
  
UWAGA! Nalezy unikac uzywania opcji -D i stosowac ja wylacznie wtedy, gdy jest to konieczne.
+
UWAGA! Należy unikać używania opcji -D i stosować ją wyłącznie wtedy, gdy jest to konieczne.
Zaleznie od wersji mudliba moze wystepowac opcja -f, dzialajac tozsamo jak -D.
+
Zależnie od wersji mudliba może występować opcja -f, działając tożsamo jak -D.
  
 
==== Pobieranie szczegółowych informacji o danym obiekcie ====
 
==== Pobieranie szczegółowych informacji o danym obiekcie ====
  
Komendy Dump uzywamy by pobrac specyficzne informacje z danego obiektu. Wybor jest dosyc duzy.
+
Komendy Dump używamy by pobrać specyficzne informacje z danego obiektu. Wybór jest dosyć duży.
  
Skladnia: Dump <dany_obiekt> <jakie_informacje_chcemy>
+
Składnia: Dump <dany_obiekt> <jakie_informacje_chcemy>
  
Mozliwe informacje:
+
Możliwe informacje:
 
* <nic>
 
* <nic>
  
Komenda Dump <dany_obiekt> pobiera nazwe, sciezke, uid tworcy oraz euid docelowego obiektu.
+
Komenda Dump <dany_obiekt> pobiera nazwę, ścieżkę, uid twórcy oraz euid docelowego obiektu.
  
 
* <zmienna>
 
* <zmienna>
  
O ile zmienna nie jest rownoznaczna z jednym z ponizej opisanych parametrow, to jest interpretowana jako zmienna w badanym obiekcie. Komenda zwraca nam tedy wartosc tejze zmiennej.
+
O ile zmienna nie jest równoznaczna z jednym z poniżej opisanych parametrów, to jest interpretowana jako zmienna w badanym obiekcie. Komenda zwraca nam wtedy wartość tejże zmiennej.
  
UWAGA! Jest to naprawde uzyteczny sposob uzycia tej komendy. Przydac sie moze przy roznego rodzaju debugowaniu kodu. Nie trzeba bowiem ingerowac w kod obiektu by dowiedziec sie jaka wartosc ma zmienna w danym obiekcie. Oczywiscie wciaz sporo jest przypadkow, w ktorych to nie wystarczy i trzeba uzyc wlasnych funkcji debugu.
+
UWAGA! Jest to naprawdę użyteczny sposób użycia tej komendy. Przydać się może przy różnego rodzaju debugowaniu kodu. Nie trzeba bowiem ingerować w kod obiektu by dowiedzieć się jaką wartość ma zmienna w danym obiekcie. Oczywiście wciąż sporo jest przypadków, w których to nie wystarczy i trzeba użyć własnych funkcji debugu.
  
 
* alarms
 
* alarms
: Zwraca wszystkie biezace alarmy &bdquo;biegajace po&rdquo; danym obiekcie.
+
: Zwraca wszystkie bieżące alarmy &bdquo;biegajace po&rdquo; danym obiekcie.
  
 
* cpu
 
* cpu
: Parametr ten zwraca ile czasu procesora zostalo zuzyte przez badany obiekt. Dziala wylacznie gdy wlaczono PROFILE_OBJS w config.h drivera. Opcji tej uzywa sie generalnie przy rozwijaniu mudliba lub tez badaniu jego wydajnosci.
+
: Parametr ten zwraca ile czasu procesora zostało zużyte przez badany obiekt. Działa wyłącznie gdy włączono PROFILE_OBJS w config.h drivera. Opcji tej używa się generalnie przy rozwijaniu mudliba lub tez badaniu jego wydajności.
  
 
* flags
 
* flags
: Zwraca wszystkie pochodzace z drivera flagi zwiazane z badanym obiektem, wraz z innymi informacjami o stanie obiektu. Informacje te sa uzyteczne jedynie dla osob pracujacych nad udoskonaleniem drivera.
+
: Zwraca wszystkie pochodzące z drivera flagi związane z badanym obiektem, wraz z innymi informacjami o stanie obiektu. Informacje te są użyteczne jedynie dla osób pracujących nad udoskonaleniem drivera.
  
 
* functions
 
* functions
 
: Zwraca wszystkie funkcje zdefiniowane w danym obiekcie.  
 
: Zwraca wszystkie funkcje zdefiniowane w danym obiekcie.  
  
:UWAGA! Zwraca jedynie funkcje z obiektu polozonego najwyzej w hierarchii dziedziczenia. Tzn. nie podaje funkcji, ktore obiekt nabyl z innego dziedziczonego obiektu.  
+
:UWAGA! Zwraca jedynie funkcje z obiektu położonego najwyżej w hierarchii dziedziczenia. Tzn. nie podaje funkcji, które obiekt nabył z innego dziedziczonego obiektu.  
  
 
* info
 
* info
: Parametr ten zwraca pewne podstawowe zwiazane z driverem informacje przyporzadkowane do danego obiektu. Ponownie, informacje te sa uzyteczne jedynie dla osob pracujacych nad driverem.
+
: Parametr ten zwraca pewne podstawowe związane z driverem informacje przyporządkowane do danego obiektu. Ponownie, informacje te są użyteczne jedynie dla osób pracujących nad driverem.
  
 
* inherits
 
* inherits
: Uzyteczny parametr zwracajacy liste obiektow, po ktorych dany obiekt dziedziczy.
+
: Użyteczny parametr zwracający listę obiektów, po których dany obiekt dziedziczy.
  
 
* inv | inventory
 
* inv | inventory
: Zwraca inwentarz/ekwipunek/zawartosc obiektu badanego wraz z numerami odpowiadajacymi zawartym tam obiektom, ktorych mozna uzyc przy dalszych wywolaniach, np. funkcji na mieczu, ktory jest w ekwipunku gracza X. [patrz: opis odwolan do obiektow w 2.5]
+
: Zwraca inwentarz/ekwipunek/zawartość obiektu badanego wraz z numerami odpowiadającymi zawartym tam obiektom, których można użyć przy dalszych wywołaniach, np. funkcji na mieczu, który jest w ekwipunku gracza X. [patrz: opis odwołań do obiektów w 2.5]
W niektorych wersjach mudliba wystepuje jako osobna komenda tracera Inventory [I]
+
W niektórych wersjach mudliba występuje jako osobna komenda tracera Inventory [I]
  
 
* items
 
* items
: Zwraca liste wszystkich pseudo-przedmiotow, np. takich, ktore jedynie dodaja opis, lub komendy, ktore dodano do badanego obiektu.  
+
: Zwraca listę wszystkich pseudo-przedmiotów, np. takich, które jedynie dodają opis, lub komendy, które dodano do badanego obiektu.  
  
 
* light
 
* light
: Ten parametr zwraca liste stanow swiatla dla danego obiektu i informuje o efektach dla obiektow w nim zawartych. Poda zatem obecny stan oswietlenia i czy obiekt generuje lub pobiera swiatlo.
+
: Ten parametr zwraca listę stanów światła dla danego obiektu i informuje o efektach dla obiektów w nim zawartych. Poda zatem obecny stan oświetlenia i czy obiekt generuje lub pobiera światło.
  
 
* profile
 
* profile
: Dziala wylacznio gdy skompilowany driver ma wlaczona flage PROFILE_FUNS. Zwraca wszystkie zapisane informacje profilowania powiazanych z danym obiektem. Uzywane glownie przy pracy nad optymalizacja i rozszerzeniem drivera.
+
: Działa wyłącznie gdy skompilowany driver ma włączoną flagę PROFILE_FUNS. Zwraca wszystkie zapisane informacje profilowania powiązanych z danym obiektem. Używane głównie przy pracy nad optymalizacja i rozszerzeniem drivera.
  
 
* props | properties
 
* props | properties
: Zwraca wszystkie wlasnosci [propy] danego obiektu.
+
: Zwraca wszystkie własności [propy] danego obiektu.
  
 
* shadows
 
* shadows
: Zwraca wszystkie shadowy powiazane z danym obiektem.
+
: Zwraca wszystkie shadowy powiązane z danym obiektem.
  
 
* vars | variables
 
* vars | variables
: Zwraca liste wszystkich zmiennych danego obiektu.
+
: Zwraca listę wszystkich zmiennych danego obiektu.
  
 
* wizinfo
 
* wizinfo
: Zwraca specjalna informacje zapisano w obiekcie poprzez wlasnosc OBJ_S_WIZINFO. Glownie zawiera wazne informacje dla innych czarodziei, jak zajmowac sie danym obiektem, czego z nim robic sie nie powinno, itd.  
+
: Zwraca specjalna informacje zapisano w obiekcie poprzez własność OBJ_S_WIZINFO. Głównie zawiera ważne informacje dla innych czarodziei, jak zajmować się danym obiektem, czego z nim robić się nie powinno, itd.  
  
UWAGA! To czy taka informacja znajdzie sie w obiekcie zalezy wylacznie od kreatora tego obiektu i jego zastosowania.
+
UWAGA! To czy taka informacja znajdzie się w obiekcie zależy wyłącznie od kreatora tego obiektu i jego zastosowania.
  
 
==== Ed(ycja) pliku powiązanego z obiektem ====
 
==== Ed(ycja) pliku powiązanego z obiektem ====
  
Komenda Ed uruchamia (dość toporny) edytor ed z otwartym plikiem, do ktorego odwołuje sie badany obiekt.
+
Komenda Ed uruchamia (dość toporny) edytor ed z otwartym plikiem, do którego odwołuje się badany obiekt.
  
Skladnia: Ed <dany_obiekt>  
+
Składnia: Ed <dany_obiekt>  
  
 
==== Przenoszenie istoty żywej/obiektu do danego środowiska ====
 
==== Przenoszenie istoty żywej/obiektu do danego środowiska ====
  
Komenda Move pozwala na przeniesienie dowolnego obiektu do innego okreslonego srodowiska. W przypadku przenoszenia istoty zywej uzyte zostanie odwolanie do funkcji move_living() z flaga teleportacji, w przypadku niepowodzenie przy przenoszeniu istoty zywej podejmowana jest proba przez odwolanie do funkcji move(). Dla obiektow zawsze uzywana jast funkcja move(). Gdy uzyjemy opcji -f, wymuszajacej przeniesienie, wowczas zawsze wykonywane jest move( ,1).
+
Komenda Move pozwala na przeniesienie dowolnego obiektu do innego określonego środowiska. W przypadku przenoszenia istoty żywej użyte zostanie odwołanie do funkcji move_living() z flagą teleportacji, w przypadku niepowodzenia przy przenoszeniu istoty żywej podejmowana jest próba przez odwołanie do funkcji move(). Dla obiektów zawsze używana jest funkcja move(). Gdy użyjemy opcji -f, wymuszającej przeniesienie, wówczas zawsze wykonywane jest move( ,1).
  
Skladnia: Move [-f] <dany_obiekt> [<obiekt_docelowy>]
+
Składnia: Move [-f] <dany_obiekt> [<obiekt_docelowy>]
 
  / w [] podano parametry opcjonalne /
 
  / w [] podano parametry opcjonalne /
  
 
==== Wykonywanie komendy wewnątrz innego obiektu ====
 
==== Wykonywanie komendy wewnątrz innego obiektu ====
  
Komenda In dziala podobnie do komendy At, z ta roznice, ze jej dzialanie odbywa sie w inwentarzu dowolnego obiektu, gdzie to wykonujacy jest na chwile przenoszony. Stosowac z rozwaga i wylacznie gdy to konieczne. Nalezy zwrocic uwage, ze wiekszosc obiektow nie jest przeznaczonych do przyjmowania obiektow gracza wewnatrz siebie.
+
Komenda In działa podobnie do komendy At, z tą różnicą, że jej działanie odbywa się w inwentarzu dowolnego obiektu, gdzie to wykonujący jest na chwile przenoszony. Stosować z rozwagą i wyłącznie gdy to konieczne. Należy zwrócić uwagę, że większość obiektów nie jest przeznaczonych do przyjmowania obiektów gracza wewnątrz siebie.
  
Skladnia: In <dany_obiekt> <komenda>  
+
Składnia: In <dany_obiekt> <komenda>  
  
 
==== More &ndash; przeglądanie pliku powiązanego z obiektem ====
 
==== More &ndash; przeglądanie pliku powiązanego z obiektem ====
  
More to bardzo uzyteczna komenda, stanowiaca swoiste rozszerzenie Cat w tryb stronnicowany. Zezwala na wygodne przejrzenie kodu danego obiektu, ktory dzielony jest w zaleznosci od potrzeb, na kilka stron.
+
More to bardzo użyteczna komenda, stanowiąca swoiste rozszerzenie Cat w tryb stronnicowany. Zezwala na wygodne przejrzenie kodu danego obiektu, który dzielony jest w zależności od potrzeb, na kilka stron.
  
Skladnia: More <dany obiekt>  
+
Składnia: More <dany obiekt>  
  
 
==== Set &ndash; ustawianie zmiennej narzędziowej tracera ====
 
==== Set &ndash; ustawianie zmiennej narzędziowej tracera ====
  
W tym miejscu odwolam sie do tego co napisano wczesniej. Jednym z najwiekszych niebezpieczenst z pojawiajacych sie przy stosowaniu narzedzi tracera jest zmiana liczby porzadkujacej, okreslajacej pozycje obiektu na ktorym dzialamy, w inwentarzu jego srodowiska.
+
W tym miejscu odwołam się do tego co napisano wcześniej. Jednym z największych niebezpieczeństw pojawiających się przy stosowaniu narzędzi tracera jest zmiana liczby porządkującej, określającej pozycje obiektu na którym działamy, w inwentarzu jego środowiska.
  
Skladnia: Set $<zmienna> <dany_obiekt>
+
Składnia: Set $<zmienna> <dany_obiekt>
  
Zalozmy, by ulatwic omowienie problemu, ze chcemy odnowic stworzony przez nas miecz, znajdujacy sie w ekwipunku gracza Adama. Przyjmijmy, ze miecz ten zawieral w poprzedniej wersji istotne wady i chcemy go zastapic nowa wersja, ale tak by Adam w ogole sie nie spostrzegl. Aby znalezc dokladna liczbe okreslajaca pozycje miecza w ekwipunku Adama uzywamy Dump *adam inv [lub I *adam], niech bedzie to liczba 5. Nastepnie wykonujemy komende Destruct *adam:#5. W sytuacji gdy Adam w miedzyczasie odlozyl miecz, lub np. schowal go do plecaka, i zamiast miecza zniszczylismy jego cenne 3000 mithrylowych monet! To na pewno by mu sie nie spodobalo :)
+
Załóżmy, by ułatwić omówienie problemu, że chcemy odnowić stworzony przez nas miecz, znajdujący się w ekwipunku gracza Adama. Przyjmijmy, że miecz ten zawierał w poprzedniej wersji istotne wady i chcemy go zastąpić nową wersją, ale tak by Adam w ogóle się nie spostrzegł. Aby znaleźć dokładną liczbę określającą pozycje miecza w ekwipunku Adama używamy Dump *adam inv [lub I *adam], niech będzie to liczba 5. Następnie wykonujemy komendę Destruct *adam:#5. W sytuacji gdy Adam w międzyczasie odłożył miecz, lub np. schował go do plecaka, i zamiast miecza zniszczyliśmy jego cenne 3000 mithrylowych monet! To na pewno by mu się nie spodobało :)
  
Aby uniknac takich problemow nalezy zastosowac nastepujaca czynnosc. Pobieramy odwolanie do obiektu, jak w przykladzie powyzej i zapisujemy to w zmiennej. W przykladzie mamy: miecz posiada w chwili obecnej numer 5 w ekwipunku Adama, wykonujemy przeto Set $miecz *adam:#5. Po tym jak nastapia przetasowania w ekwipunku Adama tracer poinformuje nas, ze zmienna sword wskazuje teraz na stos mithrylowych monet. Zalozmy, ze miecz spadl na pozycje 4. Ustawiamy Set $miecz *adam:#4 i na powrot zmienna wskazuje wlasciwy obiekt. Wykonujemy Destruct $miecz i miecza juz nie ma. Prawda, ze proste? Teraz wystarczy go podmienic nowym.
+
Aby uniknąć takich problemów należy zastosować następującą czynność. Pobieramy odwołanie do obiektu, jak w przykładzie powyżej i zapisujemy to w zmiennej. W przykładzie mamy: miecz posiada w chwili obecnej numer 5 w ekwipunku Adama, wykonujemy przeto Set $miecz *adam:#5. Po tym jak nastąpią przetasowania w ekwipunku Adama tracer poinformuje nas, że zmienna sword wskazuje teraz na stos mithrylowych monet. Załóżmy, ze miecz spadł na pozycje 4. Ustawiamy Set $miecz *adam:#4 i na powrót zmienna wskazuje właściwy obiekt. Wykonujemy Destruct $miecz i miecza już nie ma. Prawda, ze proste? Teraz wystarczy go podmienić nowym.
  
Operacje odnowienia tego miecza, daloby sie wykonac na wiele innych, latwiejszych i na pewno mniej ryzykownych sposobow, powyzszy przyklad mial za zadanie jedynie zilustrowac mechanizm dzialania komendy Set.
+
Operacje odnowienia tego miecza, dałoby się wykonać na wiele innych, łatwiejszych i na pewno mniej ryzykownych sposobów, powyższy przykład miał za zadanie jedynie zilustrować mechanizm działania komendy Set.
  
Mozna ustawic dowolna liczbe zmiennych, nalezy jednak pamietac, ze po wylowaniu sie przepadna one. Podobnie, gdy obiekt ulegnie zniszczeniu, ten sam los spotka wskazujaca nan zmienna.
+
Można ustawić dowolną liczbę zmiennych, należy jednak pamiętać, że po wylogowaniu się przepadną one. Podobnie, gdy obiekt ulegnie zniszczeniu, ten sam los spotka wskazującą nań zmienną.
  
Set bez zadnych argumentow zwroci nam pelna liste ustawionych przez nas zmiennych.
+
Set bez żadnych argumentów zwróci nam pełną listę ustawionych przez nas zmiennych.
  
Ciekawostka jest fakt, ze ostatni obiekt na ktorym wywolano komende tracera zostaje przypisany pod zmienna $. Czyli po wywolaniu na obiekcie jakiejs funkcji tracera, nastepna mozna wywolac juz na zmiennej $ (<Komenda_tracera> $ <opcje>), a zostanie ona wywolana na tym samym obiekcie.
+
Ciekawostką jest fakt, że ostatni obiekt na którym wywołano komendę tracera zostaje przypisany pod zmienną $. Czyli po wywołaniu na obiekcie jakiejś funkcji tracera, następną można wywołać już na zmiennej $ (<Komenda_tracera> $ <opcje>), a zostanie ona wywołana na tym samym obiekcie.
  
 
==== Aktualizacja, przeładowanie, sklonowanie i zastąpienie danego obiektu nową wersją ====
 
==== Aktualizacja, przeładowanie, sklonowanie i zastąpienie danego obiektu nową wersją ====
  
Komenda Replace moze okazac sie bardzo uzyteczna.  
+
Komenda Replace może okazać się bardzo użyteczna.  
[Nie zostala uwzgledniona wsrod narzedzi tracera w mudlibie CDpl.01.00, porownaj ?odnow]
+
[Nie została uwzględniona wśród narzędzi tracera w mudlibie CDpl.01.00, porownaj ?odnow]
  
Przy jej uzyciu mozesz zaktualizowac obiekt nadrzedny danego obiektu, ponownie sklonowac nowa wersja i nadpisac ja na stara, ktora ulegnie degradacji.
+
Przy jej użyciu możesz zaktualizować obiekt nadrzędny danego obiektu, ponownie sklonować nową wersję i nadpisać ją na starą, która ulegnie degradacji.
  
Jesli nowa wersja nie da sie przeniesc w sposob bezposredni, kolejna proba przeniesienia nastepuje na zasadzie wymuszenia.
+
Jeśli nowa wersja nie da się przenieść w sposób bezpośredni, kolejna próba przeniesienia następuje na zasadzie wymuszenia.
  
Jesli wystapi blad krytyczny podczas odnawiania, proces zostanie przerwany, a stara wersja nie zostanie nadpisana.
+
Jeśli wystąpi błąd krytyczny podczas odnawiania, proces zostanie przerwany, a stara wersja nie zostanie nadpisana.
  
Skladnia: Reload <dany obiekt>  
+
Składnia: Reload <dany obiekt>  
  
 
==== Tail &ndash; przeglądanie powiązanego z obiektem pliku od końca ====
 
==== Tail &ndash; przeglądanie powiązanego z obiektem pliku od końca ====
  
Komenda Tail dziala podobnie jak Cat i More, z ta roznica, ze wyswietla ostatnie linijki kodu.
+
Komenda Tail działa podobnie jak Cat i More, z tą różnicą, że wyświetla ostatnie linijki kodu.
  
 
== Zaawansowany LPC i Mudlib ==
 
== Zaawansowany LPC i Mudlib ==
  
W rozdziale tym zajmiemy sie nieco bardziej zaawansowanymi sprawami
+
W rozdziale tym zajmiemy się nieco bardziej zaawansowanymi sprawami
dotyczacymi LPC. Musisz '''naprawde''' zrozumiec podstawy LPC, ktore wylozylem
+
dotyczącymi LPC. Musisz '''naprawdę''' zrozumieć podstawy LPC, które wyłożyłem
w poprzednich rozdzialach, zeby moc przyswoic sobie to, co tu opisuje.
+
w poprzednich rozdziałach, żeby móc przyswoić sobie to, co tu opisuje.
  
Nie miej zadnych oporow przed przegladaniem poprzednich rozdzialow w razie
+
Nie miej żadnych oporów przed przeglądaniem poprzednich rozdziałów w razie
potrzeby, czytajac ten tekst.
+
potrzeby, czytając ten tekst.
  
 
=== Funkcja jako typ danych, część 3 ===
 
=== Funkcja jako typ danych, część 3 ===
  
Typ funkcyjny (function) jako taki nie byl dotychczas szczegolowo omawiany w tym podreczniku, choc czasami uzywany. Jednak, ze wzgledu na jego wage nalezy go omowic. Dokladna znajomosc jego wlasnosci pozwoli ci stworzy czesto bardzo skomplikowane i zlozone, lecz jednoczesnie efktywne i optymalne partie kodu.  
+
Typ funkcyjny (function) jako taki nie był dotychczas szczegółowo omawiany w tym podręczniku, choć czasami używany. Jednak, ze względu na jego wagę należy go omówić. Dokładna znajomość jego własności pozwoli ci stworzy często bardzo skomplikowane i złożone, lecz jednocześnie efektywne i optymalne partie kodu.  
Warto wiedziec, ze wszystkie funkcje ktore zwykly przyjmowac nazwy funckji jako stringi, w chwili obecnej sa zdolne do pobrania wskaznika do danej funkcji jako argumentu, co jest efektywniejsze.   
+
Warto wiedzieć, że wszystkie funkcje które zwykły przyjmować nazwy funkcji jako stringi, w chwili obecnej zdolne do pobrania wskaźnika do danej funkcji jako argumentu, co jest efektywniejsze.   
  
 
==== Podstawowe informacje o typie funkcyjnym ====
 
==== Podstawowe informacje o typie funkcyjnym ====
  
Dostep do funkcji mozna uzyskac przez wskazniki funkcji. Jak pokazano wczesniej, kazda wywolanie funkcji sprowadza sie do wskaznika funkcji powiazanego z lista argumentow.
+
Dostęp do funkcji można uzyskać przez wskaźniki funkcji. Jak pokazano wcześniej, każde wywołanie funkcji sprowadza się do wskaźnika funkcji powiązanego z lista argumentów.
Rozpatrzmy to na prostym przykladzie.
+
Rozpatrzmy to na prostym przykładzie.
  
 
void
 
void
 
moja_funkcja(string str, int value)
 
moja_funkcja(string str, int value)
 
{
 
{
    write("Jako string podano: '" + str + "', zas jako liczbe: " + value + ".\n");
+
    write("Jako string podano: '" + str + "', zaś jako liczbę: " + value + ".\n");
 
    return;
 
    return;
 
}
 
}
  
Wywolanie funkcji nastepuje przez odwolanie sie do niej za posrednictwem jej nazwy i nastepujacych po niej argumentow podanych w nawiasie.  
+
Wywołanie funkcji następuje przez odwołanie się do niej za pośrednictwem jej nazwy i następujących po niej argumentów podanych w nawiasie.  
  
     Przykladowo:
+
     Przykładowo:
 
     moja_funkcja("smurf", 1000);
 
     moja_funkcja("smurf", 1000);
  
Teraz rozszerzymy to zgodnie z pomyslem typu funkcyjnego, gdzie mozna pobrac adres funkcji i nastepnie przypisac innej zmiennej.   
+
Teraz rozszerzymy to zgodnie z pomysłem typu funkcyjnego, gdzie można pobrać adres funkcji i następnie przypisać innej zmiennej.   
  
Przykladowo:
+
Przykładowo:
 
     function nowa_funkcja;
 
     function nowa_funkcja;
  
     nowa_funkcja = moja_funkcja;      // lub rownowaznie
+
     nowa_funkcja = moja_funkcja;      // lub równoważnie
 
     nowa_funkcja = &moja_funkcja();
 
     nowa_funkcja = &moja_funkcja();
  
     moja_funkcja("smurf", 1000);  // lub rownowaznie
+
     moja_funkcja("smurf", 1000);  // lub równoważnie
 
     nowa_funkcja("smurf", 1000);
 
     nowa_funkcja("smurf", 1000);
  
Zwrocmy uwage, ze nim zmiennej nowa_funkcja przypisano poprawna wartosc, nie odwolywala sie ona do zadnej funkcji. Uzycie jej w takim przypadku doprowadzilo by do bledu run-time.
+
Zwróćmy uwagę, że nim zmiennej nowa_funkcja przypisano poprawna wartość, nie odwoływała się ona do żadnej funkcji. Użycie jej w takim przypadku doprowadziło by do błędu run-time.
  
 
==== Okrojona lista argumentów ====
 
==== Okrojona lista argumentów ====
  
W poprzednim rozdziale moglismy zaobserwowac, ze mozliwe jest przypisanie odwolania do jednej funkcji zmiennej o innej nazwie. Innym przydatnym zastosowaniem moze okazac sie mozliwosc takiego przypisania, ze niektore argumenty dla oryginalnej funkcji jest traktowana jako stala, pozostawiajac reszte jako zmienne.
+
W poprzednim rozdziale mogliśmy zaobserwować, że możliwe jest przypisanie odwołania do jednej funkcji zmiennej o innej nazwie. Innym przydatnym zastosowaniem może okazać się możliwość takiego przypisania, że niektóre argumenty dla oryginalnej funkcji jest traktowana jako stała, pozostawiając resztę jako zmienne.
Zilustrujemy to przykladem:
+
Zilustrujemy to przykładem:
  
 
     void
 
     void
Linia 4693: Linia 4693:
 
         mod_tell = &tell_player(this_player(), );
 
         mod_tell = &tell_player(this_player(), );
  
         mod_tell("Witaj!"); // Rownowazne wzgledem
+
         mod_tell("Witaj!"); // Równoważne względem
  
 
         tell_player(this_player(), "Witaj!");
 
         tell_player(this_player(), "Witaj!");
 
     }
 
     }
  
Dziala to dobrze, bez wzgledu na ilosc argumentow. Pozostale argumenty zostana wypelnione od lewej do prawej.  
+
Działa to dobrze, bez względu na ilość argumentów. Pozostałe argumenty zostaną wypełnione od lewej do prawej.  
  
Przykladowo biorac funkcje o naglowku
+
Przykładowo biorąc funkcję o nagłówku
 
void func(int a, int b, int c, int d, int e)  
 
void func(int a, int b, int c, int d, int e)  
przy zmiennej okreslonej nastepujaco
+
przy zmiennej określonej następująco
 
function moja_funkcja = func(1, , 3, 4,);
 
function moja_funkcja = func(1, , 3, 4,);
otrzymamy, ze wywolanie
+
otrzymamy, że wywołanie
 
moja_funkcja(100, 101);  
 
moja_funkcja(100, 101);  
dziala tak samo jak wywolanie
+
działa tak samo jak wywołanie
 
func(1, 100, 3, 4, 101);
 
func(1, 100, 3, 4, 101);
  
 
==== Złożenia funkcji i kompleksowe struktury ====
 
==== Złożenia funkcji i kompleksowe struktury ====
  
W tym miejscu wazne jest, by czytelnik poruszal sie zrecznie w plaszczyznach problemow omowionych w poprzednich rozdzialach.  
+
W tym miejscu ważne jest, by czytelnik poruszał się zręcznie w płaszczyznach problemów omówionych w poprzednich rozdziałach.  
Pojawia sie pytanie: &lsquo;Co mozna umiescic w wywolanie funkcji tego typu?&rsquo;.
+
Pojawia się pytanie: &lsquo;Co można umieścić w wywołanie funkcji tego typu?&rsquo;.
  
 
Odpowiedz brzmi: &lsquo;Prawie wszystko&rsquo;.
 
Odpowiedz brzmi: &lsquo;Prawie wszystko&rsquo;.
  
Jest to oczywiscie zalezne od umiejetnosci wlasnych i gibkosci poruszania sie w ramach kodu.
+
Jest to oczywiście zależne od umiejętności własnych i gibkości poruszania się w ramach kodu.
  
Wiele osob miewa problemy ze zrozumieniem operatorow.  
+
Wiele osób miewa problemy ze zrozumieniem operatorów.  
Wsrod nich mamy: +, -, *, /, %, &, |, ^, >>, <<, <, >, <=, >=, ==, !=, [].  
+
Wśród nich mamy: +, -, *, /, %, &, |, ^, >>, <<, <, >, <=, >=, ==, !=, [].  
  
Dosyc czesto programista chce wykonac jedna operacje i jej wynik przeslac bezposrednio jako argument dla innej.  
+
Dosyć często programista chce wykonać jedną operację i jej wynik przesłać bezpośrednio jako argument dla innej.  
  
Typ funkcyjny obsluguje superpozycje [zlozenie] funkcji poprzez operator @.
+
Typ funkcyjny obsługuje superpozycję [złożenie] funkcji poprzez operator @.
  
Dziala on jak matematyczne zlozenie, od prawej do lewej! Podobnie jak operator.
+
Działa on jak matematyczne złożenie, od prawej do lewej! Podobnie jak operator.
  
Oto przyklad, ktory, miejmy nadzieje, rozjasni wszystko:
+
Oto przykład, który, miejmy nadzieje, rozjaśni wszystko:
  
 
Niech pewna tablica zawiera imiona pewnych graczy:
 
Niech pewna tablica zawiera imiona pewnych graczy:
Linia 4733: Linia 4733:
 
                   string *arr = ({ "Galarel", "Adren", "Kael", "Aendill", "Thorin", "Hanor" });
 
                   string *arr = ({ "Galarel", "Adren", "Kael", "Aendill", "Thorin", "Hanor" });
  
Przyjmijmy, ze chcemy docelowo pobrac z tablicy imiona, ktore skladaja sie z wiecej niz pieciu liter. Mozna to oczywiscie zrobic za pomoca petli, jak ponizej:
+
Przyjmijmy, że chcemy docelowo pobrać z tablicy imiona, które składają się z więcej niż pięciu liter. Można to oczywiście zrobić za pomocą pętli, jak poniżej:
  
  
Linia 4750: Linia 4750:
  
  
Oczywiscie, mozna uzyc efunkcji filter:
+
Oczywiście, można użyć efunkcji filter:
  
 
                 int
 
                 int
Linia 4764: Linia 4764:
 
                 }
 
                 }
  
Ale w takim przypadku musze pisac osobna funkcje filtrujaca. Na szczescie mozna pojsc jeszcze dalej, do efektownego zapisu jednolinijkowego !
+
Ale w takim przypadku muszę pisać osobną funkcje filtrującą. Na szczęście można pójść jeszcze dalej, do efektownego zapisu jednolinijkowego !
 
                 // ... tu mamy zdefiniowana tablice arr
 
                 // ... tu mamy zdefiniowana tablice arr
 
                  
 
                  
 
                 result = filter(arr, &operator(<)(5) @ strlen);
 
                 result = filter(arr, &operator(<)(5) @ strlen);
  
UWAGA! Niezmiernie wazne jest tu wywolanie operator(). Jak powiedzielismy przebiega ono od prawej do lewej, przeto dochodzi do momentu, gdy program ma do czynienia z nastepujacym porownaniem: 5 < strlen(name).
+
UWAGA! Niezmiernie ważne jest tu wywołanie operator(). Jak powiedzieliśmy przebiega ono od prawej do lewej, przeto dochodzi do momentu, gdy program ma do czynienia z następującym porównaniem: 5 < strlen(name).
  
Teraz nieco skomplikujemy sytuacje. Chcemy pojsc dalej i pobrac z tablicy wylacznie Smiertelnikow [ktorych imiona skladaja sie z conajwyzej pieciu znakow.]
+
Teraz nieco skomplikujemy sytuacje. Chcemy pójść dalej i pobrać z tablicy wyłącznie Śmiertelników [których imiona składają się z co najwyżej pięciu znaków.]
  
Oczywiscie jest to proste i wyglada nastepujaco:
+
Oczywiście jest to proste i wygląda następująco:
 
   
 
   
 
                 // ... tu mamy zdefiniowana tablice arr
 
                 // ... tu mamy zdefiniowana tablice arr
Linia 4780: Linia 4780:
 
                         &operator(==)(0) @ SECURITY->query_wiz_rank);
 
                         &operator(==)(0) @ SECURITY->query_wiz_rank);
  
Tutaj rozszerzono po prostu wywolanie o kolejne zlozenie, przy wczesniejszym pobraniu osob o imionach o dlugosci do 5 znakow.
+
Tutaj rozszerzono po prostu wywołanie o kolejne złożenie, przy wcześniejszym pobraniu osób o imionach o długości do 5 znaków.
  
Pojdzmy dalej. Jak myslisz co sie stanie gdy wykonasz nastepujace polecenie? Co pojawi sie na ekranie?
+
Pójdźmy dalej. Jak myślisz co się stanie gdy wykonasz następujące polecenie? Co pojawi się na ekranie?
  
 
                 exec return implode(sort_array(map(filter(users(), sizeof @  
 
                 exec return implode(sort_array(map(filter(users(), sizeof @  
Linia 4788: Linia 4788:
 
                             deep_inventory)->query_real_name(), capitalize)), ", ");
 
                             deep_inventory)->query_real_name(), capitalize)), ", ");
  
Na pewno wiekszosci czytelnikow wydaje sie to prosciutkie.
+
Na pewno większości czytelników wydaje się to prościutkie.
  
Opisze, co sie tu dzialo. Po pierwsze, przebiegamy przez wszystkich graczy [w tym czarodziejow] i przeszukujemy cale ich ekwipunki. Filtruje osoby, ktore posiadaja tarcze i pobieram ich imiona. Tablice wynikow sortuje, przeksztalcam by byly napisane z wielkiej litery i tworze zen jeden string z przecinikiem (,) po kazdym imieniu. W rezultacie na ekranie pojawia sie ten wlasnie string.
+
Opisze, co się tu działo. Po pierwsze, przebiegamy przez wszystkich graczy [w tym czarodziejów] i przeszukujemy całe ich ekwipunki. Filtruje osoby, które posiadają tarczę i pobieram ich imiona. Tablice wyników sortuje, przekształcam by były napisane z wielkiej litery i tworze z nich jeden string z przecinkiem (,) po każdym imieniu. W rezultacie na ekranie pojawia się ten właśnie string.
  
Innymi slowy: otrzymuje spis osob, ktore maja przy sobie tarcze.
+
Innymi słowy: otrzymuje spis osób, które mają przy sobie tarcze.
  
:'''UWAGA!''' Czas na przestroge. Oczywiscie gladko otrzymalem liste osob, majacych w ekwipunku tarcze (nawet jesli jest w plecaku). Jednak zwrocmy uwage, ze przy duzej liczbie graczy liczba obiektow, ktore maja przy sobie osiaga wielkie pulapy, moze nawet kilka tysiecy! I teraz na kazdym z nich wywolujemy funkcje sprawdzania czy to tarcza. Jak widac cala operacja jest niezwykle pamieciozerna, na szczescie potrzeba przeprowadzenia podobnej operacji rzadko sie zdarza.
+
:'''UWAGA!''' Czas na przestrogę. Oczywiście gładko otrzymałem listę osób, mających w ekwipunku tarcze (nawet jeśli jest w plecaku). Jednak zwróćmy uwagę, że przy dużej liczbie graczy liczba obiektów, które maja przy sobie osiąga wielkie pułapy, może nawet kilka tysięcy! I teraz na każdym z nich wywołujemy funkcje sprawdzania czy to tarcza. Jak widać cala operacja jest niezwykle pamięciożerna, na szczęście potrzeba przeprowadzenia podobnej operacji rzadko się zdarza.
Chodzi glownie o to by juz na etapie pisania kodu przewidywac jakie beda konsekwencje jego uzycia.
+
Chodzi głównie o to by już na etapie pisania kodu przewidywać jakie będą konsekwencje jego użycia.
  
 
=== 3.2 Pisanie efektywnego kodu ===
 
=== 3.2 Pisanie efektywnego kodu ===
  
Ten temat jest blisko powiazany z tym co powiedzialem wczesniej, ze
+
Ten temat jest blisko powiązany z tym co powiedziałem wcześniej, że
'''nie''' bede wyjasniac jak programowac. Wycofam swe slowa &ndash; troszeczke &ndash;
+
'''nie''' będę wyjaśniać jak programować. Wycofam swe słowa &ndash; troszeczkę &ndash;
i opowiem o kilku sprawach co robic, albo nawet co wazniejsze, czego '''nie'''
+
i opowiem o kilku sprawach co robić, albo nawet co ważniejsze, czego '''nie'''
robic.
+
robić.
  
 
==== Efektywne pętle ====
 
==== Efektywne pętle ====
  
Ten temat moze sie wydawac raczej trywialny. W koncu na ile sposobow
+
Ten temat może się wydawać raczej trywialny. W końcu na ile sposobów
mozna zapisac petle? Raczej na niewiele. Zacznijmy od najczestszego bledu.
+
można zapisać pętle? Raczej na niewiele. Zacznijmy od najczęstszego błędu.
Zalozmy, ze mamy jakas duza tablice, nazwijmy ja &lsquo;wielka_tab&rsquo; i zalozmy,
+
Załóżmy, że mamy jakąś dużą tablice, nazwijmy &lsquo;wielka_tab&rsquo; i załóżmy,
ze chcemy &lsquo;przeleciec&rsquo; petla przez wszystkie jej elementy. Co robisz w takiej
+
że chcemy &lsquo;przelecieć&rsquo; pętlą przez wszystkie jej elementy. Co robisz w takiej
sytuacji? &bdquo;Proste!&rdquo; wyjasniasz, &bdquo;Oczywiscie, ze zwykla petla &lsquo;for&rsquo;!&rdquo;.  
+
sytuacji? &bdquo;Proste!&rdquo; wyjaśniasz, &bdquo;Oczywiście, że zwykła pętla &lsquo;for&rsquo;!&rdquo;.  
Pewnie... Ale jak to zapiszesz? Najczesciej ludzie robia to w ten sposob:
+
Pewnie... Ale jak to zapiszesz? Najczęściej ludzie robią to w ten sposób:
  
 
         int i;
 
         int i;
Linia 4817: Linia 4817:
 
         for (i = 0 ; i < sizeof(wielka_tab) ; i++)
 
         for (i = 0 ; i < sizeof(wielka_tab) ; i++)
 
         {
 
         {
             // Tu robimy cos z tablica
+
             // Tu robimy coś z tablicą
 
         }
 
         }
  
No tak... a wiec co jest zle? Jesli przejrzysz rozdzial mowiacy
+
No tak... a więc co jest źle? Jeśli przejrzysz rozdział mówiący
o dzialaniu instrukcji &lsquo;for&rsquo;, to zobaczysz, ze wykonywane sa trzy czesci
+
o działaniu instrukcji &lsquo;for&rsquo;, to zobaczysz, że wykonywane trzy części
w okraglych nawiasach oddzielone srednikami. Pierwsza tylko na poczatku,
+
w okrągłych nawiasach oddzielone średnikami. Pierwsza tylko na początku,
druga (srodkowa) za '''kazdym''' cyklem petli i trzecia rowniez za kazdym razem
+
druga (środkowa) za '''każdym''' cyklem pętli i trzecia również za każdym razem
na koncu kazdego cyklu.
+
na końcu każdego cyklu.
  
Oznacza to, ze funkcja &lsquo;sizeof()&rsquo; zostaje wykonana za kazdym cyklem petli.
+
Oznacza to, że funkcja &lsquo;sizeof()&rsquo; zostaje wykonana za każdym cyklem pętli.
Jest to raczej marnotrastwo, biorac pod uwage fakt, ze tablice rzadko
+
Jest to raczej marnotrawstwo, biorąc pod uwagę fakt, że tablice rzadko
zmieniaja rozmiar. Jesli by robily to czesto, bylaby to inna para kaloszy,
+
zmieniają rozmiar. Jeśli by robiły to często, byłaby to inna para kaloszy,
ale jako ze nie robia... Nie. Napisz to w ten sposob:
+
ale jako że nie robią... Nie. Napisz to w ten sposób:
  
 
         int i, sz;
 
         int i, sz;
Linia 4835: Linia 4835:
 
         for (i = 0, sz = sizeof(wielka_tab) ; i < sz ; i++)
 
         for (i = 0, sz = sizeof(wielka_tab) ; i < sz ; i++)
 
         {
 
         {
             // Tu robimy cos z tablica.
+
             // Tu robimy coś z tablicą.
 
         }
 
         }
  
Widzisz? Zmienne &lsquo;i&rsquo; oraz &lsquo;sz&rsquo; tylko na poczatku dzialania petli maja
+
Widzisz? Zmienne &lsquo;i&rsquo; oraz &lsquo;sz&rsquo; tylko na początku działania pętli mają
przypisywane wartosci. Licznik &lsquo;i&rsquo; zostaje ustawiony na 0, a zmiennej
+
przypisywane wartości. Licznik &lsquo;i&rsquo; zostaje ustawiony na 0, a zmiennej
&lsquo;sz&rsquo; zostaje przypisany rozmiar tablicy. Przez caly czas dzialania
+
&lsquo;sz&rsquo; zostaje przypisany rozmiar tablicy. Przez cały czas działania
petli porownywane sa same zmienne, zamiast ciaglego obliczania
+
pętli porównywane są same zmienne, zamiast ciągłego obliczania
nie zmieniajacego sie rozmiaru tablicy.
+
nie zmieniającego się rozmiaru tablicy.
  
Mozesz mi wierzyc albo nie, ale to jest bardzo czesty blad, prawie
+
Możesz mi wierzyć albo nie, ale to jest bardzo częsty błąd, prawie
kazdy go robi. Oszczednosci w moim sposobie moga sie nie wydawac tak
+
każdy go robi. Oszczędności w moim sposobie mogą się nie wydawać tak
wielkie... ale... pomnoz to razy wszystkie petle w mudzie i przez liczbe
+
wielkie... ale... pomnóż to razy wszystkie pętle w mudzie i przez liczbę
razy kiedy sa wykonywane, a otrzymasz calkiem niezla liczbe. Kosztem
+
razy kiedy wykonywane, a otrzymasz całkiem niezłą liczbę. Kosztem
drugiego sposobu w porownaniu do pierwszego jest dodanie jednej zmiennej
+
drugiego sposobu w porównaniu do pierwszego jest dodanie jednej zmiennej
lokalnej i to jest wystarczajaco mala cena.
+
lokalnej i to jest wystarczająco mała cena.
  
Pamietaj o tym problemie nie tylko w przypadku tablic, ale rowniez
+
Pamiętaj o tym problemie nie tylko w przypadku tablic, ale również
w mappingach lub innych ogolnych pojemnikach, zawierajacych rzeczy, przez
+
w mappingach lub innych ogólnych pojemnikach, zawierających rzeczy, przez
ktore chcesz &lsquo;przeleciec&rsquo; petla. Rozwiazanie, z wyjatkiem malych roznic
+
które chcesz &lsquo;przelecieć&rsquo; pętlą. Rozwiązanie, z wyjątkiem małych różnic
 
w rozpoznawaniu rozmiaru pojemnika jest zawsze takie same.
 
w rozpoznawaniu rozmiaru pojemnika jest zawsze takie same.
  
 
==== Słów kilka o makrodefinicjach ====
 
==== Słów kilka o makrodefinicjach ====
  
Czesto popelnianym bledem jest umieszczanie DUZYCH tablic i/lub mappingow jako makrodefinicji. Wyobrazmy sobie jeden wielki mapping zawierajacy definicje rang gildiowych, opisy, przerozne limity umiejetnosci mozliwych tamze do wytrenowania, modyfikatory, etc, gdzie ranga gildiowa spelnia funkcje indeksu. Bardzo czesto trzeba sie bedzie do niego odwolywac, przez centralny obiekt administracyjny gildii, etc. Mamy cos takiego:
+
Często popełnianym błędem jest umieszczanie DUŻYCH tablic i/lub mappingów jako makrodefinicji. Wyobraźmy sobie jeden wielki mapping zawierający definicje rang gildiowych, opisy, przeróżne limity umiejętności możliwych tamże do wytrenowania, modyfikatory, etc, gdzie ranga gildiowa spełnia funkcje indeksu. Bardzo często trzeba się będzie do niego odwoływać, przez centralny obiekt administracyjny gildii, etc. Mamy coś takiego:
  
         // Gorna partia pliku
+
         // Górna partia pliku
 
          
 
          
         #define GUILD_MAP ([ 0: ({ "poczatkujacy", "troche", 3, 2343, ... }), \
+
         #define GUILD_MAP ([ 0: ({ "początkujący", "trochę", 3, 2343, ... }), \
 
                     1: ({ ....                                          \
 
                     1: ({ ....                                          \
                     ... /* i dalej okolo 20 lub wiecej podobnych linijek */        \
+
                     ... /* i dalej około 20 lub więcej podobnych linijek */        \
 
                   ])
 
                   ])
 
          
 
          
         // kodowy przyklad uzycia
+
         // kodowy przykład użycia
 
         write("Masz obecnie range: " + GUILD_MAP[rank][1] + "\n");
 
         write("Masz obecnie range: " + GUILD_MAP[rank][1] + "\n");
 
         // dalszy kod
 
         // dalszy kod
  
Spojrz na to czytelniku uwaznie. Przypomne w tym miejscu co daja makrodefinicje: otoz dzialaja nastepujaca, ilekroc podczas wykonywania programu system napotka na wzor podany przy makrodefinicji (tu: GUILD_MAP) zamienia go przez rozwiniecie podane w makrodefinicji (tu: caly ten mapping). Krotko: ilekroc uzyjemy odwolania do GUILD_MAP wstawiany jest calutki mapping. Za kazdym razem gdy go wstawiamy driver musi ponownie interpretowac jego zawartosc, sortowac i indeksowac. Jest to zatem strasznie nieoptymalne, pamieciozerne i czasochlonne.
+
Spójrz na to czytelniku uważnie. Przypomnę w tym miejscu co dają makrodefinicje: otóż działają następująco, ilekroć podczas wykonywania programu system napotka na wzór podany przy makrodefinicji (tu: GUILD_MAP) zamienia go przez rozwinięcie podane w makrodefinicji (tu: cały ten mapping). Krótko: ilekroć użyjemy odwołania do GUILD_MAP wstawiany jest calutki mapping. Za każdym razem gdy go wstawiamy driver musi ponownie interpretować jego zawartość, sortować i indeksować. Jest to zatem strasznie nieoptymalne, pamięciożerne i czasochłonne.
  
Zamiast makrodefinicji o wiele efektywniej mozna to zrobic tak, uzywajac zmiennej:
+
Zamiast makrodefinicji o wiele efektywniej można to zrobić tak, używając zmiennej:
  
         // Gorna partia pliku
+
         // Górna partia pliku
 
          
 
          
 
         mapping GuildMap;
 
         mapping GuildMap;
Linia 4883: Linia 4883:
 
             // kod
 
             // kod
 
          
 
          
             GuildMap =  ([ 0: ({ "poczatkujacy", "troche", 3, 2343, ... }), \
+
             GuildMap =  ([ 0: ({ "początkujący", "trochę", 3, 2343, ... }), \
 
                     1: ({ ....                                          \
 
                     1: ({ ....                                          \
                     ... /* i dalej okolo 20 lub wiecej podobnych linijek */        \
+
                     ... /* i dalej około 20 lub więcej podobnych linijek */        \
 
                   ]);
 
                   ]);
  
 
         }
 
         }
 
          
 
          
         // kodowy przyklad uzycia
+
         // kodowy przykład użycia
         write("Masz obecnie range: " + GuildMap[rank][1] + "\n");
+
         write("Masz obecnie rangę: " + GuildMap[rank][1] + "\n");
 
         // dalszy kod...
 
         // dalszy kod...
  
 
=== Pułapki i niuanse ===
 
=== Pułapki i niuanse ===
  
Przy kodowaniu nie trudno o pomylke. Czesto kod wyglada na nasze oko ladnie, ale tak naprawde jest szalenie nieefektywny. W tym rozdziale ponownie zajmiemy sie czesto popelnianymi bledami.  
+
Przy kodowaniu nie trudno o pomyłkę. Często kod wygląda na nasze oko ładnie, ale tak naprawdę jest szalenie nieefektywny. W tym rozdziale ponownie zajmiemy się często popełnianymi błędami.  
  
Wiele z tych bledow juz opisano we wczesniejszych rozdzialach, ale nie zaszkodzi powtorzyc.
+
Wiele z tych błędów już opisano we wcześniejszych rozdziałach, ale nie zaszkodzi powtórzyć.
  
 
==== Mappingi/Tablice &ndash; bezpieczeństwo ====
 
==== Mappingi/Tablice &ndash; bezpieczeństwo ====
  
Problem jak wspomniano w rozdziale 2.2.9 polega na tym, ze mappingi i tablice nie sa kopiowane, za kazdym razem gdy sa przenoszone. Operacje odbywaja sie jedynie na wskazniku. Moze to byc podstawa do wielu niebezpiecznych luk w kodzie.  
+
Problem jak wspomniano w rozdziale 2.2.9 polega na tym, że mappingi i tablice nie kopiowane, za każdym razem gdy przenoszone. Operacje odbywają się jedynie na wskaźniku. Może to być podstawa do wielu niebezpiecznych luk w kodzie.  
  
Przyjrzyjmy sie przykladowi obiektu gildiowego, ktory zajmuje sie czlonkami gildii. Globalna zmienna Rada, ktora jest zapisywana przez save_object() zawiera rade gildii.
+
Przyjrzyjmy się przykładowi obiektu gildiowego, który zajmuje się członkami gildii. Globalna zmienna Rada, która jest zapisywana przez save_object() zawiera radę gildii.
  
 
         string *Rada;
 
         string *Rada;
Linia 4914: Linia 4914:
 
         }
 
         }
  
Wyglada to na prawidlowe, lecz tak naprawde zwraca jedynie wskaznik do oryginalnej tablicy. Jesli ktos inny zechce dodac czlonka do rady gildii moze to zrobic! wystarczy, taka sztuczka:
+
Wygląda to na prawidłowe, lecz tak naprawdę zwraca jedynie wskaźnik do oryginalnej tablicy. Jeśli ktoś inny zechce dodać członka do rady gildii może to zrobić! wystarczy, taka sztuczka:
  
 
         void
 
         void
Linia 4923: Linia 4923:
 
             rada_po_moich_poprawkach = OBIEKT_GILDIOWY->zwroc_rade();
 
             rada_po_moich_poprawkach = OBIEKT_GILDIOWY->zwroc_rade();
 
          
 
          
             rada_po_moich_poprawkach += ({ "olorin" }); // I dodaje Olorina do rady gildii i kazdy
+
             rada_po_moich_poprawkach += ({ "olorin" }); // I dodaje Olorina do rady gildii i każdy
         // moze to zrobic!
+
         // może to zrobić!
 
         }
 
         }
  
Jak to zatem poprawic? Wystarczy zmodyfikowac funkcje zwroc_rade, by zwracala return Rada + ({}); i wszystko bedzie w porzadku. Latwo przegapic, a jakiez to wazne!
+
Jak to zatem poprawić? Wystarczy zmodyfikować funkcję zwroc_rade, by zwracała return Rada + ({}); i wszystko będzie w porządku. Łatwo przegapić, a jakież to ważne!
  
 
==== Zapętlanie alarmów ====
 
==== Zapętlanie alarmów ====
  
Przyjrzyjmy sie nastepujacej funkcji:
+
Przyjrzyjmy się następującej funkcji:
 
 
 
         public void
 
         public void
Linia 4940: Linia 4940:
 
         }
 
         }
  
Co bedzie efektem wywolania tej funkcji? Otoz, co sekunde bedzie generowany nowy alarm, wywolujacy siebie samego co sekunde. Co to oznacza? Spojrzmy na opis efektow:
+
Co będzie efektem wywołania tej funkcji? Otóż, co sekundę będzie generowany nowy alarm, wywołujący siebie samego co sekundę. Co to oznacza? Spójrzmy na opis efektów:
  
 
         1 sekunda:
 
         1 sekunda:
             Buu! 0 (oryginalne wywolanie)
+
             Buu! 0 (oryginalne wywołanie)
 
         2 sekunda:
 
         2 sekunda:
             Buu! 1 (powtorzenie 1 sek 0)
+
             Buu! 1 (powtórzenie 1 sek 0)
 
             Buu! 1 (nowe od 1 sek 0)
 
             Buu! 1 (nowe od 1 sek 0)
 
         3 sekunda:
 
         3 sekunda:
             Buu! 1 (powtorzenie 1 sek 0)
+
             Buu! 1 (powtórzenie 1 sek 0)
             Buu! 2 (powtorzenie 2 sek 1)
+
             Buu! 2 (powtórzenie 2 sek 1)
             Buu! 2 (powtorzenie 2 sek 1)
+
             Buu! 2 (powtórzenie 2 sek 1)
 
             Buu! 2 (nowe od 2 sek 1)
 
             Buu! 2 (nowe od 2 sek 1)
 
             Buu! 2 (nowe od 2 sek 1)
 
             Buu! 2 (nowe od 2 sek 1)
 
         4 sekunda:
 
         4 sekunda:
             Buu! 1 (powtorzenie 1 sek 0)
+
             Buu! 1 (powtórzenie 1 sek 0)
 
             Buu! 2 (nowe od 3 sek 1)
 
             Buu! 2 (nowe od 3 sek 1)
             Buu! 2 (powtorzenie 2 sek 1)
+
             Buu! 2 (powtórzenie 2 sek 1)
             Buu! 2 (powtorzenie 2 sek 1)
+
             Buu! 2 (powtórzenie 2 sek 1)
             Buu! 3 (powtorzenie 3 sek 2)
+
             Buu! 3 (powtórzenie 3 sek 2)
             Buu! 3 (powtorzenie 3 sek 2)
+
             Buu! 3 (powtórzenie 3 sek 2)
             Buu! 3 (powtorzenie 3 sek 2)
+
             Buu! 3 (powtórzenie 3 sek 2)
             Buu! 3 (powtorzenie 3 sek 2)
+
             Buu! 3 (powtórzenie 3 sek 2)
 
             Buu! 3 (nowe od 3 sek 2)
 
             Buu! 3 (nowe od 3 sek 2)
 
             Buu! 3 (nowe od 3 sek 2)
 
             Buu! 3 (nowe od 3 sek 2)
Linia 4969: Linia 4969:
 
           ... itd.
 
           ... itd.
  
Jak widac przyrost jest tutaj lawinowy, co w krotkim czasie doprowadzi do padu gry. Taka praktyka jest tak glupia ze konsekwencje dla czarodzieja, ktory by cos takiego lub podobnego zrobil bylyby bardzo ostre.  
+
Jak widać przyrost jest tutaj lawinowy, co w krótkim czasie doprowadzi do padu gry. Taka praktyka jest tak głupia że konsekwencje dla czarodzieja, który by coś takiego lub podobnego zrobił byłyby bardzo ostre.  
Oczywiscie na obecnym etapie rozwoju wiekszosc LPMudów jest zabezpieczona przed uzyciem partykularnie tozsamej konstrukcji.
+
Oczywiście na obecnym etapie rozwoju większość LPMudów jest zabezpieczona przed użyciem partykularnie tożsamej konstrukcji.
  
 
=== Korzystanie z wewnętrznej dokumentacji ===
 
=== Korzystanie z wewnętrznej dokumentacji ===
  
Na zakonczenie slow kilka o wewnetrznej dokumentacji muda, gdzie znalezc mozna zazwyczaj mnostwo przydatnych rzeczy [zalezy to glownie od administratorow muda i osob zajmujacych sie dokumentacja ;)]
+
Na zakończenie słów kilka o wewnętrznej dokumentacji muda, gdzie znaleźć można zazwyczaj mnóstwo przydatnych rzeczy [zależy to głównie od administratorów muda i osób zajmujących się dokumentacją ;)]
  
Efunkcje, sfunkcje i lfuns opisano w manulach dostepnych pod komenda &lsquo;man&rsquo;. Mozna oczywiscie wyszukiwac za pomoca odpowiedniej opcji interesujace nas funkcje. Ponadto w kilku rozdzialach opisano rozne przydatne rzeczy. [szczegoly ?man]
+
Efunkcje, sfunkcje i lfuns opisano w manualach dostępnych pod komendą &lsquo;man&rsquo;. Można oczywiście wyszukiwać za pomocą odpowiedniej opcji interesujące nas funkcje. Ponadto w kilku rozdziałach opisano różne przydatne rzeczy. [szczegóły ?man]
  
Przykladowo chcemy pobrac user id obiektu, ale nie pamietamy dokladnie jaka funkcja jest za to odpowiedzialna. Pamietamy jednak, ze jej nazwa konczyla sie literami &lsquo;id&rsquo;!
+
Przykładowo chcemy pobrać user id obiektu, ale nie pamiętamy dokładnie jaka funkcja jest za to odpowiedzialna. Pamiętamy jednak, że jej nazwa kończyła się literami &lsquo;id&rsquo;!
  
 
         > man -k *id
 
         > man -k *id
Linia 4985: Linia 4985:
 
         export_uid  geteuid    getuid      seteuid    setuid
 
         export_uid  geteuid    getuid      seteuid    setuid
  
Widzimy zatem, ze wyszukiwarka znalazla piec funkcji odpowiadajacych podanemu kryterium. Wyswietlono wszyskie pasujace wyniki, po przeszukaniu wszystkich stron man.  
+
Widzimy zatem, że wyszukiwarka znalazła pięć funkcji odpowiadających podanemu kryterium. Wyświetlono wszystkie pasujące wyniki, po przeszukaniu wszystkich stron man.  
  
Wiemy juz, ze chodzilo o getuid. By poczytac o niej wpisujemy man getuid (lub nawet man simul_efun getuid &ndash; efekt bedzie w tym przypadku taki sam).
+
Wiemy już, że chodziło o getuid. By poczytać o niej wpisujemy man getuid (lub nawet man simul_efun getuid &ndash; efekt będzie w tym przypadku taki sam).
  
Doswiadczeni programisci LPC znaja na pamiec powiazania funkcji i wiedza co dziedziczyc by uzyskac do porzadany efekt. Wie takze mniej wiecej jakie funkcje zawarte sa w danym obiekcie, oczywiscie nie zna wszystkich.  
+
Doświadczeni programiści LPC znają na pamięć powiązania funkcji i wiedza co dziedziczyć by uzyskać do pożądany efekt. Wie także mniej więcej jakie funkcje zawarte w danym obiekcie, oczywiście nie zna wszystkich.  
Kazdy koder przeto powinien czesto korzystac z komendy &lsquo;sman&rsquo;. Stanowi on kolekcje naglowkow funkcji zawartych w kodzie z /cmd, /lib, /obj, /secure, /std i /sys.
+
Każdy koder przeto powinien często korzystać z komendy &lsquo;sman&rsquo;. Stanowi on kolekcje nagłówków funkcji zawartych w kodzie z /cmd, /lib, /obj, /secure, /std i /sys.
  
Takze tutaj mozliwe jest przeszukiwanie smanu wg podanych kryteriow [szczegoly ?sman].
+
Także tutaj możliwe jest przeszukiwanie smanu wg podanych kryteriów [szczegóły ?sman].
  
Przykladowo, chcemy znalezc funkcje ktora zwraca nazwe gildii zawodowej do ktorej dany gracz nalezy. Nie wiemy jednak jaki obiekt definiuje ja ani jak sie ta funkcja zwie.
+
Przykładowo, chcemy znaleźć funkcję która zwraca nazwę gildii zawodowej do której dany gracz należy. Nie wiemy jednak jaki obiekt definiuje ja ani jak się ta funkcja zwie.
 
   
 
   
 
         > sman -k *guild*
 
         > sman -k *guild*
Linia 5048: Linia 5048:
 
         query_guild_pref_total  set_guild_stat
 
         query_guild_pref_total  set_guild_stat
  
W gaszczu funkcji odnajdujemy te jedna, a szczegoly odczytamy uzywajac komendy sman /std/guild/guild_occ_sh query_guild_name_occ.
+
W gąszczu funkcji odnajdujemy tę jedną, a szczegóły odczytamy używając komendy sman /std/guild/guild_occ_sh query_guild_name_occ.
  
 
Z czasem lepiej będziesz orientować się w Mudlibie i łatwiej będzie ci tak formułować zapytanie, żeby otrzymać wystarczająco zawężoną odpowiedź za pierwszym razem. Nie żeby było coś złego w parokrotnym przeszukiwaniu, od tego w końcu jest sman!
 
Z czasem lepiej będziesz orientować się w Mudlibie i łatwiej będzie ci tak formułować zapytanie, żeby otrzymać wystarczająco zawężoną odpowiedź za pierwszym razem. Nie żeby było coś złego w parokrotnym przeszukiwaniu, od tego w końcu jest sman!
  
:''' Koniec czesci glownej Manualu LPC ...
+
:''' Koniec części głównej Manualu LPC ...
:''' Uzupelnienie by Kael
+
:''' Uzupełnienie by Kael
  
 
== DODATEK A. Formanty odmiany polskiej ==
 
== DODATEK A. Formanty odmiany polskiej ==
Linia 5059: Linia 5059:
 
1) odmiana przez przypadki
 
1) odmiana przez przypadki
 
  PL_MIA  0 - mianownik
 
  PL_MIA  0 - mianownik
  PL_DOP  1 - dopelniacz
+
  PL_DOP  1 - dopełniacz
 
  PL_CEL  2 - celownik
 
  PL_CEL  2 - celownik
 
  PL_BIE  3 - biernik
 
  PL_BIE  3 - biernik
  PL_NAR  4 - nadrzednik
+
  PL_NAR  4 - nadrzędnik
 
  PL_MIE  5 - miejscownik
 
  PL_MIE  5 - miejscownik
  
np. chcac poznac imie gracza w bierniku stosujemy
+
np. chcąc poznać imię gracza w bierniku stosujemy
 
   this_player()->query_name(PL_BIE);
 
   this_player()->query_name(PL_BIE);
 
lub  
 
lub  
Linia 5072: Linia 5072:
 
2) rodzaje gramatyczne  
 
2) rodzaje gramatyczne  
  
  PL_MESKI_OS            0 - wskazuje na odmiane meska osobowa obiektu (osoba tu oznacza istote zywa myslaca, np. elf, ogr ale nie kot)  
+
  PL_MESKI_OS            0 - wskazuje na odmianę męską osobową obiektu (osoba tu oznacza istotę żywą myślącą, np. elf, ogr ale nie kot)  
  PL_MESKI_NOS_ZYW        1 - wskazuje na odmiane nieosobowa dla istoty zywej (np. kota)
+
  PL_MESKI_NOS_ZYW        1 - wskazuje na odmianę nieosobową dla istoty żywej (np. kota)
  PL_MESKI_NOS_NZYW      2 - wskazuje na odmiane nieosobowa dla przedmiotu (np. dlugopis)
+
  PL_MESKI_NOS_NZYW      2 - wskazuje na odmianę nieosobową dla przedmiotu (np. długopis)
 
  PL_MESKI_NZYW          2 - to samo co PL_MSESKI_NOS_NZYW  
 
  PL_MESKI_NZYW          2 - to samo co PL_MSESKI_NOS_NZYW  
  
  PL_ZENSKI              3 - wskazuje na odmiane zenska
+
  PL_ZENSKI              3 - wskazuje na odmianę żeńską
  
  PL_NIJAKI_OS            4 - wskazuje na odmiane nijaka osobowa
+
  PL_NIJAKI_OS            4 - wskazuje na odmianę nijaką osobową
  PL_NIJAKI_NOS          5 - wskazuje na odmiane nijaka nieosobowa
+
  PL_NIJAKI_NOS          5 - wskazuje na odmianę nijaką nieosobową
  
 
== DODATEK B. Rodzaje obrażeń ==
 
== DODATEK B. Rodzaje obrażeń ==
  W_IMPALE    rany klute
+
  W_IMPALE    rany kłute
  W_SLASH    rany ciete
+
  W_SLASH    rany cięte
 
  W_BLUDGEON  obuchowe
 
  W_BLUDGEON  obuchowe
  
  W_NO_DT    brak obrazen
+
  W_NO_DT    brak obrażeń
  MAGIC_DT    obrazenia magiczne
+
  MAGIC_DT    obrażenia magiczne
  
 
[[Kategoria:Mechanika Arkadii]]
 
[[Kategoria:Mechanika Arkadii]]

Aktualna wersja na dzień 18:23, 22 gru 2018

Wersja tekstowa z wyszczególnionym autorstwem rozdziałów znajduje się na MUD-a Barsawia. Tu znajduje się ładnie, w założeniu, sformatowana i w wielu miejscach poprawiona wersja HTML.
This is a tutorial for LPC and basic LPmud mudlib.

  The document was written for the Genesis driver and Genesis mudlib.
Gamedriver version CD.04.02, Mudlib CD.00.31 though in all likelyhood
it will still be usable for three-four versions further.

  Copyright (C) 1996 Ronny Wikh

  The use of this manual in any commercial venture is expressly
forbidden, neither may it be offered as inducement to purchase other
services or products.

  Permission is granted to make and distribute verbatim copies of this
manual provided the copyright notice and this permission notice are
preserved in full on all copies.

  Permission is granted to copy and distribute modified versions of this
manual under the conditions for verbatim copying, provided that the
entire resulting derived work is distributed under the terms of a
permission notice identical to this one.

  Permission is granted to copy and distribute translations of this
manual into another language, under the above conditions for modified
versions, except that this permission notice may be stated in a
translation approved by the holders of the above copyright.

Przetłumaczyli na polski / Translated into polish:

  • Alvin.Arkadia [arkadia (@) arkadia.rpg.pl] (25/08/96)
  • Kael.Barsawia [barsawia (@) irc.pl] (28/02/03)

Przedmowa

LPC

Granie w LPMudy jest bardzo ekscytującym zajęciem, a ich tworzenie jeszcze bardziej. Jest bardzo niewiele rzeczy, których nie da się zakodować. Nieraz trzeba jednak używać różnych sztuczek. Największą zaletą MUD-ów jest naturalnie to, ze może w nie grać naraz wielu graczy; widok setek, a nawet tysięcy ludzi używających i czerpiących radość z Twojego kodu sprawia wielką przyjemność.

Ten podręcznik ma na celu nauczenie Cię niezbędnych podstaw potrzebnych do tworzenia kodu w LPMudach i na Genesis w szczególności. Nie jest to jednak łatwa lektura, wiec spodziewaj się, ze zajmie Ci kilka ładnych dni przyswojenie sobie tego, czego spróbuje Cię nauczyć. Pomimo tego groźnie brzmiącego ostrzeżenia, życzę Ci przyjemnego kodowania, po tym, jak już przełamiesz pierwsze lody.

-- Ronny Wikh, 25 stycznia 1996

Warunki rozpowszechniania

Podręcznik ten został napisany w dobrej wierze dla ludzi, którzy piszą MUD-y dla własnej przyjemności. Oznacza to, ze nie będę wymagał żadnych opłat za używanie i rozprowadzanie tego tekstu, pod warunkiem, ze taki właśnie jest cel jego użytkowania.

W szczególności chce się upewnić, że ci, którzy czerpią korzyści finansowe z prowadzenia MUD-a nie używają tego dokumentu jako pomocy w zarabianiu pieniędzy.

Notki tłumaczy

Alvin

Tłumaczenie może być miejscami niezręczne. Jest też spora szansa, że napotkasz jakieś błędy. W obu przypadkach wal z nimi proszę do mnie, czyli do Alvina.

Mam nadzieję, że Ci się na coś zda to tłumaczenie.

Kael

Wszelkie, błędy, merytoryczne uwagi i propozycje dotyczące tego wydania proszę zgłaszać na adres: rafal.dorociak (małpa) wp.pl

Starałem się używać w miarę przystępnego języka, w partiach, które tłumaczyłem, nierzadko odbiegając nieco od oryginału, tudzież dodając własne uwagi.

Życzę miłego korzystania i wielu przyjemnych chwil z kodem. :)

Wstęp

Na początku moim zamierzeniem było, by dowolna osoba mogła korzystać i uczyć się z tego dokumentu. Teraz jednak wydaje mi się to raczej niemożliwe, więc skoryguje to troszeczkę. To jest podręcznik, z którego może korzystać każdy, kto ma przynajmniej blade pojecie o programowaniu oraz chęci do nauki. Nie musisz znać C zanim zaczniesz i mam nadzieję, że nawet kompletni laicy będą w stanie się nauczyć kodowania, choć oczywiście będą mieli o wiele więcej roboty.

Doświadczeni programiści, a nawet ci, którzy pisali już kiedyś mudy będą musieli zaznajomić się z tym podręcznikiem, gdyż wyjaśnia on pojęcia charakterystyczne tylko dla tej gry, aczkolwiek będą mogli po prostu przejrzeć większość tekstu i skupić się na bardziej zawiłych fragmentach. Tobie, czytelniku, zostawiam wybór tego co jest dla Ciebie ważne, a co nie, gdyż chyba tylko Ty wiesz czego się jeszcze powinieneś nauczyć.

Jako, ze język LPC w aktualnej postaci jest mocno powiązany z mudlibem, który jest w nim napisany, zahaczę również o jego część. Jednakże nie będę Cie uczył szczegółowo jak z niego korzystać – będziesz musiał przeczytać inne podręczniki. Wszystko to czyni ten dokument wyspecjalizowanym na LPMudy, a na Genesis w szczególności. Pamiętaj o tym, jeśli piszesz pod innym mudem.

Mam nadzieję, że ten podręcznik okaże się pomocny i warty wysiłku, który trzeba poswięcić na jego przeczytanie. Z pewnością napisanie go nie było takie proste, więc niech lepiej jego lektura nie będzie bezowocna! :)

Podziękowania

Chciałbym zacząć od podziękowań dla Thorstena Lockerta, Christiana Markusa i Bobby'ego Bodenheimera (znanych raczej jako Plugh, Olorin i Plarry), za pomoc przy korekcie tekstu, wartościowe sugestie i ogólnie za wsparcie i pomocna dłoń w czasie tworzenia tego podręcznika.

Bez nich realizacja tego ciągnącego się projektu zajęłaby jeszcze więcej czasu (niewiarygodne, ale bardzo prawdziwe), a końcowy efekt byłby znacznie gorszy.

Mnóstwo ludzi na Genesisie przyczyniła się do kształtu tego tutoriala i z wdzięcznością wspominam o ich cennych sugestiach i pytaniach, ukazujących co ludzie chcieliby wiedzieć o LPC.

Forma podręcznika

Podręcznik jest podzielony na trzy rozdziały, o rosnącym stopniu zaawansowania. Pierwszy rozdział wyjaśni podstawy kodowania i LPC bez zagłębiania się w szczegóły.

Drugi rozdział traktuje o trudniejszych sprawach; wyjaśni w pełni wszystkie aspekty funkcji i operatorów, które mogły zostać troszeczkę zbyt pobieżnie potraktowane w rozdziale pierwszym.

Trzeci, końcowy rozdział traktuje o tym, co mogło zostać pominięte w dwóch poprzednich. Tak naprawdę, to nie o wszystkim; podręcznik nie wyjaśni wszystkich zawiłości gamedrivera i mudliba. Jeśli szukasz informacji o tworzeniu własnego mudliba albo o robieniu innych bardzo zaawansowanych rzeczy, będziesz musiał sam do tego dojść, czytając kod źródłowy.

Jeśli jesteś początkującym wizardem, możesz być trochę przytłoczony na początku, patrząc na ten obszerny podręcznik. Jednakże jest ważne, żebyś przeczytał wszystko i nie zostawiał niczego na później. Bez wątpienia będziesz musiał poznać zagadnienia ze wszystkich trzech rozdziałów, choć przeważnie będziesz korzystał z wiedzy zawartej w dwóch pierwszych. Kurcze! Jest wielu starych wizardów, którzy ledwo co opanowali pierwszy rozdział! Jest to jeden z podstawowych powodów, dla których pisze ten podręcznik...

Ten podręcznik jest dość rozległy, pomimo tego, że jest przeznaczony tylko do nauki kodowania w domenach. Oznacza to, że nie jest to kompletna instrukcja LPC. Kilka rzadziej używanych efunkcji jest pominiętych, gdyż są one używane tylko przez ludzi piszących oraz ulepszających mudliba i gamedrivera. Przykro mi, jeśli właśnie tego tu szukałeś – będziesz się musiał rozejrzeć za jakimś innym źródłem informacji.

Mała uwaga o płci. Przez cały czas używam męskiego rodzaju. Nie dlatego, że dyskryminuje programistki, ale dlatego, że język angielski jest nastawiony na męski rodzaj. (przyp. tłum. hmm.. nie jestem pewien jak z polskim, ale to co autor dalej pisze to prawda) Myślę, że mógłbym za każdym razem dopisywać też żeńską formę, lecz uważam, ze byłoby to trochę głupawe. Dopóki angielski nie stanie się w pełni neutralnym płciowo językiem, będę się trzymał męskiego rodzaju.

Rozdziały, które szczegółowo opisują efunkcje/sfunkcje/lfunkcje/makra, będą miały wymienione w nawiasach odwołania do nazw powiązanych z omawianymi rzeczami, aby ułatwić Ci późniejsze wyszukiwanie tych informacji.

Historia LPC

LPC jest językiem programowania MUD-ów, stworzonym przez Larsa Pensjoego na potrzeby własnego pomysłu LPMuda, interaktywnego, wielużytkownikowego środowiska, przeznaczonego do kilku różnych celów, wśród których gra nie była jedynym. Od czasu pierwszego pojawienia się go, w 1988 roku, język znacznie się zmienił.

Prace nad nim zostały przejęte około 1990 roku, przez innych ludzi z Chalmers Datofrerening, głównie przez Jakoba Hallena, Lennarta Augustssona i Andersa Chrigstroema. Rozszerzyli i udoskonalili znacznie język, aczkolwiek, jak nazwa LPC wskazuje, wciąż wykazuje on powiązania z językiem ‘C’. Różnice głównie polegają na dołączeniu struktury obiektowej, jak również na dodaniu kilku typów danych, w celu uprzystępnienia programowania. LPC nie jest tak swobodnym językiem jak klasyczny ‘C’, choć z drugiej strony bardziej się nadaje do celu, dla jakiego został stworzony – do pisania środowiska gry.

Gamedriver/Mudlib

Definicja różnicy pomiędzy gamedriverem i mudlibem może się zdawać trudna do określenia, lecz tak naprawdę jest bardzo prosta. Gamedriver jest to program, który jest uruchomiony na serwerze muda. Jest interpreterem poleceń połączonym z jądrem zarządzania obiektami (object management kernel). Można nawet powiedzieć, ze jest to swoisty system operacyjny. Definiuje i rozumie język LPC, interpretuje i wykonuje instrukcje podane mu poprzez obiekty LPC. Gamedriver zajmuje się tylko obliczeniami i wszelka treść MUD-a jest mu podawana z zewnątrz do uruchomienia.

Mudlib jest biblioteką podstawowych obiektów LPC wspólnych dla całej gry. Zawiera zestaw obiektów LPC, które są używane w grze: ogólny obiekt gracza, potwora, pokoju itd. To co Ty piszesz, czyli kod domenowy, jest przechowywane i obsługiwane osobno i korzysta z podstawowych elementów środowiska gry umieszczonych w mudlibie.

Struktura administracyjna

Można powiedzieć, że gra jest podzielona na trzy charakterystyczne części, tak jak to już zostało powyżej zasugerowane; Gamedriver, mudlib i „kod domenowy”. Gamedriver i mudlib już zostały opisane. Domeny są sposobem na zorganizowanie pisania kodu. Domena to grupa wizardów, pracujących nad osiągnięciem z góry określonego celu. Może nim być jakaś ograniczona przestrzeń, fragment świata gry, lub rzeczy takie jak gildie bądż sekty, których członkami gracze mogą zostać.

W każdej domenie jest jeden szef, Lord domeny. Jest on swego rodzaju nadzorcą. Decyduje co się w domenie ma dziać i w jakim kierunku prace powinny zdążać. W domenie cały kod może być łatwo wymieniany i jest mocno ze sobą powiązany.

Oczywiście, są również więzy pomiędzy różnymi domenami, aczkolwiek są one zazwyczaj słabsze, a kod jest rzadko wymieniany.

Jako początkujący wizard, będziesz próbował znaleźć sobie jakąś domenę, w której praca wyda ci się interesująca i inspirująca.

Pisanie kodu

Może się wydać nieco przedwczesnym mówienie Ci jak kod powinien wyglądać, zanim nawet nie nauczyłeś się jak go pisać. Jednakże jest to fundamentalna sprawa o wielkiej ważności. Ktoś fajnie powiedział, że właściwe pisanie kodu uczyni Twe zęby bielszymi, włosy ciemniejszymi i pozytywnie wpłynie na twoje życie seksualne. Tego chyba nie mogę zagwarantować, ale na pewno drastycznie polepszy to jakość, tego co piszesz, przy bardzo niskich nakładach pracy. Polega to głównie na samodyscyplinie.

Oto kilka dobrych argumentów na pisanie poprawnego kodu:

  • Czyni Twój kod znacznie bardziej przejrzystym, nie tylko dla innych, lecz również dla Ciebie, szczególnie jeśli sam będziesz musiał przeczytać i poprawić go sześć miesięcy po tym, jak go napisałeś.
  • Jeśli jest on bardziej przejrzysty dla innych, to łatwiej im będzie zrozumieć to, co skleciłeś i co za tym idzie łatwiej będzie im pomóc ci w razie problemów.
  • Właściwe pisanie kodu czyni go lepszym, możesz wierzyć lub nie. Przyczyna jest prosta: niepoprawne pisanie programu zwiększa bardzo szanse na to, że przeoczysz jakieś banalne błędy, schowane pośród ściśniętego kodu.
  • Może ci być naprawdę ciężko znaleźć ludzi, którzy będą chcieli odpluskwić źle sformatowany kod. Ja osobiście nie pomogę osobom w usunięciu błędów z programu, który wygląda okropnie. Przyczyna jest prosta: jest to gra nie warta świeczki. W źle wyglądającym kodzie kryje się wiele głupich błędów (przeważnie brakujące, albo przemieszczone nawiasy), które wyjdą na wierzch od razu po tym, jak właściwie sformatujesz program.

Teraz będzie trochę przydługa instrukcja mówiąca o tym, jak należy układać kod w czasie pisania. Przeczytaj to, nawet jeśli w pełni nie rozumiesz o czym jest mowa, a później wróć i przeczytaj raz jeszcze po tym, jak zdobędziesz niezbędne umiejętności. W ten sposób utrwali Ci się właściwy sposób pisania programów.

  1. Jedno wcięcie ma długość 4 spacji, nie mniej, nie więcej. Nowe wcięcie zaczyna się na początku każdego bloku.
  2. Pomiędzy słowami kluczowymi a otwierającym nawiasem ‘(’, jeśli takowy jest, powinna być jedna spacja.
        while (test)
            wyrażenie;
  3. Nawiasy otwierające i zamykające ten sam blok powinny być w tej samej kolumnie; w kolumnie pierwszej litery wyrażenia otwierającego blok.
        if (to)
        {
            wyrażenie;
        }
        else if (tamto)
        {
            inne_wyrażenie;
        }
        else
        {
            standardowe_wyrażenie;
        }
    

    Natrafiamy teraz na punkt o prawie religijnym znaczeniu dla niektórych programistów. Przedstawiciele innej sekty wyznają, że nawias otwierąjacy blok powinien znajdować się na końcu linii z wyrażeniem otwierającym blok. Rób tak jak ja mówię, albo utkniesz w tym cholernym COBOLu na zawsze :) Teraz na serio, wybierz jedna metodę i trzymaj się jej. Jest naprawdę bardzo ważną rzeczą to, abyś utrzymywał tę samą długość wcięcia przez cały czas. Zmienne długości wcięć to coś, co nie może być tolerowane.

  4. Liczne argumenty oddzielone od siebie przecinkami, mają po każdym takowym jedną spację. Listy argumentów oddzielone ‘;’ i operatory numeryczne maja po jednej spacji przed i po operatorze.
        int a, b, c;
    
        for (a = 0; a < 10; a++)
        {
            b = function_call(a, b * 2);
            c = b * 3 / 4;
        }
  5. Jeśli wyrażenie pętli jest puste, umieść kończący ‘;’ w oddzielnej linii. while (!(zmienna = funkcja(zmienna))) ; Przyczyną jest to, ze jakbyś umieszczał ‘;’ w tej samej linii, byłoby bardzo łatwo przeoczyć prawdziwe błędy takie jak ten, wynikający z czystego lenistwa.
        for (i = 0 ; i < 100 ; i++);
        {
            <ten blok jest wykonywany tylko raz, i to za każdym razem>
        }
  6. Wszystkie wyrażenia ‘#define’ i ‘#include’ powinny być umieszczone na samym początku pliku. Możliwe jest ich rozrzucenie, lecz to tylko gmatwa.
  7. To samo z prototypami funkcji i zmiennymi typu global/static. Zbierz je do kupy, z odpowiednim komentarzem w nagłówku i umieść na początku pliku. Oczywiście możesz je rozrzucić po całym pliku, ale jakże łatwe będzie przeoczenie ich później...
  8. Przy deklaracji funkcji, typ zwracanej wartości umieszczaj w linii przed nazwą funkcji.
        public void
        moja_funkcja(int a, int b)
        {
            < kod >
        }
  9. Łam długie linie kodu w odpowiednich miejscach tak, żeby nie wychodziły poza 80-znakowy ekran. Wygląda to potwornie i ciężko się to czyta, nie mówiąc o problemach związanych z późniejszym drukowaniem.
  10. Plik powinien się zaczynać następującym nagłówkiem:
        /*
        * <nazwa pliku>
        *
        * <Krótki opis tego co plik robi, nie więcej niż 5-7 linii.
        * ...
        * ... >
        *
        * Copyright (C): <twoje imię, nazwisko i rok>
        *
        */

    Już TERAZ przeczytaj prawa autorskie, by wiedzieć jakie zasady obowiązują co do kodu, który piszesz dla gry. Powinny one się znajdować w pliku ‘/doc/COPYRIGHT’. Jeśli ich tam nie ma, to po prostu spytaj jednego z administratorów gry. (przyp. tłum. U nas jeszcze nie ma czegoś takiego i nie będzie do czasu aż ktoś nie napisze ;) )

  11. Każda funkcję zaczynaj od nagłówka, który wygląda tak:
        /*
        * Nazwa funkcji: <Nazwa funkcji>
        * Opis:          <Krótki opis tego, co funkcja robi, zazwyczaj
        *                 nie więcej niż trzy linie. >
        * Argumenty      <Lista wszystkich argumentów, jeden na linie
        *                   arg1 - opis nie dłuższy niż jedna linia.
        *                   arg2 - następny argument, itd. >
        * Zwraca:        <Co funkcja zwraca>
        */
    Jeśli funkcja nie potrzebuje żadnych argumentów, lub nie zwraca niczego, to po prostu usuń te linie z nagłówka.
  12. Umieść stosowne komentarze przy kodzie tu i ówdzie, tam gdzie uznasz, ze może on być niezrozumiały. Pamiętaj również o tym, że na twoim (zakładanym) poziomie kompetencji wiele rzeczy może się wydawać niezrozumiałymi :) Czyń według własnego uznania.
        /*
        * Komentarz poprzedzający kod, 
        * opisujący co on robi
        */
        < kod >
  13. Upewnij się, że nazwy funkcji i zmienne lokalne są zapisane małymi literami alfabetu, z ewentualnymi odstępami pomiędzy wyrazami w postaci podkreślenia (np. ‘nazwa_funkcji()’). Zmienne globalne powinny mieć duże pierwsze litery słów (np. ‘int GlobalTime;’). ‘#define’ powinny być zapisane dużymi literami (np. ‘#define AMEBA "jednokomórkowe żyjątko"’). W ten sposób łatwo będzie rozpoznać, jaki jest to rodzaj symbolu. Najprostszym sposobem załapania w jaki sposób należy pisać, jest używanie edytora emacs, ustawionego na tryb c++. Taki tryb rozumie operatory ‘::’, aczkolwiek wymaga kilku podpowiedzi w materii tabulacji itp. Umieść te linie w pliku .emacs i wszystko powinno działać jak powinno:
    W oryginalnym tutorialu ta lista jest dłuższa, nie znam się na emacsie więc nie wiem czy to istotne.
        ;; emacs lisp script start
         
        (setq auto-mode-alist (append '(
          ("\\.l" . my-c++-mode)
          ("\\.y" . my-c++-moe)
          ("\\.c" . my-c++-mode)
          ("\\.h" . my-c++-mode))
            auto-mode-alist))
         
        (defun my-c++-mode () (interactive)
          (c++-mode)
          (setq c-indent-level 4)
          (setq c-brace-offset 0)
          (setq c-label-offset -4)
          (setq c-continued-brace-offset -4)
          (setq c-continued-statement-offset 4))
          ;; emacs end

    Emacs ma jeszcze wbudowaną możliwość, która się przydaje przy odpluskwianiu wypocin innych ludzi. Poprawienie wcięć w kodzie jest tak proste, jak wstukanie ‘M-<’, ‘M->’, ‘M-x indent-region’.

Wprowadzenie do LPC

Ten rozdział nauczy cię zupełnych podstaw programowania, potrzebnych do zrozumienia całości. Oprócz tego wprowadzi cię w programowanie obiektowe oraz opisze kawałek mudliba.

Podstawowe zagadnienia związane z programowaniem

Zaczynamy od podstawowych zagadnień związanych z programowaniem, strukturą i środowiskiem LPC.

Co to jest programowanie?

To jest całkiem filozoficzne pytanie. Trzymajmy się jednakże praktycznej strony i zostawmy cały bitowy zen tym, którzy się tym zajmują.

W zasadzie programowanie to sztuka identyfikowania problemu i zamieniania rozwiązania w symbole, które komputer jest w stanie zrozumieć. Dobry programista ma wysoko rozwiniętą zdolność widzenia, jak problem może być podzielony na mniejsze problemiki, z których każdy można rozwiązać na kilka sposobów. Wie on także, które z rozwiązań jest najefektywniejsze i powinno być użyte w danej sytuacji, biorąc pod uwagę szybkość działania i zużycie pamięci.

Programista, jak już poprzednio zasugerowałem, mówi komputerowi jak rozwiązać problem. Maszyna nie może sama wymyślić rozwiązania. Jednakże jest ona o wiele szybsza od człowieka, więc problemy które możesz rozwiązać, ale które zajęłyby ci mnóstwo czasu, są szybciutko rozwiązywane przez komputer.

To, czego musisz się nauczyć, to sposób myślenia, który umożliwi ci ‘rozbijanie’ problemów na kroki, które musisz wykonać od stanu początkowego do rozwiązanego problemu. Musisz także poznać metody czynienia tych kroków. Naturalnie, ten podręcznik nie nauczy cię jak programować, a jedynie przedstawi ci język, za pomocą którego, będziesz mógł wpisać program.

Kto w takim razie nauczy cię programowania, jeśli nie wiesz jak? Hmm.. W pierwszym rzędzie inni wizardzi oraz ty sam. Innymi słowy ciężka praca. Niestety nigdy nie ma żadnych skrótów, niezależnie od tego, czego potrzebujesz się nauczyć. Jednakże ponieważ jest to świetna zabawa, miejmy nadzieję że zdobywanie tych umiejętności sprawi Ci wielką frajdę.

Kompilowany/interpretowany kod

Programy są niczym więcej, jak tylko plikami zawierającymi zbiór instrukcji zrozumiałych dla komputera. Programować oznacza wpisywać listy komend w taki sposób, by komputer doszedł do zamierzonego przez nas celu. Zazwyczaj program jest kompilowany – tłumaczony – na niskopoziomowy kod wyrażony w symbolach binarnych (wysokie i niskie stany napięć w pamięci komputera), które maszyna rozumie bez problemu. Język, w którym programujesz jest po prostu wygodnym kompromisem, który rozumiesz zarówno Ty jak i komputer. Przyczyna dla której kompilujesz, jest to, ze tłumaczenie kodu jest dość skomplikowane i zajmuje sporo czasu. Lepiej zrobić to raz, przechować wyniki i odwoływać się bezpośrednio do nich w razie potrzeby.

LPC jednakże nie jest kompilowany – jest interpretowany. Instrukcje są czytane i tłumaczone jedna po drugiej, wykonane i zapomniane. W sumie nie jest to tak do końca prawda. W rzeczywistości gamedriver tłumaczy program w LPC do prostej, przejściowej postaci kodu instrukcji. Ten zestaw kodów komend tworzy tak zwany ‘master object’ i jest przechowywany w pamięci komputera. Kiedy uruchamiasz program LPC, instrukcje sa przeglądane linia po linii, tak jak zostało to powyżej opisane, powodując wykonanie poprzednio określonego zestawu czynności zdefiniowanego przez instrukcje.

Różnica pomiędzy posiadaniem zinterpretowanego kodu, a posiadaniem skompilowanego polega na tym, ze o ile skompilowany kod jest szybszy, to zinterpretowana wersja jest o wiele łatwiejsza do zmodyfikowania. Jeśli chcesz dokonać zmiany w skompilowanej wersji, musisz zmienić kod źródłowy, zrekompilować, przechować nowa wersje i dopiero wypróbować. Gdy masz zinterpretowany kod, wystarczy ze zmienisz źródło i uruchomisz go. W LPC musisz jeszcze poinstruować gamedrivera, aby zniszczył stary ‘master object’, ale o tym później.

Programy

Programy LPC, jak zostało wyżej powiedziane, mają postać plików zawierających instrukcje dla komputera, napisane w języku LPC. Ich nazwy muszą się kończyć literami ‘.c’ (np. ‘test.c’), dzięki czemu gamedriver wie z czym ma do czynienia. Nazwa pliku może być dowolnym łańcuchem znakowym o długości mniejszej niż 32 znaki, zaczynającym się od litery alfabetu. W praktyce jednakże, lepiej jest ograniczać nazwy plików do 16 znaków, na które składają się tylko małe litery alfabetu. Jeśli chcesz by nazwa składała się z dwóch słów, możesz je oddzielić za pomocą znaku ‘_’ (np. ‘moja_nazwa_pliku.c’).

Obiekty

„Obiekt” w LPC jest po prostu załadowaną do pamięci komputera kopią istniejącego programu – jest jednym ucieleśnieniem jakiegoś kodu. Kiedy program jest ładowany do pamięci, by utworzyć „master object”, kod jest kompilowany i wytwarza poprzednio opisany zestaw instrukcji. Dołączany przy tym jest także mały obszar pamięci przeznaczony dla „zmiennych globalnych” (opisane dalej). Kiedy kopia, „klon” programu jest tworzona, specjalny odnośnik zwany „wskaźnikiem obiektu” (object pointer) jest kreowany. Dostaje on adres do master objectu oraz unikalny obszar pamięci. Kiedy klonujesz obiekt jeszcze raz, tworzony jest nowy wskaźnik i przydzielana jest nowa pamięć. Gdy obiekt jest niszczony, zaalokowana pamięć zostaje uwolniona, tak aby inne obiekty mogły z niej korzystać. Sam master object, czyli wyżej opisana lista instrukcji zostaje nienaruszona. Obiekt zawsze jest klonowany z master objectu. Jeśli chcesz klonować nowa wersję obiektu, najpierw musisz uaktualnić (update) master object, aby gamedriver wiedział, ze nowa lista instrukcji ma być kompilowana ze zmienionego kodu źródłowego.

W takiej sytuacji, istniejące klony nie będą zmieniane tylko z tego powodu, ze master object uległ zmianie. Będą miały odniesienie do starej listy instrukcji. Ważne jest abyś pamiętał, że zachowanie starych klonów nie zmienia się tylko dlatego, że uaktualniłeś master object. Jak widzisz jest możliwe posiadanie klonów obiektów, które się zachowują różnie, po prostu dlatego, że są one skompilowane z różnych źródeł, sklonowane pomiędzy uaktualnieniami i zmianami w kodzie. Może to być przyczyną dużych nieporozumień, więc pamiętaj o tym.

Struktura obiektu

Obiekt jest złożony z „funkcji” i „zmiennych”. Funkcja to zestaw instrukcji, do których można się odwołać za pomocą nazwy. Zmienna to swego rodzaju pojemnik, w którym można przechowywać dane do użytku przez funkcje. Kilka funkcji jest zdefiniowane z góry w gamedriverze i są one nazywane „funkcjami zewnętrznymi” (external functions), albo „efunkcjami” (efuns). Funkcje zdefiniowane w kodzie LPC są nazywane „lokalnymi funkcjami” (local functions), albo „lfunkcjami&rbdquo; (lfuns). Żeby jeszcze bardziej pogmatwać sprawę są funkcje, które zachowują się jak efunkcje, ale są napisane w kodzie LPC. Są one zwane „symulowanymi efunkcjami” (simulated efuns), albo „sfunkcjami” (sfuns).

Efunkcja to taka, której się nie da stworzyć w LPC. Na przykład funkcja write(), która umożliwia wyświetlenie tekstu na ekranie gracza. Niemożliwe jest stworzenie jej z innych funkcji LPC, więc musi ona być w gamedriverze. Ta efunkcja jest dostępna dla wszystkich programów LPC. Efunkcje nie wiedzą w jakim środowisku są używane i nie martwią się o to, czy są stosowane do symulowania smaku truskawki, czy jako część gry.

Funkcja taka jak ‘add_exit()’, która dodaje wyjście z pokoju jest dostępna tylko obiektom typu pokój i napisano ja w LPC. Lfunkcje z zasady są częścią struktury środowiska w jakim obiekty są użyte. Nasz przykładowy ‘add_exit()’ świetnie radzi sobie z takimi rzeczami, jak kierunki wyjść i koszty podróży (zmęczenie), ale jest ograniczony tylko do tego – nie potrafi nic więcej.

Funkcja ‘creator()’ jest dobrym przykładem trzeciego rodzaju. Jest ona dostępna wszędzie. Zwraca kto stworzył podany obiekt. Ta informacja jest jednak bardzo charakterystyczna dla środowiska, ponieważ używa takich pojęć jak lokalizacja kodu. Taki rodzaj funkcji jest łatwy do napisania w LPC, ale z drugiej strony musi być dostępny we wszystkich obiektach, tak jak by to były efunkcje. Z tej przyczyny został stworzony specjalny obiekt ‘/secure/simul_efun.c’, który jest dostępny ze wszystkich innych obiektów w grze. Znajdziesz tam wszystkie sfunkcje. To wszystko jest w sumie niewidzialne dla ciebie; ty po prostu używasz ich jako efunkcji, bez zawracania sobie głowy, że jest to jakaś sfunkcja.

Podstawy LPC

LPC jest bardzo podobny do języka C, choć można się dopatrzeć kilku różnic. Jak doświadczony programista zapewne dostrzeże, jest trochę uproszczony poprzez dodanie dla wygody kilku nowych typów i zestawu funkcji obsługujących je. Różnice nie są jednak na tyle poważne, by mogły spowodować jakieś problemy, o ile się o nich pamięta.

Komentarze

Może to trochę dziwnie wyglądać, że zaczynam akurat od tego, ale komentarze występują wszędzie, więc musisz umieć rozpoznawać je od samego początku.

Są dwa typy komentarzy:

    <kod> // To jest komentarz trwający do końca tej linii.
    
    <kod> /* To jest komentarz ograniczony */ <kod>

Jak widzisz, pierwszy typ komentarzy zaczyna się od znaków ‘//’ i trwa aż do końca linii. Jeśli chcesz mieć więcej linii komentarzy, to musisz na początku każdej napisać ‘//’.

Drugi typ jest taki, że ma określoną długość. Zaczyna się od znaków ‘/*’ i kończy znakami ‘*/’. Ten rodzaj komentarzy jest użyteczny, gdy chcesz zawrzeć tekst, który zajmuje wiele linii.

UWAGA! Komentarz ‘/* */’ nie może być zagnieżdżony, tzn nie możesz zrobić czegoś takiego jak w tym przykładzie:

    /* Komentarz /* Zagnieżdżony komentarz */ kontynuacja pierwszego */

W takiej sytuacji komentarz skończy się na pierwszym napotkanym ‘*/’, pozostawiając tekst ‘kontynuacja pierwszego */’ kompilatorowi, który będzie to próbował zinterpretować tak, jakby to był kod LPC. Oczywiście coś takiego nie zadziała i otrzymasz komunikat błędu.

Typy danych

Obiekty przechowują informacje w „zmiennych”. Jak sama nazwa wskazuje, są one oznaczonymi schowkami, które mogą magazynować informacje, które się zmieniają od czasu do czasu. Obiekt operuje na nich poprzez funkcje, które to zarówno używają jak i zwracają dane rożnych typów.

W zasadzie tylko jeden typ danych jest potrzebny, coś w rodzaju uniwersalnego pojemnika, który może przechowywać cokolwiek zechcesz. W rzeczywistości jest o wiele lepiej, jeśli rozróżnisz różne typy informacji od siebie. Może się wydawać, że to tylko przysporzy ci jeszcze więcej problemów, ale tak naprawdę to redukuje to ryzyko błędu, podnosi czytelność oraz znacznie przyspiesza kodowanie i odpluskwianie obiektu.

W LPC jest możliwość używania tylko danych ‘ogólnego użytku’, o których wcześniej mówiłem. We wcześniejszych wersjach języka, był to jedyny dostępny typ. Jednakże w LPC, którego używamy dziś, wielce korzystne jest unikanie tego jak się tylko da. Zaczynaj swe programy tą instrukcją:

    #pragma strict_types

Mówi ona gamedriverowi by sprawdził wszystkie funkcje, czy są dostosowane do sytuacji w jakiej są używane i co za tym idzie, czy nie powodują żadnych błędów. Jest to wielka pomoc we wczesnym wykrywaniu błędów i dzięki temu nie będziesz musiał się później głowić, kiedy program nie będzie działał do końca tak, jak byś chciał.

Istnieją następujące typy danych:

  • void
‘nic’
Ten typ danych jest używany w funkcjach, które nie zwracają żadnych danych.
  • int
‘liczba całkowita’
Dowolna liczba całkowita z szerokiego zakresu, zależnego od mocy komputera, np. od -2147483648 do 2147483647. Może to być choćby 3, 17, -32, 999.
  • float
‘liczba zmiennoprzecinkowa’
Dowolna liczba wymierna mieszcząca się w bardzo szerokim zakresie, zależnym od mocy komputera, np. od 1.17549435e-38 do 3.40282347e+38, przykładowo 1.7, -348.4, 4.53e+4.
Jeśli są tu jacyś miłośnicy FORTRANa, to niech uważają na to, ze numery typu ‘1.’ albo ‘.4711’ nie sa rozpoznawane jako zmiennoprzecinkowe. Musicie podać zarówno całkowitą, jak i ułamkową część, nawet jeśli któraś z nich jest zerowa.
  • string
‘łańcuch znaków’
Stringi to po prostu łańcuchy znaków (liczb, liter – na przykład słowa), zawarte w angielskich cudzysłowach, np. "x", "łańcuch", "Kolejny długi łańcuch z numerkiem 5 w środku". Stringi mogą zawierać specjalne znaki, takie jak znak nowej linii ("\n"). Wiele wyrażeń języka LPC może obsługiwać stringi bezpośrednio, w przeciwieństwie do C. Czyni to je bardzo użytecznymi i łatwymi w obsłudze.
  • mapping
‘Lista powiązań’
Mappingi to kolejny bardzo wygodny wynalazek LPC (uwaga, bardzo pamięciożerny, używaj go z umiarem). Mapping jest to lista połączonych ze sobą wartości. Załóżmy, że chcesz przechować wiek kilku osób, np. ze Olek ma 23 lata, Piotr 54, a Ania 17. W LPC można to zapisać w ten sposób:
([ "Olek":23, "Piotr":54, "Ania":17 ])
Jak widzisz wartość po lewej stronie została powiązana z wartością po prawej. Możesz wyłowić jakieś powiązanie, poprzez podanie lewej strony.
  • object
‘wskaźnik obiektu’
Jest to odnośnik do klonu kodu LPC, który jest załadowany do pamięci.
  • function
‘wskaźnik funkcji’
Odnośnik do funkcji.
  • Array
‘tablica’
Wszystkie powyższe typy mogą występować jako tablice. Wtedy, w definicji, przed nazwą każdej zmiennej wstawia się ‘*’, np. ‘int *a, b;’ definiuje dwie zmienne: tablice a oraz pojedynczą zmienną b.
W LPC tablice wyglądają bardziej jak listy, niż jak prawdziwe tablice. Istnieje wiele funkcji, które zostały napisane by przyspieszyć i ułatwić ich obsługę.
  • mixed
No i na koniec ogólny typ, który zastępuje wszystkie inne, swego rodzaju klucz uniwersalny. Pod zmienne mixed można podstawić wartość dowolnego innego typu. Jeszcze raz powtórzę, że używanie go poza sytuacjami, kiedy to jest absolutnie konieczne, tylko prowokuje błędy.
Hmm, jak mi zwrócono uwagę, to co napisałem może brzmieć ciut za surowo. Typ mixed jest całkiem często używany. Chodziło mi o to, ze o ile da się zastosować jakiś normalny typ, to się go powinno użyć. Nie zastępuj go zmienną typu mixed tylko dlatego, ze jesteś leniwy.

Deklaracje zmiennych

Zmienna jest to łańcuch znaków identyfikujący ‘skrytkę’, w której dane są przechowywane. Skrytka ma podana nazwę, składającą się z maks. 32 znaków, gdzie pierwszy z nich musi być literą alfabetu. Ustalono, że wszystkie zmienne zdefiniowane wewnątrz funkcji będą się składały z samych małych liter. Globalne zmienne zaś, będą miały pierwszą literę dużą i resztę małą. Ustalono również, że nie będzie się oddzielało wyrazów znakami innymi niż ‘_’. Nazwy zmiennych zawsze powinny odzwierciedlać to, do czego są używane. Zmienne deklarujemy w następujący sposób:

   <typ danych> <nazwa zmiennej>, <nast. zmienna>, ..., <ostatnia zmienna>;
   np:
       int        licznik;
       float      wysokość, waga;
       mapping    map_wieku;

Zmienne muszą być zadeklarowane na początku bloku (tuż po znaku otwierającym blok ‘{’), przed kodem właściwym. Zmienne globalne, czyli takie, które są dostępne w całym programie, powinny być zadeklarowane na początku pliku.

Kiedy deklaracje są wykonywane w czasie uruchamiania programu, są one ustawiane na 0, a NIE na ich ‘puste’(null) wartości. Innymi słowy np. mappingi, tablice i stringi będą zawsze ustawiane na 0, a nie na ‘([])’, ‘({})’ i ‘""’ tak, jak byś mógł przypuszczać. Możliwe jest zainicjalizowanie zmiennych już w samej deklaracji. Jest to nawet bardzo dobry zwyczaj.

A robi się to tak:

    <typ danych> <nazwa zmiennej> = <wartość>, itp.
    np.
        int        licznik = 8;
        float      wysokość = 3.0, waga = 1.2;
        mapping    map_wieku = ([]);
        object     *potwory = ({});

Powodem dla którego tablice i mappingi powinny być inicjalizowane na swoje wartości ‘puste’ (czyli kolejno ‘({})’ i ‘([])’) jest to, ze inaczej będą ustawione na 0, co może być przyczyną niekompatybilności typów i może spowodować później jakieś problemy.

Definicje funkcji

Funkcja musi dać znać jaki typ danych zwraca, o ile w ogóle coś zwraca. Tak jak zmienne, funkcje to są nazwy składające się z maks. 32 znaków, gdzie pierwszy z nich to litera. Przyjęte jest, że nazwy wszystkich funkcji będą pisane małymi literami, a do oddzielania wyrazów będzie się używalo tylko znaku ‘_’. Nazywaj funkcje tak, aby jasno odzwierciedlały to co robią. Deklaracja funkcji wygląda tak:

    /*
     * Nazwa funkcji: <Nazwa funkcji>
     * Opis:          <Co ona robi>
     * Argumenty:     <Lista i krótki opis argumentów>
     * Zwraca:        <Co funkcja zwraca>
     */
    <typ zwracanej wartości>
    <nazwa funkcji>(<lista argumentów>)
    {
       <kod funkcji>
    }
    /*
     * Function name: oblicz_srednice
     * Description:   Oblicza średnicę okręgu przy podanym obwodzie
     * Variables:     obwod - obwód okręgu
     *                name - nazwa dana okręgowi
     * Returns:       Srednice.
     */
    float
    oblicz_srednice(float obwod, string nazwa)
    {
        float rval;
    
        // Obwod = pi * srednica
        rval = obwod / 3.141592643;
        write("Srednica okregu " + nazwa + " wynosi " + ftoa(rval) + "\n");
    
        return rval;
    }

Argumenty są od siebie oddzielone przecinkami, tak jak w deklaracjach zmiennych. Ustalasz tu jaki typ danych będzie wysłany do funkcji i kojarzysz nazwę zmiennej z tym typem, do późniejszego użytku przez funkcje. Dane z argumentów, będą mogły być użyte tylko wewnątrz funkcji, no chyba, że wyślesz je na zewnątrz poprzez wywołanie innej funkcji.

(Żeby zachować trochę miejsca oraz zwiększyć przejrzystość podręcznika nie będę umieszczał nagłówka przed wszystkimi moimi krótkimi przykładami funkcji).

Funkcja, która nie zwraca żadnych danych, powinna mieć typ zadeklarowany jako ‘void’.

    void
    write_all(string komunikat)
    {
        users()->catch_msg(komunikat);
    }
Instrukcje i wyrażenia

Żebym mógł cokolwiek wyjaśnić, wpierw określę co oznacza „instrukcja” (statement), a co „wyrażenie” (expression).

Instrukcje

Instrukcja jest swego rodzaju zdaniem, stworzonym z jednego lub więcej wyrażeń. Instrukcje zazwyczaj zajmują jedna linie kodu. Czasem zachodzi potrzeba złamania ich gdy są za długie, w celu zwiększenia czytelności. W większości instrukcji wystarczy, że złamiesz je w przerwie pomiędzy dwoma wyrazami. Inaczej jest w łańcuchach znakowych – w takim przypadku musisz wstawić backslash (‘\’) na końcu pierwszej łamanej linii. Robi się to po to, by gamedriver wiedział co jest grane.

    write("To jest przykład \
          złamanego łańcucha(stringa).\n");

Jednakże łamanie stringów za pomocą backslasha wygląda bardzo nieładnie i czyni tekst trudnym do przeczytania. Zazwyczaj jest możliwość łamania linii naturalnie, na końcu stringa, albo nawet podzielenia łańcucha w połowie i dodaniu obu części za pomocą operatora ‘+’. Tak naprawdę, jedynym miejscem gdzie backslash jest naprawdę niezbędny, to instrukcja ‘#define’ – ale o tym później.

    write("To jest lepszy sposób " +
          "łamania stringów.\n");

Instrukcje w LPC zazwyczaj są zakończone średnikiem ‘;’. Jest to również dobre miejsce na zakończenie linii. Nic nie wstrzymuje cię przed zaczynaniem kolejnej instrukcji zaraz po poprzedniej, w tej samej linii, tylko że wygląda to potwornie.

Wyrażenia

Wyrażenie jest to opis jednej lub więcej czynności, których wykonanie powoduje uzyskanie danych jakiegoś rodzaju. Na przykład taki ‘+’. Używa on dwóch wyrażeń, które w sumie dają jakiś wynik. Zmienna też jest wyrażeniem, ponieważ przechowuje ona dane jako wynik. Następująca kombinacja dwóch wyrażeń i operatora tez jest poprawnym wyrażeniem: ‘a + b’ – gdzie ‘a’ oraz ‘b’ sa zmiennymi (wyrażeniami), a ‘+’ jest operatorem. ‘a = b + c;’ jest pełną instrukcją kończącą się na średniku ‘;’.

Wywołania funkcji są poprawnymi wyrażeniami. Są zapisane w postaci nazwy oraz pary nawiasów, w których znajdują się argumenty, które funkcja wykorzystuje wewnątrz. Na przykład funkcja ‘max()’, która zwraca wyższy z dwóch podanych argumentów. Aby znaleźć wyższą liczbę, np spośród ‘4’ i ‘10’, trzeba napisać ‘max(4, 10)’, co zwróci wartość ‘10’ i dzięki temu możemy to nazwać wyrażeniem. Oczywiście warto by jakoś ten wynik przechować.

Instrukcje grupujące (bloki)

Jest wiele instrukcji, na przykład instrukcje warunkowe, które w określonych warunkach tylko raz wykonają podaną instrukcję. Załóżmy, że w takim wypadku chcesz wykonać kilka instrukcji, a nie tylko jedną. Do tego celu istnieje specjalna instrukcja zwana blokiem (instrukcja grupująca). Znak ‘{’ definiuje początek bloku, a ‘}’ jego koniec. Wewnątrz tych nawiasów możesz umieścić tyle instrukcji (włączając w to definicje zmiennych), ile tylko zechcesz. Instrukcja grupująca nie kończy się średnikiem ‘;’ i nic nie zmieni, jeśli przez przypadek umieścisz jeden.

Instrukcja ‘;’

Jak już powiedziałem, ‘;’ jest przeważnie używany to zakańczania instrukcji, ale powinieneś wiedzieć, że średnik ‘;’ jest również instrukcją samą w sobie.

Sam ‘;’ będzie po prostu pustą instrukcją, która nie powoduje niczego. Jest ona użyteczna, gdy potrzebujesz zastosować pętle testowe (pętle będą opisane później), które będą się tylko wykonywały (i nie będą robiły przy tym żadnych innych rzeczy, jak to zwykle bywa z pętlami).

Zasięg i prototypy

„Zasięg” to termin określający gdzie funkcja lub deklaracja zmiennej jest poprawna. Ponieważ programy są czytane od góry do dołu, od lewej do prawej (zupełnie tak, jak ty czytasz tę stronę), zadeklarowane funkcje

i zmienne są dostępne na prawo i poniżej ich deklaracji. Zasięg ich, może być jednak ograniczony.

Zmienna zadeklarowana wewnątrz funkcji, jest dostępna aż do znaku zakończenia bloku ‘}’, w którym to ta zmienna jest zdefiniowana.

    < Początek pliku >
    int GlownyLicznik;
    
    // Tylko GlownyLicznik jest tu dostępny
    
    void
    var_func(int arg)
    {
        int zmienna_1;
    
        // GlownyLicznik, arg oraz zmienna_1 są tu dostępne
        < kod >
    
        {
            string zmienna_2;
    
            // GlownyLicznik, arg, zmienna_1 i zmienna_2 są dostępne w tym
            // bloku
            < kod >
        }
    
        // GlownyLicznik, arg i zmienna_1 są tu dostępne
        < kod >
    
        {
            int zmienna_2;
            mapping zmienna_3;
    
            // GlownyLicznik, arg, zmienna_1, zmienna_2 oraz zmienna_3 są tu
            // dostępne
            // *UWAGA* zmienna_2 jest NOWA i nie ma nic wspólnego z tą
            // zdefiniowana funkcje wcześniej
            < kod >
        }
    
        // GlownyLicznik, arg oraz zmienna_1 są tu dostępne
        < kod >
    }
    
    // Tu tylko GlownyLicznik (i funkcja var_func) jest tu dostpny

Deklaracje funkcji obowiązuje ta sama zasada. Nie możesz tylko zadeklarować jednej funkcji wewnątrz drugiej. Wyobraż sobie jednak sytuacje, gdzie masz dwie funkcje i jedna z nich korzysta z drugiej.

    int  /* Definicja func_1. */
    func_1()
    {
        < kod >
        func_2("test"); /* Wywołanie func_2 z argumentem "test". */
    }
    
    void  /* Definicja func_2. */
    func_2(string dane)
    {
        < kod >
    }

I tu natrafiasz na problem, ponieważ pierwsza funkcja próbuje wykorzystać drugą, zanim tamta jest zadeklarowana. Jeśli poinstruowałeś gamedriver, żeby wymagał zgodności typów poprzez napisanie ‘#pragma strict_types’, to wyskoczy ci komunikat błędu. Aby tego uniknąć możesz przekomponować program w taki sposób, by ‘func_2’ była zadeklarowana _przed_ ‘func_1’. Czasem jednak nie zawsze jest to możliwe, a poza tym może na tym ucierpieć wygląd programu. Jest jeszcze inny, lepszy sposób. Polega on na napisaniu „prototypu funkcji”. Powinien on zostać umieszczony na początku pliku, tuz po instrukcjach ‘inherit’ i ‘#include’. Powinien wyglądać identycznie jak deklaracja funkcji.

A więc mamy:

    < początek pliku, instrukcje `inherit' i `#include' >
    
    void func_2(string dane);    // <- to jest prototyp funkcji func_2
    
    < definicje zmiennych globalnych, itp. >


    void 
    func_1()
    {
        < kod func_1() >
        func_2("jakiś string");  // wywołanie func_2()
    }
    void 
    func_2(string dane)
    {
        < kod funkcji func_2 >
    }

Operatory (operator expressions)

W języku LPC jest zdefiniowana spora grupa operatorów, czyli takich cosików, które operują na wyrażeniach. Będę używał pewnego skrótowego sposobu notacji, dzięki któremu zaoszczędzi się sporo miejsca.

  • ‘W’
Będzie oznaczało dowolne wyrażenie, nawet bardzo pogmatwane.
  • ‘Z’
Będzie oznaczało jakąś zmienna.
Operatory różne
  • (W)
W jest obliczane przed robieniem czegokolwiek poza nawiasami. Jest to użyteczne w izolowaniu wyrażeń, które trzeba wykonać w jakiejś określonej kolejności, albo gdy nie jesteś pewien jaka jest kolejność działań (o tym później).
  • W1, W2
W1 jest wykonywane jako pierwsze i jego rezultat zostaje zapamiętany, wtedy wykonywane jest W2, a jego rezultat jest wyrzucany. Na koniec przechowany rezultat W1 jest zwracany jako wynik całego wyrażenia.
Instrukcja ‘a = 1, 2, 3;’ ustawi wartość ‘a’ na ‘1’.
  • Z = W
Zmienna ma przypisywaną wartość W. Rezultatem całego wyrażenia jest również wartość W.
‘a = b = 4;’ ustali wartość a oraz b na 4. Może to być tez zapisane w ten sposób:
‘a = (b = 4);’, co ilustruje kolejność wykonywania.


Operatory arytmetyczne
  • W1 + W2
Wyrażenia są obliczane i ich rezultaty zostają do siebie dodane.
Możesz sumować ze sobą integery, floaty, stringi, tablice i mappingi. Stringi, tablice i mappingi są po prostu ze sobą wiązane – początek drugiego jest przyłączany do końca pierwszego argumentu.
Istnieje także możliwość dodawania integerów do stringów. W takiej sytuacji integer będzie przekonwertowany w stringa i doklejony do niego.
  • W1 - W2
W2 jest odejmowane od W1.
Możesz odejmować integery, floaty i dowolne tablice, które mają ten sam typ. Jeśli element z odejmowanej tablicy istnieje w tej, od której się odejmuje, to jest on po prostu usuwany. Jeśli nie ma takowego, to tablica pozostaje nienaruszona.
  • W1 * W2
W1 jest mnożone przez W2.
Działa tylko na integerach i floatach.
  • W1 / W2
W1 jest dzielone przez W2.
Działa tylko na integerach i floatach.
  • W1 % W2
Reszta z dzielenia ‘W1 / W2’ jest zwracana.
Działa to tylko dla integerow.
‘14 % 3’ zwróci 2, ponieważ ‘14 / 3 - 2 = 0’.
  • -W
Zwróci W z odwróconym znakiem.
Działa to tylko dla integerow i floatów.
  • W++ (lub ++W)
Zwiększa W o 1.
Jeśli plusy znajdują się przed wyrażeniem, to wyrażenie zwraca nowa wartość. Jeśli są za wyrażeniem, to zwraca jeszcze starą.
  • W-- (lub --W)
Zmniejsza W o 1.
Jeśli plusy znajdują się przed wyrażeniem, to wyrażenie zwraca nowa wartość. Jeśli są za wyrażeniem, to zwraca jeszcze starą.


Operatory booleanowskie (binarne)

Operatorów booleanowskich można używać tylko na integerach. Wyjątkiem jest operator ‘&’, który może być zastosowany również na tablicach. Integer ma długość 32 bitów. Jednakże w przykładach będę ukazywał tylko 10 ostatnich bitów, jako ze pozostałe będą miały wartość 0 i będą one mogły być przez to zignorowane, z wyjątkiem operatora ‘~’.

 1. W1 & W2    W1 i W2. (iloczyn logiczny)
               1011101001   (= 745)
               1000100010 & (= 546)
               ------------
               1000100000   (= 544) => 745 & 546 = 544
               Użyty na dwóch tablicach, zwróci nam nowa tablice 
               zawierającą wszystkie elementy, które wstępują w obu
               tablicach jednocześnie.


 2. W1 | W2    W1 lub W2. (suma logiczna)
               1011101001   (= 745)
               1000100010 | (= 546)
               ------------
               1011101011   (= 747) => 745 | 546 = 747


 3. W1 ^ W2    W1 xor (exclusive or - nierównoważność; LUB wykluczające) W2.
               1011101001   (= 745)
               1000100010 ^ (= 546)
               ------------
               0011001011   (= 203) => 745 ^ 546 = 203


 4. ~W         1-complement of E (odwrócenie W).
               00000000000000000000001011101001 ~ (= 745)
               ----------------------------------
               11111111111111111111110100010110   (= -746) => ~745 = -746


 5. W1 << W2   W1 jest przesuwane w lewo o W2 kroków.
               5 << 4 => 101(b) << 4 = 1010000(b) = 80


 6. W1 >> W2  
               W1 jest przesuwane w prawo o W2 kroków.
               1054 >> 5 => 10000011110(b) >> 5 = 100000(b) = 32


Operatory warunkowe (logiczne)
 1. W1 || W2   Zwraca prawdę jeśli W1 lub W2 zwraca prawdę. Nie obliczy
               W2 jeśli W1 zwraca prawdę.
 2. W1 && W2   Zwraca prawdę, jeśli zarówno W1 jak i W2 zwraca prawdę.
               Nie wykona W2 jeśli W1 zwraca fałsz.
 3. !E         Zwraca prawdę, jeśli W jest fałszywe i vice versa.


Operatory porównawcze
 1. W1 == W2   Zwraca prawdę, jeśli W1 jest równe W2. Może być użyty na
               wszystkich typach. Obejrzyj specjalny rozdział o tablicach
               i mappingach, gdyż ten operator działa na nich inaczej, niż
               by ci się mogło wydawać.
 2. W1 != W2   Zwraca prawdę jeśli W1 jest różne od W2. Może być użyty
               na wszystkich typach. Obejrzyj specjalny rozdział o 
               tablicach i mappingach, gdyż ten operator działa na nich
               inaczej, niż by ci się mogło wydawać.
 3. W1 > W2    Zwraca prawdę, jeśli W1 jest większe od W2. Może być użyte
               na wszystkich typach poza tablicami i mappingami.
 4. W1 < W2    Zwraca prawdę, jeśli W1 jest mniejsze od W2. Może być użyte
               na wszystkich typach z wyjątkiem tablic i mappingów.
 5. W1 >= W2   Zwraca prawdę, jeśli W1 jest większe lub równe W2. Może być
               użyte na wszystkich typach poza tablicami i mappingami.
 6. W1 <= W2   Zwraca prawdę, jeśli W1 jest mniejsze lub równe W2. Może być
               użyte na wszystkich typach z wyjątkiem tablic i mappingów.

Podwójne operatory przypisania

Wszystkie arytmetyczne i booleanowskie operatory można zapisać w prostszy sposób, o ile chcesz jedynie obliczyć działanie jednej zmiennej z jakimś wyrażeniem, a wynik przechować w tej zmiennej.

Powiedzmy, że chcesz wykonać ‘a = a + 5;’. Lepiej można to zapisać tak: ‘a += 5;’. Wartość drugiego wyrażenia jest dodawana do pierwszego i w nim przechowana (czyli w tym przypadku w zmiennej a).

Wszystkie inne operatory można zapisać w ten sam sposób, tj. najpierw zmienna, potem ‘=’ poprzedzone operatorem, a na końcu wyrażenie.

    a >>= 5;       /* a = a >> 5; */
    b %= d + 4;    /* b = b % (d + 4); */
    c ^= 44 & q;   /* c = c ^ (44 & q); */

Priorytet operatorów i kolejność wykonywania działań

W poniższej tabeli zawarte są reguły dotyczące kolejności i łączenia wszystkich operatorów, włączając w to te, o których jeszcze nie było mowy. Operatory będące w tej samej linii w tabeli mają taki sam priorytet, a operatory w kolejnych rzędach mają malejąca ważność. Na przykład ‘*’, ‘/’ i ‘%’ mają taki sam priorytet i jest on wyższy od tego, który mają ‘+’ i ‘-’.

Zauważ, że priorytet logicznych operatorów bitowych ‘&’, ‘^’ i ‘|’ jest niższy od priorytetu ‘==’ i ‘!=’. Powoduje to, że wyrażenia testujące bity, takie jak

    if ((x & MASKA) == 0) ...

powinny być całe ujęte w nawiasy, żeby dały poprawne wyniki.

 1. () []                      Od lewej do prawej
 2. ! ~ ++ -- - (typ) * &      Od prawej to lewej
 3. * / %                      Od lewej do prawej
 4. + -                        Od lewej do prawej
 5. << >>                      L->P
 6. < <= > >=                  L->P
 7. == !=                      L->P
 8. &                          L->P
 9. ^                          L->P
10. |                          L->P
11. &&                         L->P
12. ||                         L->P
13. ?:                         L->P
14. = += == etc.               P->L
15. ,                          L->P

Instrukcje warunkowe

W LPC często korzysta się z instrukcji warunkowych. Jest wiele metod ich zapisywania. Bardzo ważną rzeczą jest to, ze w testach zero ‘0’ jest traktowane jako „falsz”, zaś dowolna inna wartość jako „prawda”. Oznacza to, iż puste tablice ‘({})’, puste stringi ‘""’ oraz puste mappingi ‘([])’ również są „prawda”, ponieważ nie są ‘0’. Jeśli chcesz poznać ich rozmiar, albo zbadać ich zawartość, to musisz się posłuzyć specjalnymi funkcjami - o nich będzie mowa później.

Instrukcje if/else

‘if’ stanowi najczęściej spotykana instrukcję warunkową. Jest łatwa w użyciu i może być łączona z instrukcją ‘else’ w razie potrzeby obsłużenia obu wyników testu (tj. fałszu i prawdy). Stosuje się ją w taki sposób:

    if (wyrażenie) instrukcja;
    np.
        if (a == 5)
            a -= 4;

Jeśli chcesz obsłużyć fałszywy wynik testu (jest on wtedy, gdy wyrażenie w nawiasie zwróciło fałsz), to musisz dodać intrukcję ‘else’:

    if (wyrażenie) instrukcja_prawda else instrukcja_fałsz;
    np.
        if (a == 5)
            a -= 4;
        else
            a += 18;

Jeśi zmienna ‘a’ będzie się równała 5, to od niej zostanie odjęte 4, w przeciwnym wypadku, do zmiennej ‘a’ zostanie dodane 18.

Pamiętaj, że możesz w miejsce instrukcji wstawić również jakiś blok.


Instrukcja switch

Jeślibyś chciał przetestować jedną zmienną na wiele różnych wartości, to skończyłoby się to na dosyć długiej liście instrukcji ‘if-else-if-else’. Jednakże, jeśli typ wartości na które testujesz zmienną jest integerem, floatem lub stringiem to możesz użyc o wiele lepszej i bardziej skondensowanej metody testowania. Załóżmy, że chcesz napisać następujący kod:

    if (imie == "lewy")  // jeśli imie == "lewy" to...
    {
        miasto = "lu";
        opis = "wesoły";
    }
    else if (imie == "alvin") // w przeciwnym wypadku, jeśli imie == "alvin"
    {
        miasto = "wa";
        opis = "bursztynooki";
    }
    else if (imie == "athmagor") // jeśli nie "alvin" i nie "lewy", to może "athmagor"
    {
        miasto = "lu";
        opis = "piekny";
    }
    else
    {
        miasto = "x";
        opis = "nieznany";
    }

Lepszym sposobem na zapisanie tego jest:

    switch (imię)
    {
    case "lewy":
        miasto = "lu";
        opis = "wesoły";
        break;
    
    case "alvin":
        miasto = "wa";
        opis = "bursztynooki";
        break;
    
    case "athmagor":
        miasto = "lu";
        opis = "piękny";
        break;
    
    default:
        miasto = "x";
        opis = "nieznany";
    }

Ta instrukcja działa bardzo prosto: ‘switch’ bierze wyrażenie spomiędzy nawiasów i porównuje je z każdym z wyrażeń występujących po ‘case’.

UWAGA Wyrażenie po ‘case’ musi być wartością stałą. Nie może to być

zmienna, wywołanie funkcji, ani żadne inne wyrażenie tego typu.

Po tym, jak kompilator stwierdzi, że wartość w nawiasie równa się wartości po ‘case’, wszystkie instrukcje występujące po ‘case’ zostaną wykonane, aż do momentu kiedy natrafi na instrukcję ‘break’. Jeśli nie znajdzie żadnych równających się wartości, wykonywane zostaną instrukcje po ‘default’.

UWAGA! Nie ma przymusu pisania części ‘default’. Jest to jednak bardzo

polecane, gdyż wykonanie jej zazwyczaj oznacza, że stało się coś, co nie było przewidziane w czasie pisania programu. Jeśli uważasz, że testowana zmienna będzie przyjmowała jakiś ograniczony zestaw wartości, to warto mieć w zapleczu jakiś komunikat błędu, który zostanie wyświetlony użytkownikowi, gdy stanie się coś nieoczekiwanego.

Gdy zapomnisz umieścić komendy ‘break’, kolejne wyrażenia ‘case’ będą wykonywane. Może to nie brzmieć tak, jakbyś chciał, ale na przykład jeśli powyższe imiona ‘alvin’ i ‘lewy’ miałyby wygenerować wspólne czynności, to mógłbyś napisać:

    case "lewy":
        /* nie ma break */
    case "alvin":
        < kod >
        break;

... i oszczędzić trochę miejsca. Używanie switcha nie czyni kodu szybszym, jedynie zwiększa czytelność i zmniejsza ryzyko popełnienia jakiegoś błędu w czasie pisania. Pamiętaj umieścić jakiś komentarz, w miejscu gdzie powinien być break, gdyż potem możesz zapomnieć o tym, iż specjalnie go nie umieściłeś. Dobrym pomysłem jest wstawianie jednej pustej linii po każdym breaku, żeby dać ‘chwile oddechu’, co zwiększy czytelność.

Wyrażenie ?:

Jest to bardzo skondensowana forma pisania instrukcji ‘if/else’ i zwracania różnych wartości w zależności od tego, jak wypadł test. Oczywiście nie jest to instrukcja, lecz wyrażenie, ponieważ zwraca jakąś wartość. Nie mówiłem o tym jak omawiałem wyrażenia, bo było by mi ciężko to wytłumaczyć, przed wyjaśnianiem instrukcji ‘if/else’.

Załóżmy, że chcesz napisać coś takiego:

    if (testowane_wyrażenie)
        zmienna = wyrażenie_if;
    else
        zmienna = wyrażenie_else;

Możesz to napisać w znacznie bardziej skondensowany sposób:

    zmienna = testowane_wyrazenie ? wyrazenie_if : wyrazenie_else;
    np.
        nazwa = dzień == 2 ? "wtorek" : "inny dzień";

Jest kwestią sporną, czy ten sposób zapisywania czyni kod bardziej, czy mniej czytelnym. Ale myślę, że jedno wyrażenie tego typu ułatwia czytanie kodu, zaś kombinacja kilku pogarsza tylko czytelność. Coś takiego, jak w następującym przykładzie z pewnosciś nie jest ulepszeniem:

    nazwa = dzień == 2 ? godzina == 18 ? "czas na kwas" : "wtorek"
                       : "inny dzień";

Pętle

Pętla to rodzaj instrukcji, która wykonuje określone czynności dopóki nie zostaną spełnione pewne wcześniej zaprogramowane warunki. Są dwa rodzaje pętli i oba używają instrukcji warunkowych.

Instrukcja while

Instrukcja while jest bardzo prosta w użyciu. Już z samej nazwy (while oznacza „dopoki”) można łatwo wywnioskować co ona robi. Będzie ona cały czas wykonywała inna instrukcje, dopóki wyrażenie kontrolne nie zwróci fałszu. Ma ona następującą składnię:

    while (<wyrazenie_kontrolne>) instrukcja_wykonywana;

Pamiętaj, że wyrażenie kontrolne jest sprawdzane na samym początku, jeszcze przed wywołaniem instrukcji po raz pierwszy. Jeśli już na samym początku wyrażenie kontrolne zwróci fałsz, to instrukcja nigdy nie zostanie wykonana.

    a = 0;
    while (a != 4)
    {
        a += 5;
        a /= 2;
    }

Dopóki a ma wartość różną od 4, jest do niego dodawane 5, a potem jest dzielone przez 2. (Wiem, wiem, ten przykład nie ma najmniejszego sensu :)).

Instrukcja for

Jeśli potrzebujesz zwykłego licznika, to powinieneś użyć instrukcji ‘for’. Ma ona następującą składnię:

for (instrukcja_inicjalizujaca; wyrazenie_kontrolne; instrukcja_konca_cyklu)
    instrukcja_powtarzana;

Całą instrukcję można zapisać w inny równoważny sposób, za pomocą instrukcji while:

 instrukcja_inicjalizujaca;
 while (wyrazenie_kontrolne)
 {
     instrukcja_powtarzana;
     instrukcja_konca_cyklu;
 }

Na samym początku, instrukcja ‘for’ wykonuje instrukcję inicjalizującą. Stosuje się ją do początkowego ustawienia liczników, lub innych wartości używanych w czasie wykonywania pętli. Od tego momentu trwa jej właściwa część. Każdy cykl zaczyna się od obliczenia „wyrażenia_kontrolnego” i sprawdzenia jego wyniku. Jeśli jest prawdziwy, czyli wyrażenie zwróci od razu po niej instrukcja_konca_cyklu. W instrukcji_powtarzanej zawarte jest zazwyczaj to, co się chce by pętla cyklicznie wykonywała, a w instrukcji_konca_cyklu zazwyczaj zwiększa lub zmniejsza się liczniki.

W poprzednim ustępie wiele razy użyłem słowa zazwyczaj. Zrobiłem to dlatego, że nie musisz robić tego w ten sposób; nie ma niczego co by cię przymuszało do takiego wykorzystania tej instrukcji, jaki podałem.

Załóżmy, że chcesz obliczyć sumę wszystkich liczb całkowitych (integerów) z przedziału od 7 do 123 i nie znasz na to wzoru ((x1^2 + x2^2) / 2). Najłatwiejszą (co nie oznacza najefektywniejszą) metodą, jest użycie pętli.

    wynik = 0;
    for (licznik = 7 ; licznik < 124 ; licznik++)
        wynik += licznik;

Na początku wynik jest zerowany. Po tym następuje właściwa część instrukcji ‘for’. Zaczyna się od ustawienia zmiennej licznik na 7. Teraz następuje wejście do pętli i test, czy licznik (równy 7) jest mniejszy niż 124. Jest tak, więc wartość licznika jest dodawana do zmiennej. Zmienna licznik jest zwiększana o jeden i następuje ponowne wejście do pętli. Dzieje się tak, do momentu aż wartość licznika osiągnie 124. Ponieważ nie jest on wtedy mniejszy od 124, wykonywanie pętli zostaje zakończone.

UWAGA! Wartość licznika po wykonaniu instrukcji ‘for’ jest równa 124,

a nie 123, jak niektórzy ludzie sądzą. wyrazenie_kontrolne musi zwrócić fałsz, żeby pętla mogła się skończyć i co za tym idzie licznik musi mieć wartość 124.

Instrukcje break i continue

Czasem, podczas wykonywania instrukcji ‘switch’, ‘for’ lub ‘while’ zachodzi potrzeba wyjścia z pętli i kontynuacji poza nią. W tym celu używa się instrukcji ‘break’.

    while (warunek_koncowy < 9999)
    {
        // Jeśli funkcja time() zwróci 29449494, to przerwij wykonywanie
        // pętli
        if (time() == 29449494)
            break;
    
        < kod >
    }
    
    // Kompilator wykonuje ten kod zarówno po użyciu instrukcji `break',
    // jak i gdy pętla się skończy.
    < kod >

Nieraz chcesz, by pętla wykonała się dodatkowo podczas użycia instrukcji ‘for’ lub ‘while’. Do tego celu używa się instrukcji ‘continue’.

    // Dodaje wszystkie parzyste liczby
    suma = 0;
    for (i = 0 ; i < 10000 ; i++)
    {
        // Zacznij pętle od początku, gdy `i' zwraca nieparzysta liczbę
        if (i % 2)
             continue;
    
        suma += i;
    }

Każde użycie continue w tym przykładzie powoduje przeskoczenie jednego cyklu, gdyż licznik zostaje zwiększony, ale zasadnicza część kodu pozostaje nie wykonana, bo instrukcja ‘continue’ znajduje się przed nią.

Tablice i Mappingi

Nadszedł czas na zagłębienie się w dwa specjalne typy: „tablice” i „mappingi”. Ich zastosowanie może wyglądać podobnie, lecz w rzeczywistości bardzo się od siebie różnią.

Dla obu typów napisano wiele użytecznych efunkcji, które operują i pobierają z nich informacje. Jednakże zostaną opisane one później, a na razie przedstawiś tylko kilka z nich.

Jak używać i deklarować tablice

Tablice w LPC to tak naprawdę nie są prawdziwe tablice. Lepiej określa je to, że są to listy z określonym porządkiem. Różnica może się wydawać niewielka, lecz dla informatyków jest ogromna.

Tablice mają określony typ, co oznacza, że mogą przyjmować tylko takie elementy, u których jest on zgodny z tablicą. Innym ograniczeniem jest to, że tablice mogą być tylko jednowymiarowe. Istnieje jednak możliwość obejścia tego, gdyż tablice z elementami typu mixed mogą przyjmować wartości dowolnego typu, czyli nawet inne tablice. Powinieneś jednak pamiętać o ostrożnym posługiwaniu się typami tablic, żeby nie popełnić jakiegoś błędu.

Tablice definiuje się w ten sposób:

    <typ> *<nazwa_tablicy>;
    np.
        int *moja_tab, *twoja_tab;
        float *inna_tab;
        object *tab_ob;

Początkową wartością tych tablic jest ‘0’, a nie pusta tablica. Powtarzam: są one ustawiane na zero – ‘0’, nie na pustą tablicę – ‘({ })’. Pamiętaj o tym!

Tablice alokuje i inicjalizuje się w ten sposób:

    <tablica> = ({ elem1, elem2, elem3, ..., elemN });
    np.
        moja_tab = ({ 1, 383, 5, 391, -4, 6 });

Dostęp do elementów tablicy można uzyskać dzięki podaniu numeru indeksu w nawiasach, po nazwie zmiennej zawierającej tablice. (Załóżmy, że zmienna wart jest zadeklarowana jako integer).

    <zmienna> = <tablica>[<indeks>];
    np.
        wart = moja_tab[3];

LPC, tak samo jak C, zaczyna liczenie od 0 i przez to czwarty element ma numer 3.

Żeby ustawić wartość już istniejącego elementu na nową, używa się operatora ‘=’.

        moja_tab[3] = 22;        // => ({ 1, 383, 5, 22, -4, 6 })
        moja_tab[3] = 391;       // => ({ 1, 383, 5, 391, -4, 6 })

W celu pobrania wycinka tablicy, wystarczy w nawiasach podać od którego, do którego elementu chce się wyciąć.

    <tablica> = <inna_tablica>[<odkąd>..<dokąd>];
    np.
        twoja_tab = moja_tab[1..3];

... spowoduje, że nowa tablica ‘twoja_tab’ będzie miała wartość ‘({ 383, 5, 391 });’. Jeśli starej tablicy przypiszesz nową wartość, poprzednia zawartość zostanie stracona.

np.

        moja_tab = ({ });

... spowoduje, że ‘moja_tab’ stanie się pustą tablicą. Stara zostanie zdealokowana, a pamięć przez nią zajęta uwolniona.

Jeśli podasz indeks, który wyjdzie poza rozmiar tablicy, to wyskoczy błąd i wykonywanie obiektu zostanie przerwane. Jednakże, gdy indeks przy podawaniu zasięgu, np. indeks dokąd wyjdzie poza tablice, to błąd nie zostanie zakomunikowany, zasięg zaś zostanie tak ograniczony, by się zmieścił w rozmiarze tablicy.

Jeśli chcesz stworzyć pustą tablice, z wartościami ustawionymi na 0 (typ nie gra roli) o określonej długości, to możesz użyć efunkcji ‘allocate()’.

    <tablica> = allocate(<wielkość>);
    np.
        twoja_tab = allocate(3);        // => twoja_tab = ({ 0, 0, 0 });

Dodawanie tablic do siebie daje się łatwo dokonać za pomocą operatora ‘+’. Wystarczy, że zsumujesz je tak, jak się sumuje liczby. Operator ‘+=’ również do tego celu świetnie pasuje.

    moja_tab = ({ 9, 3 }) + ({ 5, 10, 3 }); // => ({ 9, 3, 5, 10, 3 })

Użycie operatorów ‘-’ i ‘-=’ to najłatwiejszy sposób na usuwanie elementów z tablicy. Uważaj jednakże, gdyż operator ten usunie wszystkie elementy, które będą miały wartość taką samą jak elementy, które chcesz usunąć.

    moja_tab -= ({ 3, 10 }); // => ({ 9, 5 })

I jeszcze mały przykład na wycinanie i doklejanie elementów tablic.

    moja_tab = ({ 9, 3, 5, 10, 3 });
    moja_tab = moja_tab[2..4] + moja_tab[0..0]; // => ({ 5, 10, 3, 9 })
        <tablica> moja_tab[0..0]   // = ({ 9 })
        <int>     moja_tab[0]      // = 9

UWAGA! Zwróć uwagę na róznicę!!! W pierwszej linii jest tablica, a w drugiej integer!

Jak deklarować i używać Mappingi

Mapping to lista powiązanych par wartości. Mają one wszystkie typ ‘mixed’. Oznacza to, że część indeksowa pary nie musi mieć przez cały czas tego samego typu. Jest to jednak bardzo doradzane, z powodu błędów jakie nieostrożne używanie typu ‘mixed’ może spowodować.

Zaröwno indeks, jak i wartość może przyjmować dowolny typ. Istnieje tylko jedno ograniczenie tyczące się indeksów – nie może być dwóch indeksów o takiej samej wartości w jednym mappingu.

Może to trochę pogmatwanie brzmieć z początku, ale tak naprawdę to to jest bardzo banalne. Chyba łatwiej zrozumieć będzie jednak, jeśli od razu przystąpie do ilustrowania tego przykładami.

Mappingi deklaruje się tak jak wszystkie inne zmienne. Zacznijmy więc od kilku prostych deklaracji.

    mapping moj_map;
    int     wartość;

Alokować i inicjalizować można na trzy różne sposoby.

    1: <mapping> = ([ <indeks1>:<wartosc1>, <indeks2>:<wartosc2>, ... ]);
    
    2: <mapping>[<indeks>] = wartość;
    
    3: <mapping> = mkmapping(<tablica indeksów>, <tablica wartości>);

Pierwszy sposób jest prosty i naturalny.

    1: my_map = ([ "adam":5, "brunon":8, "czeslaw":-4 ]);

Działanie drugiego sposobu polega na tym, że gdy nie istnieje w mappingu para o podanym indeksie – to jest tworzona, jeśli istnieje – to wartość pary jest zastępowana nową.

    2: my_map["adam"] = 1;    // Tworzy parę "adam":1
       my_map["brunon"] = 8;  // Tworzy parę "brunon":8
       my_map["adam"] = 5;    // Zamienia starą wartość w "adam" na 5.
       ...

Trzeci sposób wymaga dwóch tablic – jednej zawierającej indeksy, a drugiej przechowującej wartości. Tworzenie tablic zostalo opisane w poprzednim rozdziale.

3: moj_map = mkmapping( ({ "adam", "brunon", "czeslaw" }), ({ 5, 8, -4 }) );

W przeciwieństwie do tablic, mappingi nie mają ustalonego porządku. Wartości są poukładane w taki sposöb, by maksymalnie skrócić czas ich wyszukania. Istnieją funkcje, które umożliwiają pobranie listy elementów (indeksów, lub wartości) z mappinga. Pamiętaj tylko, że mogą one bbyć yc w dowolnej kolejności i nigdy nie ma pewności, że będą tak samo ułożone pomiędzy dwoma wywołaniami funkcji „pobierających”. W praktyce jednak, kolejność jest zmieniana tylko w czasie dodawania lub usuwania elementów.

Mappingi łączy się za pomocą operatorów ‘+’ i ‘+=’, zupełnie tak samo jak w przypadku tablic.

    moj_map += ([ "dobroslawa":5, "edmund":33 ]);

Usuwanie elementów z mappingu już nie jest takie proste. Służy do tego specjalny efun ‘m_delete()’ (również opisany dalej).

    moj_map = m_delete(moj_map, "brunon");
    moj_map = m_delete(moj_map, "dobroslawa");

Jak widzisz, elementy usuwa się po kolei, podając indeks jako identyfikator pary. Inna rzeczą, którą sobie musisz szybko uświadomić jest to, że indeksy muszą być unikalne, nie może być dwóch takich samych „uchwytów” do par. Jednakże wartości oczywiście mogą być takie same.

Poszczególne wartości można pobrać poprzez zwykłe podanie indeksu.

    wartość = moj_map["czeslaw"]; // => -4

Próba pobrania elementu o nieistniejącym indeksie nie wywoła żadnego błędu, a jedynie zwróci 0. Musisz więc być bardzo ostrożny, gdyż 0 może oznaczać różne rzeczy, tzn. wartość 0 może oznaczać, że element o podanym indeksie nie ma części z wartością, ale może oznaczać też, że wartością jest 0.

    wartość = moj_map["rgojhijaeh"]; // => 0

Preprocesor

Preprocesor nie jest częścią języka LPC. Jest to specjalny proces, który jest uruchamiany przed rozpoczęciem kompilacji. Można go postrzegać jako bardzo sprytnego „podstawiacza” stringów; Określone łańcuchy znakowe są zastępowane innymi.

Pierwszym znakiem każdej dyrektywy preprocesora jest ‘#’. Poza tym muszą się one zaczynać w pierwszej kolumnie (na samym początku linii). Możesz je umieścić gdziekolwiek w kodzie, acz jak się dowiesz później, większość z nich jest przypisana początkowi programu.

Instrukcja #include

Jest to najczęściej używana dyrektywa preprocesora. Mówi ona mu, aby zastąpił linie w której ona się znajduje zawartością całego pliku, o nazwie podanej po instrukcji.

Dane, które umieszczasz we włączanym pliku to takie, których raczej nie będziesz nigdy zmieniał i takie, które będziesz włączał w wiele plików. Zamiast wpisywania tego samego tekstu w różne pliki w nieskończoność i co za tym idzie zwiększania szansy na jakieś kretyńskie błędy, po prostu zbierasz powtarzające się dane w jeden lub kilka plików i włączasz(include) je w te programy, w które trzeba.

Składnia jest bardzo prosta:

    #include <plik_standardowy>
    #include "plik_dodatkowy"

UWAGA! Zauważ, że nie ma ‘;’ na końcu linii!

Dwa różne sposoby zapisywania nazw plików zależą od tego, gdzie się one znajdują. Jest spora liczba standardowych bibliotek w grze, rozrzuconych po sporej liczbie katalogów. Zamiast zapamiętywania dokładnie gdzie one są, wystarczy że podasz sama nazwę pliku, który chcesz włączyć.

    #include <stdproperties.h>
    #include <adverbs.h>

Gdy chcesz włączyć jakieś pliki własne, które nie są żadną standardową biblioteką, to musisz dokładnie podać ich lokacje. Możesz to zrobić zarówno poprzez podanie ścieżki od katalogu, gdzie się znajduje program, albo poprzez podanie pełnej, absolutnej ścieżki od głównego katalogu.

    #include "/d/Standard/login/login.h"
    #include "moje_def.h"
    #include "/sys/adverbs.h"  // Ten sam, co krótszy przykład powyżej

Gdy chcesz włączyć standardowe biblioteki, to zawsze używaj notacji < >. (czyli podawaj sama nazwę biblioteki, ujętą w nawiasy < > ). Powodem nie jest tylko to, że tak jest krócej, ale to, że gdy biblioteki zostaną przemieszczone gdzie indziej, twój program przestanie działać. Gdy użyjesz notacji <> to zawsze zostaną znalezione.

Włączane pliki mogą mieć dowolną nazwę, ale ustalono, że będą miały końcówkę ‘.h’, żeby moc je jasno odróżnić od innych plików.

Jest nawet możliwe włączenie plików ‘c’, tzn. całych plików zawierających programy. Jednakże, jest to bardzo zła rzecz. Nie rób tego NIGDY! Czemu? Po pierwsze programy śledzące błędy gubią numeracje linii we włączanych plikach i przez to podają złe numery linii. Po drugie, gdy włączasz nieskompilowany kod w wiele różnych obiektów, marnujesz pamięć oraz CPU, gdyż ten sam plik musi być wielokrotnie kompilowany i przechowywany osobno, dla każdego obiektu, który go używa. A poza tym samo czytanie takiego programu może być istną torturą.

Co ma więc tak naprawdę rozszerzenie nazwy pliku do jego zawartości? Tak naprawdę to nic nie ma... Jednakże jest przyjęte, że kod i funkcje są przechowywane w plikach z rozszerzeniem ‘.c’, a definicje z rozszerzeniem ‘.h’. Mudlib zazwyczaj korzysta z tego rozdziału i może nie rozpoznawać jako kodu źródłowego niczego, poza plikami z końcówką ‘c’.

Instrukcja #define

Jest to bardzo potężne „makro”, komenda preprocesora, która jest ciągle nadużywana. Mądrze postąpisz, jeśli będziesz używał jej ostrożnie i tylko do prostych rzeczy.

Ma ona następującą składnię:

    #define <identyfikator> <tekst zastępujący>
    #undef <identyfikator>

Dowolny taki sam tekst w pliku jak ‘<identyfikator>’ zostanie zastąpiony ‘<tekstem zastępującym>’, jeszcze przed kompilacją. ‘#define’ jest ważne od linii, w której zostało zdefiniowane do końca pliku, albo do momentu wykonania komendy ‘#undef’, która usuwa makro.

Pomimo tego, że makrem może być dowolny tekst, jest w zwyczaju(rób tak!), że nazwę makra pisze się wyłącznie dużymi literami. Jest to po to, żeby można było wyróżnić makra w tekście, w którym każdy(ty tez!) pisze nazwy funkcji i zmiennych małymi literami.

Umieszczaj wszystkie definicje na początku pliku, albo biedny koleś, który będzie ci pomagał w wyłapaniu błędów, będzie miał prawdziwe piekiełko z poszukiwaniem pochowanych definicji. Jeśli to będzie ktoś, kogo sam poprosiłeś o pomoc (gdyż masz błędy powstałe z powodu brzydko napisanego kodu), to najprawdopodobniej powie ci żebyś sobie wsadził taki program w wiadome miejsce i wrócił dopiero wtedy, jak się nauczysz poprawnie i ładnie pisać.

Prostymi definicjami są na przykład ścieżki, nazwy i wszystkie inne stale dowolnego rodzaju, których nie chcesz w kółko zapisywać, albo chcesz mieć możliwość łatwej ich modyfikacji, bez zmieniania tego samego w dziesiątkach miejsc.

     MAX_LOGIN   100          /* Maksymalna liczba zalogowanych
                                        graczy */
     LOGIN_OB    "/std/login" /* Obiekt logowania        */
     POWIT_TEKST "Witamy!"    /* Komunikat logowania     */

Gdziekolwiek wystąpi identyfikator makra, zostanie on zastąpiony tym, co się znajduje w definicji pomiędzy identyfikatorem, a końcem linii. Podchodzą pod to także komentarze, które jednak i tak zostaną później wyrzucone.

    tell_object(gracz, POWIT_TEKST + "\n");

Komentarz typu ‘//’ nie jest w takiej sytuacji dobra rzeczą, gdyż kończy się on dopiero na końcu linii.

     POWIT_TEKST    "Witamy!"    // Komunikat logowania

...będzie zamienione w poprzednim przykładzie na:

    tell_object(gracz,    "Witamy!"    // Komunikat logowania + "\n");

...co spowoduje ujęcie w komentarz wszystkiego, co wystąpi po ‘//’, aż do końca linii.

Gdy makro wychodzi poza linie, możesz przedłużyć ją za pomocą znaku ‘\’ który zaznacza, że definicja jest kontynuowana w następnej linii. Jednakże musisz zakończyć linie tuż po ‘\’ i NIE nie może być za nim żadnych spacji, ani innych znaków.

     DLUGA_DEFINICJA  "początek stringa  \
                              i jego koniec."

Definicje naśladujące funkcje są często stosowane i nieraz nadużywane. Jedyna naprawdę ważną zasadą, obowiązującą przy pisaniu takich makr jest to, że każdy argument musi być ujęty w nawiasy. Jeśli napiszesz inaczej, to możesz otrzymać bardzo dziwne wyniki.

    1:  POMNOZ_TO(a, b) a * b        /* Źle */
    2:  POMNOZ_TO(a, b) (a * b)      /* Nie wystarczająco */
    3:  POMNOZ_TO(a, b) ((a) * (b))  /* Właściwie */

Co za różnica pewnie zapytasz? Spójrz w takim razie na ten przykład:

    wynik = POMNOZ_TO(2 + 3, 4 * 5) / 5;
    

Po podstawieniu wygląda to tak:

    1: wynik = 2 + 3 * 4 * 5 / 5;       // = 14, Zły
    2: wynik = (2 + 3 * 4 * 5) / 5      // = 12, Też zły
    3: wynik = ((2 + 3) * (4 * 5)) / 5  // = 20, Właściwy!

Nadużycia definicji polegają zazwyczaj na złym ich ułożeniu i użyciu skomplikowanych makr wewnątrz innych makr (co czyni kod prawie niemożliwym do zrozumienia). Podstawową zasadą jest pisanie krótkich i prostych makr. Rób tak, albo marny będzie twój koniec ;)

Instrukcje #if, #ifdef, #ifndef, #else i #elseif

Są to dyrektywy preprocesora, które służą selekcjonowaniu określonych części kodu i usuwaniu innych w zależności od stanu zmiennych preprocesora.

Dzięki nim część kodu może zostać uniewidoczniona dla kompilatora – coś jak inteligentne komentarze.

Instrukcja ‘#if’ jest bardzo podobna do zwykłej instrukcji if. Jest tylko trochę inaczej zapisywana.

Załóżmy, że możesz mieć gdzieś następującą definicję:

     CODE_VAR  2
    
    lub
    
     CODE_VAR  3

Wtedy możesz napisać:

    #if CODE_VAR == 2
        <kod ten będzie dopuszczony do kompilacji tylko jeśli CODE_VAR == 2>
    #else
        <kod ten będzie zachowany tylko wtedy gdy CODE_VAR != 2>
    #endif

Możesz w ogóle nie pisać instrukcji ‘#else’ jeśli tego nie chcesz.

Wystarczy napisać następującą instrukcje, żeby zaistniała dana definicja preprocesora:

     CODE_VAR    /* Definiuje istnienie CODE_VAR */

I teraz możesz napisać:

    #ifdef CODE_VAR
        <Kod, który będzie zachowany tylko, jeśli CODE_VAR istnieje>
    #else
        <kod, który będzie zachowany tylko wtedy, gdy CODE_VAR nie jest
         zdefiniowane>
    #endif

lub

    #ifndef CODE_VAR
       <kod, który będzie zachowany tylko wtedy, gdy CODE_VAR *nie* jest
        zdefiniowane>
    #else
       <kod, który będzie zachowany, jeśli CODE_VAR istnieje>
    #endif

I ponownie, instrukcje ‘#else’ można ominąć.

Komendy preprocesora ‘#if/#ifdef/#ifndef’ są prawie wyłącznie używane do dodania odpluskwiającego kodu, który nie powinien być cały czas aktywny lub do pisania rzeczy, które będą różnie pracowały w zależności od bardzo rzadko zmieniających się parametrów.

Cała konfiguracja mudliba jest sprawdzana w ten sposób. Na przykład jest definicja MET_ACTIVE, która służy ustalaniu czy system met/nonmet (nie każdy zna każdego gracza) ma być włączony czy nie. Gdy administrator muda zdecyduje się na wyłączenie tego, wystarczy że w jednym pliku konfiguracyjnym zamieni ‘#define MET_ACTIVE’ na ‘#undef MET_ACTIVE’. (W Arkadii wiele takich definicji znajdziesz w ‘/config/sys/local.h’).

Podstawy Mudliba i LPC

W rozdziale tym nauczysz się wszystkiego, czego potrzebujesz by pisać kod w środowisku gry. Będę unikał bardziej skomplikowanych tematów, pozostawiając ich omówienie na trzeci rozdział. Dowiesz się tu wielu rzeczy na temat mudliba i pracy gamedrivera – wiedzy niezbędnej do pisania działającego i efektywnego kodu.

Małe wybiegnięcie w przyszłość

Aby móc pokazać ci przykłady tego, czego będę cię próbował nauczyć, muszę ci wpierw z wyprzedzeniem wyjaśnić kilka funkcji. Będzie to powtórzone w odpowiednim kontekście później. Tutaj jest zamieszczony jedynie krótki przegląd, żebyś wiedział co robię.

Żeby pokazać jakiś tekst na ekranie gracza, używa się efunkcji ‘write()’. Istnieją dwa specjalne znaki, które są dość często używane do formatowania tekstu. Są to: znak „tabulacji” i znak „nowej linii”. Zapisuje je się w ten sposób: (kolejno) ‘\t’ i ‘\n’. Znak „tabulacji” wstawia osiem spacji, a znak „nowej linii”, jak sama nazwa wskazuje łamie linie, czyli kończy stara i przechodzi do nowej.

    void write(string tekst)
    np.
        write("Siemanko!\n");
        write("\to jest wcięty string.\n");
        write("Ten string\n jest rozłożony na kilka linii\n\ti częściowo" +
               "\nwcięty.\n");
    
        /* Wynikiem będzie:
    
           Siemanko!
                   To jest wcięty string.
           Ten string
           jest rozłożony na kilka linii
                   i częściowo
           wcięty.
         */

Jeśli masz tablice, mapping albo zwykłą zmienną dowolnego typu i chcesz wyświetlić to na ekranie, np. żeby pomóc sobie w wyszukiwaniu błędów, to sfunkcja ‘dump_array()’ może się okazać bardzo pomocna. Podajesz zmienną, którą chcesz wyświetlić jako argument i jej zawartość zostanie ukazana na ekranie.

    void dump_array(mixed dane)
    np.
        string *imie = ({ "franek", "zdzichu", "bolo" });
        dump_array(imie);
    
        /* Wynikiem będzie
    
           (Array)
           [0] = (string) "franek"
           [1] = (string) "zdzichu"
           [2] = (string) "bolo"
         */

Drugi rzut oka na LPC

Wytłumaczę ci teraz pominięte w poprzednim rozdziale informacje o LPC. Ta wiedza jest ci potrzebna do tworzenia działających obiektów w środowisku gry.

Wywołania funkcji

Są dwa rodzaje wywołań funkcji: wewnętrzne i zewnętrzne. Jedynym rodzajem o jakim dotąd mówiliśmy były wywołania wewnętrzne, choć zewnętrzne też się znalazły w kilku miejscach.

Tworzenie wewnątrzobiektowych wywołań funkcji
 [call_self]

Robienie wewnętrznych wywołań funkcji jest tak proste, jak pisanie nazwy funkcji i argumentów w nawiasach po niej. Lista argumentów jest albo listą wyrażeń, albo w ogóle jej nie ma. Wywołanie funkcji jest oczywiście wyrażeniem również.

    <funkcja>(<lista argumentów>);
    np.
        zmienna = jakas_funkcja(1.0) * 4;

Jest inny sposób na zrobienie tego. Jeśli nazwę funkcji masz przechowana w jakiejś zmiennej i chcesz ja wywołać, to pomocna może się okazać funkcja ‘call_self()’:

    call_self(<"nazwa funkcji">, <lista argumentów>);
    np.
        zmienna = call_self("jakas_funkcja", 1.0) * 4;

Jeśli używając ‘call_self()’ podasz nazwę nieistniejącej funkcji, wyskoczy błąd i wykonywanie obiektu zostanie przerwane.

Tworzenie prostych zewnątrzobiektowych wywołań funkcji
 [call_other]

Wywołanie zewnętrzne, to wywołanie funkcji z innego obiektu. Żeby móc to zrobić, potrzebujesz odnośnika do obiektu w którym chcesz ją wywoływać. Nie mówiliśmy jeszcze o tym, jak zdobyć odnośnik do obiektu, ale załóżmy że już go masz.

mixed <odnośnik do obiektu/ścieżka do obiektu>-><funkcja>(<lista argumentów>);
mixed call_other(<odn ob/ścieżka ob>, "<funkcja>", <lista argumentów>);

np.

        /*
         * Załóżmy, że chce wywołać funckje 'oblicz_cos' w obiekcie
         * "/d/Mojadomena/wiz/obiekt" i że mam jakiś właściwy wskaźnik
         * obiektu przechowany w zmiennej 'obiekcik'
         */
        zmienna = obiekcik->oblicz_cos(1.0);
        zmienna = "/d/Mojadomena/wiz/obiekt"->oblicz_cos(1.0);
        zmienna = call_other(obiekcik, "oblicz_cos", 1.0);
        zmienna = call_other("/d/Mojadomena/wiz/obiekt", "oblicz_cos", 1.0);
        /*
         * Te cztery linijki robią dokładnie to samo
         */

Jak widzisz, efunkcja ‘call_other()’ działa analogicznie do ‘call_self()’.

Kiedy zewnętrznie wywołujesz funkcje posługując się ścieżką do obiektu, tak zwany „master object” zostaje wywołany. Jeśli obiekt, w którym wywołujesz nie został jeszcze załadowany do pamięci, to zostanie. Gdy spróbujesz wywołać nieistniejącą funkcję, zostanie zwrócone 0 bez żadnych komunikatów błędu. Jeśli wywołasz funkcję z obiektu, który ma jakieś błędy, to zostanie wyświetlony komunikat błędu i działanie obiektu który wykonał wywołanie zostanie przerwane.

Po co więc ten ‘call_self()’ w takim razie? Przecież można używać zamiast niego ‘call_other()’ przez cały czas z takim samym skutkiem? Nie do końca tak. ‘call_other()’ miesza jeszcze sporo w czymś co się nazywa dostępem do funkcji w obiekcie – o tym jeszcze nie mówiłem. Różnica polega na tym, że ‘call_self’ rzeczywiście działa jak jakiekolwiek wywołanie wewnętrzne (ma dostęp do wszystkich funkcji w danym obiekcie), podczas gdy ‘call_other()’ jest typowym wywołaniem zewnętrznym. Pamiętaj o tym, kiedy będę omawiał różnice pomiędzy dostępem w wywołaniach wewnętrznych i zewnętrznych.

Tworzenie wielokrotnych zewnątrzobiektowych wywołań funkcji

Możesz wywoływać wiele obiektów na raz, tak samo prosto jakbyś to robił z jednym. Jeśli masz tablice stringów ze ścieżkami, albo wskaźników do obiektów lub mapping, w którym wartości są ścieżkami do plików lub wskaźnikami do obiektów, to możesz w jednej instrukcji wywołać je wszystkie. Rezultatem będzie albo tablica z wynikami, jeśli wywoływałeś za pomocą tablicy, albo mapping z takimi samymi indeksami z jakimi wywoływałeś, jeśli korzystałeś z mappinga.

    (tablica/mapping)    <tablica/mapping>-><funkcja>(<lista argumentow>);
    np.
        /*
         * Potrzebuje mappingu, gdzie indeksami będą imiona graczy, a
         * wartościami ich hitpointy.
         */
        object *ludzie;
        mapping hp_map;
        string *imiona;
    
        // Przypisuje listę wszystkich graczy.
        ludzie = users();
    
        // Bierze ich imiona.
        imiona = ludzie->query_real_name();
    
        // Tworzy mapping, przy pomocy którego będą robione wywołania.
        // Element = imie:wskaźnik
        hp_map = mkmapping(imiona, ludzie)
    
        // Zastępuje wskaźniki wartościami hitpoint.
        hp_map = hp_map->query_hp();
    
        // A wszystko to mogłoby być zrobione prościej w ten sposób:
        hp_map = mkmapping(users()->query_real_name(), users()->query_hp());

Dziedziczenie obiektów

Załóżmy, że chcesz zakodować jakąś rzecz, drzwi na przykład. Musisz więc zaprogramować możliwość otwierania i zamykania przejścia pomiędzy dwoma pokojami. Być może chcesz również móc otwierać i zamykać zamek w drzwiach. O wszystko to musisz zadbać w swoim kodzie. Na dodatek, będziesz musiał kopiować ten sam kod za każdym razem, kiedy dodasz nowe drzwi i będziesz chciał żeby różniły się tylko opisem.

Po jakimś czasie wkurzysz się na to wszystko, częściowo dlatego, że odkryjesz ze inni wizardzi napisali własne drzwi, które działają prawie - lecz nie do końca – tak jak twoje, przez co twoje świetne obiekty będą bezużyteczne poza twoja domeną.

Myślenie zorientowane obiektowe polega na tym, że zamiast powtarzać takie pisanie w kółko, tworzy się podstawowy obiekt drzwi, który może robić wszystko to, co uważasz, że typowe drzwi powinny móc. Wtedy dziedziczy si go w naszych konkretnych drzwiach, konfiguruje się je (nie obiekt dziedziczony), korzystając z możliwości drzwi „rodzica”.

Jest nawet możliwe dziedziczenie kilku obiektów, choć „wielokrotne dziedziczenie” niesie ze sobą kilka trudnych problemów do rozwiązania, więc staraj się unikać tego.

Składnia na dziedziczenie obiektów jest bardzo prosta. Na początku pliku pisze się coś takiego:

    inherit "<ścieżka do pliku>";
    np.
       inherit "/std/door"; // (door oznacza drzwi)
       inherit "/std/room.c";

UWAGA! To NIE jest dyrektywa preprocesora, a zwykła instrukcja, więc stawia się ‘;’ na końcu i NIE pisze się ‘#’ przed nią. Jeśli chcesz, możesz wyszczególnić w ścieżce, że chodzi o plik c, aczkolwiek to nie jest konieczne.

„Dziecko” dziedziczy wszystkie funkcje i wszystkie zmienne, które są zadeklarowane w sposób pozwalający na ich dziedziczenie. Jeśli masz funkcje o takiej samej nazwie jak w obiekcie „rodzicu”, to twoja „zamaskuje” funkcje „rodzica”. Kiedy funkcja będzie wołana poprzez wywołanie zewnętrzne, to wykonana zostanie twoja funkcja. Wewnętrzne wywołania w „rodzicu” będą się jednak odnosiły do jego funkcji. Nieraz potrzebujesz wywołać funkcje „rodzica” z „dziecka” – robi się to poprzez dodanie ‘::’ przed wewnętrznym wywołaniem funkcji.

    void
    moja_funkcja()
    {
        /*
         * Ta funkcja istnieje i w naszym obiekcie(dziecku) i w obiekcie
         * który dziedziczymy(rodzicu), ale potrzebujemy ją
         * wywołać z rodzica.
         */
        ::moja_funkcja();        // Wywołuje moja_funkcje w rodzicu.
    }

Nie da się wywołać zamaskowanej funkcji w rodzicu poprzez wywołanie zewnętrzne, możliwe jest to tylko z rodzica. Gdy obiekt dziedziczy inny, który z kolei dziedziczy jeszcze inny, np. C dziedziczy B, a ten dziedziczy A, to wtedy maskowana funkcja z A, jest możliwa do wywołania tylko w B, a w C już nie.

Cienie : Maskowanie funkcji w czasie wykonywania obiektu

Jest coś takiego w LPC, co jest zwane „cieniowaniem”. Purytanie wolą jednak nazywać to „obrzydlistwem” i przy każdej okazji krzyczą żeby to usunąć, gdyż występuje to prawie przeciwko wszystkiemu, czego uczy się o dobrym programowaniu. „Cieniowanie” jest raczej użyteczne w grze, aczkolwiek może spowodować trochę problemów głównie związanych z bezpieczeństwem. Używaj tego z rozwagą!

Gdy jeden obiekt cieniuje drugi, wszystkie powtarzające się funkcje i zmienne z cieniującego obiektu, zamaskują te z cieniowanego obiektu. Wywołania cieniowanych funkcji, spowodują wykonanie zamiast nich, tych z cieni. Cień praktycznie ‘stanie’ się obiektem, który cieniuje. Od zwykłego maskowania różni je to, że funkcje są maskowane w czasie kompilacji. Obiekty-cienie zaś są dodawane do obiektów już w czasie ich działania. Można w ten sposób dodać do obiektu jakieś właściwości, które nie są przewidziane w kodzie.

To wszystko na razie na ten temat. Sposób tworzenia i zastosowanie cieni będą szczegółowo omówione później. Jak na razie wiesz wszystko, co potrzebujesz wiedzieć.

Identyfikacja typów

 [intp, floatp, functionp, stringp, objectp, mappingp, pointerp]

Ze względu na fakt, że wszystkie zmienne są na początku ustawiane na 0 i że wiele funkcji zwraca 0 w razie błędu, warto móc zbadać jaki typ wartości odebraliśmy. Również, gdy używa się typu ‘mixed’, niezbędna jest informacja jaki typ wartości zawiera zmienna. Do tego celu istnieją specjalne funkcje kontrolne, które zwracają 1 (prawdę) jeśli testowane wartości są danego typu, a 0 jeśli nie.

int intp(mixed)

    Sprawdza, czy podana wartość jest typu integer

int floatp(mixed)

    Sprawdza, czy podana wartość jest typu float

functionp(mixed)

    Sprawdza, czy podana wartość jest wskaźnikiem funkcji (typ function)

int stringp(mixed)

    Sprawdza, czy podana wartość jest stringiem

int objectp(mixed)

    Sprawdza, czy podana wartość jest wskaźnikiem obiektu (typ object)

int mappingp(mixed)

    Sprawdza, czy podana wartość jest mappingiem

int pointerp(mixed)

    Sprawdza, czy podana wartość jest tablicą
UWAGA! Funkcje te sprawdzają typ wartości, a NIE samą wartość

w znaczeniu poprawności działania. Innymi słowy ‘intp(0)’ zawsze zwróci prawdę, tak jak to zrobi ‘mappingp(([]))’.

Kwalifikatory typów

Typy, które przypisujesz zmiennym i funkcjom mogą mieć różne kwalifikatory, zmieniające sposób ich działania. Bardzo ważne jest to, by o nich cały czas pamiętać i używać w odpowiednich miejscach. Większość z nich inaczej działa na zmiennych, a inaczej na funkcjach, więc małe kłopoty związane z ich używaniem są dosyć powszechne wśród programistów. Postaraj się przejść przez to teraz, a nie będziesz miał później żadnych problemów.

Kwalifikator zmiennej ‘static’

Jest to dosyć kłopotliwy kwalifikator, gdyż działa on inaczej nawet na różne zmienne, w zależności od tego gdzie one są! Zmienne globalne (jak na pewno wiesz) definiowane są na początku pliku, na zewnątrz funkcji. Są one dostępne we wszystkich funkcjach, tzn. ich „zasięg” rozciąga się na cały obiekt i nie jest ograniczony do jednej funkcji.

Istnieje możliwość nagrania wszystkich zmiennych globalnych jakiegoś obiektu, za pomocą pewnej efunkcji (opisana będzie dalej). Jednakże, jeśli zmienna globalna jest zdefiniowana jako ‘static’, to nie będzie nagrana.

    static string   JakasNazwa;        // Nie nagrywana zmienna globalna.
Kwalifikator funkcji ‘static’

Do funkcji, które są zadeklarowane z użyciem kwalifikatora ‘static’ nie można stosować zewnętrznych wywoływań, tylko wewnętrzne. Czyni to taka funkcje ‘niewidzialna’ i niedostępna dla innych obiektów. Między innymi tu jest właśnie ta różnica pomiędzy wewnętrznymi a zewnętrznymi wywołaniami funkcji, o której wcześniej mówiłem.

Kwalifikator funkcji/zmiennej ‘private’

Zmienne lub funkcje, które zostały zadeklarowana jako ‘private’ nie będą mogły być dziedziczone przez inne obiekty. Dostęp do nich ma tylko obiekt, który je definiuje.

Kwalifikator funkcji/zmiennej ‘nomask’

Funkcje i zmienne, które są zadeklarowane jako ‘nomask’ nie mogą być maskowane w żaden sposób, ani przez cieniowanie, ani przez dziedziczenie. Jeśli spróbujesz zamaskować takową, to otrzymasz komunikat błędu.

Kwalifikator funkcji/zmiennej ‘public’

Jest to standardowy kwalifikator dla wszystkich funkcji i zmiennych. Oznacza to, że nie narzuca on żadnych innych ograniczeń ponad te, które są narzucone przez język.

Kwalifikator funkcji ‘varargs’

Funkcje, które są zdefiniowane jako ‘varargs’ będą mogły otrzymywać różną liczbę argumentów. W takim przypadku nie ma potrzeby podawania ich wszystkich przy wywoływaniu, żeby było ono prawidłowe. Mogą być ustalone standardowe wartości dla argumentów, które nie otrzymały żadnych wartości.

    varargs void
    mojafun(int a, string str = "pelle", float c = 3.0);
    {
    }

Jak widzisz, kilka argumentów dostało standardowe wartości, które będą użyte w przypadku gdy dany argument nie zostanie podany. Jeśli nie ustalisz standardowej wartości, to wartość nie podanego argumentu zostanie ustawiona na 0.

Drugi rzut oka na typy danych

Jeden typ danych był dotąd mniej lub bardziej ignorowany. Jest nim typ ‘function’. Jako, że obiekty mają swój własny typ, funkcje mają taki również. Dzięki niemu możesz mieć zmienne, poprzez które będziesz mógł wywoływać funkcje. Przeważnie jest on używany w połączeniu z innymi funkcjami, które używają go jako parametru.

Zmienna tego typu definiuje się tak jak każda inna:

    <typ danych> <nazwa zmiennej>, <inna zmienna>, ..., <ost. zmienna>;
    np.
        function moja_fun, *tablica_fun;

Przypisywanie odnośników funkcji do nich nie jest jednak takie proste. Możesz przypisać dowolny typ funkcji do zmiennej tego typu: czy efunkcja, czy sfunkcja, czy lfunkcja nie czyni żadnej różnicy. Istnieje nawet możliwość przypisania odwołania do funkcji zewnętrznej.

Przypisanie wskażnika do funkcji wymaga tego, by dana funkcja była zdefiniowana, albo poprzez prototyp, albo poprzez deklaracje. Załóżmy, że jak na razie interesują cię tylko proste wskaźniki do funkcji.

    <zmienna typu `function'> = <nazwa funkcji>;
    <zmienna typu `function'> = &<nazwa funkcji>();
    np.
        moja_fun = allocate;
        moja_fun = &allocate();

Wskaźnika do funkcji używa się tak samo jak zwykłego wywołania funkcji.

    int *i_tabl;
    
    i_tabl = allocate(5);   // Korzystamy z powyższego 
    i_tabl = moja_fun(5);   // ... przypisania wskaźnika funkcji.

Wystarczy tego na teraz. Później wyjaśnię jak tworzyć bardziej skomplikowane struktury funkcji.

Drugi rzut oka na instrukcję switch/case

Instrukcja switch jest bardzo sprytna i można w niej stosować oraz testować, czy jakaś wartość typu integer mieści się w podanym zakresie:

    public void
    kolo_fortuny()
    {
        int i;
    
        i = random(10);     // Przypisuje losową liczbę z zakresu 0-9
    
        switch (i)
        {
        case 0..4:
            write("Spróbuj jeszcze raz, frajerze!\n");
            break;
    
        case 5..6:
            write("Ekstra, masz trzecie miejsce!\n");
            break;
    
        case 7..8:
            write("Tak! Drugie miejsce!\n");
            break;
    
        case 9:
            write("OOPS! Udało ci się!\n");
            break;
    
        default:
            write("Ktoś grzebał przy kole... Wykręć 997!\n");
            break;
        }
    }

catch/throw: Obsługa błędów w czasie działania programu

Czasami zachodzi potrzeba wywołania funkcji, o której wiadomo, że może zwrócić błąd runtime (czyli taki, który wynikł po kompilacji, już w czasie działania programu). Na przykład w swoim programie możesz chcieć sklonować jakiś obiekt (opisane później) albo załadować plik. Jeśli nie ma takiego pliku, lub gdy masz niewystarczające przywileje, to wyskoczy błąd runtime i wykonanie programu zostanie zatrzymane. W tych okolicznościach przydałaby się możliwość przechwycenia błędu i wyświetlenia własnego komunikatu o błędzie, lub też wykonania innej czynności. Jest to możliwe dzięki funkcji ‘catch()’. Jako argument podaje się w niej zmienną typu ‘function’. Jeśli ‘catch()’ zwróci 1 (prawdę), to będzie to oznaczało, że wystąpił błąd w czasie wykonywania podanej funkcji. Jeśli wszystko z podaną funkcją będzie ok, to zwróci 0.

    int catch(function)
    np.
        if (catch(tail("/d/Relikwa/fatty/tajna_mapa_do_ukrytych_orzeszkow")))
        {
            write("Przykro mi, nie ma możliwości przeczytania tego "+
                  "pliku.\n");
            return;
        }

Jest także możliwe wywołanie błędu i co za tym idzie przerwanie programu. Jest to użyteczne, gdy chcesz powiadomić użytkownika o nieplanowanym zdarzeniu, które wystąpiło w czasie działania programu. Zazwyczaj się to umieszcza w części ‘default’ komendy switch, o ile (oczywiście) nie używasz tej części jako pewnego rodzaju łapacza wszelkich pozostałych możliwości.

    throw(mixed info)
    np.
        if (test < 5)
            throw("BLAD: Zmienna 'test' ma wartość mniejsza niż 5.\n");

Wskaźniki do tablic i Mappingów

W terminologii informatycznej tablice i mappingi są używane jako "odniesienie poprzez wskaźnik", podczas gdy inne zmienne jako "odniesienie poprzez wartość". Oznacza to, że tablice i mappingi, w przeciwieństwie do innych rodzajów zmiennych w czasie kopiowania nie są dublowane. Zamiast nich, dublowany jest tylko wskaźnik do oryginalnej tablicy czy mappinga. Co to wszystko oznacza?

Hmm... Po prostu to:

    object *tab, *tab_2;
    
    tab = ({ 1, 2, 3, 4 });    // Jakaś tablica.
    
    tab_2 = tab;               // Załóżmy (źle), ze 'tab_2' 
                               // staje się kopia tablicy 'tab'.
    
    // Zmieniamy pierwszy element (1) na 5.
    tab_2[0] = 5;

... I teraz ... logicznie rzecz biorąc wydawałoby się, że wartość pierwszego elementu tab_2 równa jest 5, podczas gdy ten sam element w ‘tab’ wynosi 1. Nie jest tak jednak, gdyż to co zostało skopiowane do zmiennej tab_2 nie było tablicą, tylko wskaźnikiem do tej samej tablicy co ‘tab’. Oznacza to, że nasze działanie zmieniło pierwszy element w oryginalnej tablicy, do której obie zmienne mają wskaźniki. Wydawało by się, że ‘tab_2’ i ‘tab’ obie się zmieniły, podczas gdy w rzeczywistości zmianie uległa tylko tablica, do której obie zmienne się odwoływały.

Dokładnie to samo się stanie w przypadku mappingów, gdyż pod tym względem działają one tak samo.

Więc.. jak w takim razie obejść to? Czasem naprawdę zachodzi potrzeba pracy na kopii, a nie na oryginale tablicy czy mappingu. Rozwiązanie jest bardzo proste. Trzeba się tylko upewnić, że kopia została stworzona z innej tablicy lub mappingu.

                _ To jest po prostu pusta tablica.
               /
    tab_2 = ({ }) + tab;
                       \_ A to jest ta szczególna, o którą nam chodzi.

W tym przykładzie ‘tab_2’ staje się sumą tablicy pustej i tablicy `tab', stworzona jako zupełnie nowa tablica. Pozostawi to w przyszłości oryginał bez zmian, dokładnie tak jak chcieliśmy. Możesz uczynić dokładnie to samo z mappingami. Nie gra roli to, czy pustą tablicę lub mapping dodajesz z przodu, czy z tyłu, o ile w ogóle dodajesz.

Interfejs LPC/Mudliba

Jest sporo rzeczy, które możesz zechcieć zrobić, których nie da się wyrazić tylko za pomocą elementów języka LPC. Jest to na przykład obsługa łańcuchów znakowych czy też zapisywanie do plików. Rzeczy te są bowiem częścią ‘standardowego zestawu funkcji’, który zawiera większość języków programowania. Ten rozdział ma nauczyć cię podstaw robienia wszystkich tego typu rzeczy, potrzebnych do tworzenia obiektów LPC.

Istnieje określony zestaw globalnych właściwości, co do których możesz mieć pewność, że będą dostępne zawsze w dowolnym obiekcie. Oto one:

  • creator
Każdy obiekt jest tworzony przez kogoś. Tożsamość autora zależy od lokacji źródła (pliku źródłowego, czyli takiego, z którego obiekt jest wykonywany) w systemie plików. Jeśli obiekt znajduje sie w katalogu zwykłego czarodzieja, wtedy jako jego autora podaje się imię tego czarodzieja. W innym przypadku podawana jest nazwa domeny. Dla obiektów mudliba, autorem jest ‘root’ w przypadku obiektów o prawach administratora, albo ‘backbone’ w przypadku reszty.
  • uid/euid
"uid" (User ID – czyli identyfikator użytkownika) obiektu określa najwyższy możliwy poziom przywilejów dla danego obiektu. Samo uid jest używane tylko do ograniczania na "euid" (Effective User ID – czyli rzeczywisty identyfikator uużytkownika tego samego lub innego obiektu. Euid jest używane w sytuacjach, gdy przywileje obiektu muszą być sprawdzone, np. dostęp do pliku (czytanie/zapisywanie/usuwanie), bądź tworzenie obiektu.
  • living
Żeby obiekt mógł przyjmować bądź wydawać komendy, musi być livingiem.

Definicja obiektów standardowych i bibliotecznych

Jak już poprzednio mówiłem, gamedriver prawie nic nie wie o grze. A co najwyżej tak mało, jak to możliwe. To mudlib musi się tym wszystkim zaopiekować. Zajmiemy się teraz podstawowymi sprawami z nim związanymi, np. poruszaniem się, poziomami oświetlenia, tym jak obiekty powinny się kontaktować z graczami, itp. Po tym stworzymy obiekty, które będą wykorzystywały omówione funkcje.

Zwykły czarodziej nie musi przesiadywać długich godzin zastanawiając się jak napisać obiekt, będący w prawidłowej relacji z innymi i rozpatrując jeszcze tysiące innych, kłopotliwych spraw. Zamiast tego dziedziczy odpowiadający mu, jeden ze standardowych obiektów. Później tylko dorzuca don kilka ustawień, czyniąc go unikalnym względem innych obiektów tego typu.

Konsekwencją tego jest sytuacja w której wszystkie obiekty w grze w 100% polegają na fakcie, że określone typy obiektów (pokój, potwór, broń) mają określone zestawy powszechnych cech i możliwości. Po prostu muszą mieć, by mogły współpracować w ustalony sposób. Jeśliby nie mogły, jeśliby każdy człowiek miał inne rozwiązanie tego samego problemu, obiekty dzialałyby tylko na terenie danego czarodzieja i nigdzie poza nim. Nie byłoby możliwe używanie miecza w całej grze. Ba! Nawet nie możnaby go nigdzie przemieścić. Oczywiście oznacza to, że musimy wymuszać jednomyślność, gdyż niemożliwe jest stworzenie (i użytkowanie) obiektów, które nie dziedziczą tych specjalnych obiektów. Jak sam później zobaczysz możliwym byłoby napisanie miecza, który nie będzie dziedziczyć standardowego obiektu broni, tyle że wtedy dzierżenie go stałoby się barierą nie do przebycia...

Istnieją różne standardowe obiekty używane do różnych celów, ale najważniejszym z nich jest ‘/std/object.c’.

Podstawowy obiekt: /std/object.c

Ten obiekt jest wielozadaniowy. WSZYSTKIE obiekty mające ‘fizyczna’ postać dziedziczą go. Dziedzicząc jeden ze standardowych obiektów możesz być pewien, że pośrednio dziedziczysz także ‘object.c’, gdyż jest on dziedziczony przez przeważającą większość standardowych obiektów.

Standardowy obiekt definiuje następujące rzeczy:

  • zawartość (inventory)
Obiekt może "zawierać" inne obiekty. W rzeczywistości jest to zwykła lista obiektów, które są trzymane wewnątrz obiektu posiadającego tę listę. Jednakże bardzo proste jest uwidocznienie tego, jako ekwipunku gracza, wnętrza torby, pokoju, pudełka itp.
  • środowisko (environment)
Obiekt "otaczający" inny obiekt, odwrotność zawartości. Jeśli obiekt A jest w ‘ekwipunku’ obiektu B, to obiekt B otacza obiekt A. Obiekt może mieć wiele innych obiektów w swoim ‘ekwipunku’, lecz tylko jeden obiekt-środowisko. Wszystkie nowo utworzone obiekty nie mają żadnego środowiska.
  • lista komend
Lista poleceń powiązanych z funkcjami. Obiekt udostępnia polecenia wszystkim "żyjącym" obiektom, które się znajdują zarówno w jego ekwipunku jak i w środowisku. Żyjące obiekty mogą wydać takie polecenie i obiekt udostępniający je wywoła powiązaną z poleceniem funkcję.
  • właściwości (properties)
Właściwości są czysto umowna sprawą. Jest to najzwyklejszy w świecie mapping, którego indeksy są jakimiś zarezerwowanymi nazwami, a wartości zmiennymi, które wpływają na jakieś ogólnie dostępne stany. Typowymi właściwościami są waga, cena, poziom światła i nieco bardziej abstrakcyjne pojęcia takie jak możliwość upuszczenia, wzięcia czy sprzedania obiektu. Możliwy do użycia zestaw właściwości zależy już od konkretnych obiektów.
  • poziom światła
Obiekt ma określony poziom światła. Zazwyczaj nie wpływa on na otoczenie, choć możliwe są do utworzenia zarówno źródła światła, jak i źródła ciemności. Poziom światła jest jedną z właściwości.
  • waga/objętość
Wartości te określają, jak dużo obiekt waży i ile miejsca zajmuje. W przypadku ‘pojemnikow’ takich jak torby definiuje to także ich pojemność.
  • widzialność
Niektóre obiekty mogą być łatwiejsze do znalezenia niż inne.
  • nazwy i opisy
Jak obiekt się nazywa i jakim widzi go gracz.
Standardowe klasy obiektów

Istnieje spora liczba standardowych obiektów których się używa (stosuje się termin ‘dziedziczy’) w różnych sytuacjach. By dowiedzieć się nieco więcej o każdym z nich, będziesz potrzebował osobnej dokumentacji. Ten spis dostępnych obiektów standardowych ma za zadanie przynajmniej ukierunkować cię w poszukiwaniach.

Jak już wcześniej mówiłem, większości z nich nie wykorzystuje się na codzień. Z kilkoma jednak wkrótce staniesz się za pan brat, gdyż opisywanych przez nie typów obiektów nie da sie napisać nie dziedzicząc ich.

  • /std/armour.c
Dowolne zbroje
  • /std/board.c
Tablice ogłoszeniowe
  • /std/book.c
Książka którą możesz otwierać i zamykać, ze stronami, które możesz przewracać i czytać.
  • /std/coins.c
Podstawa wszystkich rodzajów pieniędzy
  • /std/container.c
Dowolny obiekt, który może zawierać w sobie inne (pojemnik)
  • /std/corpse.c
Ciało martwych potworów/graczy/NPC-ów (NPC – Non Player Character, czyli wszystkie żyjące istoty, które nie są graczami)
  • /std/creature.c
Proste żyjące istoty
  • /std/domain_link.c
Dziedziczone przez obiekty uczestniczące w tzw. ‘preloadingu’ (po każdej Apokalipsie, przed dopuszczeniem graczy do gry, ważniejsze obiekty są od razu wczytywane do pamięci, by zredukować laga)
  • /std/door.c
Drzwi, które łączą dwa pokoje
  • /std/food.c
Jedzenie różnego rodzaju pokarmów
  • /std/guild (katalog)
Obiekty związane z gildiami (gildie i shadowy)
  • /std/heap.c
Obiekty dowolnego rodzaju, które mogą być umieszczone w stosach
  • /std/herb.c
Zioła
  • /std/key.c
Klucze do drzwi
  • /std/leftover.c
Pozostałości po rozłożonych ciałach
  • /std/living.c
Żyjące obiekty
  • /std/mobile.c
Przemieszczające się żyjące obiekty
  • /std/monster.c
Dowolne potwory
  • /std/npc.c
Sprytniejsze istoty humanoidalne
  • /std/object.c
Podstawowy obiekt
  • /std/poison_effect.c
Obsługuje działanie różnych trucizn
  • /std/potion.c
Mikstury
  • /std/receptacle.c
Dowolnego rodzaju pojemniki, które można otwierać/zamykać
  • /std/resistance.c
Obsługuje odporności na różne rzeczy
  • /std/room.c
Dowolny rodzaj lokacji
  • /std/scroll.c
Pergaminy, kartki itp
  • /std/shadow.c
Używane jako podstawa w tworzeniu obiektów typu ‘shadow’
  • /std/spells.c
Obiekty czarów, tomów itp.
  • /std/torch.c
Pochodnie/lampy itp.
  • /std/weapon.c
Bronie wszelakiej maści
Standardowe obiekty biblioteczne

To są pomocnicze pliki. Używa się ich w połączeniu z obiektami z katalogu ‘/std/’. Na przykład chcąc zakodować karczmę , dziedziczymy ‘/std/room.c’ oraz ‘/lib/pub.c’.


  • /lib/bank.c
Dowolny rodzaj banku
  • /lib/cache.c
‘Cache’ używany w celu przyspieszenia regularnie wykorzystywanych obiektów
  • /lib/guild_support.c
Dodatkowe funkcje dla gildii
  • /lib/herb_support.c
Dodatkowe funkcje do ziół
  • /lib/more.c
Mozliwosc ‘more’ (przeglądania plików)
  • /lib/pub.c
Funkcje przydatne przy pisaniu karczm
  • /lib/shop.c
Sklepy dowolnego rodzaju
  • /lib/skill_raise.c
Trenowanie umiejętności
  • /lib/store_support.c
Pomocne funkcje w magazynach (niezbędny dodatek do sklepów)
  • /lib/time.c
Funkcje obsługujące czas
  • /lib/trade.c
Pomocne we wszystkim co jest związane z handlem

Jak zdobyć odnośniki do obiektów

Obiekty, jak już wcześniej mówiłem, występują w dwóch rodzajach – twz. „master object” i „klon”. W większości przypadków operuje się na sklonowanych obiektach. Są to między innymi takie, które możesz przesunąć, dotknąć, obejrzeć itp, lub dowolne inne, które występują w więcej niż jednej kopii. Operowanie na „master objectach” ogranicza się zazwyczaj do pokoi i souli (soule to obiekty wykorzystywane przez obiekty graczy, które dodają im różne komendy).

Każdy załadowany obiekt ma swój „master object”, który jest reprezentacja tego obiektu w pamięci, a zarazem pierwszym klonem. Zawiera on informacje o mechanizmie działania obiektu. Klony składają się jedynie z zestawu zmiennych i odnośnika do swojego „master objectu”. Öw zestaw zmiennych odróżnia poszczególne klony od siebie (np. dwa miecze tego samego typu mogą się różnić stopniem stępienia).

Jeśli zniszczysz „master object”, jego klony istniejące już w grze pozostaną (informacja o mechaniźmie działania obiektu gdzieś pozostanie w pamięci). Związany jest z tym błąd w myśleniu wielu początkujących czarodziejów: Uważają, że przeładowując „master object” do nowej postaci (po zmianach które wprowadzili w kodzie), zmienią się wszystkie już istniejące klony. Nic bardziej mylnego – po przeładowaniu (updatowaniu) „master objectu” dopiero nowe klony będą zawierały wprowadzone zmiany.

Ładowanie „master objectu” do pamięci odbywa się poprzez wywołanie na ścieżce do jego pliku dowolnej, nieistniejącej funkcji (ścieżka do pliku stanowi jeden ze sposobów na odniesienie się do „master objectu”).

Przeładowywanie obiektu (zwane również jego uaktualnianiem lub updatowaniem) polega na usunięciu z pamięci starego „master objectu” i załadowaniu nowego.

Jak w takim razie zdobyć odnośnik do obiektu? To zależy. Odnośnikiem do obiektu może być zarówno wskaźnik do obiektu, jak i ścieżka do źródła obiektu w systemie plików. Metody ich uzyskiwania są różne w różnych sytuacjach. Wytłumaczę teraz je wszystkie.

Odnośniki do aktualnego obiektu
 [this_object, previous_object, calling_object]

Każdy obiekt zawsze może zdobyć odnośnik do samego siebie. Służy do tego efunkcja ‘this_object()’:

    object this_object()
    np.
        object ja;
    
        ja = this_object();

Żeby zbadać który obiekt wywołał aktualnie uruchomioną funkcję za pomocą wywołania zewnętrznego, można użyć efunkcji ‘previous_object()’:

    object previous_object(void|int głębokość)
    np.
        object p_ob, pp_ob;
    
        p_ob = previous_object();     // Obiekt, który wywołał tę funkcję
        pp_ob = previous_object(-2);  // Obiekt, który wywołał inny obiekt,
                                      // który wywołał tę funkcję

Jeśli nie podasz żadnego argumentu, albo podasz -1, funkcja zwróci obiekt, który ją wywołał. Zmniejszanie argumentu spowoduje podanie dalszych poprzedników, np. ‘previous_object(-4)’ zwróci obiekt, który wywołał obiekt, który wywołał obiekt, który wywołał twój obiekt. O ile łańcuch wywołań był na tyle długi. Jeśli głębokością przekroczy się długość łańcucha wywołań, to funkcja zwróci 0.

Mam nadzieję, że zauważyłeś, że funkcja ta sprawdza tylko zewnętrzne wywołania. Istnieje inna efunkcja, która działa tak samo, tyle że dla wszystkich rodzajów wywołań (wewnętrzne lub zewnętrzne):

    object calling_object(void|int głębokość)

Używa się jej tak samo jak poprzedniej

A więc... skąd możesz wiedzieć czy który odnośnik właśnie dostałeś jest właściwy czy nie (tzn. że to nie jest np. zero albo coś innego)? Jeśli jeszcze pamiętasz, istnieje pewna efunkcja zwana ‘objectp()’? Doskonale by się do tego celu nadawała – zwróci 1, jeśli podany argument będzie prawidłowym wskaźnikiem do obiektu.

    int objectp(mixed ob)
    np.
        if (objectp(calling_object(-2)))
            write("Tak, ob który wywołał ob, który nas wywołał " +
                  "istnieje!\n");
        else
            write("Nie ma takiego.\n");
Tworzenie obiektów
 [setuid, getuid, seteuid, geteuid, creator, set_auth, query_auth,

clone_object]

Wpierw musisz się upewnić, że obiekt, który próbuje stworzyć inny ma do tego wystarczające przywileje. Reguły są bardzo proste: Obiekt, który ma właściwe euid może klonować dowolny inny obiekt. Właściwe euid, to dowolne, różne od 0. Uid i Euid jest standardowo ustawiane na 0 we wszystkich nowych obiektach i oznacza, że obiekt w ogóle nie ma przywilejów.

Zazwyczaj masz ograniczony wybór euid, który możesz ustawić obiektom. Jeśli jesteś zwykłym wizardem ogranicza się on tylko do twojego imienia. Lord może jako euid wstawić swoje imię, lub dowolnego innego wizarda w swojej domenie (wyjątkiem są tylko Archowie). I oczywiście obiekty z uid ‘root’ mogą ustawić dowolne euid jakie chcą.

A więc... uid jest nadrzędne w stosunku do euid. Uid ogranicza euid jakie możesz ustawić. Stanowi maksymalny poziom, jaki można ustawić euid. Standardową wartość uid ustawia się poprzez wywołanie sfunkcji setuid():

    void setuid()
    np.
        setuid();

Proste, nie? Wykonanie tej komendy spowoduje przypisanie uid uzyskanego z pozycji źródła pliku w systemie plików. Działa to na takich samych zasadach jak w pobieraniu wartości zwracanej przez sfunkcję creator(), co zostało opisane wcześniej.

    string creator(mixed odnośnik)
    e.g.
        string moj_tworca;
    
        moj_tworca = creator(this_object());

Aby poznać wartość aktualnego uid, używa się sfunkcji ‘getuid()’.

    string getuid()
    np.
        string aktualny_uid;
    
        aktualny_uid = getuid();

Więc... uid ma już ustawiony maksymalny priorytet, taki jaki ma klonujący. Euid jednakże jest cały czas równe 0. Ponieważ euid ustala rzeczywiste przywileje, będzie to oznaczało, że obiekt nie ma jeszcze żadnych praw.

Do nadawania wartości euid używa się sfunkcji ‘seteuid()’, gdzie jako argument podaje się nową wartość euid, o ile ma się prawa, żeby tak ustawić (jest to sprawdzane). Funkcja zwraca 0 w razie niepowodzenia. Nie podanie żadnego argumentu spowoduje wyzerowanie euid.

    int seteuid(void|string przyw)
    np.
        if (seteuid("lewy"))
           write("Właśnie TAK! Jestem panem WSZECHŚWIATA!\n");
        else
            write("Ehhhhh...\n");

Jak zwykle jest podobna sfunkcja, która zwraca aktualne euid:

    string geteuid()
    np.
        write("Aktualnym euid jest " + geteuid() + .\n");

Wszystkie sfunkcje ‘setuid()’, ‘getuid()’, ‘seteuid()’ i ‘geteuid()’ korzystają z efunkcji ‘set_auth()’ i ‘get_auth()’. Używa się ich do zmieniania specjalnej zmiennej z prawami w danym obiekcie. W razie próby użycia ‘set_auth()’ gamedriver wywołuje specjalna funkcje kontrolną w master object, która sprawdza czy ma się do tego odpowiednie prawa. Jest tak, gdyż tej zmiennej można przypisać dowolny string, a sposób w jaki jej używamy jest sztucznie ustalony, tak jak uznaliśmy, że najlepiej będzie rozwiaząć to w systemie bezpieczeństwa.

Kiedy próbujesz wykonać operacje, która wymaga odpowiednich przywilejów, taka jak zapisywanie do pliku, czy klonowanie obiektu, to gamedriver wywołuje inne specjalne funkcje w master object, żeby się upewnić czy masz odpowiednie prawa. Wszystko to zależy od tego, czy informacje zawarte w zmiennej z prawami mają ściśle określoną, wymaganą postać. W związku z tym nie jesteś w stanie zdziałać nic wiecej za pomocą ‘set_auth()’, niż byś mógł z ‘setuid()’ i ‘seteuid()’. ‘query_auth()’ nie jest zabezpieczone przed wywoływaniem, lecz nie znajdziesz tam żadnych ciekawych informacji.

Tak prawdę mowiąc, informacja zawarta w zmiennej z prawami, której to wartość zwraca ‘query_auth()’ ma po prostu postać uid i euid oddzielonych dwukropkiem.

Skoro już wiemy jak nadawać przywileje innym obiektom, spróbujmy korzystając z nich coś sklonować. Efunkcja do tego celu służąca nazywa się ‘clone_object()’. Ładuje i tworzy obiekt z pliku źródłowego. Gdy operacja klonowania się nie uda, na przykład ze względu na jakieś błędy w kodzie pliku źródłowego, zostanie wyświetlony komunikat błędu zaś wykonanie obiektu przerwane.

    object clone_object(string scieżka)
    np.
        object magiczny_pierscien;
    
        // Tak się ustawia prawa obiektu do klonowania
        setuid();          // pobiera uid na podstawie katalogu
        seteuid(getuid()); // jako euid podstawia pobrane uid
    
        // No i samo klonowanie
        magiczny_pierscien = clone_object("/d/Domena/wiz/mag_pierscien");

Oczywiście wystarczy, że ustawisz uid/euid obiektu RAZ – później już nie musisz tego robić przed wykonaniem każdej operacji, która wymaga odpowiednich praw. Najbardziej rozpowszechnioną metodą jest umieszczenie wywołań funkcji ustawiających uid/euid w funkcji, która jest wywoływana w momencie tworzenia obiektu – ale o tym później.

A teraz... tablice i mappingi istnieją tak długo, jak długo korzystają z nich jakieś zmienne. Gdy ostatnia zmienna odnosząca się do nich zostaje wyzerowana, to dane zawarte w nich są również czyszczone. Czy jest tak samo z obiektami? NIE! Obiekt pozostanie w grze tak długo, jak gamedriver jest uruchomiony lub dopóki doraźnie go nie zniszczysz.

Odnajdywanie odnośników do innych obiektów
 [file_name, find_object, object_clones, find_living, set_living_name,

MASTER_OB, IS_CLONE]

Jak już mówiłem odnośniki do obiektów mogą być zarówno stringami jak i wskaźnikami do obiektów. Zamienianie odnośnika ze wskaźnika w łańcuch znakowy robi się przy pomocy efunkcji ‘file_name()’:

    string file_name(object ob)
    np.
        write("To jest obiekt: " + file_name(this_object()) + ".\n");

String, zwracany przez ‘file_name()’ jest tekstową reprezentacją wskaźnika do obiektu. Ma postać ‘<sciezka do pliku>#<numer obiektu>’, na przykład ‘"/d/Domena/wiz/mag_mikstura#2321"’. Ten łańcuch jest właściwym odnośnikiem do danego obiektu.

Aby zamienić tekstowy odnośnik do pliku we wskaźnik, używa się efunkcji ‘find_object()’.

    object find_object(string odn_ob)
    np.
        object obiekt;
    
        // Master object
        obiekt = find_object("/d/Domena/wiz/mag_mikstura");
    
        // Konkretny klon
        obiekt = find_object("/d/Domena/wiz/mag_mikstura#2321");

Jeśli funkcja nie znajdzie obiektu (może być podana zła ścieżka, dany klon może nie istnieć lub obiekt może być nie załadowany), to zwraca 0.

Czasem przydaje się mieć odnośniki do wszystkich klonów danego obiektu. Do znalezienia ich służy efunkcja ‘object_clones()’. Zwróci ona tablice zawierającą wskaźniki wszystkich klonów master objectu, na który wskazuje odnośnik podany jako argument. Oznacza to, ze możesz podać zarówno wskaźnik do master objectu, jak i do jednego z jego klonów – nie robi to różnicy. Jeśli funkcja nie będzie w stanie znaleźć żadnych klonów, to zwroci pusta tablice.

    object *object_clones(object odn_ob)
    np.
        object *lista_klonow;
    
        lista_klonow = object_clones(find_object("/d/Domena/wiz/mag_mikstura"));

Niektóre obiekty nazywa się "żyjącymi" (living). W grze objawia się to między innymi tym, że mogą one zostać zaatakowane i (być może) zabite. Żyjące obiekty rejestrują się na specjalnej liście w gamedriverze. Robią to po to, by można je łatwiej znaleźć. Specjalna efunkcja ‘find_living()’ szuka w tej liście podanej nazwy żyjącego obiektu.

    object *find_living(string imię, void|int 1)
    np.
        object Balrog_ob, *Balrogowie;
    
        // Szuka potwora 'Balrog' w grze.
        Balrog_ob = find_living("Balrog");

Jeśli podasz ‘1’ jako drugi argument, efunkcja zwróci listę wszystkich obiektów z tą nazwą.

    Balrogowie = find_living("Balrog", 1);

Jeśli efunkcja nie będzie mogła znaleźć obiektu z podanym imieniem, zwróci 0.

Żeby obiekt znalazł się na liście imion, musi w sobie wywołać efunkcje ‘set_living_name()’.

    void set_living_name(string imię)
    np.
        // Jest to część funkcji create() w kodzie Balroga.
        set_living_name("Balrog");

Pamiętaj o tym, że jeśli masz wiele obiektów z tą samą nazwą, to ‘find_living()’ zwróci losowo jeden z nich.

Żeby zdobyć odnośnik do master_objectu obiektu do którego masz wskaźnik, musisz zamienić go na stringa i wtedy oderwać od niego numer klonu. Istnieje już jednak makro znajdujące się w standardowym pakiecie ‘/sts/macros.h’ robiące to za ciebie. Po prostu dodaj na początku pliku linijke ‘#include <macros.h>’ i użyj makra ‘MASTER_OB’.

    string MASTER_OB(object ob)
    np.
        string master;
    
        // Zalozmy ze /sys/macros.h już zostało przyłączone do tego pliku.
        master = MASTER_OB(find_living("Balrog"));

Makro zwraca odnośnik do master objectu Balroga w postaci tekstowej, więc jeśli chcesz mieć wskaźnik do master objectu musisz posłużyć się funkcją ‘find_object()’, jako argument podając świeżo uzyskany string.

Klon najłatwiej odróżnić od master objectu poprzez porównywanie tekstowego odnośnika. W tym celu można użyć makra IS_CLONE. Jest ono również dostępne w ‘/sys/macros.h’. Korzysta ono z ‘this_object()’ i nie wymaga żadnego argumentu.

    int IS_CLONE
    np.
        if (IS_CLONE)
            write("Jestem klonem!\n");
Odnośniki do interaktywnych obiektów
 [find_player, this_interactive, this_player]

Jeśli szukasz jakiegoś konkretnego gracza, to do tego celu mógłbyś użyć ‘find_living()’, a potem upewnić się, że zwrócona wartość jest interaktywnym obiektem. Jednakże o wiele szybciej jest użyć efunkcji ‘find_player()’, która robi dokładnie to samo, z jednym wyjątkiem – może być tylko jeden gracz o podanym imieniu w grze. Jeśli będzie on aktualnie zalogowany, to otrzymasz do niego odnośnik.

    object *find_player(string imię)
    np.
        object lewy;
    
        lewy = find_player("Lewy");
    
        if (pointerp(Lewy))
            lewy->catch_msg("Siemanko, bracie!\n");
        else
            write("Eh.. chyba go nie ma.\n");

Bardzo często potrzeba wiedzieć kto wydał komendę, którą zapoczątkowała wykonanie jakiejś określonej funkcji. Efunkcja ‘this_interactive()’ zwróci do niego wskaźnik. Jeśli łańcuch komend został zapoczątkowany przez jakiś niezależny, nieinterakcyjny obiekt, to zwróci ona 0.

Częściej jednak nie interesuje cię kto zapoczątkował łańcuch, lecz raczej na kogo dany obiekt kieruje swoją uwagę. Ten właśnie obiekt zwróci efunkcja ‘this_player()’. Innymi słowy, podczas gdy obiekt powinien zwracać uwagę (listy komend, komunikaty, itp) na obiekt podany przez this_player(), inny gracz zwrócony przez ‘this_interactive()’ mógł stać się przyczyną wykonania łańcucha komend w aktualnym obiekcie. Wartość ‘this_interactive()’ nigdy nie może być zmieniana, w przeciwieństwie do wartości ‘this_player()’. Więcej o tym będzie później.

    object this_player();
    object this_interactive();
    np.
        object tp, ti;
    
        tp = this_player();
        ti = this_interactive();
    
        if (objectp(ti))
        {
            if (ti != tp)
            {
                tp->catch_msg("Zapppp!\n");  // Ale genialne tłumaczenie,
                ti->catch_msg("Zapnąłeś go!\n");  // nie? :-)
            }
            else
                ti->catch_msg("Fzzzzz...\n");
        }
Niszczenie obiektów
 [destruct, remove_object]

Prędzej czy później będziesz chciał się pozbyć jakiegoś obiektu. Służy do tego efunkcja ‘destruct()’. Jednakże gamedriver pozwoli tylko na zniszczenie samego siebie, tzn. na wywołanie destruct() tylko z obiektu który ma być zniszczony – żadne wywołanie zewnętrzne nie wchodzi w rachubę. To oznacza, że każdy obiekt potrzebuje funkcji, która wywołuje destruct(), którą mógłbyś wywołać z innego obiektu, żeby móc zniszczyć go z zewnętrz. Jeśli jednak obiekt nie zawiera funkcji korzystającej z ‘destruct()’, to nie będziesz w stanie go ruszyć w ten sposób.

Istnieje wyjście z tego, które umożliwia ci zniszczenie dowolnego obiektu, ale jest nim komenda, którą musisz wydać własnoręcznie. Nie możesz użyć jej z wewnątrz programu.

Standardowy obiekt, który później będzie bardziej szczegółowo omówiony, definiuje funkcje ‘remove_object()’, którą możesz wykonać, aby zniszczyć obiekt. Ponieważ wszystkie obiekty w grze MUSZĄ dziedziczyć go, możesz mieć pewność, że w każdym znajdziesz tę funkcję. Możliwe jest zamaskowanie jej i co za tym idzie zablokowanie. Robienie tego, będzie jednak zwykłym sabotażem, wiec nawet nie myśl o robieniu czegoś takiego. Pozwoliliśmy na jej maskowanie, dlatego żebyś mógł dodać tam jakiś kod, który w razie niszczenia obiektu zrobi kilka innych rzeczy, a nie po to byś mógł pisać niezniszczalne obiekty.

    void remove_object()
    np.
        void
        usun_balroga(string imie_bal)
        {
            object bal;
    
            bal = find_living(imie_bal);
            if (objectp(bal))
                bal->remove_object();
        }

Gdy bezpośrednio używasz ‘destruct()’ albo wywołujesz w tym samym obiekcie ‘remove_object()’, DWA razy się upewnij, że żaden kod nie jest po tym wykonywany. Widzisz, wykonywanie nie jest przerywane od razu po komendzie do zniszczenia – obiekt jest tylko oznaczany jako „już zniszczony”, a sama destrukcja dzieje się po skończeniu wykonywania go. Oznacza to, że wywołania funkcji lub komendy wydane po komendzie destrukcji mogą spowodować błędy runtime w innych obiektach.

    void destruct()
    np.
        void
        zniszcz_mnie()
        {
            write("Żegnaj, okrutny świecie!\n");
            destruct();
        }

Gdy obiekt jest niszczony, WSZYSTKIE wskaźniki do niego (nie odnośniki tekstowe) w grze są ustawiane na 0. Ze względu na ten fakt, warto się upewnić, czy stary wskaźnik do obiektu jest wciąż właściwy, przed robieniem z nim czegokolwiek. Nigdy nie masz pewności – może został usunięty w międzyczasie.

Jeśli niszczony obiekt zawiera jakieś inne obiekty, to w czasie destrukcji one są również usuwane. Wyjątkiem są obiekty interakcyjne, gracze. Kiedy uaktualniasz (update) pokój, skutecznie go niszczysz. Jeśli są w nim jacyś gracze, to zostaną oni przemieszczeni do startowych lokacji. Jeśli dany pokój jest startową lokacją lub jest jakiś problem z przemieszczaniem ich tam (błędny kod lokacji, albo niemożność przemieszczenia), to ci gracze zostaną również usunięci.

Obsługa poleceń przez obiekty

 [init, add_action, enable_commands, disable_commands, living, command,

commands, get_localcmd, query_verb, notify_fail, update_actions]

Jak na razie wiesz na pewno, że nic w tej grze nie jest proste. Aby pogmatwać wydawanie poleceń, mamy ich dwa rodzaje. Jednym z nich jest taki, o którym już wcześniej mówiliśmy, czyli polecenia definiowane przez ‘fizyczne’ obiekty. Drugim rodzajem są komendy tzw. soul (duszowe?). Są one wymysłem czysto mudlibowym. Będzie o nich mowa w trzecim rozdziale.

Polecenia dodane przez fizyczne obiekty działają w ten sposób: Po wejściu takiego obiektu w kontakt z innym obiektem, np. graczem (czyli:

albo 1:obiekt wchodzi w ekwipunek ("zawartość" obiektu) gracza,
albo 2:gracz i obiekt znajdują się we wspólnym środowisku, np. w tym samym pokoju,
albo 3:gracz wchodzi w "zawartość" obiektu np do pokoju),

W obiekcie który wszedł wołana jest lfunkcja ‘init()’. Jest ona zwykłą funkcją, taką jak każda inna, lecz przeznaczona jest do dodawania poleceń poprzez efunkcje ‘add_action()’. Innymi słowy, gdy wchodzisz do jakiegoś obiektu, do listy twoich poleceń zostają dodane polecenia z nowego środowiska, z twojego ekwipunku oraz polecenia innych obiektów będących w tym samym środowisku co ty.

Funkcja ‘add_action()’ przypisuje polecenie jakiejś funkcji. Po wpisaniu słowa-polecenia, zostaje wykonana połączona funkcja, z argumentem stanowiącym resztę wpisanego polecenia (np. gdyby całe polecenie brzmiało ‘zabij zielonego smoka’, to słowem-poleceniem byłoby ‘zabij’, a argumentem ‘zielonego smoka’). Oprócz poprzednich dwóch argumentów(tj. funkcja oraz słowo-polecenie), add_action() może przyjąć również trzeci. Jeśli równa się on 1, to przypisana funkcja będzie wykonana, nawet gdy komenda będzie stanowiła tylko część zdefiniowanego w add_action() polecenia. Na przykład, jeśli zdefiniowane polecenie brzmiałoby ‘zabij’, to wywołanie przypisanej funkcji mogłoby być dokonane już przez komendę "za" albo "zab".

    add_action(function funkcja, string slowo_polecenie, void|int 1)
    np.
        init()
        {
            /*
             * Funkcje 'zrob_uklon()' i 'wyjdz_z_gry()' są zdefiniowane
             * gdzieś w tym obiekcie. Jednakże, jeśli ich deklaracje
             * znajdują się za tą funkcją, to w nagłówku programu muszą 
             * być ich prototypy.
             */
            add_action(zrob_uklon, "ukłon");    // Przyzwyczaj się lepiej
            add_action(&wyjdz_z_gry(), "quit"); // do różnych rodzajów
                                                // deklaracji odnośników
                                                // do funkcji.
        }

Czy jest to prawda dla każdego rodzaju obiektu? Czy każdy obiekt otrzyma ten zestaw komend? Ano nie. Tylko "żyjące" obiekty. Obiekt może zostać uczyniony "żyjącym" poprzez efunkcje ‘enable_commands()’, a martwym bądź bezwładnym poprzez efunkcje ‘disable_commands()’. Zauważ, że „zyjacy” obiekt dla gamedrivera oznacza „taki, który może przyjmować i wydawać polecenia” – dla mudliba znaczy to trochę więcej.

Możesz używać tych efunkcji za każdym razem, gdy zechcesz włączyć lub wyłączyć zdolność do obsługiwania poleceń w obiekcie. Pamiętaj tylko o tym, że gdy "ożywisz" obiekt, to nie przybędzie mu komend z racji tego, że znajduje się np w pokoju który dodaje polecenia. Są one dodawane tylko przy wchodzeniu do środowiska pokoju. Żeby je uzyskał będziesz musiał więc przenieść go z, a potem z powrotem do pomieszczenia.

Możesz sprawdzić, czy obiekt jest "żyjący" przy pomocy efunkcji ‘living()’.

    enable_commands()
    disable_commands()
    int living(object ob)
    np.
        public void
        zmien_stan_ozywienia()
        {
            if (living(this_object()))
                disable_commands();
            else
                enable_commands();
        }

Polecenia mogą być dodawane i obsługiwane tylko przez obiekty, które się znajdują w środowisku lub zawartości danego obiektu. Kiedy jest on usuwany z zasięgu obiektów dodających polecenia, możliwość ich wykonania jest również usuwana.

Jak widzisz, gamedriver oczekuje, że funkcja ‘init()’ jest zdefiniowana w każdym obiekcie, który chce dodać polecenia żyjącym obiektom. Bądź jednak ostrożny w czasie używania tej funkcji. Jeśli na przykład używasz ‘init()’ do sprawdzania, czy wchodzący obiekt ma prawo się tam znaleść i przenosi go gdzieś jeśli nie ma prawa, to najprawdopodobniej będziesz miał kłopoty. Będzie tak dlatego, że gdy spróbujesz dodać polecenia już po przesunięciu, to w rzeczywistości będziesz to robił na nieobecnym obiekcie. Gamedriver spostrzeże to i skończy się to błędem. Chciałbym ci doradzić, byś nie używał funkcji ‘init()’ do żadnych innych celów, niż do dodawania poleceń. Możesz ewentualnie sprawdzać czy obiekt, który wywołał ‘init()’ powinien otrzymać polecenia, czy nie (jeśli chcesz ograniczyć dostęp do niektórych poleceń), ale nie wrzucaj tam niczego innego.

W większości obiektów, które dziedziczą pośrednio lub bezpośrednio podstawowy obiekt, musisz wywołać także rodzica ‘init()’, gdyż bez tego twój init() może stracić kilka bardzo ważnych rzeczy. Po prostu umieszczaj instrukcje ‘::init();’ na początku każdego swojego init'a, jeszcze przed instrukcjami ‘add_action()’, a wszystko będzie w porządku.

Aby wykonać jakieś polecenie w żyjącym obiekcie, użyj efunkcji ‘command()’.

    int command(string komenda)
    np.
        command("kichnij");
        command("załóż hełm");

Aby otrzymać listę dostępnych poleceń, używa się efukcji ‘commands()’ lub ‘get_localcmd()’ w zależności od tego, jakiego typu informacji się potrzebuje. ‘commands()’ zwraca tablice tablic, zawierających wszystkie komendy dostępne dla określonego obiektu plus te, które obiekt sam definiuje wraz z funkcjami, które są w razie ich wykonania wywoływane. ‘get_localcmd()’ jest prostsza i zwraca tylko tablice z słowami-poleceniami. Gdy żaden obiekt nie jest wyszczególniony, używa się jako standardowej wartości ‘this_object()’. Obejrzyj manuala dla ‘commands()’ by dowiedzieć się jaki format ma zwracana tablica.

    mixed commands(void|object ob)
    string *get_localcmd(void|object ob)

Gdy używasz jednej funkcji do wielu słów-poleceń, zachodzi potrzeba zbadania, które konkretnie zostało użyte. Do tego używa się efunkcji ‘query_verb()’.

    string query_verb()
    np.
        init()
        {
            ::init();
            add_action(&moja_funkc(), "apa");  // wszystkie trzy
            add_action(moja_funkc, "bepa");    // polecenia wywołują
            add_action(&moja_funkc(), "cepa"); // te same funkcje...
        }
    
        public int
        moja_funkc()
        {
            switch (query_verb()) // ...ale każde polecenie jest w niej
            {                     // rozpatrywane oddzielnie
            case "apa":
                < kod >
                break;
    
            case "bepa":
                < kod >
                break;
    
            case "cepa":
                < kod >
                break;
            }
            return 1;
        }

Funkcje obsługujące polecenia powinny zwracać 1, gdy zostały poprawnie użyte, tzn kiedy została wywołana właściwa funkcja, z właściwym argumentem. Kiedy zwrócisz 0, gamedriver będzie szukał innych funkcji przypisanych do identycznego słowa-polecenia, do czasu aż któraś z nich zwróci 1, albo nie będzie już żadnych innych. Jest specjalna efunkcja zwana ‘notify_fail()’, której możesz użyć do przechowania komunikatu błędu, który zostanie wyświetlony gdy żadna z funkcji nie ‘przygarnie’ polecenia. Zamiast standardowego, bezsensownego komunikatu błędu ‘What?’, możesz dać graczowi wydającemu polecenie jakąś lepszą informację (na Arkadii ‘Slucham?’). W przypadku gdy wszystkie funkcje powiązane z identycznym słowem poleceniem zwrócą 0, to ostatnio zdefiniowany ‘notify_fail()’ zostanie użyty do wyświetlenia komunikatu błędu.

    notify_fail(string komunikat)
    np.
        public void
        init()
        {
            ::init();
            add_action(&zrob_uklon(), "ukłon");
        }
    
        public int
        zrob_uklon(string komu)
        {
            if (!find_player(komu))
            {
                notify_fail("Nie tu nikogo takiego!\n");
                return 0;
            }
    
            < kod ukłonu >
    
            return 1;
        }

Jeśli jesteś absolutnie pewien, że polecenie odnosiło się do twojego obiektu i chcesz przerwać wykonanie nawet jeśli twój obiekt znalazł w nim błąd (złe argumenty, kontekst, lub cokolwiek innego), to możesz zwrócić 1. Pamiętaj jednak, że wtedy musisz użyć innej metody wyświetlenia komunikatu błędu na ekranie niż ‘notify_fail()’, gdyż ta funkcja ma szanse być użyta tylko gdy zwrócisz 0.

Gdy twój obiekt zmienia dostępne polecenia w czasie swojego działania i chcesz, by okoliczne żyjące obiekty uaktualniły swój zestaw komend, musisz wywołać efunkcje ‘update_actions()’ na swoim obiekcie. Jeśli nie sprecyzujesz w argumencie żadnego obiektu, to domyślnie zostanie przyjęty ‘this_object()’. Wszystkie okoliczne żyjące obiekty unieważnią stary zestaw komend od twojego obiektu i wywołają sobie jeszcze raz ‘init()’ z niego, żeby pobrać nowy zestaw.

    update_actions(object ob)

Alarmy: Niesynchroniczne wywołania funkcji

 [set_alarm, remove_alarm, get_alarm, get_all_alarms]

Gamedriver oblicza tak zwany „evaluation cost”, albo prościej „eval cost” (koszt wykonania). Jest to zwykły sposób mierzenia, jak bardzo obiekt obciąża CPU. Każdy obiekt ma nadany limit kosztu wykonania. Kiedy jest on wyczerpany, wykonanie obiektu zostaje przerwane. Ograniczenie to zostało narzucane, by gra nie była za długo przeciążana. Istnienie tego pociąga za sobą jednak pewne konsekwencje. Jakieś duże obliczenia mogą się po prostu nie zmieścić w limicie eval costu, więc w takiej sytuacji potrzeba rozbić program na mniejsze części.

Czasem przydaje się opóźnić wykonanie jakiejś funkcji, a czasem warto, by funkcja była wykonywana cyklicznie. Pozwala na to wykorzystanie alarmów. Ustawia się je efunkcja ‘set_alarm()’.

    int set_alarm(float opóżnienie, float cykl, function funkcja_alarm)
    remove_alarm(int alarm_id)
    mixed get_alarm(int alarm_id)
    mixed get_all_alarms()

Funkcja zwraca unikalny dla tego obiektu numer alarmu, który możesz później wykorzystywać do manipulowania nim. Informacje o danym alarmie uzyskuje się wywołując funkcje ‘get_alarm()’, jako argument podając numer alarmu. Do usuwania ich służy efunkcja ‘remove_alarm()’, a do zdobycia informacji o wszystkich alarmach służy efunkcja ‘get_all_alarms()’. Ta ostatnia jest przeważnie wykorzystywana w sytuacjach, gdy zapomni się przechować gdzieś identyfikator alarmu, albo chce się wyświetlić informacje o obiekcie. Efunkcja ‘set_alarm()’ pozwala na ustalenie opóźnienia, po jakim podana funkcja zostanie wykonana po raz pierwszy, oraz opóźnienia pomiędzy kolejnymi cyklicznymi wywołaniami. Każde wywołanie alarmu zaczyna się z eval costem równym 0. Ponieważ funkcje są wywoływanie przez alarm niesynchronicznie względem użytkownika obiektu, ‘this_player()’ i ‘this_interactive()’ zwrócą 0. Pamiętaj o tym, gdyż niektóre efunkcje korzystają z wartości zwracanej przez ‘this_player()’.

UWAGA! PRZECZYTAJ TO UWAŻNIE!

Bardzo łatwo wpaść w nawyk dzielenia programu na wiele ‘alarmowych’ wywołań w małych odstępach czasowych. Jednakże NIE do tego służą alarmy. Śmiertelnym grzechem jest robienie funkcji alarmowej, która tworzy powtarzające się alarmy wewnątrz powtarzających się alarmów. Ilość alarmów wtedy gwałtownie wzrasta i CAŁA GRA momentalnie staje. Jest to tak nieprawdopodobnie kretyńskie, że grozi natychmiastowym demotem, więc upewnij się, że wszytko jest W PORZĄDKU za pierwszym razem. Ogólnie rzecz biorąc, odstępy pomiędzy cyklicznymi alarmami, powinny być dłuższe niż jedna dwie sekundy, tak samo jak odstępy przed pojedynczymi wywołaniami.

Funkcje alarmowe będą szerzej opisane i zademonstrowane w trzecim rozdziale.

Środowisko i zawartość

 [move_object, move, enter_inv, enter_env, leave_inv, leave_env,

environment, all_inventory, deep_inventory, id, present]

Jak już wcześniej mówiłem, każdy obiekt ma zarówno „wnętrze”, jak i „otoczenie”. Otoczenie, lub inaczej mówiąc „środowisko” może być tylko jednym obiektem, podczas gdy we wnętrzu, w „zawartości” może się znajdować wiele obiektów.

świeżo sklonowany obiekt znajduje się w swego rodzaju pustce, gdyż nie ma żadnego środowiska. Żeby mógł znaleźć się w fizycznym świecie gry, musi być tam przesunięty. Jednakże, nie wszystkie obiekty mogą być przemieszczane. Żeby DOWOLNY obiekt, który chce być gdzieś umieszczony, albo chce samemu zawierać jakieś inne obiekty działał, MUSI dziedziczyć ‘/std/object.c’ gdzieś w swoim łańcuchu dziedziczeń. Po co to ograniczenie? Dlatego, że standardowy obiekt definiuje sporą liczbę pomocnych funkcji i inne obiekty polegają na tym, że będa one w twoim obiekcie.

Oto najważniejsze spośród nich:

  • move()
Przemieszcza obiekt do innego, obsługując rachunek wagi/objętości. Zwraca, czy przesunięcie powiodło się. Jest odpowiedzialna za wywoływanie następujących funkcji:
  • enter_inv()
Wywoływana jest w obiekcie, gdy inny obiekt wchodzi w jego zawartość.
  • leave_inv()
Wywoływana w obiekcie, gdy inny obiekt wchodzi w jego zawartość.
  • enter_env()
(!!!)
  • leave_env()
(!!!)

UWAGA! Powyższe funkcje będą wywołane TYLKO wtedy gdy to właśnie ‘move()’ zostanie użyty do przesunięcia. Dlatego tak ważne jest to, żebyś przemieszczał w ten sposób, a nie poprzez efunkcje, która robi to bezpośrednio.

Lfunkcja ‘move()’ korzysta z efunkcji ‘move_object()’. ALE pamiętaj, gdy odwołasz się bezpośrednio do tej drugiej, to stany obiektu takie jak oświetlenie, waga, czy objętość nie zostaną uaktualnione. Jak już poprzednio mówiłem, fiaskiem skończy się próbą przeniesienia obiektu do innego przy pomocy ‘move_object()’, gdy któryś z nich nie dziedziczy ‘/std/object.c’. Dodatkowo, efunkcja może być wywołana tylko z obiektu, który chce być przesunięty. To samo dotyczy oczywiście lfunkcji ‘move()’.

W celu uzyskania odnośnika do obiektu środowiska, używa się efunkcji ‘environment()’. Jak już wcześniej powiedziałem, żaden obiekt nie ma zdefiniowanego środowiska zaraz po utworzeniu – otrzymuje je dopiero, gdy jest gdzieś przenoszony. Kiedy obiekt chociaż raz opuści ‘pustkę’, w której się znajduje na początku, to już nigdy nie może tam wrócić, tzn. nie można przenieść obiektu do ‘0’. Obiektami, po których możesz się spodziewać, że nie będą miały żadnego środowiska są pokoje, dusze, cienie i obiekty daemon.

Masz do wyboru dwie funkcje, spośród których możesz wybierać, gdy chcesz zdobyć skład obiektu. Efunkcja ‘all_inventory()’ zwraca tablice ze wszystkimi obiektami będącymi zawartością podanego obiektu. Efunkcja ‘deep_inventory()’ zwraca tablice, zawierającą nie tylko to, co ‘all_inventory()’, ale rownież obiekty, które są we wnętrzu obiektów, które są we wnętrzu podanego obiektu, itd.

    object *all_inventory(object ob)
    object *deep_inventory(object ob)
    np.
    /*
     * Funkcja wyświetla ekwipunek Fatty na ekranie. Od argumentu będzie
     * zależało, czy będzie to tylko ekwipunek widoczny na pierwszy rzut
     * oka, czy cały.
     */
    void
    fatty_powiedz_aaaaa(int wszystko)
    {
        object fatty_ob, *listaob;
    
        if (!objectp((fatty_ob = find_player("fatty"))))
        {
            write("Przykro mi, Fatty nie ma dziś w grze.\n");
            return 0;
        }
    
        listaob = wszystko ? deep_inventory(fatty_ob) 
                           : all_inventory(fatty_ob);
    
        write("Oto " + (wszystko ? "cala " : "") +
                    " zawartość wielkiego brzucha Fatty:\n");
        dump_array(listaob);
    }

Co powiesz na sprawdzenie, czy dany obiekt jest obecny w zawartości innego? Bazowy obiekt ‘/std/object.c’ definiuje zarówno nazwę jak i opis w obiektach. Również w nim znajduje się lfunkcja ‘id()’, która sprawdza czy podany argument jest jedną z nazw danego obiektu. Jeśli jest to zwraca 1(prawdę). Efunkcja ‘present()’ przeszukuje zawartości obiektów, sprawdzając czy jest tam obiekt o podanym odnośniku lub nazwie. Gdy w argumencie wpiszesz to drugie, to ‘present()’ skorzysta z poprzednio omówionej funkcji ‘id()’ do sprawdzenia, czy w przeszukiwanym aktualnie obiekcie istnieje takowa nazwa. Wykonanie funkcji skończy się tak szybko, jak szybko znajdzie ona pierwszy pasujący do opisu obiekt. Oznacza to, że jeśli jest więcej takich obiektów, to funkcja zwróci ci tylko jeden z nich.

    object present(object ob|string odnob, object *listaob|object ob|void)
    np.
    /*
     * Szuka orzeszków u Fatty
     */
    void
    znajdz_orzeszek()
    {
        object fatty_ob;
    
        fatty_ob = find_player("fatty");
    
        // Nie można znaleźć Fatty!
        if (!objectp(fatty_ob))
        {
            write("Fatty chwilowo nie ma, spróbuj później.\n");
            return;
        }
    
        if (present("orzeszek", fatty_ob))
            write("Tak, Fatty wydaje się być bardzo zadowolony z życia.\n");
        else
            write("Na twoim miejscu trzymałbym się z dala od " +
                  "Fatty, dopóki nie zaspokoi głodu.\n");
    }

Jeśli nie podasz drugiego argumentu w ‘present()’, to funkcja poszuka obiektu w zawartości ‘this_object()’, czyli tego obiektu, z którego została wywołana. Gdy w drugim argumencie podasz tablice, to funkcja przeszuka wszystkie obiekty z listy. Jeśli nie znajdzie niczego, to zwróci 0.

Funkcje obsługujące łańcuchy znakowe

 [break_string, capitalize, lower_case, sprintf, strlen, wildmatch]

W środowisku gry opartym na tekście, naturalne jest to, ze twórcy zadali sobie trochę trudu w stworzeniu łatwych i wszechstronnych funkcji obsługujących łańcuchy znakowe. Jak już wiesz, stringi można sumować przy pomocy operatora ‘+’, a nawet łączyć je z liczbami całkowitymi bez żadnych kłopotów. Floaty i wskaźniki do obiektów muszą już jednak być konwertowane. Te pierwsze przy pomocy efunkcji ‘ftoa()’ (opisanej później), a te drugie poprzez już opisaną efunkcje ‘file_name()’.

Jedną z najczęściej sprawdzanych rzeczy w stringach, poza tym co zawierają, jest ich długość. Uzyskuje się ją przy pomocy efunkcji ‘strlen()’. Jako argument można podać również liczby całkowite (zwróci wtedy 0), dzięki czemu można ją wykorzystywać także do sprawdzania czy zmienna typu string została już zainicjalizowana.

    int strlen(string str)
    np.
        string str = "Fatty jest spasionym, zatwardziałym szowinistą";
        write("Długość stringa '" + str + "' wynosi " + strlen(str) +
              " znaków.\n");

Nieraz zachodzi potrzeba takiego przeformatowania stringa, by zaczynał się z dużej litery. Służy do tego efunkcja ‘capitalize()’. Oprócz tego istnieje efunkcja ‘lower_case()’, która zamienia wszystkie litery w podanym stringu na małe.

    string capitalize(string str)
    string lower_case(string str)

np.

    void
    // Wyświetli podane imię, odpowiednio sformatowane.
    wyswietl_ladne_imie(string imie)
    {
        string nowe_imie;
    
        // Zalozmy, ze imie = "fAttY"
        nowe_imie = lower_case(imie);
        // Teraz imie = "fatty"
        nowe_imie = captialize(imie);
    
        write("Imie brzmi: " + imie + "\n");
    
        /* Efektem jest:
    
           Imie brzmi: Fatty
         */
    }

Czasem przydałoby się połamać stringa na mniejsze kawałki(np długości linijki ekranu), by ładniej wyglądał i nadawał się do wyświetlenia. Służy do tego efunkcja ‘break_string()’. Dzięki niej możesz nawet dodać spacje na początku połamanych łańcuchów znakowych. Jej działanie polega na wstawianiu znaku nowej linii po odpowiedniej ilości słów, tak by jedna część zmieściła się w podanym limicie znaków. Trzeci argument mówiący ile ma być spacji wcięcia, albo podający string wstawiany na początku każdego fragmentu jest opcjonalny.

    string break_string(string str, int dlug_lam, 
                        int dlugosc_wciecia|string wstawiany_string|void)

np.

        string str = "To jest string, który chce przedstawić na różne " +
                     "sposoby.";
    
        write(break_string(str, 26) + "\n");
        write(break_string(str, 26, 5) + "\n");
        write(break_string(str, 26, "Fatty mowi: ") + "\n");
    
        /* Efektem będzie:
           To jest string, ktory chce
           przedstawic na rozne
           sposoby.
                To jest string, ktory chce
                 przedstawic na rozne
                sposoby.
           Fatty mowi: To jest string, ktory chce
           Fatty mowi: przedstawic na rozne
           Fatty mowi: sposoby.
         */

Bardzo często będziesz chciał przedstawić zawartość zmiennej na ekranie. Jak już pokazałem, możesz to zrobić poprzez zamienienie wartości zmiennej w stringa i wyświetlenie jej. Integerów nawet nie trzeba konwertować - wystarczy, że dodasz go przy pomocy operatora ‘+’. Otrzymasz jednak coś, co nie będzie sformatowane i być może będzie wymagało jakiś przeróbek. Czasem możesz chcieć wyświetlić treść zmiennych w postaci tabelki i będziesz wtedy musiał bawić się w różne duperele takie jak uzależnianie ilości spacji od długości zmiennej itp. Zamiast tego, możesz skorzystać z efunkcji ‘sprintf()’.

‘sprintf()’ pobiera dwa stringi. Pierwszy to jest ten, który chcesz przeformatować. Drugi zaś zawiera wskazówki, według których formatowanie ma się odbywać. Wynikiem jest gotowy łańcuch znaków, który możesz wyświetlić np. przy pomocy ‘write()’.

Wszystkie znaki z drugiego stringa, poza specjalnymi zaczynającymi się od ‘%’ będą przekopiowane do wynikowego stringa. Znaki kontrolne mają postać: "%<wyznacznik szerokości><wyznacznik typu>".

Szerokość jest liczba całkowitą, oznaczającą długość „okienka” w którym dane będą wyświetlane oraz czy dana ta ma być równana do lewej czy do prawej strony. Dodatni numer oznacza, że do prawej, zaś ujemny, że do lewej. Jeśli nie podasz szerokości, to zmienna zostanie włożona w okienko o długości równej długości zmiennej. Wyznacznik typu składa się z jednej lub więcej liter, określających jaki typ zmiennej będzie tu użyty.

A oto lista wyznaczników typu:

  • d
  • i
Argument jest liczbą całkowitą.
         string str;
         int a;
         a = 7;
         str = sprintf("test: >%-3d%i<", 1, a);
         
         write(str + "\n");
         
         // Efektem jest:
         // test: >1  7<
  • s
Argument jest łańcuchem znaków.
  • c
Podany argument jest numerem ASCII, znaku który ma być wyświetlony.
  • o
Liczba ma być przedstawiona w systemie ósemkowym.
  • x
Liczba ma być przedstawiona w systemie szesnastkowym.
  • X
Liczba ma być przedstawiona w systemie szesnastkowym (dużymi literami).
  • O
Argument jest typem danych LPC. Jest to świetna rzecz do wyszukiwania błędów (odpluskwiania), gdyż możesz wyświetlić dzięki niej zawartość DOWOLNEJ zmiennej.

np.

         write(sprintf("1:%d 2:%s 3:%c 4:%o\n5:%x 6:%X 7:%O\n", 5,
                       "hupp happ", 85, 584, 32434, 85852, strlen));
         
         // Efektem bedzie:
         // 1:5 2:hupp happ 3:U 4:1110
         // 5:7eb2 6:14F5C 7:<<FUNCTION &strlen()>>

Specyfikator ten jest jak na razie jedynym, w którym da się wyświetlić floaty.

To była lista wszystkich wyznaczników typów. Do wyznaczników szerokości można jeszcze dodać te elementy:

  • ' '
Liczbowy argument zostanie poprzedzony jedną spacją, o ile jest dodatni. Pozwala to na robienie fajnych tabel bez zawracania sobie głowy tym, że liczby z minusem zajmują o jeden znak więcej.
  • +
Dodatnie argumenty liczbowe zostaną poprzedzone plusem.
  • 'X'
Znak(i) w apostrofach będzie poprzedzał argument.
  • |
Argument zostanie wycentrowany w okienku.
         write((sprintf(">%19|s<\n", "Fatty grubas")));
         // Efektem będzie:
         // >   Fatty grubas    <


  • #
Oznacza to tryb tablicowy. Efektem będzie lista słów oddzielonych ‘\n’ w tabeli o szerokości okienka. Oczywiście można użyć tego tylko do stringów.
  • =
Ten wyznacznik jest poprawny tylko dla stringów. Wyświetla rezultat w kolumnach, o ile argument jest szerszy od okienka.
  • *
Argument obok gwiazdki jest rozmiarem okienka. Jeśli połączysz to z trybem tablicowym, to otrzymasz fajne tabelki.
  • @
Argumentem jest tablica. Oczywiście musisz połączyć to z wyznacznikiem typu, zaznaczając typ elementów.

Bardzo często zachodzi potrzeba sprawdzenia, czy dany string jest częścią jakiegoś innego. Jeśli nie jesteś zainteresowany informacja gdzie on dokładnie wystąpił, a tylko czy w ogóle, to efunkcja ‘wildmatch()’ będzie czymś w sam raz dla ciebie. Po prostu zwraca 1, jeśli podany string wystąpił gdzieś, w jakimś innym, większym stringu. Mniejszy string może składać się tez z prostych symboli-masek.

  • *
Odpowiada dowolnej liczbie dowolnych znaków (użyteczne np. przy szukaniu stringa typu „obojętnie kto mówi: ty draniu obojętnie co”)
  • ?
Odpowiada jednemu dowolnemu znakowi
  • [xyz]
Porównuje dowolne znaki spośród tych w nawiasach kwadratowych
  • [^xyz]
Porównuje dowolne znaki nie będące pomiędzy nawiasami kwadratowymi
  • \c
Porównuje c, nawet jeśli jest to znak specjalny
    int wildmatch(string matryca, string str);

np.

        // Cokolwiek, co się kończy na .foo
        wildmatch("*.foo", "bar.foo") == 1
        // Cokolwiek zaczynającego się od a, b lub c i zawierającego
        // conajmniej jeden znak więcej
        wildmatch("[abc]?*", "axy") == 1
        wildmatch("[abc]?*", "dxy") == 0
        wildmatch("[abc]?*", "a") == 0

Funkcje obsługujące znaczniki bitowe

 [clear_bit, set_bit, test_bit]

Nieraz zachodzi potrzeba przechowania sporej liczby informacji typu ‘tak/nie’. Bardzo prostym i przy okazji niezbyt dobrym sposobem byłoby stworzenie sporej liczby integerów, po jednym na każdą informacje i wstawianie w nie 0 albo 1, żeby przedstawić jakiś stan. Daje to łatwy dostęp i jest zrozumiałe, ale jak przychodzi do przechowania większej ilości informacji, to się zaczynają problemy z straszną pamięciożernością tej metody.

Zamiast tego, można używać stringów, gdzie każdy bit w znaku (jest ich 8 na znak) może przechowywać informacje typu tak/nie. Maksymalna liczba bitów w łańcuchu wynosi około 1200 = długość stringa około 150 znaków. Choć raczej wątpię, że wykorzystasz je wszystkie.

Poszczególne bity ustawia się przy pomocy efunkcji ‘set_bit()’, która wymaga dwóch argumentów. Pierwszym jest zmienna typu string, w której bit ma być ustawiony, drugim zaś numer bitu, który chcesz włączyć. ‘clear_bit()’ działa analogicznie do ‘set_bit()’, tylko że zeruje(wyłącza) podany bit. Jeśli chcesz sprawdzić jaką wartość zawiera dany bit, to powinieneś użyć efunkcji ‘test_bit()’.

Nie musisz inicjalizować stringów, które chcesz wykorzystać do przechowywania bitów. Zarówno ‘set_bit()’ jak i ‘clear_bit()’ zwracają zmodyfikowany string, a w przypadku gdy nie jest on wystarczająco szeroki to zostanie rozszerzony przez ‘set_bit()’. ‘clear_bit()’ jednakże nie skróci stringa.

    string set_bit(string bitstr, int numer_bitu)
    string clear_bit(string bitstr, int numer_bitu)
    int test_bit(string bitstr, int numer_bitu)
    np.
        // Wlacza 23 bit
        string bf;
    
        bf = "";
        bf = set_bit(bf, 22);
    
        // Zeruje 93 bit
        bf = clear_bit(bf, 92);
    
        // Sprawdza 3 bit
        if (test_bit(bf, 2))
            write("Wlaczony!\n");
        else
            write("Wyzerowany!\n");

Funkcje obsługujące czas

 [time, ctime, file_time, last_reference_time, object_time]

Z jakiejś nieznanej przyczyny wszystkie pomiary czasu w UNIXe, a co za tym idzie w mudzie, zaczynają się od 1 stycznia 1970 roku. Być może twórcy tego systemu wymyślili sobie, że z komputerowego punktu widzenia nie ma powodu, by chcieć ustawić jakaś wcześniejszą datę. W każdym razie tak jest i nic na to nie można poradzić. Mierniki czasu są integerami i zliczają czas od wyżej wymienionej daty w sekundkach.

Efunkcja ‘time()’ zwraca aktualny czas. Możesz wykorzystać ją w tej postaci, albo przekonwertować zwracaną wartość w jakiś zrozumiały string przy pomocy efunkcji ‘ctime()’. W celu otrzymania czasu, w którym plik został utworzony, używa się efunkcji ‘file_time()’. Istnieje analogiczna efunkcja odnosząca się do obiektów – ‘object_time()’.

Warto czasem wiedzieć kiedy ostatni raz obiekt był używany, tzn. kiedy ostatni raz była wywołana w nim jakaś funkcja. Jeśli jako pierwszą instrukcje wywołasz ‘last_reference_time()’ to otrzymasz ten czas. Pamiętaj jednakże, że po wykonaniu tej funkcji, czas ostatniego wywołania przyjmie wartość czasu bieżącego.

    int time()
    string ctime(int tm)
    int file_time(string obref)
    int object_time(object ob)
    np.
        // last_reference_time() wywołujemy jako pierwsze
        write("Ten obiekt ostatni raz był użyty " + 
            ctime(last_reference_time()) + "\n");
        write("Aktualny czas: " + ctime(time()) + ".\n");
        write("Ten obiekt istnieje juz " + 
            (time() - object_time(this_object())) + " sekund.\n");

Funkcje obsługujące konwersję tablice-stringi

 [explode, implode]

Istnieje możliwość podzielenia stringa na mniejsze kawałki na podstawie jakiegoś innego łańcucha, albo sklejenia rżźnych stringów umieszczonych w tablicy w jeden. Do tego celu służą efunkcje ‘explode()’ i ‘implode()’.

Efunkcja ‘explode()’ wymaga dwóch argumentów: pierwszym jest string, który chce się podzielić, drugim zaś jakiś inny string, którego ‘explode()’ szuka w większym jako znacznika, gdzie go podzielić (np. explode(jakis_tekst, " ") zwróci jakiś_tekst podzielony na słowa;

znacznikiem dzielącym jest tutaj spacja). 

Efunkcja zwraca tablice składającą się z podzielonych stringów. ‘implode()’ jako argumentów wymaga tablicy i stringa, a zwraca string składający się z posklejanych elementów tablicy, połączonych stringiem z drugiego argumentu.

    string *explode(string str, string matryca)
    string implode(string *lista_str, string str_laczacy)
    np.
        string owoce = "jabłko i banan i ananas " +
                       "i pomarancz i fatty który je to wszystko";
        string *lista_owocow;
    
        lista_owoców = explode(owoce, " i ");
        dump_array(lista_owocow);
    
        /* Efektem bedzie:
            (Array)
            [0] = (string) "jabłko"
            [1] = (string) "banan"
            [2] = (string) "ananas"
            [3] = (string) "pomarańcz"
            [4] = (string) "fatty który je to wszystko"
         */
    
         owoce = implode(lista_owocow, ", ");
         write(owoce + "\n");
    
        // Efektem będzie:
        // jabłko, banan, ananas, pomarańcz, fatty który je to wszystko

Funkcje obsługujące tablice

 [allocate, member_array, sizeof, pointerp]

Zacznę od małej powtórki z tablic. Mogą one zawierać dowolne typy danych, włączając w to inne tablice. Pamiętaj o tym, że tablice w przeciwieństwie do typów danych (poza mappingami) są kopiowane poprzez odnośnik, a nie poprzez wartość. Oznacza to, że gdy przypisujesz tablice zmiennej, nie kopiujesz jej, a jedynie przechowujesz odnośnik, wskaźnik do tablicy w zmiennej.

    np.
        string *arr1, *arr2;
    
        arr1 = ({ 1, 2, 3 });
        arr2 = arr1;
    
        arr2[1] = 5;
    
        dump_array(arr1);
        /*
         * Efektem jest:
         *
         * (Array)
         * [0] = (int) 1
         * [1] = (int) 5
         * [2] = (int) 3
         */

A więc jak widzisz, zmiana zawartości tablicy ‘arr2’ zmienia również zawartość tablicy ‘arr1’. Żeby uczynić ją unikalną, musisz wpierw wykonać kopie ‘arr1’, na przykład poprzez dodanie do niej pustej tablicy ‘({ })’.

Jak już wiesz, tablice będą automatycznie zaalokowane poprzez zwykłe wpisanie czegoś w nie bądź poprzez dodanie elementu lub innej tablicy. Jeśli jednak chcesz natychmiast zaalokować tablice do odpowiedniego rozmiaru to możesz użyć efunkcji ‘allocate()’. Rozmiar podaje się jako jedyny argument. Funkcja zainicjalizuje podaną liczbę elementów i ustawi je wszystkie na 0, niezależnie od typu tablicy.

    mixed *allocate(int rozmiar)
    np.
        string *str_tabl;
    
        str_tabl = allocate(3);
        str_tabl[1] = "Fatty jest sflaczałym szowinistą";
        dump_array(str_tabl);
    
        /* Efektem jest:
            (Array)
            [0] = (int) 0
            [1] = (string) "Fatty jest sflaczałym szowinistą"
            [2] = (int) 0
         */

Jeśli chcesz sprawdzić, czy dane wyrażenie jest elementem tablicy i jeśli tak, to jaki jest indeks tego elementu, to możesz skorzystać z efunkcji ‘member_array()’, podając jako argumenty tablice i szukany element. Funkcja zwróci numer indeksu, jeśli znajdzie element, albo -1 gdy poszukiwania zakończa się niepowodzeniem. Jeśli w tablicy będzie więcej odpowiadających elementów, to ‘member_array()’ zwróci indeks pierwszego z nich.

    int member_array(mixed element, mixed tablica)

np.

        int *tab = ({ 1, 55443, 123, -3, 5, 828, 120398, 5, 12 });
        int indeks;
    
        // Wszystkie '5' zostaną zastąpione przez '33'
        while ((indeks = member_array(5, tablica)) != -1)
            tab[indeks] = 33;

Bardzo wazna efunkcja dotyczącą tablic jest ‘sizeof()’. Zwraca ona rozmiar podanej tablicy, tzn. liczbę elementów znajdujących się w niej. Czesto zachodzi potrzeba napisania pętli oblatującej wszystkie elementy tablicy, albo po prostu znalezienia indeksu ostatniego elementu i wtedy ta efunkcja się bardzo przydaje.

UWAGA! Indeks ostatniego elementu wynosi rozmiar_tablicy - 1 : (sizeof(tablica) - 1), gdyż numeracja indeksów zaczyna się od 0.

    int sizeof(mixed tab)
    np.
        string *tab = ({ "Fatty", "szowinitsa" });
    
        write(implode(tab, " ") + " jest źle.\n");
    
        tab[sizeof(tab) - 1] = "szowinista";
    
        write(implode(tab, " ") + " jest poprawnie.\n");

Efunkcja ‘pointerp()’ może być zastosowana do sprawdzenia, czy zmienna zawiera tablice (dowolnego typu), czy nie. Jest bardzo przydatna, jeśli masz do czynienia z funkcjami, które mogą zwrócić 0 (wartość NULL), gdy coś pójdzie nie tak jak powinno.

    int pointerp(mixed tab)
    np.
        string *tab;
    
        if (pointerp((tab = find_player("zdzichu")->pobierz_gildie())))
            write("Zdzichu należy do: " + implode(tab, ", ") + ".\n");
        else
            write("Zdzichu nie należy do żadnej gildii!.\n");

Funkcje obsługujące Mappingi

 [mkmapping, mappingp, m_sizeof, m_delete, m_indices, m_values,
  m_restore_object, m_save_object]

Tak jak to wcześniej mówiłem, mappingi są listami indeksów powiązanych z wartościami. Podając indeks, otrzymujesz przypisana do niego wartość. Zawartość mappingów jest ułożona w specjalnie posortowany sposób, dzięki czemu dostęp do nich jest bardzo szybki. Jednakże mają pewną wadę – są strasznie pamięciożerne i zużywają bardzo dużo miejsca w porównaniu do tablic.

Jak się alokuje mappingi? Jest to bardzo proste. Wystarczy, ze zadeklarujesz go, a potem przypiszesz jedna wartość do indeksu. Jeśli indeks będzie już istniał, wartość przy nim zostanie zastąpiona nową. A jeśli nie, to zostanie dodana nowa para. Do tworzenia mappingów można tez użyć efunkcji ‘mkmapping()’ i jako argumenty podać dwie tablice, jedną z samymi indeksami, a drugą z wartościami. Pamiętaj tylko, że muszą mieć one taki sam rozmiar.

    mapping mkmapping(mixed tab_ind, mixed tab_war)
    np.
        string *ind_tab, *wart_tab;
        mapping mp;
    
        mp["lewy"] = "wielki";
        mp["alvin"] = "straszny";
        mp["fatty"] = "grubas";
    
        // ... jest tym samym co ...
    
        ind_tab = ({ "lewy", "alvin", "fatty" });
        wart_tab = ({ "wielki", "unikalny", "grubas" });
        mp = mkmapping(ind_tab, wart_tab);
    
        // Możesz oczywiście podać te tablice bezpośrednio,
        // bez posługiwania się zmiennymi

Tak jak w tablicach, tu też jest funkcja sprawdzająca czy dana zmienna zawiera mapping, czy nie. Jest nią ‘mappingp()’. Używaj jej do tego samego celu, tzn. gdy jakaś funkcja może, ale nie musi zwracać mappingu, a ty chcesz mieć pewność zanim zaczniesz indeksować zwracaną wartosć.

Do znajdywania rozmiaru mappingu może ci posłużyć efunkcja ‘m_sizeof()’. Działa dokładnie tak samo, jak odpowiednik u tablic, zwracając liczbę elementów (par) w mappingu.

W mappingach usuwanie elementów już nie jest takie proste, jak w tablicach. Służy do tego efunkcja ‘m_delete()’. Jej działanie nie polega na bezpośrednim usunięciu elementu, tylko na stworzeniu nowego mappingu i przekopiowaniu tam zawartości starego bez podanego elementu.

    mapping m_delete(mapping map, mixed elem)
    np.
        mapping mp, m_nowy;
    
        mp["lewy"] = "wielki";
        mp["alvin"] = "straszny";
        mp["fatty"] = "grubas";
    
        m_nowy = m_delete(mp, "fatty");
        dump_array(m_nowy);
    
        /* Wynik:
         *
         *  (Mapping) ([
         *    "lewy":"wielki"
         *    "alvin":"straszny"
         *  ])
         */

A jak zdobyć wszystkie elementy mappingu? Na przykład chcielibyśmy jakiejś odwrotności ‘mkmapping()’. Do tego służą dwie funkcje: ‘m_indices()’ oraz ‘m_values()’, które zwracają (kolejno) wszystkie indeksy oraz wszystkie wartości danego mappingu.

Doszliśmy do dosyć niestabilnej kwestii – kolejności elementów w mappingach. Jak wcześniej mówiłem, mappingi nie mają jej na stałe zdefiniowanej. Tzn. mają, ale nie jest to temat którym powinieneś sobie zawracać głowę. Zmienia się ona gdy dodajesz, albo usuwasz jakąś parę. W każdym razie ważne jest to, że gdy pobierzesz wszystkie indeksy i wartości z jakiegoś mappinga (za pomocą m_indices() i m_values()), to elementy otrzymanych tablic będą sobie odpowiadały o ile pomiędzy pobieraniami nie wykonywałeś żadnych operacji na tym mappingu.

    mixed m_indices(mapping mapp);
    mixed m_values(mapping mapp);
    np.
        // Funkcja wyświetla mapping i jego zawartość
        void
        dump_mapping(mapping mp)
        {
            int i, sz;
            mixed ind, war;
    
            ind = m_indices(mp); // Pomiędzy tymi dwoma instrukcjami, nie
            war = m_values(mp); // powinno być żadnych działań na mappingu.
    
            sz = sizeof(ind);
    
            for (i = 0 ; i < sz ; i++)
                write(sprintf("%O", ind[i]) + " przypisane do " +
                      sprintf("%O", war[i]) + "\n");
        }
    
        /* Na przykład uruchamiamy: dump_mapping(([ "fatty" : "grubas",
         *                                          "lewy" : "wielki",
         *                                          "alvin" : "straszny"
         *                                         ]));
         * Otrzymujemy:
         *
         * "alvin" przypisane do "straszny"
         * "fatty" przypisane do "grubas"
         * "lewy" przypisane do "wielki"
         */

Są, albo będą dwie funkcje, które zapisują i odtwarzają dane obiektu. Niestety, jak na razie mają one jeszcze błędy i nie działają dokładnie tak, jakbyśmy sobie tego życzyli. Powinny one funkcjonować w ten sposób: ‘m_save_object()’ stworzy mapping, zawierający wszystkie globalne, nie-statyczne zmienne, których nazwy będą indeksami. Będziesz mógł wtedy zgrać go bezpośrednio do pliku, albo przekazać go dalej, jako argument jakiejś funkcji. Odwrotnością tego będzie funkcja ‘m_restore_object()’. Będzie ona przyjmowała mapping jako argument, rozkładała jego elementy ustawiając zmiennym globalnym odpowiadające wartości.

Konwersja typów

 [atoi, atof, ftoa, itof, ftoi, str2val, val2str, sscanf]

Większość rzeczy wpisywanych przez gracza to stringi; wpisujesz coś i gra powinna odpowiednio na to zareagować. Wymaga to istnienia funkcji, które zanalizują twoje komendy i przetłumaczą to na wartości, których możesz użyć w swoich programach. Analiza składni komend, jest lekko mówiąc bardzo skomplikowana i zostawiam to na trzeci rozdział. Teraz skupmy się tylko na konwertowaniu samych wartości. Jak mówiłem, to co gracze wpisują jest w formie stringów, przez co zamienianie stringów w integery oraz floaty i vice versa może być dosyć pożyteczną umiejętnością.

Zacznijmy od integerów. Załóżmy, że otrzymałeś string zawierający jakąś wartość numeryczną i chcesz jej użyć do jakichś obliczeń. Musisz więc przekształcić stringa w integera. Służy do tego efunkcja ‘atoi()’; podajesz string jako argument i otrzymujesz integer – bardzo proste. Łańcuch nie może jednak zawierać żadnych znaków innych niż cyfry. W przeciwnym wypadku funkcja zwróci 0.

    int atoi(string str)
    np.
        int war;
    
        war = atoi("23");
    
        write("23 + 3 = " + (war + 3) + "\n");

Liczby zmiennopozycyjne (floaty) mają analogiczna efunkcje, ‘atof()’, która przekształca stringa we floata. Jak już wiesz, floaty nie mogą być przekonwertowane w stringi w ten sam sposób, jak integery, czyli poprzez dodanie ich do innego stringa. Funkcja ‘ftoa()’ zamienia floata w stringa. Tak samo jak w przypadku ‘atoi()’, jeśli w ‘atof()’ podasz jakiś string, w którym będą znaki nie-numeryczne, to zwróci 0 (wyjątkiem będą specjalne znaki, takie jak np. ‘.’.

Do zamiany pomiędzy integerem a floatem służą efunkcje ‘itof()’ oraz ‘ftoi()’. Pamiętaj tylko, że gdy zamieniasz floata w integera, to część dziesiętna nie jest zaokrąglana, tylko obcinana.

Jest wiele momentów, w których możesz chcieć przechować wartość w stringu, a potem przemienić ją z powrotem. Służą do tego efunkcje ‘val2str()’ oraz ‘str2val()’. Da się wyświetlić zawartość ‘val2str()’, ale nie do tego została ona stworzona. Możesz przechować dowolną wartość zmiennej, korzystając z tych funkcji

Najczęściej jednak używanym konwerterem danych jest efunkcja ‘sscanf()’. Możesz sprecyzować, gdzie ma ona szukać wartości (podobnie jak w ‘sprintf()’), w celu pobrania ich i przechowania w jakiejś zmiennej. Jest ona charakterystyczna, gdyż ustawia wartości zmiennych podanych w argumencie, więc niemożliwe jest pobranie jej adresu. Poza tym, działa ona bardzo prosto. Podaje się matryce, string do przeszukania i zmienne, w których dane powinny być przechowane. Funkcja zwraca liczbę znalezionych wartości.

String, który podasz jako matryce, jest interpretowany dosłownie, z wyjątkiem poniższych łańcuchów kontrolnych.

  • %d
porównuje liczbę całkowitą.
  • %s
porównuje łańcuch znaków.
  • %f
porównuje liczbę zmiennopozycyjną.
  • %%
porównuje znak `%'.
    int sscanf(string str, string matryca, <zmienne>...);
    np.
        int szer;
        float waga;
        string orgstr;
        string typ_szer, typ_wagi;
    
        /*
         * Załóżmy, że zadawane jest pytanie :
         * "Jak ci się wydaje, jak ciężki i szeroki jest Fatty?".
         * Oprócz tego załóżmy, że odpowiedź jest dana w formie
         * '<ilość> <rodzaj> i <ilość> <rodzaj>', na przykład:
         * '4 metry i 3.2 tony'. Przyjmijmy, że pierwsza wartość może być
         * tylko integerem, a trzecia tylko floatem.
         *
         * Załóżmy jeszcze, że odpowiedź jest podana w zmiennej `orgstr'
         */
    
        if (sscanf(orgstr, "%d %s i %f %s", szer, typ_szer,
                   waga, typ_wagi) != 4)
        {
            write("Podaj pełną odpowiedź!\n");
            return;
        }
    
        write("Aha, uważasz, że Fatty jest szeroki na " + szer + " " +
              typ_szer + " i wazy " + ftoa(waga) + " " + typ_wagi ".\n");

Funkcje matematyczne

 [random, rnd, sin, cos, tan, asin, acos, atan, atan2, exp, log, pow,

sinh, cosh, tanh, asinh, acosh, atanh, abs, fact, sqrt]

Efunkcja ‘random()’ zwraca dowolną liczbę całkowitą z zakresu od 0, do podanej w argumencie liczby minus jeden. Na przykład ‘random(8)’ zwróci losową liczbę z zakresu 0-7.

Reszta funkcji matematycznych pobiera jako argumenty i zwraca floaty. Funkcje trygonometryczne używają radianów, nie stopni. Pamiętaj o tym.

Oto pełna lista funkcji matematycznych:

  • float rnd()
Zwraca losową liczbę z zakresu od 0 do 1
  • float sin(float)
Oblicza sinus podanego kąta.
  • float cos(float)
Oblicza cosinus podanego kąta.
  • float tan(float)
Oblicza tangens podanego kąta.
  • float asin(float)
Oblicza arcus sinus z zakresu od -pi/2 do pi/2.
  • float acos(float)
Oblicza arcus cosinus z zakresu od 0 do pi.
  • float atan(float)
Oblicza arcus tangens z zakresu od -pi/2 do pi/2.
  • float atan2(float x, float y)
Compute the argument (phase) of a rectangular coordinate in the range -p to p.
(!!!) – Hmm, ktoś wie co to może znaczyć?
  • float exp(float)
Compute the exponential function using the natural logarithm e as base
(!!!) – Rety, następny kwiatek... ln do potęgi ?
  • float log(float)
Oblicza logarytm naturalny.
  • float sinh(float)
Oblicza wartość sinusa hiperbolicznego.
  • float cosh(float)
Oblicza wartość cosinusa hiperbolicznego.
  • float tanh(float)
Oblicza wartość tangensa hiperbolicznego.
  • float asinh(float)
Oblicza wartość arcus sinusa hiperbolicznego.
  • float acosh(float)
Oblicza wartość arcus cosinusa hiperbolicznego.
  • float atanh(float)
Oblicza wartość arcus tangensa hiperbolicznego.
  • float abs(float)
Oblicza wartość absolutną argumentu (moduł).
  • float fact(float)
Oblicza silnię (funkcja gamma) podanego argumentu.
  • float sqrt(float)
Oblicza pierwiastek kwadratowy podanego argumentu.

Obsługa plików

 [save_object, restore_object, save_map, restore_map, write_bytes,

read_bytes, write_file, read_file, file_size, file_time, rename, rm, ed]

Przechowywanie informacji w plikach jest bardzo ważne. Zaraz pokaże ci kilka funkcji, które ci w tym pomogą. Pozwól mi jednak strzelić małe kazanie, na temat zużycia CPU:

Odczyt i zapis plików jest sprawą bardzo obciążającą CPU. Może nie w porównaniu do innych procesów, ale podczas operacji dyskowej CPU nie może robić nic innego. Innymi słowy, wczytywanie i zapisywanie dużych porcji danych znacznie spowalnia grę. Żeby ograniczyć zbyt duże obciążanie pamięci, dysku i CPU, narzucono limit nie pozwalający obsłużyć więcej niż 50 kb danych na raz. Pliki mogą być większe, ale ty nie możesz zapisywać ani wczytywać fragmentów większych niż ten limit. Oznacza to, że musisz podzielic pracę nad większymi plikami na kawałki wykonywane sekwencyjnie, z ewentualną przerwą pomiędzy nimi, żeby reszta gry też miała czas na zrobienie czegoś. Więc, proszę pamietaj o tym, że to ograniczenie nie zostało stworzone, by ci utrudnić życie i żeby było obchodzone różnymi krętymi sposobami, tylko aby ci przypominać, że obciążasz zasoby muda i że powinieneś innym też dać żyć. Amen.

Zacznijmy od bardzo prostej sprawy: nagrywania i odtwarzania obiektów. Zazwyczaj, chce się przechować zmienne globalne obiektu w pliku, po to by je później odtworzyć. Do tego celu służą efunkcje ‘save_object()’ oraz ‘restore_object()’. W obu musisz podać ścieżkę wraz z nazwą pliku jako argument i oczywiście mieć prawa do zapisu lub do odczytu. Wynikiem ‘save_object()’ będzie plik, którego nazwa bedzie miala końcówkę ‘.o’. Przy odtwarzaniu musisz pamiętać o tym, by ją podać. Przy nagrywaniu to nie jest konieczne – funkcja sama doda rozszerzenie ‘.o’, gdy ty o tym zapomnisz. ‘restore_object()’ zwraca 1, gdy odtwarzanie zakończyło się sukcesem, zaś w przeciwnym wypadku zwraca 0. Zawartością nagranego pliku jest lista wszystkich zmiennych globalnych z ich wartościami, oddzielonymi spacjami. Format zapisu przechowanej zmiennej jest taki sam, jak w przypadku już omówionej funkcji ‘val2str()’.

Mappingi są najbardziej wygodnym typem do przechowywania zmiennych. Wystarczy umieścić dane w mappingu, gdzie indeksami są nazwy zmiennych, a potem nagrać mapping przy pomocy efunkcji ‘save_map()’, by później odtworzyć go poprzez ‘restore_map()’. Przewaga tej metody nad ‘save/restore_object()’ jest to, że nie jesteś ograniczony wyłącznie do nie-statycznych zmiennych i możesz przechować co ci się żywnie podoba. Minusem jest to, że odtwarzanie danych z mappingu jest już ciut bardziej skomplikowane.

    void save_object(string sciezka_nagr);
    int restore_object(string sciezka_odcz);
    void save_map(mapping mapp, string sciezka_nagr);
    mapping restore_map(string sciezka_odcz);
    np.
        /*
         * Załóżmy, że mamy takie definicje zmiennych globalnych:
         *
         * string imie, *opis;
         * int flip;
         * mapping dane_map, smap;
         *
         * Załóżmy, że interesuje nas przechowanie zmiennych imię, opis, 
         *                                         flip i dane_map
         */
    
        // Ustawienie dziedzicznych przywilejów poprzez nadanie obiektowi 
        // euid tworcy pliku
        setuid();
        seteuid(getuid());
    
        // Metoda nagrywania 1
        save_object("mojplik");
        // Metoda odtwarzania 1
        if (restore_object("mojplik"))
            write("Tak!\n");
        else
            write("Nieeee..\n");
    
        // Metoda nagrywania 2
        smap = ([ "imie" : imie,
                  "opis" : opis,
                  "flip" : flip,
                  "dmap" : dane_map ]);
        save_map(smap, "mojplik");
    
        // Metoda odtwarzania 2
        smap = restore_map("mojplik");
        if (m_sizeof(smap))
        {
            imie = smap["imie"];        // Odtwarza imie
            opis = smap["opis"];        // Odtwarza opis
            flip = smap["flip"];        // Odtwarza flip
            dane_map = smap["dmap"];    // Odtwarza dane_map
            write("Tak!\n");
        }
        else
            write("Nieeee..\n");

Musisz zapamiętać, że format zapisu używany przez ‘save_object()’ i przez ‘save_map()’ jest taki sam, dzięki czemu mozna odtworzyć plik zapisany przez ‘save_object()’ przy pomocy ‘restore_map()’ i wybrac z otrzymanego mappinga, tylko te elementy, na których nam zależy. Załóżmy, że byś chciał odtworzyć tylko zmienną ‘opis’ w powyższym przykładzie. Nie martwił byś się wtedy innymi zmiennymi i użyłbyś wtedy okrojonej drugiej metody odtwarzania. Uważaj na odczytywanie danych, które zostały nagrane przez ‘save_map()’ instrukcja ‘restore_object()’. Żeby wszystko było poprawnie, indeksy nagranego mappingu musiały by mieć takie same nazwy, jak zadeklarowane w obiekcie zmienne globalne. W powyższym przykładzie są różnice pomiędzy nimi, więc odtworzenie metodą 1, danych zgranych metodą 2 skończyłoby się błędem. W odwrotnej sytuacji, czyli przy wczytywaniu metodą 2 danych przechowanych metodą 1 nie wyskoczy żaden błąd, ale nie uda sie odtworzyć zmiennej ‘dane_map’.

To były wszystkie metody przechowywania zmiennych w plikach. Nieraz może zajść potrzeba nagrania danych w wolnej formie, a nie tylko w zmiennych. Do tego celu służą efunkcje ‘write_bytes()’ i ‘read_bytes()’, albo ‘write_file()’ i ‘read_file()’. W zasadzie obie pary funkcji działają bardzo podobnie, tzn wczytują i nagrywają stringi o określonej długości. Jedyną różnicą jest to, że ‘write_bytes()’ może być użyte do nagrywania na stare dane w pliku (overwriting), podczas gdy ‘write_file()’ jest w stanie zapisywać tylko na końcu. No i ‘read_bytes()’ operuje na bajtach, a ‘read_file()’ na pełnych liniach. Obie zapisujące funkcje zwracają 1 w przypadku powodzenia, zaś 0 gdy coś się nie uda. Obie wczytujące funkcje zwracają string, zawierający odczytaną porcje danych w przypadku sukcesu, a 0 w przypadku niepowodzenia, więc sprawdzaj rezultat przy pomocy ‘stringp()’, żeby się upewnić, że wszystko było ok.

    int write_bytes(string ścieżka, int poz, string tekst);
    string read_bytes(string ścieżka, void|int poz, void|int num);
    int write_file(string ścieżka, string tekst);
    string read_file(string ścieżka, void|int poz, void|int num);

Z pliku również da się uzyskać informacje. W poznaniu rozmiaru pliku może dopomóc efunkcja ‘file_size()’. Można ją też wykorzystać do sprawdzenia obecności pliku. Jeśli liczba zwrócona przez funkcje jest dodatnia, to oznacza to, że jest to rozmiar. Gdy nie ma pliku, funkcja zwraca -1, a gdy plik jest katalogiem, -2. Aby poznać czas ostatniej modyfikacji można użyć efunkcji ‘file_time()’.

    int file_size(string ścieżka);
    np.
        void plik_info(string ścieżka)
        {
            int typ, tm;
    
            rozm = file_size(ścieżka);
            tm = file_time(ścieżka);
    
            write("Plik '" + ścieżka + "' ");
            switch (rozm)
            {
            case -1:
                write("nie istnieje.\n");
                break;
    
            case -2:
                write("jest katalogiem, ostatni raz zmodyfikowanym " +
                    ctime(tm) + ".\n");
                break;
    
            default:
                write("ma rozmiar " + rozmiar + " bajtów, ostatni raz " +
                    "był zmodyfikowany " + ctime(tm) + ".\n");
                break;
            }
        }

Jeśli chcesz zmienić nazwę albo przemieścić plik, to efunkcja ‘rename()’ jest w sam raz dla ciebie. Ale uważaj, gdyż jej działanie polega na skopiowaniu pliku, a potem skasowaniu starej wersji. Może być również użyta do przesunięcia katalogów. Jeśli życzysz sobie usunąć cały plik, to możesz skorzystać z efunkcji ‘rm()’. Zwraca ona 1 w przypadku sukcesu, a 0 w przypadku niepowodzenia. Uważaj, bo ‘rename()’ działa dokładnie odwrotnie, gdyż zwraca 1 w przypadku niepowodzenia, a 0 jak wszystko jest ok.

    int rename(string stara_sciezka, string nowa_sciezka);
    int rm(string ścieżka);
    np.
        if (rm("mojplik"))
            write("Ok, skasowane.\n");
        else
            write("Sorki, coś nie idzie.\n");
    
        if (rename("plik_stary", "plik_nowy"))
            write("Niestety, wciąż po staremu...\n");
        else
           write("Ok!\n");

Wewnętrzny edytor ‘ed’ jest efunkcją, która operuje na plikach. Możesz go uźywac do czegokolwiek, lecz pamiętaj, że większość ludzi nie wie jak z niego korzystać. Pamiętaj także, że efunkcja ‘ed()’ może być użyta do stworzenia nowego pliku, albo edycji starego, na podstawie praw obiektu, który ją wywołał. Jeśli podasz wskaźnik do funkcji, to przy wyjściu z ‘ed()’ zostanie ona wywołana. Jeśli nie podasz ścieżki, użytkownik będzie musiał sam wpisać ścieżkę i nazwę pliku wewnątrz edytora.

    void ed(void|string sciezka, void|function fun_wyjsciowa);

Obsługa katalogów

 [mkdir, rename, rmdir, get_dir]

Tworzenie, zmienianie nazwy i usuwanie katalogów robi się przy użyciu efunkcji (kolejno) ‘mkdir()’, ‘rename()’, ‘rmdir()’. Aby je wykonać musisz oczywiście mieć prawa do zapisu w katalogu, w którym to robisz. ‘mkdir()‘ i ‘rmdir()’ zwracają 1 w przypadku sukcesu, a 0 w razie niepowodzenia. ‘rename()’ jak już mówiłem działa odwrotnie i zwraca 1 w przypadku niepowodzenia. ‘rmdir()’ kasuje tylko puste katalogi, tzn takie, które nie zawierają żadnych plików ani katalogów.

    int mkdir(string ścieżka);
    int rename(string stara_sciezka, string nowa_sciezka);
    int rmdir(string ścieżka);

W celu pobrania zawartości katalogu używa się efunkcji ‘get_dir()’. Zwraca ona tablicę albo zawierającą wszystkie pliki w podanym katalogu w przypadku sukcesu, albo pustą w przypadku niepowodzenia.

    string *get_dir(string ścieżka);
    np.
        string *zawartosc_kat;
        int i, sz;
    
        zawartosc_kat = get_dir("/d/Domena/fatty");
    
        for (i = 0, sz = sizeof(zawartosc_kat) ; i < sz ; i++)
        {
            // Obejrzyj kod funkcji plik_info w poprzednim przykładzie
            file_info(zawartosc_kat[i]);
        }

Wejście/wyjście konsoli

 [write, write_socket, cat, tail, input_to]

Już zapewne jesteś za pan brat z efunkcją ‘write()’, która wyświetla dane na ekranie osoby zdefiniowanej jako słuchacz w danym obiekcie. Może nim być gracz, ale może również być nim jakiś inny obiekt. Zazwyczaj funkcja wystarcza, gdy masz pełną kontrole nad tym co i do kogo piszesz. Jednakże istnieje pewna podobna funkcja, która czasem staje się potrzebna. Nazywa się ona ‘write_socket()’. Działa ona prawie tak jak ‘write()’, tylko że wypisuje teksty wyłącznie na ekranie interakcyjnego użytkownika. Jeśli nie ma takowego, to zamiast do niego, pisze w głównym logu błędów. W czasie kodowania zwykłych obiektów nie będziesz musiał jej używać. Przeważnie, lub nawet zawsze jest wykorzystywana w konkretnych obiektach mudliba, częściowo tych, które mają do czynienia z logującymi się graczami.

Wypisywanie write'm jest niezłe, ale czasem możesz mieć chrapkę na błyskawiczne wyświetlenie plików, lub przynajmniej ich części. Do tego służy efunkcja ‘cat()’. Ukaże ona na ekranie określoną porcje podanego pliku na ekranie. Istnieje też inna efunkcja zwana ‘tail()’, która wyświetli ostatnie 1080 bajtów pliku. ‘cat()’ zwraca liczbę ukazanych linii, zaś ‘tail()’ zwraca 1 w przypadku sukcesu, a 0 w przypadku niepowodzenia.

    int cat(string ścieżka, int start, int dlug);
    int tail(string ścieżka);

np.

        // Wyświetla PLIKTEST, od 20 do 100 linii
        cat("PLIKTEST", 20, 80)
    
        // Wyświetla końcówkę tego samego pliku
        tail("PLIKTEST");

Większość rzeczy wpisywanych w grze przez graczy przychodzi w formie argumentów do funkcji. Czasem jednak potrzeba zapytać gracza o coś, a on musi odpowiedzieć. Pojawia się tu problem, gdyż wykonanie obiektów w gamedriverze jest sekwencyjne; jeśli się zatrzymasz z wykonywaniem czekając na odpowiedź, cała gra zatrzyma się wraz z tobą, do czasu aż leniwy gracz się namyśli (o ile kiedykolwiek) i odpowie. Z pewnością taka sytuacja nie jest dobrym rozwiązaniem. Zamiast tego, można wywołać efunkcje ‘input_to()’, która pozwala ci ustalić którą funkcja zostanie wywołana mając za argument jakakolwiek odpowiedź gracza, po ukończeniu wykonywania aktualnej funkcji. Brzmi to trochę skomplikowanie, ale naprawdę wcale takie nie jest. Spójrz tylko na przykład:

    void input_to(string funkcja, void|int bezecha, void|mixed arg);

np.

        string imię, płeć, zajęcie;
    
        pytajaca_fun()
        {
            write("Wpisz swoje imie: ");
            input_to("fun_2");
        }
    
        fun_2(string wpis)
        {
            imie = wpis;
            write("\nWpisz swoją płeć (Facet/Kobitka): ");
            input_to("fun_3");
        }
    
        fun_3(string wpis)
        {
            plec = wpis;
            write("\nCzym się zajmujesz?: ");
            input_to("fun_4");
        }
    
        fun_4(string wpis)
        {
            zajęcie = wpis;
            write("\n\nDzięki za współpracę!\n");
        }

Jeśli jako drugi argument do ‘input_to’ podasz 1, to cokolwiek gracz wpisze, nie zostanie wyświetlone na ekranie. Używa się tego przy hasłach i innych ważnych informacjach. Trzeci, opcjonalny argument jest przekazywany do funkcji, którą podałeś jako odbiorcę.

Niektóre komendy MUD-a

Może to wyglądać nieco dziwnie, że rozdział ten umieściłem tak nisko. Problem ten podobny jest do zagadnienia : co było pierwsze jajko czy kura. Używanie tych komend jest niemożliwe, zanim nie zrozumie się na czym to wszystko naprawdę polega. By to zrozumieć zawsze można empirycznie eksperymentować z komendami... Jeśli rozpocząłeś lekturę tego manualu przed eksperymentowaniem, to rozdział ten na pewno ci się przyda.

Prosiłbym przede wszystkim byś skupił się na swoich umiejętnościach edytorskich. Naucz się obsługiwać ed, który jest jedynym wewnątrzmudowym edytorem, lub używaj zewnętrznego edytora i pliki ślij przez ftp. Polecam edytor emacs i ftp. Pamiętaj, że pliki możesz tworzyć także pod Windowsem np. w Notatniku, ale zanim wyślesz pliki na serwer musisz je konwertować na format UNIXa.

Poniższe komendy są dobrze opisane w mudzie, po prostu wpisz ?<komenda>, by uzyskać pełen opis. To co ja napisałem to zwykłe podsumowanie i streszczenie tego.

 load: Ładowanie obiektu do pamięci
 clone: Ładowanie i klonowanie obiektu do gry 
 destruct: Niszczenie sklonowanego obiektu 
 update: Aktualizacja obiektu 
 odnow: Odnawianie obiektu

Kompilacja i ładowanie obiektów do pamięci gamedrivera

Komendą load nakazujesz gamedriverowi, by spróbował skompilować i załadować dany plik do pamięci. Komendy tej używa się zazwyczaj, by sprawdzić czy dany plik w ogóle się załaduje. Istnieją także obiekty, które nie są przeznaczone do klonowania (np. pokoje), a dzięki load udostępniamy je dla innych obiektów.

Jeżeli plik się nie załaduje, komunikat o błędzie ładuje w dwóch miejscach: głównym /lplog oraz w logu błędów należącym do domeny/czarodzieja, który stworzył tenże obiekt. Dla domen ścieżka do ostatniego ma kształt /d/<Domena>/log/errors, zaś dla czarodziei /d/<Domena>/<czarodziej>/log/errors.

Niestety komunikaty o błędach są nierzadko trudne do zrozumienia. Nie mogę zatem w tej materii służyć pomocą, bo po prostu jest ogromna ilość różnych typów takich komunikatów, zresztą zapewne z większością się spotkacie :) Wiele komunikatów łatwo jest zrozumieć, a z czasem każdy koder nauczy się rozpoznawać i zapobiegać tym ciekawszym...

Komenda load może obsługiwać także większą ilość plików jednocześnie w porcjach w odstępach czasowych. Ładowanie jednakże, to raczej obciążająca proces operacja, więc lepiej unikać masowego ładowania plików.

CIEKAWOSTKA:

Używając ls z opcją F (tzn. ls -F) widzisz w liście plików i katalogów , które zostały już załadowane do pamięci (są one oznaczone gwiazdką). Warto zatem używając komendy <alias ls ls -F> nadpisać tę komendę.

Kompilacja, ładowanie i klonowanie obiektów do gry

Jeżeli dany obiekt został załadowany do pamięci, można go sklonować. Zresztą jeśli potrzeba komenda clone ładuje i klonuje jednocześnie. Jeśli natkniesz się na problemy pamiętaj by sprawdzić logi błędów.

Niszczenie sklonowanego obiektu

Komendy destruct używamy, by usunąć sklonowany obiekt z pamięci. Komenda ta niszczy jedynie dany obiekt nic więcej.

Aktualizacja załadowanego obiektu

Komendy update używamy w przypadku gdy w pliku zaszlł zmiana i chcemy, aby gamedriver ponownie go skompilował.

Odnawianie obiektu

Warto także zwrócić uwagę na komendę odnów. Komenda ta łączy w sobie cztery inne – niszczy stary klon (destruct), przeładowuje obiekt na nową wersję (update, load) i klonuje z powrotem nową wersję obiektu (clone) w miejsce, gdzie był stary klon. W przypadku, gdy poda się ścieżkę zamiast nazwy obiektu, komenda przeładuje podany obiekt i sklonuje jego nową wersję.

Narzędzie Tracer

Wbudowany zestaw narzędzi czarodzieja, zwany Tracerem, może okazać się nieodłączny przy oddziaływaniu na wnętrze obiektu, który już załadowano do gry. Pozwala bowiem wywoływać przeróżne funkcje w dowolnym obiekcie, bez względu na to gdzie się znajduje, pozwala także na wyświetlenie inwentarza obiektu, a nawet przenoszenie obiektów z różnych miejsc w inne. Warto poznać ten zestaw narzędzi dogłębnie.

Niektórzy skarżą się, że Tracer jest dosyć ciężki w użyciu. Jest tak dlatego, bo nie chciało im się poczytać więcej o Tracerze w manualu gry. Wewnętrzny manual gry jest napisany trudnym językiem, więc postanowiłem bliżej zająć się Tracerem.

Warto zwrócić uwagę, że każda komenda z Tracera zaczyna się wielką literą. Służy to odróżnieniu od zwykłych komend, z których niektóre nazywają się tożsamo. Komendy Tracera są dostępne jedynie dla pełnych wizardów (Praktykant i dalej).

Na początek przyjrzyjmy się temu, jak tracer widzi obiekty. Podam jak odwoływać się do danego obiektu w danej sytuacji.

  • nazwa

W ten sposób można odwoływać się do obiektów znajdujących się w ekwipunku lub w twoim środowisku (np. na tej samej lokacji). Uwaga! Wiele obiektów może dzielić te same nazwy (np. ‘czlowiek’, ‘zbroja’, ‘miecz’). (użycie np. In człowiek ...)

  • "opis"

Tu używamy krótkiego opisu obiektu, znajdującego się w ekwipunku lub w tym samym środowisku (np. tej samej lokacji). Zazwyczaj podaje się dokładniejszy opis, nie tylko sama nazwę. (użycie np. Destruct "wysoki elf")

  • ścieżka

Tu podajemy pełną ścieżkę Jeżeli chcesz operować na określonym pojedynczym obiekcie, dodajesz numer klonowania. Np. sama ścieżka ~Ansalon/guild/society/obj/nametag, albo ~Ansalon/guild/society/obj/nametag#22144 dla określonego obiektu.

  • @nazwa

W tym przypadku operujemy na istocie żywej (potworze NPC), która znajduje się gdziekolwiek w grze. Zauważmy, że tracer znajdzie nazwę ustawioną poprzez wywołanie efunkcji set_living_name() (opisaną wcześniej w manualu), a nie przez nazwę określoną przez set_name(), czy tez ustaw_nazwe(), ustaw_imie(), itp. Istota żywa (gracz) jest automatycznie dodawana do listy livingów.

  • *nazwa

This specifies the name of a player, and nothing else.

  • here

W tym przypadku działamy na środowisku, w którym się znaleźliśmy, np. na pokoju gdzie stoimy.

  • me

Tutaj natomiast na samym sobie, swoim obiekcie gracza.

  • #numer

Tutaj określamy numer obiektu. Jeżeli np. wiesz, że miecz, który masz przy sobie jest trzecim obiektem, wówczas odwołanie ma kształt #3. Należy pamiętać, że kolejność obiektów może zmienić się bez ostrzeżenia, wówczas obiekt #3 będzie zupełnie innym niż chcieliśmy. Zamiast tego należy używać zmiennych tracera (opisanych przy komendzie Set), by problemu uniknąć..

  • $zmienna

Określa zawartość zmiennej tracera (opisane przy komendzie Set). Obiekty często istnieją w różnych rodzajach środowisk. Czasem w tym samym miejscu co inne, czasem wewnątrz innych, itp. W celu określenia relacji typu „drugi obiekt wewnątrz misia w tym samym pokoju, w którym stoję” podaje się listę specyfikacji obiektów rozdzielona dwukropkiem (:). Środowisko danego obiektu jest wówczas określone przez :^.

Przykładowo, poprzedni opis o misiu wyglądałby tak:^mis:#2. To naprawdę nie jest aż tak skomplikowane jak wygląda.

Inny przykład: „miecz w torbie trzymanej przez Adama”: *adam:torba:miecz.

At: Wykonanie polecenia w środowisku gracza
Call: Wywołanie funkcji obiektu/na obiekcie
Cat: Podgląd pliku związanego z danym obiektem 
Clean: Niszczenie wszystkich nie żywych przedmiotów w obiekcie 
Destruct: Niszczenie określonego obiektu
Dump: Wyświetlenie przeróżnych informacji o obiekcie
Ed: Edycja pliku związanego z obiektem
Goto: Wejście do środowiska obiektu 
In: Wykonanie komendy w innym obiekcie
Inventory: Wyświetlenie inwentarza obiektu
Items: Przeglądanie itemów zawartych w danym obiekcie/środowisku
Light: Wyświetlenie listy obiektów danego środowiska
More: Przeglądanie pliku związanego z obiektem w porcjach
Move: Przemieszczenie obiektu do miejsca docelowego
Set: Ustawienie zmiennej tracera
Tail: Przeglądanie porcjami pliku związanego z obiektem, od końca

Wykonywanie komend w środowisku gracza

Komenda ta tak naprawdę przenosi wywołującego do środowiska w którym przebywa dany gracz w celu wykonania danej komendy. Przeniesienie to odbywa się w sposób bezwzględny, tzn. żadne sprawdzenia możliwości przeniesienia nie są sprawdzane.

Składnia: At <imie_gracza_w_mianowniku> <polecenie>

Zazwyczaj istnieją lepsze sposoby do osiągnięcia podobnego celu, jednak dla pewnych kwestii komenda At okazuje się nieodzowna. Dobrym na to przykładem jest sytuacja, gdy chcemy spojrzeć na pokój, w którym znajduje się dany gracz, np. Adam. W tym celu wykonujemy:

At adam sp

UWAGA! Warto zachować ostrożność przy wykonywaniu tego wobec śmiertelników, istnieją sytuacje, w których wywołujący może okazać się widoczny dla gracza. Warto przeto użyć ‘invis’ nim wywołamy podobną komendę.

Wywoływanie funkcji na obiekcie

Komenda Call jest szczególnie użyteczna. Dzięki niej możesz wywoływać funkcję na obiekcie podając dowolne parametry [w praktyce jednak nie każdy typ argumentu da się tą drogą bezproblemowo przekazać].

Składnia: Call <dany_obiekt> arg1%%arg2%%...

Przykładowo, aby ustawić własność OBJ_I_VOLUME na 55 w drugim obiekcie z ekwipunku Adama wykonujemy:

Call *adam:#2 add_prop _obj_i_volume%%55

Warto zauważyć, że odwołano się bezpośrednio do stringu "_obj_i_volume", albowiem jest on dopiero na poziomie <stdproperties.h> definiowany jako zamiennik dla OBJ_I_VOLUME, ale tego Call niestety nie uwzględnia.

Przeglądanie zawartości pliku związanego z danym obiektem

Komenda Cat, wspólnie z More jest dosyć ciekawą i często użyteczną komendą. Zwraca ona źródło pliku związanego z danym obiektem, o ile mamy prawa odczytu dla niego. Oczywiście Cat zwraca jedynie 100 pierwszych linijek kodu, by przejrzeć całość należy użyć stronnicowania [zobacz komendę More].

Składnia: Cat <dany_obiekt>

Zniszczenie wszystkich nieinteraktywnych zawartych w obiekcie docelowym

Clear jest użyteczną komendą dla czyszczenia zawartości danego obiektu. Często używa się jej, gdy np. mamy dużo obiektów w danej lokacji i chcemy szybkim ruchem je usunąć albo gdy nierozważny czarodziej sklonuje obiekt, którego działania nie do końca przewidział i przykładowo sparaliżuje go on, wówczas dowolny inny czarodziej może go szybko uratować używając tej komendy ;)

Składnia: Clean <dany_obiekt>

UWAGA! Wszystkie nieinteraktywne obiekty, jak np przedmioty i npce, ulegną zniszczeniu.

Zniszczenie określonego obiektu

W celu zniszczenia konkretnego obiektu, używamy komendy Destruct. Czasami np. gdy funkcja leave_inv() obiektu zawiera wadliwy kod, dany obiekt może nie ulec zniszczeniu od razu. Należy wtedy użyć opcji -D, czyli np. Destruct -D miecz, by wymusić usunięcie danego obiektu.

Składnia: Destruct [-D] <dany_obiekt>

UWAGA! Należy unikać używania opcji -D i stosować ją wyłącznie wtedy, gdy jest to konieczne. Zależnie od wersji mudliba może występować opcja -f, działając tożsamo jak -D.

Pobieranie szczegółowych informacji o danym obiekcie

Komendy Dump używamy by pobrać specyficzne informacje z danego obiektu. Wybór jest dosyć duży.

Składnia: Dump <dany_obiekt> <jakie_informacje_chcemy>

Możliwe informacje:

  • <nic>

Komenda Dump <dany_obiekt> pobiera nazwę, ścieżkę, uid twórcy oraz euid docelowego obiektu.

  • <zmienna>

O ile zmienna nie jest równoznaczna z jednym z poniżej opisanych parametrów, to jest interpretowana jako zmienna w badanym obiekcie. Komenda zwraca nam wtedy wartość tejże zmiennej.

UWAGA! Jest to naprawdę użyteczny sposób użycia tej komendy. Przydać się może przy różnego rodzaju debugowaniu kodu. Nie trzeba bowiem ingerować w kod obiektu by dowiedzieć się jaką wartość ma zmienna w danym obiekcie. Oczywiście wciąż sporo jest przypadków, w których to nie wystarczy i trzeba użyć własnych funkcji debugu.

  • alarms
Zwraca wszystkie bieżące alarmy „biegajace po” danym obiekcie.
  • cpu
Parametr ten zwraca ile czasu procesora zostało zużyte przez badany obiekt. Działa wyłącznie gdy włączono PROFILE_OBJS w config.h drivera. Opcji tej używa się generalnie przy rozwijaniu mudliba lub tez badaniu jego wydajności.
  • flags
Zwraca wszystkie pochodzące z drivera flagi związane z badanym obiektem, wraz z innymi informacjami o stanie obiektu. Informacje te są użyteczne jedynie dla osób pracujących nad udoskonaleniem drivera.
  • functions
Zwraca wszystkie funkcje zdefiniowane w danym obiekcie.
UWAGA! Zwraca jedynie funkcje z obiektu położonego najwyżej w hierarchii dziedziczenia. Tzn. nie podaje funkcji, które obiekt nabył z innego dziedziczonego obiektu.
  • info
Parametr ten zwraca pewne podstawowe związane z driverem informacje przyporządkowane do danego obiektu. Ponownie, informacje te są użyteczne jedynie dla osób pracujących nad driverem.
  • inherits
Użyteczny parametr zwracający listę obiektów, po których dany obiekt dziedziczy.
  • inv | inventory
Zwraca inwentarz/ekwipunek/zawartość obiektu badanego wraz z numerami odpowiadającymi zawartym tam obiektom, których można użyć przy dalszych wywołaniach, np. funkcji na mieczu, który jest w ekwipunku gracza X. [patrz: opis odwołań do obiektów w 2.5]

W niektórych wersjach mudliba występuje jako osobna komenda tracera Inventory [I]

  • items
Zwraca listę wszystkich pseudo-przedmiotów, np. takich, które jedynie dodają opis, lub komendy, które dodano do badanego obiektu.
  • light
Ten parametr zwraca listę stanów światła dla danego obiektu i informuje o efektach dla obiektów w nim zawartych. Poda zatem obecny stan oświetlenia i czy obiekt generuje lub pobiera światło.
  • profile
Działa wyłącznie gdy skompilowany driver ma włączoną flagę PROFILE_FUNS. Zwraca wszystkie zapisane informacje profilowania powiązanych z danym obiektem. Używane głównie przy pracy nad optymalizacja i rozszerzeniem drivera.
  • props | properties
Zwraca wszystkie własności [propy] danego obiektu.
  • shadows
Zwraca wszystkie shadowy powiązane z danym obiektem.
  • vars | variables
Zwraca listę wszystkich zmiennych danego obiektu.
  • wizinfo
Zwraca specjalna informacje zapisano w obiekcie poprzez własność OBJ_S_WIZINFO. Głównie zawiera ważne informacje dla innych czarodziei, jak zajmować się danym obiektem, czego z nim robić się nie powinno, itd.

UWAGA! To czy taka informacja znajdzie się w obiekcie zależy wyłącznie od kreatora tego obiektu i jego zastosowania.

Ed(ycja) pliku powiązanego z obiektem

Komenda Ed uruchamia (dość toporny) edytor ed z otwartym plikiem, do którego odwołuje się badany obiekt.

Składnia: Ed <dany_obiekt>

Przenoszenie istoty żywej/obiektu do danego środowiska

Komenda Move pozwala na przeniesienie dowolnego obiektu do innego określonego środowiska. W przypadku przenoszenia istoty żywej użyte zostanie odwołanie do funkcji move_living() z flagą teleportacji, w przypadku niepowodzenia przy przenoszeniu istoty żywej podejmowana jest próba przez odwołanie do funkcji move(). Dla obiektów zawsze używana jest funkcja move(). Gdy użyjemy opcji -f, wymuszającej przeniesienie, wówczas zawsze wykonywane jest move( ,1).

Składnia: Move [-f] <dany_obiekt> [<obiekt_docelowy>]

/ w [] podano parametry opcjonalne /

Wykonywanie komendy wewnątrz innego obiektu

Komenda In działa podobnie do komendy At, z tą różnicą, że jej działanie odbywa się w inwentarzu dowolnego obiektu, gdzie to wykonujący jest na chwile przenoszony. Stosować z rozwagą i wyłącznie gdy to konieczne. Należy zwrócić uwagę, że większość obiektów nie jest przeznaczonych do przyjmowania obiektów gracza wewnątrz siebie.

Składnia: In <dany_obiekt> <komenda>

More – przeglądanie pliku powiązanego z obiektem

More to bardzo użyteczna komenda, stanowiąca swoiste rozszerzenie Cat w tryb stronnicowany. Zezwala na wygodne przejrzenie kodu danego obiektu, który dzielony jest w zależności od potrzeb, na kilka stron.

Składnia: More <dany obiekt>

Set – ustawianie zmiennej narzędziowej tracera

W tym miejscu odwołam się do tego co napisano wcześniej. Jednym z największych niebezpieczeństw pojawiających się przy stosowaniu narzędzi tracera jest zmiana liczby porządkującej, określającej pozycje obiektu na którym działamy, w inwentarzu jego środowiska.

Składnia: Set $<zmienna> <dany_obiekt>

Załóżmy, by ułatwić omówienie problemu, że chcemy odnowić stworzony przez nas miecz, znajdujący się w ekwipunku gracza Adama. Przyjmijmy, że miecz ten zawierał w poprzedniej wersji istotne wady i chcemy go zastąpić nową wersją, ale tak by Adam w ogóle się nie spostrzegł. Aby znaleźć dokładną liczbę określającą pozycje miecza w ekwipunku Adama używamy Dump *adam inv [lub I *adam], niech będzie to liczba 5. Następnie wykonujemy komendę Destruct *adam:#5. W sytuacji gdy Adam w międzyczasie odłożył miecz, lub np. schował go do plecaka, i zamiast miecza zniszczyliśmy jego cenne 3000 mithrylowych monet! To na pewno by mu się nie spodobało :)

Aby uniknąć takich problemów należy zastosować następującą czynność. Pobieramy odwołanie do obiektu, jak w przykładzie powyżej i zapisujemy to w zmiennej. W przykładzie mamy: miecz posiada w chwili obecnej numer 5 w ekwipunku Adama, wykonujemy przeto Set $miecz *adam:#5. Po tym jak nastąpią przetasowania w ekwipunku Adama tracer poinformuje nas, że zmienna sword wskazuje teraz na stos mithrylowych monet. Załóżmy, ze miecz spadł na pozycje 4. Ustawiamy Set $miecz *adam:#4 i na powrót zmienna wskazuje właściwy obiekt. Wykonujemy Destruct $miecz i miecza już nie ma. Prawda, ze proste? Teraz wystarczy go podmienić nowym.

Operacje odnowienia tego miecza, dałoby się wykonać na wiele innych, łatwiejszych i na pewno mniej ryzykownych sposobów, powyższy przykład miał za zadanie jedynie zilustrować mechanizm działania komendy Set.

Można ustawić dowolną liczbę zmiennych, należy jednak pamiętać, że po wylogowaniu się przepadną one. Podobnie, gdy obiekt ulegnie zniszczeniu, ten sam los spotka wskazującą nań zmienną.

Set bez żadnych argumentów zwróci nam pełną listę ustawionych przez nas zmiennych.

Ciekawostką jest fakt, że ostatni obiekt na którym wywołano komendę tracera zostaje przypisany pod zmienną $. Czyli po wywołaniu na obiekcie jakiejś funkcji tracera, następną można wywołać już na zmiennej $ (<Komenda_tracera> $ <opcje>), a zostanie ona wywołana na tym samym obiekcie.

Aktualizacja, przeładowanie, sklonowanie i zastąpienie danego obiektu nową wersją

Komenda Replace może okazać się bardzo użyteczna. [Nie została uwzględniona wśród narzędzi tracera w mudlibie CDpl.01.00, porownaj ?odnow]

Przy jej użyciu możesz zaktualizować obiekt nadrzędny danego obiektu, ponownie sklonować nową wersję i nadpisać ją na starą, która ulegnie degradacji.

Jeśli nowa wersja nie da się przenieść w sposób bezpośredni, kolejna próba przeniesienia następuje na zasadzie wymuszenia.

Jeśli wystąpi błąd krytyczny podczas odnawiania, proces zostanie przerwany, a stara wersja nie zostanie nadpisana.

Składnia: Reload <dany obiekt>

Tail – przeglądanie powiązanego z obiektem pliku od końca

Komenda Tail działa podobnie jak Cat i More, z tą różnicą, że wyświetla ostatnie linijki kodu.

Zaawansowany LPC i Mudlib

W rozdziale tym zajmiemy się nieco bardziej zaawansowanymi sprawami dotyczącymi LPC. Musisz naprawdę zrozumieć podstawy LPC, które wyłożyłem w poprzednich rozdziałach, żeby móc przyswoić sobie to, co tu opisuje.

Nie miej żadnych oporów przed przeglądaniem poprzednich rozdziałów w razie potrzeby, czytając ten tekst.

Funkcja jako typ danych, część 3

Typ funkcyjny (function) jako taki nie był dotychczas szczegółowo omawiany w tym podręczniku, choć czasami używany. Jednak, ze względu na jego wagę należy go omówić. Dokładna znajomość jego własności pozwoli ci stworzy często bardzo skomplikowane i złożone, lecz jednocześnie efektywne i optymalne partie kodu. Warto wiedzieć, że wszystkie funkcje które zwykły przyjmować nazwy funkcji jako stringi, w chwili obecnej są zdolne do pobrania wskaźnika do danej funkcji jako argumentu, co jest efektywniejsze.

Podstawowe informacje o typie funkcyjnym

Dostęp do funkcji można uzyskać przez wskaźniki funkcji. Jak pokazano wcześniej, każde wywołanie funkcji sprowadza się do wskaźnika funkcji powiązanego z lista argumentów. Rozpatrzmy to na prostym przykładzie.

void moja_funkcja(string str, int value) { write("Jako string podano: '" + str + "', zaś jako liczbę: " + value + ".\n"); return; }

Wywołanie funkcji następuje przez odwołanie się do niej za pośrednictwem jej nazwy i następujących po niej argumentów podanych w nawiasie.

   Przykładowo:
   moja_funkcja("smurf", 1000);

Teraz rozszerzymy to zgodnie z pomysłem typu funkcyjnego, gdzie można pobrać adres funkcji i następnie przypisać innej zmiennej.

Przykładowo:

   function nowa_funkcja;
   nowa_funkcja = moja_funkcja;       // lub równoważnie
   nowa_funkcja = &moja_funkcja();
   moja_funkcja("smurf", 1000);   // lub równoważnie
   nowa_funkcja("smurf", 1000);

Zwróćmy uwagę, że nim zmiennej nowa_funkcja przypisano poprawna wartość, nie odwoływała się ona do żadnej funkcji. Użycie jej w takim przypadku doprowadziło by do błędu run-time.

Okrojona lista argumentów

W poprzednim rozdziale mogliśmy zaobserwować, że możliwe jest przypisanie odwołania do jednej funkcji zmiennej o innej nazwie. Innym przydatnym zastosowaniem może okazać się możliwość takiego przypisania, że niektóre argumenty dla oryginalnej funkcji jest traktowana jako stała, pozostawiając resztę jako zmienne. Zilustrujemy to przykładem:

   void
   tell_player(object player, string mess)
   {
       player->catch_msg(mess + "\n");
   }
   void
   moja_funkcja()
   {
       function mod_tell;
       mod_tell = &tell_player(this_player(), );
       mod_tell("Witaj!"); // Równoważne względem
       tell_player(this_player(), "Witaj!");
   }

Działa to dobrze, bez względu na ilość argumentów. Pozostałe argumenty zostaną wypełnione od lewej do prawej.

Przykładowo biorąc funkcję o nagłówku void func(int a, int b, int c, int d, int e) przy zmiennej określonej następująco function moja_funkcja = func(1, , 3, 4,); otrzymamy, że wywołanie moja_funkcja(100, 101); działa tak samo jak wywołanie func(1, 100, 3, 4, 101);

Złożenia funkcji i kompleksowe struktury

W tym miejscu ważne jest, by czytelnik poruszał się zręcznie w płaszczyznach problemów omówionych w poprzednich rozdziałach. Pojawia się pytanie: ‘Co można umieścić w wywołanie funkcji tego typu?’.

Odpowiedz brzmi: ‘Prawie wszystko’.

Jest to oczywiście zależne od umiejętności własnych i gibkości poruszania się w ramach kodu.

Wiele osób miewa problemy ze zrozumieniem operatorów. Wśród nich mamy: +, -, *, /, %, &, |, ^, >>, <<, <, >, <=, >=, ==, !=, [].

Dosyć często programista chce wykonać jedną operację i jej wynik przesłać bezpośrednio jako argument dla innej.

Typ funkcyjny obsługuje superpozycję [złożenie] funkcji poprzez operator @.

Działa on jak matematyczne złożenie, od prawej do lewej! Podobnie jak operator.

Oto przykład, który, miejmy nadzieje, rozjaśni wszystko:

Niech pewna tablica zawiera imiona pewnych graczy:

                 string *arr = ({ "Galarel", "Adren", "Kael", "Aendill", "Thorin", "Hanor" });

Przyjmijmy, że chcemy docelowo pobrać z tablicy imiona, które składają się z więcej niż pięciu liter. Można to oczywiście zrobić za pomocą pętli, jak poniżej:


               string *
               func(string *arr)
               {
                   int i, sz;
                   string *result = ({});
                   for (i = 0, sz = len(arr); i < sz; i++)
                   {
                       if (strlen(arr[i]) > 5)
                       result += ({ arr[i] })
                   }
               return result;
               }


Oczywiście, można użyć efunkcji filter:

               int
               filterfunc(string item)
               {
                   return strlen(item);
               }
               
               string *
               func(string *arr)
               {
                   return filter(arr, filter_func);
               }

Ale w takim przypadku muszę pisać osobną funkcje filtrującą. Na szczęście można pójść jeszcze dalej, do efektownego zapisu jednolinijkowego !

               // ... tu mamy zdefiniowana tablice arr
               
               result = filter(arr, &operator(<)(5) @ strlen);

UWAGA! Niezmiernie ważne jest tu wywołanie operator(). Jak powiedzieliśmy przebiega ono od prawej do lewej, przeto dochodzi do momentu, gdy program ma do czynienia z następującym porównaniem: 5 < strlen(name).

Teraz nieco skomplikujemy sytuacje. Chcemy pójść dalej i pobrać z tablicy wyłącznie Śmiertelników [których imiona składają się z co najwyżej pięciu znaków.]

Oczywiście jest to proste i wygląda następująco:

               // ... tu mamy zdefiniowana tablice arr
               result = filter(filter(arr, &operator(<)(5) @ strlen), 
                       &operator(==)(0) @ SECURITY->query_wiz_rank);

Tutaj rozszerzono po prostu wywołanie o kolejne złożenie, przy wcześniejszym pobraniu osób o imionach o długości do 5 znaków.

Pójdźmy dalej. Jak myślisz co się stanie gdy wykonasz następujące polecenie? Co pojawi się na ekranie?

               exec return implode(sort_array(map(filter(users(), sizeof @ 
                           &filter(, &call_other(, "id", "tarcza")) @ 
                           deep_inventory)->query_real_name(), capitalize)), ", ");

Na pewno większości czytelników wydaje się to prościutkie.

Opisze, co się tu działo. Po pierwsze, przebiegamy przez wszystkich graczy [w tym czarodziejów] i przeszukujemy całe ich ekwipunki. Filtruje osoby, które posiadają tarczę i pobieram ich imiona. Tablice wyników sortuje, przekształcam by były napisane z wielkiej litery i tworze z nich jeden string z przecinkiem (,) po każdym imieniu. W rezultacie na ekranie pojawia się ten właśnie string.

Innymi słowy: otrzymuje spis osób, które mają przy sobie tarcze.

UWAGA! Czas na przestrogę. Oczywiście gładko otrzymałem listę osób, mających w ekwipunku tarcze (nawet jeśli jest w plecaku). Jednak zwróćmy uwagę, że przy dużej liczbie graczy liczba obiektów, które maja przy sobie osiąga wielkie pułapy, może nawet kilka tysięcy! I teraz na każdym z nich wywołujemy funkcje sprawdzania czy to tarcza. Jak widać cala operacja jest niezwykle pamięciożerna, na szczęście potrzeba przeprowadzenia podobnej operacji rzadko się zdarza.

Chodzi głównie o to by już na etapie pisania kodu przewidywać jakie będą konsekwencje jego użycia.

3.2 Pisanie efektywnego kodu

Ten temat jest blisko powiązany z tym co powiedziałem wcześniej, że nie będę wyjaśniać jak programować. Wycofam swe słowa – troszeczkę – i opowiem o kilku sprawach co robić, albo nawet co ważniejsze, czego nie robić.

Efektywne pętle

Ten temat może się wydawać raczej trywialny. W końcu na ile sposobów można zapisać pętle? Raczej na niewiele. Zacznijmy od najczęstszego błędu. Załóżmy, że mamy jakąś dużą tablice, nazwijmy ją ‘wielka_tab’ i załóżmy, że chcemy ‘przelecieć’ pętlą przez wszystkie jej elementy. Co robisz w takiej sytuacji? „Proste!” wyjaśniasz, „Oczywiście, że zwykła pętla ‘for’!”. Pewnie... Ale jak to zapiszesz? Najczęściej ludzie robią to w ten sposób:

        int i;
    
        for (i = 0 ; i < sizeof(wielka_tab) ; i++)
        {
            // Tu robimy coś z tablicą
        }

No tak... a więc co jest źle? Jeśli przejrzysz rozdział mówiący o działaniu instrukcji ‘for’, to zobaczysz, że wykonywane są trzy części w okrągłych nawiasach oddzielone średnikami. Pierwsza tylko na początku, druga (środkowa) za każdym cyklem pętli i trzecia również za każdym razem na końcu każdego cyklu.

Oznacza to, że funkcja ‘sizeof()’ zostaje wykonana za każdym cyklem pętli. Jest to raczej marnotrawstwo, biorąc pod uwagę fakt, że tablice rzadko zmieniają rozmiar. Jeśli by robiły to często, byłaby to inna para kaloszy, ale jako że nie robią... Nie. Napisz to w ten sposób:

        int i, sz;
    
        for (i = 0, sz = sizeof(wielka_tab) ; i < sz ; i++)
        {
            // Tu robimy coś z tablicą.
        }

Widzisz? Zmienne ‘i’ oraz ‘sz’ tylko na początku działania pętli mają przypisywane wartości. Licznik ‘i’ zostaje ustawiony na 0, a zmiennej ‘sz’ zostaje przypisany rozmiar tablicy. Przez cały czas działania pętli porównywane są same zmienne, zamiast ciągłego obliczania nie zmieniającego się rozmiaru tablicy.

Możesz mi wierzyć albo nie, ale to jest bardzo częsty błąd, prawie każdy go robi. Oszczędności w moim sposobie mogą się nie wydawać tak wielkie... ale... pomnóż to razy wszystkie pętle w mudzie i przez liczbę razy kiedy są wykonywane, a otrzymasz całkiem niezłą liczbę. Kosztem drugiego sposobu w porównaniu do pierwszego jest dodanie jednej zmiennej lokalnej i to jest wystarczająco mała cena.

Pamiętaj o tym problemie nie tylko w przypadku tablic, ale również w mappingach lub innych ogólnych pojemnikach, zawierających rzeczy, przez które chcesz ‘przelecieć’ pętlą. Rozwiązanie, z wyjątkiem małych różnic w rozpoznawaniu rozmiaru pojemnika jest zawsze takie same.

Słów kilka o makrodefinicjach

Często popełnianym błędem jest umieszczanie DUŻYCH tablic i/lub mappingów jako makrodefinicji. Wyobraźmy sobie jeden wielki mapping zawierający definicje rang gildiowych, opisy, przeróżne limity umiejętności możliwych tamże do wytrenowania, modyfikatory, etc, gdzie ranga gildiowa spełnia funkcje indeksu. Bardzo często trzeba się będzie do niego odwoływać, przez centralny obiekt administracyjny gildii, etc. Mamy coś takiego:

       // Górna partia pliku
       
       #define GUILD_MAP ([ 0: ({ "początkujący", "trochę", 3, 2343, ... }), \
                    1: ({ ....                                          \
                    ... /* i dalej około 20 lub więcej podobnych linijek */        \
                  ])
       
       // kodowy przykład użycia
       write("Masz obecnie range: " + GUILD_MAP[rank][1] + "\n");
       // dalszy kod

Spójrz na to czytelniku uważnie. Przypomnę w tym miejscu co dają makrodefinicje: otóż działają następująco, ilekroć podczas wykonywania programu system napotka na wzór podany przy makrodefinicji (tu: GUILD_MAP) zamienia go przez rozwinięcie podane w makrodefinicji (tu: cały ten mapping). Krótko: ilekroć użyjemy odwołania do GUILD_MAP wstawiany jest calutki mapping. Za każdym razem gdy go wstawiamy driver musi ponownie interpretować jego zawartość, sortować i indeksować. Jest to zatem strasznie nieoptymalne, pamięciożerne i czasochłonne.

Zamiast makrodefinicji o wiele efektywniej można to zrobić tak, używając zmiennej:

       // Górna partia pliku
       
       mapping GuildMap;
       
       create_object()
       {
           // kod
       
           GuildMap =  ([ 0: ({ "początkujący", "trochę", 3, 2343, ... }), \
                    1: ({ ....                                          \
                    ... /* i dalej około 20 lub więcej podobnych linijek */        \
                  ]);
       }
       
       // kodowy przykład użycia
       write("Masz obecnie rangę: " + GuildMap[rank][1] + "\n");
       // dalszy kod...

Pułapki i niuanse

Przy kodowaniu nie trudno o pomyłkę. Często kod wygląda na nasze oko ładnie, ale tak naprawdę jest szalenie nieefektywny. W tym rozdziale ponownie zajmiemy się często popełnianymi błędami.

Wiele z tych błędów już opisano we wcześniejszych rozdziałach, ale nie zaszkodzi powtórzyć.

Mappingi/Tablice – bezpieczeństwo

Problem jak wspomniano w rozdziale 2.2.9 polega na tym, że mappingi i tablice nie są kopiowane, za każdym razem gdy są przenoszone. Operacje odbywają się jedynie na wskaźniku. Może to być podstawa do wielu niebezpiecznych luk w kodzie.

Przyjrzyjmy się przykładowi obiektu gildiowego, który zajmuje się członkami gildii. Globalna zmienna Rada, która jest zapisywana przez save_object() zawiera radę gildii.

       string *Rada;
       
       public string
       zwroc_rade()
       {
           return Rada;
       }

Wygląda to na prawidłowe, lecz tak naprawdę zwraca jedynie wskaźnik do oryginalnej tablicy. Jeśli ktoś inny zechce dodać członka do rady gildii może to zrobić! wystarczy, taka sztuczka:

       void
       moja_funkcja()
       {
           string *rada_po_moich_poprawkach;
       
           rada_po_moich_poprawkach = OBIEKT_GILDIOWY->zwroc_rade();
       
           rada_po_moich_poprawkach += ({ "olorin" }); // I dodaje Olorina do rady gildii i każdy
       			// może to zrobić!
       }

Jak to zatem poprawić? Wystarczy zmodyfikować funkcję zwroc_rade, by zwracała return Rada + ({}); i wszystko będzie w porządku. Łatwo przegapić, a jakież to ważne!

Zapętlanie alarmów

Przyjrzyjmy się następującej funkcji:

       public void
       moje_wlasne_alarmy(int ile)
       {
           set_alarm(1.0, 1.0, moje_wlasne_alarmy(ile + 1));
           tell_object(find_player("<twoje_imie>"), "Buu! " + ile + "\n");
       }

Co będzie efektem wywołania tej funkcji? Otóż, co sekundę będzie generowany nowy alarm, wywołujący siebie samego co sekundę. Co to oznacza? Spójrzmy na opis efektów:

       1 sekunda:
           Buu! 0 (oryginalne wywołanie)
       2 sekunda:
           Buu! 1 (powtórzenie 1 sek 0)
           Buu! 1 (nowe od 1 sek 0)
       3 sekunda:
           Buu! 1 (powtórzenie 1 sek 0)
           Buu! 2 (powtórzenie 2 sek 1)
           Buu! 2 (powtórzenie 2 sek 1)
           Buu! 2 (nowe od 2 sek 1)
           Buu! 2 (nowe od 2 sek 1)
       4 sekunda:
           Buu! 1 (powtórzenie 1 sek 0)
           Buu! 2 (nowe od 3 sek 1)
           Buu! 2 (powtórzenie 2 sek 1)
           Buu! 2 (powtórzenie 2 sek 1)
           Buu! 3 (powtórzenie 3 sek 2)
           Buu! 3 (powtórzenie 3 sek 2)
           Buu! 3 (powtórzenie 3 sek 2)
           Buu! 3 (powtórzenie 3 sek 2)
           Buu! 3 (nowe od 3 sek 2)
           Buu! 3 (nowe od 3 sek 2)
           Buu! 3 (nowe od 3 sek 2)
           Buu! 3 (nowe od 3 sek 2)
       
          ... itd.

Jak widać przyrost jest tutaj lawinowy, co w krótkim czasie doprowadzi do padu gry. Taka praktyka jest tak głupia że konsekwencje dla czarodzieja, który by coś takiego lub podobnego zrobił byłyby bardzo ostre. Oczywiście na obecnym etapie rozwoju większość LPMudów jest zabezpieczona przed użyciem partykularnie tożsamej konstrukcji.

Korzystanie z wewnętrznej dokumentacji

Na zakończenie słów kilka o wewnętrznej dokumentacji muda, gdzie znaleźć można zazwyczaj mnóstwo przydatnych rzeczy [zależy to głównie od administratorów muda i osób zajmujących się dokumentacją ;)]

Efunkcje, sfunkcje i lfuns opisano w manualach dostępnych pod komendą ‘man’. Można oczywiście wyszukiwać za pomocą odpowiedniej opcji interesujące nas funkcje. Ponadto w kilku rozdziałach opisano różne przydatne rzeczy. [szczegóły ?man]

Przykładowo chcemy pobrać user id obiektu, ale nie pamiętamy dokładnie jaka funkcja jest za to odpowiedzialna. Pamiętamy jednak, że jej nazwa kończyła się literami ‘id’!

       > man -k *id
       
       --- simul_efun:
       export_uid  geteuid     getuid      seteuid     setuid

Widzimy zatem, że wyszukiwarka znalazła pięć funkcji odpowiadających podanemu kryterium. Wyświetlono wszystkie pasujące wyniki, po przeszukaniu wszystkich stron man.

Wiemy już, że chodziło o getuid. By poczytać o niej wpisujemy man getuid (lub nawet man simul_efun getuid – efekt będzie w tym przypadku taki sam).

Doświadczeni programiści LPC znają na pamięć powiązania funkcji i wiedza co dziedziczyć by uzyskać do pożądany efekt. Wie także mniej więcej jakie funkcje zawarte są w danym obiekcie, oczywiście nie zna wszystkich. Każdy koder przeto powinien często korzystać z komendy ‘sman’. Stanowi on kolekcje nagłówków funkcji zawartych w kodzie z /cmd, /lib, /obj, /secure, /std i /sys.

Także tutaj możliwe jest przeszukiwanie smanu wg podanych kryteriów [szczegóły ?sman].

Przykładowo, chcemy znaleźć funkcję która zwraca nazwę gildii zawodowej do której dany gracz należy. Nie wiemy jednak jaki obiekt definiuje ja ani jak się ta funkcja zwie.

       > sman -k *guild*
       --- /lib/guild_support:
       create_guild_support     init_guild_support
       
       --- /secure/master:
       add_guild_master                      query_guild_type
       guild_command                         query_guild_type_int
       guild_filter_type                     query_guild_type_long_string
       guild_sort_styles                     query_guild_type_string
       load_guild_defaults                   query_guilds
       query_guild_domain                    remove_guild_master
       query_guild_is_master                 set_guild_domain
       query_guild_long_name                 set_guild_long_name
       query_guild_masters                   set_guild_phase
       query_guild_phase                     set_guild_style
       query_guild_short_name                set_guild_type
       query_guild_style                     
       
       --- /std/guild/guild_base:
       list_major_guilds                     query_guild_not_allow_join_guild
       query_guild_keep_player               query_guild_skill_name
       query_guild_leader                    query_guild_style
       query_guild_member                    
       
       --- /std/guild/guild_lay_sh:
       query_guild_incognito_lay             query_guild_tax_lay
       query_guild_leader_lay                query_guild_title_lay
       query_guild_member_lay                query_guild_trainer_lay
       query_guild_name_lay                  query_guild_type
       query_guild_not_allow_join_lay        remove_guild_lay
       query_guild_style_lay                 
       
       --- /std/guild/guild_occ_sh:
       query_guild_incognito_occ             query_guild_tax_occ
       query_guild_leader_occ                query_guild_title_occ
       query_guild_member_occ                query_guild_trainer_occ
       query_guild_name_occ                  query_guild_type
       query_guild_not_allow_join_occ        remove_guild_occ
       query_guild_style_occ                 
       
       --- /std/guild/guild_race_sh:
       query_guild_family_name               query_guild_style_race
       query_guild_incognito_race            query_guild_tax_race
       query_guild_leader_race               query_guild_title_race
       query_guild_member_race               query_guild_trainer_race
       query_guild_name_race                 query_guild_type
       query_guild_not_allow_join_race       remove_guild_race
       
       --- /std/living:
       clear_guild_stat         set_guild_pref
       query_guild_pref_total   set_guild_stat

W gąszczu funkcji odnajdujemy tę jedną, a szczegóły odczytamy używając komendy sman /std/guild/guild_occ_sh query_guild_name_occ.

Z czasem lepiej będziesz orientować się w Mudlibie i łatwiej będzie ci tak formułować zapytanie, żeby otrzymać wystarczająco zawężoną odpowiedź za pierwszym razem. Nie żeby było coś złego w parokrotnym przeszukiwaniu, od tego w końcu jest sman!

Koniec części głównej Manualu LPC ...
Uzupełnienie by Kael

DODATEK A. Formanty odmiany polskiej

na podstawie /sys/pl.h 1) odmiana przez przypadki

PL_MIA  0 - mianownik
PL_DOP  1 - dopełniacz
PL_CEL  2 - celownik
PL_BIE  3 - biernik
PL_NAR  4 - nadrzędnik
PL_MIE  5 - miejscownik

np. chcąc poznać imię gracza w bierniku stosujemy

 this_player()->query_name(PL_BIE);

lub

 this_player()->query_name(3);

2) rodzaje gramatyczne

PL_MESKI_OS             0 - wskazuje na odmianę męską osobową obiektu (osoba tu oznacza istotę żywą myślącą, np. elf, ogr ale nie kot) 
PL_MESKI_NOS_ZYW        1 - wskazuje na odmianę nieosobową dla istoty żywej (np. kota)
PL_MESKI_NOS_NZYW       2 - wskazuje na odmianę nieosobową dla przedmiotu (np. długopis)
PL_MESKI_NZYW           2 - to samo co PL_MSESKI_NOS_NZYW 
PL_ZENSKI               3 - wskazuje na odmianę żeńską
PL_NIJAKI_OS            4 - wskazuje na odmianę nijaką osobową
PL_NIJAKI_NOS           5 - wskazuje na odmianę nijaką nieosobową

DODATEK B. Rodzaje obrażeń

W_IMPALE    rany kłute
W_SLASH     rany cięte
W_BLUDGEON  obuchowe
W_NO_DT     brak obrażeń
MAGIC_DT    obrażenia magiczne