[C++/Lua] IRCmud

Kommentare anzeigen Worklog abonnieren

Worklogs [C++/Lua] IRCmud

Threading & NPCs

Sonntag, 24. März 2013 von hamZta
Nachdem Login/Logout (und Disconnect) jetzt automatisch Spieler lädt bzw. speichert hab ich mich um ein weiteres Feature gekümmert.

Threads
Bisher wurde die mudlib aus dem Server heraus rein eventbasiert betrieben. Bei bestimmten Events (Objekt betritt/verlässt Channel, Objekt sagt etwas, etc..) wurden vorher festgelegte Handler in Lua aufgerufen und das Event dann behandelt. Für eine lebendige Welt ist das allerdings zu wenig, wenn kein Spieler was macht, steht die Welt still. Aus diesem Grund gibt es jetzt "Threads" auf Basis von Lua Coroutines.
Diese Threads laufen allerdings nicht gleichzeitig zum Hauptthread, sondern hintereinander. Jedes Objekt kann Threads starten. Jeden Server-Tick werden alle Threads aller Objekte der Reihe nach abgearbeitet. Threads verhalten sich kooperativ, d.h. es gibt keinen Scheduler der die Ausführung nach einer bestimmten Zeit unterbricht, sie geben die Kontrolle selbst ab.
Dafür kann der Thread sich für eine bestimmte Anzahl Ticks "schlafen legen" und wird erst wieder ausgeführt, wenn diese Zeit vorüber ist.

Damit lassen sich nicht nur durchgehende, im Hintergrund laufende Dinge umsetzen, sondern auch einfach Interaktionen durch Verzögerungen aufwerten:
Code: [AUSKLAPPEN]
function my_npc.quest_start(self, args)
  args.player:send_privmsg(self, "Let me search in my books, that might take a while!")
  args.player:send_privmsg(self, "[a]slowly walks towards the bookshelf and pulls out a dusty tome.[/a]")
  self:pause_thread(5)
  args.player:send_privmsg(self, "Ah, yes ... yes, I see. *mumbles*")
  self:pause_thread(5)
  args.player:send_privmsg(self, "Wait ... what was your question?")
end

Nebenbei kann der NPC weiterhin auf Events anderer Spieler reagieren.

