MIDGARDAudhumbla 0.7

Kapitel 10: Vererbung & Mudlib-Design

In diesem Kapitel lernst du, wie du in LPC sauber auf Ereignisse reagierst, ohne Objekte fest miteinander zu verdrahten. Hooks und Events helfen dir dabei, Code wartbar, erweiterbar und „MUD-tauglich“ zu schreiben – besonders im Midgard MUD.

Weiter zu Kapitel 9

Objektkommunikation: call_other(), Rückgabewerte und Nachrichten.

8.1 Alles ist ein Objekt – der „Baukasten“ des MUD

In LPC ist „Objekt“ nicht nur ein Modewort. Ein Objekt ist die grundlegende Einheit, aus der das gesamte Spiel zusammengesetzt ist. Wenn du in einem Raum stehst, dann stehst du nicht in „Text“, sondern in einem Raum-Objekt. Wenn du ein Schwert siehst, dann siehst du ein Schwert-Objekt. Wenn du „nimm schwert“ tippst, werden Funktionen in mehreren Objekten aufgerufen: in dir (Spielerobjekt), im Schwert, im Raum und oft sogar in Standardsystemen der Mudlib.

Ein Objekt besteht grob aus zwei Teilen:

  • Zustand: Variablen/Properties, also gespeicherte Werte (z.B. „geladen?“, „verschlossen?“, „haltbar?“)
  • Verhalten: Funktionen, die etwas tun oder Werte liefern (z.B. short(), long(), move())

Anfängerfehler ist häufig: Man versucht alles „als Text“ zu denken. In LPC ist es besser, alles als „Dinge“ zu denken, die miteinander interagieren. Wenn du das verinnerlichst, wird dir vieles leichter fallen: Inventare, Räume, Ausgänge, Monsterlogik, Quest-Trigger, Effekte – alles sind Objektkontakte.

8.2 Dateien und Objekte – was hat die .c-Datei damit zu tun?

Jede .c-Datei beschreibt einen Objekttyp. Du kannst dir das wie einen Bauplan vorstellen: Die Datei enthält Code, der sagt, wie das Objekt aussieht und was es kann. Aber solange niemand diesen Bauplan benutzt, existiert das Objekt noch nicht „in der Welt“.

Erst wenn die Datei geladen oder daraus ein Objekt geklont wird, entsteht ein echtes Objekt im Speicher des Drivers.

Merksatz: Datei = Bauanleitung, Objekt = gebautes Ding.

8.3 Blueprint vs. Clone – Bauplan und Instanz (bitte wirklich merken)

Dieses Thema ist in LDMud/Midgard MUD zentral. Du wirst es in Debugging, in Tools, in Fehlermeldungen und beim Testen ständig sehen.


// Blueprint (Bauplan, einmal im Speicher)
 /obj/waffen/schwert

// Clone (Instanz, viele möglich)
 /obj/waffen/schwert#12345
      

Der Blueprint ist das geladene Objekt ohne #. Er existiert genau einmal. Ein Clone ist eine Instanz mit eindeutigem Suffix #....

Warum so kompliziert? Weil das effizient ist: Der Code muss nur einmal geladen sein, aber viele Instanzen können existieren. Außerdem kann jede Instanz ihren eigenen Zustand haben.

Praktisches Beispiel:

  • Du legst zwei Schwerter in einen Raum.
  • Beide Schwerter sind Clones derselben Datei.
  • Eines ist verflucht, das andere nicht – das ist Instanzzustand.

Im Mirdgard MUD sind Räume oft Blueprints (ein Raum wird geladen und bleibt), während Items, Monster, Werkzeuge und vieles andere Clones sind.

8.4 Laden und Klonen – wann passiert was?

Es gibt zwei typische „Entstehungsarten“ für Objekte im MUD — und beide sind für unterschiedliche Zwecke gedacht. Wer sie verwechselt, baut entweder zu viel Speicher voll oder bekommt einen einzigen geteilten Zustand, wo eigentlich viele Instanzen sein sollten.

  • Laden (load_object): Das Objekt wird als Blueprint in den Speicher geholt — also genau einmal. Beim zweiten Aufruf bekommst du dasselbe Blueprint zurück, ohne dass etwas Neues erzeugt wird.
  • Klonen (clone_object): Aus einem Blueprint wird eine neue Instanz erzeugt. Jeder Aufruf liefert ein neues Objekt mit eigenem Zustand. Mehrfaches Klonen erzeugt mehrere unabhängige Exemplare.

