Richtlinien - Räume
Richtlinien zur Programmierung von Raeumen
==========================================
1. Was sind Raeume?
-------------------
Raeume sind im Grunde nichts anderes als Objekte, die direkt
oder indirekt von std/room inherited werden. Im allgemeinen
existieren Raeume nur als Blueprint (das gesamte Verhalten des
Spielerobjekts bzw. von std/room ist darauf ausgerichtet), da
es wenig Sinn macht, von ein und demselben Raum verschiedene
Instanzen zu erzeugen; eine ganz bestimmte Lichtung existiert
eben nur einmal im gesamten Spiel. (Sollte man dennoch mehrere
Instanzen benoetigen, kann man dies wohl am besten mit dem
Virtual Compiler erledigen oder den Raum einfach kopieren.)
Spieler bewegen sich innerhalb des Spiels so gut wie immer in
Raeumen fort (Ausnahme: Transporter, die ia. auch nur als
Blueprint (im Gegensatz zu Clones) existieren).
Raeume koennen sehr unterschiedliche Grade an Komplexitaet
erreichen. Einfache Raeume bestehen gerade mal aus der Funktion
create(), in der die verschiedenen Eigenschaften des Raumes, wie
Beschreibung, Details, Gerueche, Geraeusche, Ausgaenge etc. fest-
gelegt werden. Komplizierte Raeume haben abhaengig von verschie-
denen Hebeln oder anderen Faktoren verschiedene Details, Kurz-
oder Langbeschreibungen, etc.
2. Welche Eigenschaften sollte ein Raum unbedingt definieren?
-------------------------------------------------------------
Folgende Properties sollten in create() unbedingt gesetzt werden:
P_INT_SHORT Kurzbeschreibung (ohne Punkt und \n !!),
Dieser Text wird angezeigt, wenn ein Spieler
im "Kurzmodus" ('kurz') durch die Gegend rennt.
Beispiel: "Auf einer Wiese"
P_INT_LONG Langbeschreibung des Raumes. Dieser Text be-
schreibt den Raum moeglichst stimmig, 4 Zeilen
sollte die untere Grenze hierfuer darstellen.
Die Langbeschreibung wird beim Setzen automatisch
auf 78 Zeichen pro Zeile umgebrochen, hierbei
bleiben vorhandene \n erhalten. Man kann das
automatische Umbrechen mit einem \t (Tabulator)
als allererstes Zeichen unterdruecken, sollte
dies unbedingt notwendig sein.
P_LIGHT Ein Zahlenwert, der angibt, ob es in einem
Raum hell (1) oder dunkel (0) ist. Soll es
in einem Raum besonders hell oder besonders
dunkel sein, kommen auch weitere Werte in
Betracht, etwa benoetigt man bei Raeumen mit
Lichtlevel -1 zwei Lichtquellen, um etwas
sehen zu koennen.
P_INDOORS Ein Zahlenwert, der definiert, ob ein Raum
draussen (0, default) oder drinnen (1) ist.
Zwerge idlen in geschlossenen Raeumen etwas
schneller und im Freien etwas langsamer hoch,
bei Elfen verhaelt es sich genau umgekehrt.
Zaubersprueche, Gegenstaende und MNPCs (moving
NPCs) beruecksichtigen dies.
P_ENVIRONMENT Ein Mapping mit verschiedenen Werten, die die
Umgebung beschreiben sollen. Der wichtigste
Schluessel hierbei ist ENV_TEMPERATURE, ueber
den die Temperatur festgelegt wird (Default:
15 Grad); Spieler fuehlen sich in einem
bestimmten Temperaturbereich wohl (derzeit
10 bis 30, Hobbits: 8 bis 28 Grad), ausserhalb
desselben erleiden sie aehnliche Auswirkungen
wie bei Vergiftungen (Frieren/Schwitzen).
Weitere Schluessel in diesem Mapping:
ENV_FLAGS: EF_INDOORS (wird beim Setzen von
P_INDOORS automatisch gesetzt).
ENV_WATER: Wassertiefe (wenn ein Raum unter
Wasser ist).
ENV_TERRAIN: Landschaftstyp, moeglich ist:
TT_OTHER, TT_CAVE, TT_HOUSE, TT_HILL, TT_FOREST,
TT_TOWN, TT_MOUNTAIN, TT_DESERT, TT_JUNGLE,
TT_SWAMP, TT_PLAIN, TT_ARCTIC, TT_UNDERWATER, TT_ROAD.
Dies ist sehr wichtig in Bezug auf Bewegungspunkte
sowie fuer einige Gilden und sollte immer richtig
angegeben sein.
ENV_USER: benutzerdefinierte Infos.
3. Details
----------
Ein Raum sollte auch ausreichend detailliert sein. Was heisst
ausreichend? Naja, es sollten saemtliche wichtigen Details
abgedeckt werden, wenn etwa in der Langbeschreibung ein Tisch
erwaehnt wird, sollte dieser auch als Detail beschrieben werden,
ebenso wichtige Details in Details, es sollte jedoch nicht ins
krampfhafte Detaillieren jedes Hauptwortes ausarten, das in
Details verwendet wird (etwa wenn es bei einem Detail heisst:
"...kommt Dir unbekannt vor, vielleicht solltest Du ein Buch
zu Rate ziehen?", so sollte das Detail "Buch" zwar zu finden
sein, auf das Detail "Rate" kann man dann aber verzichten :)).
Fuer AddDetail() gilt dasselbe wie fuer das Setzen von P_INT_LONG,
der Text wird beim Setzen automatisch auf 78 Zeichen pro Zeile
umgebrochen, hierbei bleiben vorhandene \n erhalten. Man kann das
automatische Umbrechen mit einem \t (Tabulator) als allererstes
Zeichen unterdruecken.
Details koennen desweiteren auch rassenabhaengig sein; dazu muss
der Text auf ein Mapping gesetzt werden, als Keys werden hierbei
die Rassennamen in Kleinbuchstaben verwendet, 0 wird als Default
Text verwendet, falls die Rasse des Spielers vom Mapping nicht
abgedeckt wird, und sollte immer vorhanden sein.
Beispiel: ([ 0: "Du musst Dich buecken, um da reinzupassen.",
"zwerg": "Da kannst Du aufrecht durchgehen."])
Details, die man durch Lesen erkennen sollte (Schrifttafeln,
Inschriften, etc.), werden gesondert definiert und durch
"lies xy" ausgegeben.
Spezielle (zB. variable) Details (sog. special details) lassen
sich auch programmieren, werden sie vom Spieler untersucht, so
wird die angegebene Funktion aufgerufen und deren Rueckgabewert
(sollte ein String sein) an den Spieler ausgegeben.
Die entsprechenden Befehle sind AddDetail() bzw. fuer spezielle
Details AddSpecialDetail() sowie AddReadDetail() fuer lesbare
Details (fuer Einzelheiten dazu siehe entsprechende Hilfeseiten).
4. Sounds, Smells
-----------------
Eine weitere Moeglichkeit, einen Raum zu beschreiben, besteht
darin, ihm Gerueche und/oder Geraeusche mitzugeben. Einerseits
kann man den allgemeinen Geruch/Geraeusch (per DEFAULT_SMELL
bzw. DEFAULT_SOUND als Schluessel) angeben, der beim blossen
Tippen von "rieche" bzw. "lausche" angezeigt wird. Andererseits
ist es moeglich, bestimmte Gerueche/Geraeusche naeher zu
beschreiben, wie dies ja auch bei Details moeglich ist.
Die entsprechenden Befehle sind AddSmell() bzw. AddSound().
AddSpecialSmell() oder Sound gibt es nicht, aber durch eine
Closure kann man dennoch variable Gerueche/Geraeusche ver-
wirklichen (Bsp.: AddSmell ("herd", #'geruch); wobei geruch()
einen String zurueckgeben muss und vorher definiert oder
deklariert sein muss).
5. Ausgaenge, Tueren
--------------------
Normale Ausgaenge wie norden, sueden etc. fuehren in der Regel
von einem Raum zum naechsten und bilden so ganze Gegenden. Sie
werden mit AddExit() hinzugefuegt, in der Regel in create().
Einzelheiten dazu sind in der Hilfeseite zu AddExit zu finden.
Die Ausgaenge sollten sich zumindest im allgemeinen logisch
ergaenzen, das heisst man sollte sich nach dem Abschreiten von
norden-osten-sueden-westen wieder im Ausgangsraum befinden.
Sollen beim Durchschreiten eines Ausgangs bestimmte Eigenschaften
des Spielers oder aber des Raumes ueberprueft, Aktionen ausgeloest
oder sonst etwas besonderes gemacht werden, so kann man einen
sog. Special Exit angeben. Beim Durchschreiten desselben wird der
Spieler nicht in einen anderen Raum bewegt (bzw. bewegt sich),
vielmehr wird die angegebene Funktion im Raum aufgerufen, die
dann auch fuer das eigentliche Bewegen des Spielers (per move())
in einen anderen Raum zustaendig ist.
Interessant im Zusammenhang mit Nicht-Standard-Ausgaengen wie
z.B. "links" ist auch P_EXIT_PREPOSITION, setzt man diese
Property z.B. auf "nach", dann kommt beim Durchschreiten des
Ausgangs "links" statt "XY geht links." der Text "XY geht
nach links." (fuer die Ausgaenge norden/osten/... wird hierbei
automatisch "nach Sueden" etc. verwendet, ebenso fuer oben/unten).
Soll ein NPC einen bestimmten Ausgang blockieren, so ist dies
mit einer etwas umfangreicheren Notation von AddExit() moeglich,
ebenso wie einige andere Features wie Testfunktionen, Bewegungs-
meldung, Durchschreite-Meldung, etc. - ist ein Guard-NPC fuer
den Ausgang eingetragen und ein Objekt (muss theoretisch auch
noch nicht einmal ein NPC sein) mit der entsprechenden ID im
Raum anwesend, so werden Spieler und Magier mit "mschau aus"
am Durchschreiten gehindert. Fuer Einzelheiten lies Dir bitte
die Hilfeseite zu AddExit sorgfaeltig durch.
Eine weitere Moeglichkeit, Ausgaenge zu schaffen, besteht darin,
eine Tuer zu definieren. Die entsprechenden Moeglichkeiten, die
NewDoor() bietet, sind aeusserst zahlreich, man kann etwa Tueren
verschliessbar machen (der entsprechende Schluessel muss dann
eine entsprechende Funktion QueryDoorKey() definieren - ein
beliebter Fehler hier ist, zu vergessen, dass diese Funktion die
verbundenen Raeume in ALPHABETISCHER REIHENFOLGE zurueckgeben
muss), dafuer sorgen, dass sie sich beim Reset automatisch
schliessen oder oeffnen, Funktionen definieren, die vor dem
erfolgreichen Oeffnen oder Schliessen aufgerufen werden etc.
Fuer Details siehe die Hilfeseite zu NewDoor.
All diese Ausgaenge (incl. geoeffneter Tueren) werden automatisch
angezeigt (Text "Es gibt xy sichtbare Ausgaenge: x, y, ..."). Um
einzelne Ausgaenge von dieser Anzeige auszunehmen, gibt es die
Property P_HIDE_EXITS. Diese kann man auf ein Array setzen, das
die Ausgaenge enthaelt, die NICHT angezeigt werden sollen. Setzt
man diese Property auf einen Integerwert ungleich 0, so wird die
Anzeige der sichtbaren Ausgaenge KOMPLETT unterdrueckt.
6. Kommandos
------------
Kommandos im Raum werden durch ein bestimmtes VERB und dessen
PARAMETER bestimmt. Das Verb dient dabei als ausloesendes Kommando,
das eine Funktion aufruft, die wiederum dafuer verantwortlich ist,
den Parameter auf Gueltigkeit zu ueberpruefen. Definiert wird ein
Kommando mit AddCmd(x,y), wobei x das ausloesende Verb (String) oder
die ausloesenden Verben (String-Array) und y die aufzurufende
Funktion ist. AddCmd() Aufrufe stehen normalerweise in create().
Beispiel:
AddCmd ( ({"trink", "trinke"}), "wassertrinken" );
Bei "trink" oder "trinke" wird die Funktion wassertrinken()
aufgerufen, die fuer den weiteren Ablauf zustaendig ist.
Die aufgerufene Funktion bekommt die Parameter, die der Spieler nach
dem Verb mit angegeben hat, als String uebergeben (oder aber 0, wenn
der Spieler ausser dem Verb nichts weiter getippt hat!!) und muss im
Erfolgsfall 1 und sonst 0 zurueckgeben. Mit notify_fail() kann man
unter Umstaenden eine hilfreiche Fehlermeldung fuer den Spieler
definieren, die im Misserfolgsfall (also return 0) und soferne kein
anderes Objekt die entsprechende Syntax abfaengt (und seinerseits
return 1 macht) an den Spieler ausgegeben wird.
Beispiel:
int wassertrinken (string str) // fuer "trink wasser"
{
notify_fail ("WAS willst Du trinken?\n"); // Default-Fehlermeldung
if (!str) return 0; // Spieler hat nur "trink" getippt, das ist zuwenig!
notify_fail ("Das kannst Du nicht trinken.\n"); // nun andere Fehlermeldung
if (str != "wasser") return 0; // Misserfolg, diesmal mit neuer Fehlerm.
// ansonsten Erfolg
write ("Du trinkst etwas von dem erfrischenden Wasser.\n");
say (capitalize(this_player()->name())+" trinkt etwas Wasser.\n");
return 1; // war erfolgreich
}
Sehr vorsichtig sollte man beim Clonen von Gegenstaenden oder bei
Heilstellen sein, diese sollten nach Moeglichkeit zwischen zwei
Resets nur beschraenkt oft moeglich sein. Erreichen kann man dies
mit einer globalen Variable (z.B. "heilung"), die bei create() und
in reset() (beim Ueberschreiben nicht vergessen, ::reset() auf-
zurufen!!!) auf eine bestimmte Zahl gesetzt wird, und bei jedem
Heilen oder Clonen um eins erniedrigt wird. Vor dem Clonen wird
dann ueberprueft, ob heilung > 0 ist, falls nicht, muss der Spieler
bis zum naechsten Reset warten.
7. Gegenstaende, NPCs
---------------------
Ein Raum kann diverse Gegenstaende beherbergen, oder auch Monster.
Im allgemeinen wird man in einem Raum in create() mit dem Befehl
AddItem() definieren, welche Gegenstaende er beherbergt. Es kann
angegeben werden, wie sich der Raum bei einem Reset (ca. alle 45
Minuten) verhalten soll, hierzu gibt es folgende Moeglichkeiten:
REFRESH_NONE: Das Objekt wird nur beim Laden des Raumes, aber
nie bei Resets neu gecloned.
REFRESH_DESTRUCT: Das Objekt wird genau dann bei Reset neu gecloned,
wenn es inzwischen zerstoert wurde (NPCs etwa, wenn
sie getoetet wurden). Dies ist die bevorzugte
Methode fuer NPCs.
REFRESH_REMOVE: Das Objekt wird genau dann bei Reset neu gecloned,
wenn es sich nicht mehr im Raum befindet, warum
auch immer (zerstoert, mitgenommen, weggegangen...).
Dies sollte AUF KEINEN FALL fuer sich fortbewegende
NPCs verwendet werden.
REFRESH_INIT: Das Objekt wird genau dann neu gecloned, wenn ein
Lebewesen den Raum betritt. Dieser Modus ist haupt-
saechlich fuer Transporterobjekte, welche nur als
Blueprint existieren sollten, vorgesehen und sollte
auch AUSSCHLIESSLICH HIERFUER verwendet werden (siehe
dritten Parameter von AddItem weiter unten).
REFRESH_ALWAYS: Das Objekt wird bei jedem Reset neu gecloned, egal,
wo sich das beim vorigen Reset geclonte befindet.
Von dieser Moeglichkeit sollte Abstand genommen
werden, da sich dann bald Unmengen an Objekten im
Spiel befinden werden...
Als dritten Parameter kann man bei AddItem() noch ein Mapping
angeben, das diverse Properties beim geclonten Objekt setzt.
Gibt man hingegen eine 1 an, so wird das Objekt nicht gecloned,
sondern nur geladen, was fuer Transporterobjekte interessant ist.
Zu beachten: AddItem ist nur fuer Raeume definiert und hat auch nur
Sinn, wenn Sachen refresht werden sollen. Wenn man ein Object nur
durch eine bestimmte aktion herholen will, dann sollte man
clone_object() und move() verwenden. Dies wird offensichtlich,
wenn man versteht, wie AddItem arbeitet - die Funktion verwaltet
eine interne Liste fuer das Raumobjekt, die bei JEDEM RESET
abgearbeitet wird, wenn man also im Zuge einer Spieleraktion
ein Objekt via AddItem in diese Liste eintraegt, wird dieses
bei ALLEN NACHFOLGENDEN RESETS wieder betrachtet und unter
Umstaenden neu gecloned!!!
8. Diverses
-----------
Raummeldungen:
Mit AddRoomMessage() kann in periodischen Abstaenden ein Text
zufaellig aus einer Liste an den Raum ausgegeben werden. Weiters
kann auch eine Funktion (oder eine Liste von Funktionen, aus der
zufaellig eine ausgewaehlt wird) angegeben werden, die dann mit
dem Index der auszugebenden Meldung als Argument aufgerufen wird.
Autor: Woody
Letzte Aenderung: 5. Februar 1998, Woody