Als Beispiel, ein Thread der NPCs in Abständen Sätze sagen lässt:
Code: [AUSKLAPPEN]
function M.thread_mutter(self, args)
  local sleep_time = args.sleep_time
  local variation = args.variation
  local last_msg_id = 0

  while true do
    local msg_id = math.random(1, #args.sentences)

    while #args.sentences > 1 and msg_id == last_msg_id do
      msg_id = math.random(1, #args.sentences)
    end

    last_msg_id = msg_id

    self:get_channel():send_privmsg(self, args.sentences[msg_id])

    local wait_time = math.random(sleep_time - variation, sleep_time + variation)
    self:pause_thread(wait_time)
  end
end


Die Zeile "self:pause_thread(wait_time)" hält dabei den Thread an, für eine bestimmte Zeit sleep_time mit einer Varianz variation.

Im Einsatz eines NPCs sieht das dann folgendermaßen aus:
Code: [AUSKLAPPEN]
mutter_args = {
  sleep_time = 60,
  variation = 10,
  sentences = {
    "Do not litter.",
    "Citizen #17, please report to the nearest Liberation Office!",
    "[a]drives around in circles.[/a]",
  }
}
robot:create_thread("mutter", lib.npc.thread_mutter, mutter_args)


Alle 60(+-10) Ticks sagt der Roboter einen zufälligen Satz aus der Liste ([a]...[/a] macht daraus eine Aktion, wie /me <text>).

Das war's auch schon wieder, langsam hab ich alle Werkzeuge zusammen um mich wieder der mudlib zuwenden zu können. Und vielleicht auch mal mit dem eigentlichen Spiel anzufangen ... Wink

Not much to see

Mittwoch, 20. März 2013 von hamZta
Mal wieder ein Update:
In den letzten Tagen habe ich hauptsächlich an der mudlib weitergeschraubt. Dazugekommen sind folgende Sachen:
In der Welt gibt es jetzt Items. Items sind Objekte die keine Lebewesen sind. Sie können in Räumen rumliegen, manche können von Spielern aufgehoben werden, mit allen kann irgendwie interagiert werden.
Zum Beispiel trägt jeder Spieler von Beginn an einen PDA mit sich. Benutzt er den, öffnet sich ein privater Chat (Query). Dort kann man die Funktionen des PDA über Textbefehle nutzen (Notizen verwalten, Mails senden & lesen, etc). Der Besitzer eines PDAs ist dabei fix eingespeichert. Gibt man das Ding an einen anderen Spieler weiter so hat dieser Zugriff auf die Mails des eigentlichen Besitzers.

Spieler können sich jetzt auch zwischen Räumen hin- und herbewegen. Dabei gibt es anders als in alten Versionen nicht eine Liste mit anderen Räumen die man von dem aktuellen Raum aus erreicht, sondern man bewegt sich über Himmelsrichtungen.
Dazu werden im Code vorher Räume miteinander verbunden. Dadurch ergeben sich auch wieder viele Möglichkeiten (One-way-Verbindungen, versteckte Räume, Räume die eine gewisse Zugangsbeschränkung haben, ...).

Dabei hat sich auch das Channelhandling geändert. Früher war jeder Raum direkt einem IRC-Channel zugeordnet. Hat man sich bewegt hat man einen Channel verlassen und den anderen betreten. Das hatte natürlich zur Folge, dass die meisten IRC-Clients für jeden Channel ein neues Fenster/einen neuen Tab öffnen und vielleicht auch nicht automatisch wieder schließen wenn man ihn verlässt. Jetzt werden alle Räume auf einen IRC-Channel abgebildet. Dadurch bleibt immer nur ein Fenster offen, man muss nicht rumklicken und wird auch nicht mit Tableichen überhäuft.

Als nächstes sollte ich mich darum kümmern, alles ein wenig abzurunden, vor allem Login/Logout von Spielern. Danach geht's weiter zur Interaktion von Spielern mit NPCs. Dazwischen sollte ich mir überlegen wo ich überhaupt hin will damit das Spiel auch Spaß macht Wink

Look at the red cat in the huge and shiny metal box

Donnerstag, 7. März 2013 von hamZta
Ja, das Projekt gibt es noch. Ich habe vor einiger Zeit wieder angefangen daran zu arbeiten.

IRCmud ist ein sogenannter MUD-Server (http://de.wikipedia.org/wiki/Multi_User_Dungeon). Multi User Dungeons sind die Mutter aller MMORPGs. Gemeinsam mit anderen Spielern kann man durch die Welt wandern, Aufgaben lösen, kämpfen oder einfach nur chatten. Im Gegensatz zu modernen MMORPGs ist hier aber alles textbasiert!

Traditionelle MUDs sind zeilen- oder zeichenbasierte Server auf die man über telnet zugreift. Wie der Name vermuten lässt verwende ich einen anderen Ansatz und benutze statt telnet IRC als Protokoll, für das viele Nerds ohnehin schon Clients installiert haben Wink Ein Nachteil dieser Umsetzung ist, dass IRC viel weniger Freiheiten lässt was Formatierung zulässt.

Das Projekt besteht dabei aus zwei Teilen:
Der Server, der Verbindungen annimmt und den Netzwerktraffic handelt (IRCmud, C++) und die Spielmechanik inklusive Library (mudlib, Lua). Dazwischen liegt eine dünne Schicht in der der Server bei bestimmten Aktivitäten Lua-Funktionen aufruft (Spieler verbindet sich, sagt etwas, ...). Der Server unterscheidet dabei nur zwischen drei verschiedenen Entitäten: Spieler, Channel und Objekt. Objekt ist die Basisklasse, Spieler und Channel sind davon abgeleitet. Diese Klassen sind in der mudlib sichtbar und können wiederrum als Basisklassen für komplexere Konstrukte verwendet werden. Grundsätzlich gilt: Everything's an object Wink

mudlib
Die Library stellt Funktionen und Abstraktionen für Objekte zur Verfügung. So kann sie zum Beispiel Objekten Eigenschaften und Namen zuweisen. Diese Eigenschaften und Namen werden dafür verwendet, Objekte zu identifizieren.

Früher hat man sich mit starren Befehlen durch IRCmud bewegt (z.B. "!look NPC" oder "!move channel"). Klassische MUDs setzen aber Parser ein, die komplexere, an natürliche Sprache angelehnte Befehle verarbeiten können. So einen hab ich jetzt eingebaut:

Der Parser
Will man einen neuen Befehl definieren übergibt man dem Parser den Namen, ein paar Regeln und eine Callback-Funktion. Im MUD-Slang werden Befehle Verben genannt. Ein Beispiel wäre das Verb "look" (anschauen). Die einfachste Regel wäre, sich ein anderes Objekt anzusehen: "look at OBJ". Der Parser erkennt dabei das Schlüsselwort "OBJ" und akzeptiert dafür eine Liste aus Adjektiven und einen Namen. Gibt der Spieler Text ein, der mit "look" beginnt, so versucht er das eingegebene auf eine der Regeln des Verbs zu matchen.

In diesem Fall könnte der Spieler "look at cat" eingeben, der Parser würde "cat" erkennen und den Raum in dem sich der Spieler befindet nach einem Objekt namens "cat" durchsuchen. Findet er es, ruft er die Callback-Funktion mit dem gefundenen Objekt als Parameter auf. Dazu kommt noch etwas "syntactic sugar", damit es natürlicher klingt wird auch "look at the cat" richtig erkannt.

Was passiert jetzt, wenn sich im Raum zwei Katzen befinden? Auf ein "look at the cat" gibt der Server nur ein "Which cat?" zurück. Dafür kommen jetzt die o.g. Eigenschaften ins Spiel. Die Katzen können verschiedene Eigenschaften haben, zum Beispiel kann eine Katze schwarz und alt sein, die andere ist vielleicht weiß, jung und kleiner als die andere.
Mögliche Befehle:
Code: [AUSKLAPPEN]
Spieler> look at the black cat
System: You see a black and old cat.

Spieler> look at the white cat
System: You see a white, young and small cat.

Spieler> look at the white, young cat
System: You see a white, young and small cat.

Spieler> look at the black old cat
System: You saw a black and old cat.

Spieler> look at the white and small cat
System: You see a white, young and small cat.

Spieler> look at the black and small cat
System: There is no black and small cat.


Man ist also ziemlich flexibel, Grammatikextras wie Kommata oder "and" als Bindewort werden erkannt und ausgefiltert. Neben Eigenschaften kann man Objekten auch noch Synonyme zuweisen, bei einer Katze wäre zum Beispiel das englische "kitten" möglich, was ein "look at the small kitten" zusätzlich erlauben würde.

Komplexere Regeln erlauben es Spielern Objekte in anderen Objekten zu sehen: "look at OBJ in OBJ". So könnte ein Spieler zum Beispiel "look at the brown cat in the wooden box" ausführen. In den Callback kommen dann weitere Abfragen, wie: Kann der Spieler die Katze überhaupt sehen? Kann der Spieler überhaupt sehen (ist er blind)? Ist der Raum zu dunkel um etwas zu sehen (hat der Spieler eine Lichtquelle in der Hand)?

Neben OBJ gibt es noch andere Schlüsselworte:
DIR nimmt eine Himmelsrichtung als Eingabe, STR einen String (z.B. "engrave STR into OBJ" -> "engrave I was here into the mossy tombstone"), INT für Zahlen, etc.

Das ist soweit der aktuelle Stand, es gibt noch genug zu tun!

hamZta