Als Anfänger musst du nicht sofort jedes Detail kennen, aber du solltest das Prinzip verstehen — und vor allem die typischen Anwendungsfälle:

  • Wenn du ein Monster erzeugst, wird es meistens geklont. Du willst zehn verschiedene Orks haben, jeden mit eigenen Lebenspunkten.
  • Wenn du einen Raum betrittst, wird der Raum meistens geladen (wenn er noch nicht geladen ist). Es gibt nur einen einzigen „Marktplatz“ — alle Spieler sehen denselben Raum.
  • Wenn du einen Daemon brauchst (z.B. Wetterdaemon), wird er geladen. Es soll nur einen geben.
  • Wenn ein Spieler ein Item bekommt (Schwert, Trank), wird es geklont. Der Spieler hat sein eigenes Schwert mit seinem eigenen Verschleiß.

Typische Efuns (zum Verständnis):


object bp = load_object("/obj/waffen/schwert");   // Blueprint laden
object ob = clone_object("/obj/waffen/schwert");  // Clone erzeugen

// Du erkennst den Unterschied am Namen:
write(object_name(bp));   // "/obj/waffen/schwert"
write(object_name(ob));   // "/obj/waffen/schwert#12345"

// Test-Funktionen:
clonep(ob);    // 1 — ist ein Clone
clonep(bp);    // 0 — ist Blueprint
      

Implizites Laden

Du musst load_object oft gar nicht selbst schreiben. Der Driver lädt Objekte automatisch in folgenden Fällen:

  • Bei einem call_other auf einen Pfad, der noch nicht geladen ist.
  • Bei clone_object, wenn der Blueprint nicht da ist (er wird zuerst geladen, dann geklont).
  • Bei einem inherit "/std/room"; in deiner Datei — die Eltern-Datei wird geladen.

Wo passiert das im MG-Alltag?

Im Midgard MUD übernehmen Standardfunktionen und Library-Objekte das oft für dich. Beispielsweise lädt ein Raum mit AddItem("/obj/foo", REFRESH_DESTRUCT) beim ersten Reset automatisch das Objekt und klont es. Du selbst rufst clone_object nur dann, wenn du außerhalb der Standardmechanik arbeitest.

Beim Debuggen ist es Gold wert, zu wissen, ob du gerade mit dem Blueprint oder einem Clone arbeitest — daran erkennst du, ob deine Änderungen am richtigen Objekt ankommen. Eine Faustregel: Wenn der Objektname kein # enthält, ist es ein Blueprint. Sonst ein Clone.

8.5 create() – Startzustand setzen

create() ist die Initialisierung. Sie wird einmal beim Entstehen aufgerufen. Hier setzt du Properties, IDs und Startwerte.


inherit "/std/thing";
#include <properties.h>

void create() {
  ::create();

  SetProp(P_NAME, "ein Stein");
  SetProp(P_SHORT, "Ein kleiner Stein");
  SetProp(P_LONG, "Ein unscheinbarer grauer Stein.\n");

  AddId("stein");
  AddId("kleiner stein");
}
      

Zwei typische Anfängerfragen:

  • Warum ::create()? — Damit die Elternklasse ihren Teil initialisieren kann.
  • Warum AddId()? — Damit das Objekt im Spiel referenzierbar ist (z.B. [ nimm ] stein).

Wenn dein Objekt im Spiel nicht „ansprechbar“ ist, liegt es sehr oft an fehlenden IDs.

8.6 Vererbung – warum du fast nie alles selbst schreibst

Vererbung ist in LPC nicht optionaler Luxus, sondern Standardpraxis. Die Mudlib stellt dir viele Basisklassen bereit, die schon alles Grundlegende können: Inventar verwalten, Properties speichern, Bewegungen prüfen, Standardausgaben erzeugen, und vieles mehr.


inherit "/std/room";
      

Damit bekommst du z.B. für Räume bereits fertige Mechaniken: Beschreibungen, Ausgänge, Reset-Logik, Inventarhandling. Du musst nur noch konfigurieren.

Übliche Basisklassen im Midgard MUD:

Anfänger-Tipp: Lies in die Standardobjekte rein (mit Tools wie MGtool) – nicht um alles sofort zu verstehen, sondern um ein Gefühl dafür zu bekommen, was „schon da“ ist.

8.7 Funktionen überschreiben und :: benutzen

Wenn du vererbst, kannst du Funktionen überschreiben. Dann zählt deine Version. Das ist sinnvoll, wenn du Standardverhalten ändern willst.


string short() {
  return "Ein seltsam glühender Stein";
}
      

