Kapitel 2: Das LPC-Programm
2.1 Programme
Ich gebe zu, dass der Titel dieses Kapitels etwas egalgenau bezeichnet wurde.
Denn es gibt eigentlich keine LPC-Programme, da der LPC-Programmierer
OBJEKTE schreibt. Was ist der Unterschied?
Der Unterschied ist der, wie die Datei ausgefuehrt wird.
Wenn ein Programm ausgefuehrt wird, beginnt die Ausfuehrung an einem
speziell fuer dieses Programm definierten Ort im Programm.
Oder anders ausgedrueckt: Jedes Programm hat einen definierten Punkt, wo
die Ausfuehrung beginnt.
Zusaetzlich gibt es einen Punkt, an dem die Ausfuehrung abbricht.
Also laeuft ein Programm von einem definierten Startpunkt bis zu einem
definierten Endpunkt. Bei OBJEKTEN ist dies voellig anders.
LPC-Objekte sind lediglich Teile des Drivers, die in einer Bibliothek (library),
der MUDLIB abgelegt sind, und bei Bedarf von DIESEM gestartet werden.
Dieser kleine Unterschied sollte nie vergessen werden, da er fuer das
Verstaendnis von verschiedenen Dingen sehr hilfreich ist.
Denn es gibt in einigen Objekten sogenannte hooks (Haken), die waehrend der
Laufzeit des entsprechenden Objektes Anfragen an den DRIVER stellen.
Damit haben LPC-Objekte weder einen definierten Anfangs- noch einen
Endpunkt.
Im Gegenteil, manchmal werden nur Teile der Objekte benoetigt, die dann
direkt angesprochen werden, was in anderen Programmiersprachen oft nicht
so einfach moeglich ist.
Jedoch tut der DRIVER relativ wenig fuer die Spielwelt, wie Du sie kennst.
Stattdessen ruft er LPC-Objekte auf, die diese Aufgaben fuer ihn uebernehmen.
Wie in anderen Programmiersprachen, kann ein LPC-"Programm" aus einem oder
mehreren Dateien bestehen.
Soll ein LPC-Objekt ausgefuehrt werden, reicht es, es in den Speicher zu laden.
Beim Laden in den Speicher wird das Objekt aus der Datei compiliert.
Der DRIVER ruft dann Stueck fuer Stueck, was immer er benoetigt, ab.
Dazu muessen die Objekte in der Struktur, die in diesem Handbuch erklaert
werden, vorliegen.
Das Allerwichtigste, was Du bis hierher verstanden haben musst, ist, dass es
kein "Anfang" und kein "Ende" bei der Ausfuehrung eines LPC-Objektes gibt.
2.2 Driver-mudlib-Interaktion
Wie Du weisst (gehe ich von aus, hab ich ja bereits erklaert!), ist der
DRIVER ein C-Programm, das auf dem MUD-Rechner laeuft. Es verbindet Dich in
das Spiel und fuehrt den LPC-Code aus.
Niemals sollte man vergessen, dass das eine der Theorien der MUD-Programmierung
sind und nicht unbedingt der Weisheit letzter Schluss sein muss.
Es koennte das gesamte MUD in C programmiert sein.
Das wuerde das Spiel um Einiges schneller machen, jedoch wuerde es um Einiges
unflexibler werden, da die Programmierer es nicht waehrend der Laufzeit
erweitern koennten.
Das wird in den sogenannten Diku-MUDs so gehandhabt.
Auch die meisten sogenannten MMORPG (Massive Multiplayer Online Role Playing
Game, also eine Art MUD mit Grafik fuer Leute, die den Sinn von MUDs nicht
verstehen) folgen diesem nicht sehr cleveren und eher ineffizienten Ansatz,
da jede kleine Debugging-Aktion dazu fuehrt, dass das MUD nicht erreichbar ist.
Im Gegensatz dazu haben die LP-MUDS (die sich aus genau diesem Grund so weit
verbreitet haben) die Theorie, dass der Driver sollte nicht die Natur des
Spieles definieren, sondern das Spiel sollte von den Individuen, die es
benutzen, modifiziert werden koennen. Damit kannst Du es erweitern, waehrend
es laeuft. Das ist der Grund, warum LP-MUDs die Programmiersprache LPC kennen.
Das ermoeglicht es, dass man die Natur und die Grundlagen des MUD fuer den
Driver selbst zu entwickeln, und damit Einfluss auf das Spielgeschehen zu
nehmen. Der Driver liest und fuehrt die LPC-Objekte bei Bedarf aus.
LPC ist eine Sprache, die sehr viel einfacher zu verstehen ist, als dies zB
bei C der Fall ist, was die Weltengestaltung einer groesseren Gruppe von
Leuten eroeffnet.
Sobald Du eine LPC-Datei (ich gehe von korrektem LPC aus) geschrieben hast,
liegt sie harmlos auf der Festplatte des MUD-Rechners herum, bis etwas im
MUD auf diese Datei verweist. (sie referenziert)
Wenn etwas im Spiel auf die Datei verweist, wird eine Kopie in den
Arbeitsspeicher geladen und eine spezielle Funktion in diesem Objekt wird
aufgerufen, um diese Datei zu initialisieren, also alle Variablen und Werte
in diesem Objekt auf einen definierten Ausgangszustand zu setzen.
Was eine Funktion oder eine Variable ist, wird spaeter erklaert, da eine
Erklaerung an dieser Stelle zu weit fuehren wuerde, und nicht zum Thema
passt und damit wahrscheinlich verwirren wuerde.
Das, worauf es derzeit ankommt, ist, dass eine Kopie der Datei in den
Arbeitsspeicher ueberfuehrt wird. So kann es jedoch passieren, wenn dieses
Objekt mehrfach referenziert wird (ich sage nur: Waffen), dass mehrere
genau gleiche Objekte im Speicher stehen.
Da diese Aktion sehr viel Speicher benoetigt, und ein Spieler sich einen
Spass machen koennte und Objekte, die unlimitiert erschaffbar sind
(als typisches Beispiel sind Anfaengerzettel zu nennen) um damit den
Arbeitsspeicher zu ueberfluten und damit das MUD im schlimmsten Falle zum
Absturz zu bekommen, gibt es die Moeglichkeit, (auch dazu spaeter mehr)
Objekte nur virtuell in den Speicher zu laden, wenn bereits dieses Objekt
im Speicher vorhanden ist.
Technisch sieht das so aus, dass das neue Objekt auf das bereits im Speicher
stehende Objekt verweist.
Dieser kurze Ausflug sollte nur dazu dienen, Deine Befuerchtungen in diese
Richtung zu zerstreuen.
2.3 Laden eines Objektes in den Arbeitsspeicher
Da es keinen definierten Anfangspunkt zum Ausfuehren eines Objektes gibt, muss
der Driver ein Einsprungpunkt existieren, an dem er das Objekt initialisieren
kann. Diese Funktion ist in LP-MUDs, die im native-Mode laufen, die
Funktion create().
Damit entspricht diese Funktion der Funktion main() in C.
Auf die Unterschiede werden wir spaeter eingehen.
LPC-Objekte bestehen aus Variablen (wie in der Mathematik sind es Platzhalter,
deren Werte aenderbar sind) und Funktionen, die diese Variablen manipulieren
(aendern) koennen.
Funktionen manipulieren Variablen durch die Nutzung der LPC-Grammatik,
welche das Aufrufen von anderen Funktionen, extern definierte Funktionen,
(sogenannte efuns, im Gegensatz zu lfuns (lokal definierten Funktionen))
grundlegenden LPC Ausdruecken (Befehlen) und Laufzeitkontrollmechanismen
(die die Ausfuehrung des Objektes ueberwachen).
Das klingt verwirrend? Fangen wir mit Variablen an. Eine Variable ist durch
einen anderbaren Wert gekennzeichnet. Ein Beispiel dafuer waeren zB die
Lebenspunkte. (das originale Beispiel Level von Descartes ist etwas unpassend,
da sich ein Level seltener aendert) Der Inhalt der Variable (naemlich der Wert,
der die Lebenspunkte darstellt, und damit verliert der Tod viel seines
Schreckens, denn Tod bedeutet nun nur noch, dass eben der Wert der Variable
Lebenspunkte kleiner war als die offiziell festgelegte Untergrenze,
im Tamedhon <0, unterschritten hat) Bestimmte Dinge koennen nach dem Wert
fragen, um bestimmte Aktionen auszuloesen.
Das ist zum Einen: Ist der Wert der Lebenspunkte unter 0? Dann ruf die Funktion
auf, die den Spieler sterben laesst, mit allen Konsequenzen, die sich daraus
ergeben. Das sind in dem speziellen Fall: Die Variable Erfahrungspunkte wird
modifiziert (der Spieler verliert Erfahrungspunkte), das Objekt des Spielers
wird in den Totenraum bewegt, waehrend an dem Ort, wo er stirbt, ein
Leichenobjekt erstellt, die Besitztuemer des Spielers vom Spielerobjekt in
das Leichenobjekt transferiert (uebertragen, bewegt) und die Todessequenz
wird gestartet.
Gleichzeitig werden in allen anderen Objekten Funktionen, die auf den Tod
reagieren, aufgerufen.
Der NPC, der den Spieler getoetet hat, bekommt Erfahrungspunkte, bricht den
Kampf mit dem Spieler ab.
Das wird alles in einer speziellen Funktion ausgefuehrt.
Da kann man auch noch hineinschreiben, dass der NPC die Leiche pluendern kann
oder aehnliche Sachen.
Die andere Grenze wird aehnlich gehandhabt.
Ist der Wert der Lebenspunkte gleich oder hoeher der Obergrenze, die in der
Variable maximalelebenspunkte festgelegt ist, werden sie nicht mehr
regelmaessig erhoeht und auf diese Obergrenze gesetzt.
So wird mit jeder Variablen verfahren.
Aufgabe: Nenne mindestens fuenf weitere Variablen, die ein Spielerobjekt
haben kann.
Variablen muessen nicht nur mit ganzzahligen Werten (sogenannten Integerwerten,
also Werten, die keine Kommastelle haben) gefuellt sein.
Dort koennen auch Strings (also Zeichenketten, Beispiel waere der Titel)
oder Fliesskommazahlen (letzlich Zahlen mit Kommastelle, wie eine
Fliesskommazahl (typ float) berechnet wird, wuerde zu weit fuehren), aber auch
voellig andere Sachen stehen. Zumeist werden Variablen in LPC jedoch mit
int (integer, also Ganzzahlen), object (Objekten, zB welche Waffe fuehrt der
Spieler) oder string (Zeichenketten) gefuellt.
Eine spezielle Form von Variablen sind Arrays.
Arrays sind letzlich Variablen, die aus mehreren Speicherplaetzen bestehen.
Jeder Speicherplatz eines Arrays kann mit einem anderen Wert gefuellt werden.
So ist ein typisches Beispiel fuer Arrays die Ausruestung des Spielers, wo
in jedem Speicherplatz des Arrays ein anderes Objekt abgelegt werden kann.
Auch kann jeder Speicherplatz des Arrays aus einem weiteren Array bestehen.
Jedoch kann in jedem Speicherplatz eines Arrays nur ein Wert abgelegt werden,
der denselben Typ hat wie der Wert in den anderen Speicherplaetzen.
Das ist ein Nachteil eines Arrays, aber wenn man das weiss, kann man
potentielle Fehler sehr einfach umschiffen.
Jeder Speicherplatz eines Arrays ist nummeriert, beginnend mit 0.
Die letzte wichtige Art von Variablen ist das Mapping.
Ein Mapping ist eine Unterart von Arrays, wo in jedem Speicherplatz ein
anderer Wert liegt, der unterschiedlichen Datentypen zugeordnet sein kann.
So liegt in einem Speicherplatz ein ganzzahliger Wert, in einem anderen
Speicherplatz wurde ein Objekt abgelegt.
Damit die Speicherplaetze wiedergefunden werden, ist auch in einem Mapping
jeder Speicherplatz nummeriert, beginnend mit 0.
Jedoch ist auch zusaetzlich ein alternativer Name fuer den Speicherplatz
vergebbar, was bei einem Array nicht moeglich ist.
Immer, wenn ein Objekt in LPC von einem anderen Objekt referenziert wird und
in den Speicher geladen wurde, schaut der Driver nach, welche Variablen dieses
Objekt hat. (Diese Variablen haben zu diesem Zeitpunkt noch keinen Wert
zugewiesen bekommen, doch 0, das ist der Unterschied zwischen LPC und C,
in LPC werden die Speicherplaetze zu allererst mit 0 belegt, bei C werden sie
nur zugewiesen) Wenn das erledigt ist, ruft der Driver die Funktion
create() auf, die die Variablen initialisiert, also sie auf die
Grundwerte setzt (dasselbe passiert mit Arrays und Mappings, die ja
ebenfalls Variablen (wenn auch spezielle) darstellen).
Das passiert durch CALLS (Aufrufe) der Funktionen.
So werden auch Variablen manipuliert.
Allerdings ist create() nicht der Beginn eines LPC-Objektes, sondern es
ist der Punkt, wo die Ausfuehrung beginnt. Praktisch werden diese Funktionen
nicht benoetigt. Wenn das Objekt gut funktioniert, wenn alle Variablen auf
0 gesetzt sind, benoetigst Du keine create()-Funktion.
Dann startet die Ausfuehrung des LPC-Objektes etwas anders.
Jedoch darfst Du eins nie vergessen: Wenn Dein Objekt ein anderes Objekt
beerbt, also dessen Funktionalitaet besitzt, hast Du dort eine
create()-Funktion, die durch den Driver dann anstelle Deiner eigenen
Funktion aufgerufen wird.
Kommen wir nun zu dem Punkt, weshalb wir diesen gesamten Kram gerade
durchgekaut haben:
Aus was besteht ein komplettes LPC-Objekt?
Nun, ein LPC-Objekt besteht grundsaetzlich aus einer oder mehreren
Funktionen, die untereinander geschrieben wurden, und 0 oder mehrere
Variablen manipulieren.
Dabei ist die Reihenfolge der Funktionen voellig irrelevant.
-----
void init() { add_action("smile", "smile"); }
void create() { return; }
int smile(string str) { return 0; }
-----
is exactly the same as:
-----
void create() { return; }
int smile(string str) { return 0; }
void init() { add_action("smile", "smile"); }
_____
Auch ein Objekt, dass nur so aussieht, ist legal:
void nonsense() {}
Das letzte Objekt ist jedoch unbrauchbar, da es, wenn nicht gerade die
Funktion nonsense() direkt aus einem anderen Objekt referenziert wird,
sich nicht in das MUD einfuegt.
Es ist unsichtbar, kann nicht angesprochen werden etc.
Zu Funktionen im Allgemeinen und Speziellen siehe Kapitel 4.
2.4 Zusammenfassung
LPC Code hat keinen Anfangs- und keinen Endpunkt, da LPC zur Erstellung von
Objekten dient und nicht zur Erstellung von individuellen Programmen.
LPC-Objekte beinhalten eine oder mehrere Funktionen, deren Anordnung irrelevant
ist, genau wie sie auch keine oder mehr Variablen beinhalten koennen.
Die Werte der Variablen werden durch die Funktionen manipuliert.
LPC-Objekte existieren erst einmal unbeachtet auf der Festplatte des
MUD-Rechners bis sie referenziert werden. Wenn ein Objekt referenziert wird,
wird es in den Arbeitsspeicher ueberfuehrt und die Variablen initialisiert.
Andere Funktionen in dem Objekt werden vom Driver oder anderen Objekten
aufgerufen und erlauben die Interaktion zwischen Objekten und die
Manipulation von Variablen.