9.1 Warum Objektkommunikation so wichtig ist
In einem MUD passiert nichts isoliert. Eine MUD-Welt besteht aus hunderten,
manchmal tausenden Objekten, die ständig miteinander kommunizieren. Wenn du als
Spieler ein Kommando eintippst, reagieren typischerweise mehrere Objekte
nacheinander oder parallel:
- Das Spielerobjekt nimmt den Befehl entgegen, parst ihn und
identifiziert, was du willst.
- Der Raum, in dem du stehst, prüft, ob das Kommando dort erlaubt
ist (z.B. nicht im Schlaf-Raum kämpfen).
- Ein Gegenstand in deinem Inventar oder im Raum reagiert
(„öffnen“, „nehmen“, „aktivieren“) und führt seine Logik aus.
- Andere Spieler im Raum bekommen Nachrichten — eine Beobachterperspektive.
- Eventuell prüfen Daemons (Wetter, Zeit, Quest-System) noch zusätzliche Bedingungen.
All das geschieht durch Funktionsaufrufe zwischen Objekten.
Objekt A ruft eine Funktion in Objekt B auf — manchmal direkt, manchmal indirekt
über die Mudlib. Das ist die einzige Sprache, in der Objekte miteinander reden.
Stell dir das wie ein Theater vor: Jedes Objekt ist ein Schauspieler, der bestimmte
Rollen kennt (Funktionen). Andere Schauspieler sprechen ihn mit Zeichen und Gesten an
(Funktionsaufrufe). Es gibt keine Telepathie und kein gemeinsames Bewusstsein —
Information läuft nur über die Aufrufe.
Wichtiges Mindset: Objekte senden keine „Signale“, sie rufen Funktionen auf.
Alles andere — Textausgaben, Effekte, Statusänderungen — sind nur Folgen davon.
Wenn du verstehst, wer in welchem Moment welche Funktion in welchem Objekt aufruft,
verstehst du die Mechanik des MUD.
In diesem Kapitel lernst du die Grundbausteine dieser Kommunikation: Funktionsaufrufe
auf andere Objekte, Rückgabewerte, Nachrichten an Spieler und Räume, und die
Werkzeuge, die du dafür brauchst.
9.2 Funktionsaufrufe – das Grundprinzip
Eine Funktion in LPC ist immer Teil eines Objekts. Sie schwebt
nicht im luftleeren Raum, sondern gehört zu einer konkreten Datei, die als Objekt
geladen wurde. Um eine Funktion in einem anderen Objekt aufzurufen, brauchst du also
drei Dinge:
- Ein Zielobjekt — wer soll die Funktion ausführen?
- Den Namen der Funktion — welche Funktion in diesem Objekt?
- Optionale Argumente — welche Werte sollen an die Funktion gehen?
object ob = this_player(); // Zielobjekt: der Spieler
ob->QueryProp(P_LEVEL); // Funktion: QueryProp; Argument: P_LEVEL
Hier ruft das aktuelle Objekt die Funktion QueryProp() im Spielerobjekt
auf und übergibt P_LEVEL als Argument. Der Rückgabewert ist das Ergebnis
der Funktion — in diesem Fall ein Integer (das Level des Spielers).
Drei Schreibweisen für denselben Aufruf
In LPC kannst du Funktionsaufrufe auf andere Objekte in mehreren Formen schreiben:
// Pfeil-Operator (am häufigsten):
ob->QueryProp(P_LEVEL);
// call_other (die "lange" Form, identisch zu ->):
call_other(ob, "QueryProp", P_LEVEL);
// Punkt-Operator (neuere Syntax ab LDMud 3.6.2):
ob.QueryProp(P_LEVEL);
Kleiner Unterschied: -> und call_other()
liefern 0, wenn die Funktion im Zielobjekt nicht existiert.
Der Punkt-Operator . (call_strict) wirft dagegen einen Runtime-Error,
wenn die Funktion fehlt. Im Code wirst du fast immer die Pfeil-Schreibweise sehen.
call_other() ist nützlich, wenn der Funktionsname dynamisch ist
— also als String in einer Variable steht:
string fname = "QueryProp";
mixed result = call_other(ob, fname, P_LEVEL);
Zugriff auf eigene Funktionen
Wenn du eine Funktion in deinem eigenen Objekt aufrufst, brauchst du keinen
Pfeil — du schreibst einfach den Funktionsnamen:
public int query_hp() { return hp; }
public void status() {
write("HP: " + query_hp() + "\n"); // direkter Aufruf
}
Merksatz: objekt->funktion() ist die Standardsprache der Mudlib.
Du wirst diese Notation in jeder einzelnen Datei sehen. Wenn du sie verstehst, hast du
eine zentrale Hürde von LPC genommen.
9.3 Rückgabewerte – Antworten von Objekten verstehen
Fast jede Funktion gibt einen Wert zurück. Dieser Rückgabewert ist extrem wichtig,
weil er dir die Antwort des Zielobjekts liefert: Hat die Aktion
funktioniert? Welcher Wert wurde gefunden? Welches Objekt wurde geliefert?
Ohne den Rückgabewert weiß dein Code nicht, wie er weitermachen soll.
Im einfachsten Fall fragst du eine Information ab und nutzt sie sofort:
int lvl = this_player()->QueryProp(P_LEVEL);
if (lvl < 10) {
write("Du bist noch zu unerfahren.\n");
}
Häufige Rückgabetypen und ihre Bedeutung
int — wird oft als Erfolg/Misserfolg verwendet.
Konvention im Midgard: 1 = erfolgreich, 0 = nicht.
Bei Kommando-Handlern: 1 = „ich habe das Kommando bearbeitet“,
0 = „nicht für mich, gib es weiter“.
string — Texte, Beschreibungen, Namen.
Eine leere Antwort ist oft 0 (nicht "").
object — Referenz auf ein anderes Objekt.
find_player(), present(), environment()
— alle liefern Objekte oder 0, wenn nichts gefunden wurde.
object* — Array von Objekten.
all_inventory(), users(), filter() liefern
oft Listen.
0 — der Universal-Wert für „nichts“,
„fehlgeschlagen“, „nicht vorhanden“. Funktioniert für jeden Typ, weil 0 auch
„leerer Wert“ bedeutet.
Anfängerfehler: Rückgabewerte ignorieren
Viele Bugs entstehen, weil man einfach davon ausgeht, dass etwas funktioniert hat:
// Schlecht: keine Prüfung
object schwert = present("schwert", this_player());
schwert->DoWield(); // CRASH wenn schwert == 0
// Besser:
object schwert = present("schwert", this_player());
if (!schwert) {
notify_fail("Du hast kein Schwert dabei.\n");
return 0;
}
schwert->DoWield();
Faustregel: Bei jedem Funktionsaufruf, der ein Objekt oder einen Status zurückgibt,
prüfe vor dem Weiterverwenden. „Defensive Programmierung“ heißt das, und sie ist im
Midgard MUD Pflicht (siehe Kapitel 11).
9.4 call_other() – der klassische Funktionsaufruf
Intern läuft fast jeder Funktionsaufruf zwischen Objekten über die Efun
call_other(). Die Kurzschreibweise obj->funktion() ist
nur „syntaktischer Zucker“ — der Compiler übersetzt sie automatisch in einen
call_other-Aufruf. Beide Schreibweisen tun dasselbe und sind gleich
schnell.
call_other(this_player(), "QueryProp", P_HP);
Das ist funktional identisch zu:
this_player()->QueryProp(P_HP);
Wann ist call_other() besser als der Pfeil?
Der Pfeil ist immer dann am Platz, wenn du den Funktionsnamen schon zur Compilezeit
kennst. call_other() ist nützlich, wenn der Funktionsname erst zur
Laufzeit feststeht — also als String in einer Variable:
// Funktionsname dynamisch — geht nur mit call_other:
string fname = "Query" + capitalize(prop_name);
mixed result = call_other(ob, fname, P_HP);
// Mit Array von Argumenten — ebenfalls dynamisch:
mixed *args = ({ P_HP, P_MAX_HP, P_LEVEL });
foreach(mixed prop : args) {
write(call_other(ob, "QueryProp", prop) + "\n");
}
Die ->-Schreibweise mit Variable
Für die meisten Fälle reicht der Pfeil — auch wenn das Ziel-Objekt in einer
Variable steckt:
object ob = this_player();
ob->QueryProp(P_HP); // ok
this_player()->QueryProp(P_HP); // auch ok
// String als Pfad geht ebenfalls (Driver lädt das Objekt automatisch):
"/secure/master"->valid_read("/log/test", "rosa", "fun", this_object());
Du wirst call_other() selten selbst schreiben, aber du solltest wissen,
dass es existiert — vor allem beim Debuggen, beim Lesen alter Codes oder bei
dynamischen Funktionsaufrufen. Außerdem siehst du in privilege_violation()
und ähnlichen Master-Funktionen oft call_other als Operations-Bezeichner.
9.5 Nachrichten an Spieler – write(), tell_object()
Kommunikation heißt nicht nur „Code spricht mit Code“, sondern auch
„Spiel informiert Spieler“. Schließlich sollen die Spieler ja erfahren, was passiert.
Auf Spielerseite kennst du die Pendants als
[ sage ],
[ frage ],
[ antworte ],
[ teile mit ]
oder
[ lausche ].
Dafür gibt es mehrere Funktionen, die jeweils einen anderen Empfänger anvisieren —
und es lohnt sich, sie sauber zu unterscheiden, weil falsche Empfänger ein
häufiger Bug sind.
write() — schnell, aber implizit
write("Du fühlst dich beobachtet.\n");
write() sendet Text an den aktuellen Spieler —
also den, dessen Aktion gerade abläuft (this_player()).
Das ist praktisch in Kommando-Handlern, weil du dort fast immer dem Spieler antworten
willst, der das Kommando ausgelöst hat. Aber Achtung: Wenn dein Code nicht direkt aus
einer Spieleraktion läuft (z.B. aus einem call_out oder
heart_beat), kann this_player() zu 0 sein —
und dein write() verpufft dann oder verursacht einen Fehler.
tell_object() — explizit und sicher
tell_object(pl, "Ein kalter Schauer läuft dir über den Rücken.\n");
tell_object() ist expliziter: Du bestimmst, welches Objekt die Nachricht
erhält. Damit ist auch unmissverständlich, an wen die Nachricht geht — selbst wenn
this_player() gerade niemand ist. Das macht den Code robuster und
besonders in komplexen Abläufen (Hooks, Callouts, Hintergrundaktionen) zur ersten Wahl.
Vergleich
// In einem cmd_handler — beides funktioniert gleich:
write("Du nimmst das Schwert.\n"); // an this_player()
tell_object(this_player(), "Du nimmst das Schwert.\n");
// In einem call_out (asynchron) — write() ist gefährlich!
void delayed_message() {
// this_player() könnte 0 sein!
// write("Hallo!"); // unsicher
tell_object(my_target, "Hallo!\n"); // sicher
}
Anfänger-Tipp
Verwende tell_object(), wenn du sicher sein willst, wer die Nachricht
bekommt — besonders in komplexeren Abläufen. write() ist OK in
einfachen Spielerkommandos, aber sobald du irgendetwas mit Verzögerungen,
Callbacks oder Hintergrundlogik machst, geh auf tell_object().
Alle Sende-Funktionen ergänzen automatisch keinen Zeilenumbruch — du musst
\n selbst schreiben, sonst kleben deine Texte zusammen.
9.6 say(), tell_room() – Kommunikation im Raum
Oft sollen mehrere Spieler gleichzeitig informiert werden.
Dafür gibt es Raum-Nachrichten. Die Spielerseite davon kennst du als
[ sage ],
[ emote ],
[ rufe ]
oder
[ teile mit ].
say("Ein Windhauch weht durch den Raum.\n");
say() informiert alle im Raum – außer dem aktuellen Spieler.
Spieler können solche Meldungen mit
[ Senderwiederholung ]
zurückholen, falls sie etwas verpasst haben.
tell_room(environment(this_player()),
"Die Fackeln flackern unruhig.\n");
tell_room() ist noch kontrollierter und funktioniert auch,
wenn kein aktiver Spieler beteiligt ist.
Merksatz: write = ich, say = alle anderen, tell_room = ganzer Raum.
9.6a ReceiveMsg() – das moderne Midgard-Idiom
Im Midgard MUD gibt es zusaetzlich zu tell_object()/tell_room()
die Funktion ReceiveMsg(). Sie wurde eingeführt, um Meldungen
klassifizieren und filtern zu können (z.B. Kampftext getrennt von Smalltalk,
Umgebungsmeldungen anders einfaerben, Spieler kann Meldungstypen abschalten).
// Signatur (gekürzt):
// ReceiveMsg(string msg, int msg_type, string msg_action,
// string msg_prefix, mixed exclude)
//
// In Räumen:
this_object()->ReceiveMsg(
this_player()->Name(WER) + " zieht am Hebel.\n",
MT_LOOK, // Typ: Sicht-Meldung
MA_EMOTE, // Aktion: Emote
0, // Prefix
({ this_player() }) // Empfänger ausschließen
);
// In Spielern:
who->ReceiveMsg("Du fuehlst dich beobachtet.\n", MT_FEEL);
Die Konstanten MT_* (Message Type) und MA_* (Message Action)
stehen in <living/comm.h>. Beispiele:
- MT_* (Bitwerte zum Verodern):
MT_LOOK, MT_LISTEN, MT_FEEL,
MT_NOTIFICATION, MT_COMM
- MA_* (Strings, NICHT Zahlen!):
MA_SAY = "sage", MA_EMOTE = "emote",
MA_SHOUT = "rufe", MA_TELL = "teilemit"
Faustregel: Für einfache Lernobjekte reichen tell_object() und
tell_room(). Für Spielinhalte, die Spielern als Filterkanal sichtbar sind
(Kampftext, Hilferufe, Mood-Meldungen), nutze ReceiveMsg(). Lies dazu
/doc/lfun/ReceiveMsg.
9.6b Sicheres call_other() mit catch()
Wenn du eine Funktion in einem fremden Objekt aufrufst, kann dort etwas schiefgehen
(Tippfehler, Laufzeitfehler, Objekt zerstört sich selbst). Damit dein eigener Code
deshalb nicht abstuerzt, kapsle riskante Aufrufe in catch():
mixed res;
mixed err = catch(res = ob->DoSomething());
if (err) {
log_file("debug/calls",
sprintf("DoSomething schlug fehl: %O\n", err));
return;
}
// res nutzen
Im Catch wird der Standard-Errorhandler nicht ausgeloest – du bekommst nur
einen Hinweis. Mehr dazu in Kapitel 11 und unter /doc/efun/catch.
9.7 Aktionen auslösen – andere Objekte „bitten“
Kommunikation heißt oft: „Tu etwas für mich.“
Ein Objekt bittet ein anderes, eine Aktion auszuführen.
object door = present("tuer", environment(this_player()));
if (door)
door->open_door();
Hier ruft dein Objekt gezielt eine Funktion im Tür-Objekt auf.
Ob die Tür das erlaubt, entscheidet die Tür selbst.
Guter Stil: Das Zielobjekt entscheidet immer selbst.
Du solltest niemals einfach Variablen fremder Objekte manipulieren.
9.8 Sicherheitsaspekte – nicht jedes Objekt darf alles
In großen MUDs ist Sicherheit extrem wichtig. Nicht jedes Objekt darf
jede Funktion aufrufen.
Deshalb prüfen viele Funktionen:
- Wer ruft mich auf? (
previous_object())
- Ist der Spieler berechtigt?
- Ist der Aufruf logisch sinnvoll?
Als Anfänger musst du das nicht sofort implementieren, aber du solltest
wissen: Viele Funktionen können stillschweigend 0 zurückgeben,
wenn der Aufruf nicht erlaubt ist.
9.9 Typisches Beispiel: Interaktion zwischen Spieler, Objekt und Raum
int cmd_ziehen(string str) {
if (!id(str))
return 0;
write("Du ziehst am Hebel.\n");
say(this_player()->Name(WER) + " zieht an einem Hebel.\n");
environment(this_player())->on_lever_pulled();
return 1;
}
Was passiert hier?
- Spieler ruft Befehl aus
- Objekt reagiert
- Raum wird informiert
- Raum entscheidet, was weiter passiert
Dieses Muster ist extrem häufig: Objekte lösen Ereignisse im Raum aus,
der Raum reagiert mit Veränderungen.
9.10 Häufige Anfängerfehler
- Rückgabewerte ignorieren
- Direkt Variablen fremder Objekte ändern
this_player() blind verwenden
- Nachrichten an falsche Empfänger senden
- Logik in falsche Objekte packen
Wenn etwas „komisch“ wirkt, liegt es fast immer an falscher Kommunikation
zwischen Objekten.
9.11 Zusammenfassung
Du hast gelernt:
- Wie Objekte Funktionen in anderen Objekten aufrufen
- Warum Rückgabewerte entscheidend sind
- Wie Nachrichten an Spieler und Räume funktionieren
- Warum klare Zuständigkeiten wichtig sind
Objektkommunikation ist die Sprache des MUDs. Wenn du sie beherrschst,
kannst du komplexe Spielmechaniken sauber und verständlich umsetzen.
Ausblick
Im nächsten Kapitel gehen wir noch einen Schritt weiter:
Hooks, Events und systematische Reaktionen auf Ereignisse.
Damit kannst du dein Objektverhalten elegant erweitern,
ohne alles neu zu schreiben.
Weiter zu Kapitel 10
Hooks, Events und saubere Erweiterungen