Oft willst du aber nur ergänzen. Dann rufst du die geerbte Version auf:


string short() {
  return ::short() + " (er ist warm)";
}
      

Diese Technik ist ein Kernmuster: „Basisklasse macht die Arbeit, du setzt die Details drauf.“

8.8 this_object(), this_player(), environment() – dein Orientierungssystem

Diese drei Funktionen helfen dir, in der Objektwelt nicht die Orientierung zu verlieren:

  • this_object(): Das Objekt, dessen Code gerade läuft („ich“)
  • this_player(): Der Spieler, der eine Aktion auslöst (z.B. durch einen Befehl)
  • environment(x): Wo x drin ist (Raum, Spieler, Container)

object pl = this_player();
object here = environment(pl);

if (here)
  write("Du bist in: " + object_name(here) + "\n");
      

Sehr wichtig: this_player() kann in Callouts, Hintergrundjobs oder autonomen Objektaktionen auch 0 sein. Deshalb: immer prüfen, wenn du nicht sicher bist.

8.9 Inventar – Objekte in Objekten (und warum das überall auftaucht)

Inventar ist nicht nur „Spielerinventar“. Inventar ist ein allgemeines Konzept: Ein Raum enthält Objekte. Ein Spieler enthält Objekte. Ein Beutel enthält Objekte. Ein Schrank enthält Objekte. Das ist eine verschachtelte Struktur wie ein Baum.


object *inv = all_inventory(this_player());
foreach (object ob : inv)
{
  write(object_name(ob) + "\n");
}
      

Typische Helfer:

  • all_inventory(ob) – alle Objekte darin
  • present("id", ob) – Objekt anhand einer ID finden
  • deep_inventory(ob) – rekursiv, also „auch in Untercontainern“

Anfänger-Tipp: Wenn du „wo ist das Objekt?“ suchst, geh über environment() nach oben, und über all_inventory() nach unten. Das ist dein Navigationssystem.

8.10 move(), remove() und destruct() – bewegen, aufräumen, löschen

Objekte bewegen sich ständig: vom Raum ins Inventar, aus dem Inventar in den Raum, aus dem Raum in einen Container. Diese Bewegung läuft meist über Standardfunktionen der Mudlib, aber du solltest wissen: „Nehmen“ und „Ablegen“ sind technisch Bewegungen.

Das andere Ende ist das Entfernen:

  • remove(): sauberes Aufräumen (Effekte stoppen, Callouts entfernen)
  • destruct(): endgültig aus dem Speicher löschen

void remove() {
  // Hier würdest du eigene Aufräumlogik einbauen.
  ::remove();
}
      

Anfängerfehler: Man zerstört Objekte hart, ohne aufzuräumen. Das führt zu „hängenden“ Effekten oder Callouts, die auf zerstörte Objekte zeigen. Sauberer Stil spart dir Debug-Zeit.

8.11 Vollständiges Beispiel: ein kleines Objekt mit Interaktion

Hier ist ein Gegenstand, den man untersuchen kann. Er zeigt: create(), IDs, Beschreibung und eine einfache Interaktion über add_action(). (Du lernst Commands/Actions je nach Aufbau deines Manuals ausführlicher, aber hier als praxisnahes Objektbeispiel.)


inherit "/std/thing";
#include <properties.h>

void create() {
  ::create();
  SetProp(P_NAME, "ein glühender Stein");
  SetProp(P_SHORT, "Ein glühender Stein");
  SetProp(P_LONG,
    "Der Stein glüht schwach. Vielleicht kannst du ihn genauer betrachten.\n");
  AddId("stein");
  AddId("gluehender stein");
}

void init() {
  ::init();
  add_action("cmd_betrachten", "betrachte");
}

int cmd_betrachten(string str) {
  if (!str || !id(str))
    return 0;

  write("Du hältst den Stein näher ans Gesicht. Er pulsiert warm.\n");
  say(this_player()->Name(WER) + " betrachtet einen glühenden Stein.\n");
  return 1;
}
      

Hinweis: Je nach Mudlib kann das Command-System leicht variieren. Im Midgard MUD ist add_action() Standard für eigene Aktionen an Objekten.

8.11a Die wichtigsten Standardobjekte im Midgard MUD

Du programmierst praktisch nie „auf der gruenen Wiese“. Stattdessen erbst du von einer der Mudlib-Klassen unter /std/. Hier die Kandidaten, die du wahrscheinlich am häufigsten sehen wirst:

  • /std/thing – Basisobjekt für alle Dinge (Items, Steine, Bücher, …)
  • /std/container – Dinge mit Inventar (Beutel, Truhen)
  • /std/room – Räume (mit Exits, Details, Items)
  • /std/pub – Kneipenräume mit Speise-/Getraenkesystem
  • /std/transport – bewegliche Räume (Schiffe, Karren)
  • /std/living/ – Modul-Verzeichnis: life, combat, attributes, skills, … (NPCs und Spieler erben direkt aus diesen Modulen, es gibt KEIN /std/living.c)
  • /std/npc – NPCs (mit Chats, Combat, Items)
  • /std/inpc – intelligenter NPC: Tagesablauf, Routen, Flucht, KI
  • /std/mnpc – mobile NPCs, die zwischen Räumen wandern
  • /std/snpc – skalierender NPC: passt sich an Spielerlevel an
  • /std/weapon – Waffen (P_WC, P_DAM_TYPE, HitFunc)
  • /std/armour – Ruestungen (P_AC, P_ARMOUR_TYPE, DefendFunc)
  • /std/clothing – Kleidung ohne Kampfwerte
  • /std/unit – Units (Geld, Pfeile – ein Objekt = mehrere Stück)
  • /std/corpse – Leichen mit Zerfall

Konkrete Vererbungsbaeume und Beschreibungen findest du unter /doc/std/<name>, z.B. /doc/std/thing, /doc/std/room, /doc/std/snpc.

Beispiel: einfache, aber komplette Datei


// ein Stein, den man untersuchen kann
inherit "/std/thing";

#include <properties.h>
#include <language.h>

protected void create() {
  ::create();
  SetProp(P_NAME,   "Stein");
  SetProp(P_SHORT,  "Ein kleiner Stein");
  SetProp(P_LONG,   "Ein unscheinbarer grauer Stein.\n");
  SetProp(P_GENDER, MALE);
  SetProp(P_WEIGHT, 200);   // Gramm
  AddId(({ "stein", "kleiner stein" }));
  AddAdjective("klein");
}
      

8.11b P_AUTOLOADOBJ – Items überleben den Logout

Im Midgard MUD müssen Items, die ein Spieler über Logout/Reboot behalten soll, die Property P_AUTOLOADOBJ setzen. Sonst sind sie nach dem nächsten Login weg.


protected void create() {
  ::create();
  SetProp(P_NAME, "Schwert");
  // ... weitere Properties
  SetProp(P_AUTOLOADOBJ, 1);    // einfachste Variante: einfach erhalten
}
      

Wenn dein Item interne Variablen merken muss (Charge, Verfluchungsdauer, Ladezustand), kannst du P_AUTOLOADOBJ mit Set-/Querymethoden an ein Mapping/Array koppeln, in dem du die Variablen serialisierst. Siehe /doc/props/P_AUTOLOADOBJ.

8.12 Typische Anfängerfehler (Checkliste)

  • Kein ::create() → Basisklasse initialisiert nicht korrekt
  • Keine IDs → Objekt ist im Spiel schwer ansprechbar
  • Blueprint/Clone verwechselt → falsches Objekt „bearbeitet“
  • this_player() blind → in Callouts/Autologik kann es 0 sein
  • remove() ignoriert → Effekte/Callouts bleiben „hängen“

Wenn du das als Liste neben dir liegen hast, sparst du dir beim Debuggen viele Stunden.

8.13 Zusammenfassung

Du hast jetzt das wichtigste Grundmodell für LPC-Objekte:

  • Die Welt besteht aus Objekten (nicht aus „Text“)
  • Eine Datei beschreibt einen Typ, der Blueprint ist das geladene Original
  • Ein Clone ist die konkrete Instanz mit eigenem Zustand
  • create() setzt Startwerte; ::create() ist fast immer Pflicht
  • Vererbung ist der Standardweg, um solide Objekte zu bauen
  • Inventar und environment() erklären „wo ist was?“

Im nächsten Kapitel geht es darum, wie Objekte miteinander sprechen und arbeiten: Funktionsaufrufe, Rückgabewerte und Nachrichten an Spieler/Räume. Das ist die Basis für Quests, Interaktion und praktisch jede Spielmechanik.

Ausblick

Bereit für den nächsten Schritt? In Kapitel 9 lernst du, wie du gezielt Funktionen in anderen Objekten aufrufst (und warum das die „Sprache“ der Mudlib ist).

Weiter zu Kapitel 9

call_other(), Rückgabewerte, say()/tell_object() und typische Muster.