Added public files

Roughly added all public files. Probably missed some, though.
diff --git a/std/player/protocols/gmcp.c b/std/player/protocols/gmcp.c
new file mode 100644
index 0000000..b951daa
--- /dev/null
+++ b/std/player/protocols/gmcp.c
@@ -0,0 +1,785 @@
+// MorgenGrauen MUDlib
+//
+// gmcp.c -- Verwaltung von GMCP im Spielerobjekt
+//
+// $Id$
+
+#pragma strong_types,save_types
+#pragma range_check
+#pragma no_clone
+#pragma no_shadow
+#pragma pedantic
+
+#include <regexp.h>
+#include <telnet.h>
+
+#define NEED_PROTOTYPES
+#include <player/base.h>
+#include <thing/properties.h>
+#include <living/attributes.h>
+#include <player/gmcp.h>
+#include <thing/description.h>
+#include <living/description.h>
+#undef NEED_PROTOTYPES
+
+#include <properties.h>
+#include <new_skills.h>
+#include <rooms.h>
+#include <tls.h>
+
+inherit "/secure/telnetneg-structs.c";
+
+struct gmcp_mod_s {
+  string id;        // Name des GMCP-Moduls (z.B. "MG.Char")
+  int version;      // Version des aktivierten moduls
+  closure sendcl;   // Handler zum Senden (lfun-closure)
+  closure recvcl;   // Handler zum Empfangen (lfunc-closure)
+  mixed data;       // optional data of the module
+};
+
+nosave mapping gmcpdata;
+/* Struktur:
+   Jedes Modul hat einen Schluessel im Toplevel, worunter ggf. seine Daten
+   abgelegt sind. Die Daten sind eine struct gmcp_mod_s. Der Schluessel ist
+   der Modulname OHNE Version.
+   */
+#define NEED_PROTOTYPES
+#include "/secure/telnetneg.h"
+#undef NEED_PROTOTYPES
+
+//#define __GMCP_DEBUG__ 1
+// Low priority debug messages
+#define GMCP_DEBUG(pre,msg,prio) if (interactive(this_object()) \
+                            && gmcpdata) \
+                          GMCP_debug(pre,msg,prio);
+// higher priority error messages
+#define GMCPERROR(msg) if (interactive(this_object()) \
+                       && gmcpdata) \
+                     GMCP_debug("ERROR",msg,10);
+
+
+// **************** API nach Aussen folgt ab hier ********************
+
+// Wird vom Spielerobjekt gerufen, wenn sich Daten am Charakter veraendert
+// haben, die gesendet werden sollten.
+// Dies ist eigentlich nur ein Wrapper, der die Daten an den Handler eines
+// Moduls weitergibt, welches vom Client aktiviert wurde. Hierzu kommen zur
+// Zeit 2 in Frage: MG.Char (bevorzugt) und Char (minimaler Support).
+/*protected*/ int GMCP_Char(mapping data) {
+
+  if (!mappingp(gmcpdata)) return 0;
+
+  // Als erstes schauen, ob der Client MG.Char aktiviert hat.
+  struct gmcp_mod_s mod = gmcpdata["MG.char"];
+  if (structp(mod) && closurep(mod->sendcl))
+  {
+    funcall(mod->sendcl, data);
+    return 1;
+  }
+  // Dann noch das Modul char pruefen. Das ist aber ziemlich eingeschraenkt
+  // und es gibt hoffentlich nicht viele Clients, die es benutzen.
+  // (Aardwolf-Modul)
+  mod = gmcpdata["char"];
+  if (structp(mod) && closurep(mod->sendcl))
+  {
+    funcall(mod->sendcl, data);
+    return 1;
+  }
+  // Dann noch das Modul Char pruefen. Das ist aber ziemlich eingeschraenkt
+  // und es gibt hoffentlich nicht viele Clients, die es benutzen.
+  // (IRE-Modul)
+  mod = gmcpdata["Char"];
+  if (structp(mod) && closurep(mod->sendcl))
+  {
+    funcall(mod->sendcl, data);
+    return 1;
+  }
+  return 0;
+}
+
+/*protected*/ int GMCP_Channel(string msg, string channel, string sender) {
+  if (!mappingp(gmcpdata)) return 0;
+  // comm.channel Modul aktiv?
+  struct gmcp_mod_s mod = gmcpdata["comm.channel"];
+  if (structp(mod) && closurep(mod->sendcl))
+  {
+    funcall(mod->sendcl, (["chan":channel, "player": sender,
+                           "msg": msg]) );
+    return 1;
+  }
+  return 0;
+}
+
+/*protected*/ int GMCP_Room() {
+  if (!mappingp(gmcpdata)) return 0;
+  // MG.room Modul aktiv?
+  struct gmcp_mod_s mod = gmcpdata["MG.room"];
+  if (structp(mod) && closurep(mod->sendcl))
+  {
+    funcall(mod->sendcl, 0);
+    return 1;
+  }
+  return 0;
+}
+
+// **************** Ab hier folgen eher die Lowlevel-Dinge ***********
+private void GMCP_debug(string pre, string msg, int prio) {
+  struct gmcp_mod_s mod = gmcpdata["Core"];
+  if (mod && (mod->data)["Debug"] >= prio)
+    tell_object(this_object(), sprintf("GMCP %s: %s\n",pre,msg));
+}
+
+private void GMCP_send(string cmd, mixed data)
+{
+  GMCP_DEBUG("GMCP_send",sprintf("%s %O",cmd,data), 30);
+  send_telnet_neg_str(sprintf("%c%c%s %s", SB, TELOPT_GMCP,
+                              cmd, json_serialize(data)), 1);
+}
+
+private void GMCP_unregister_module(string mod)
+{
+  int version;
+  // Wenn nicht "mod version" Schema, ignorieren
+  if (sscanf(mod, "%s %d", mod, version) != 2)
+      return;
+
+  if (mod=="Core") // darf nicht abgeschaltet werden.
+      return;
+
+  m_delete(gmcpdata, mod);
+}
+
+private void GMCP_register_module(string modname)
+{
+  int version;
+  GMCP_DEBUG("register_module(): trying ... ",modname, 20);
+  // Wenn nicht "mod version" Schema, ignorieren
+  if (sscanf(modname, "%s %d", modname, version) != 2)
+      return;
+
+//  GMCP_DEBUG("register_module()",modname + " v" + version);
+
+  // Modul (ggf. mit anderer Version) bereits aktiv?
+  struct gmcp_mod_s mod = gmcpdata[modname];
+  if (structp(mod)) {
+    // wenn gleicher Name und Version, wird nix gemacht, bei anderer Version
+    // wird ein neuer Handler eingetragen und die Daten geloescht.
+    // Wenn nicht-existierende Modul/Version-Kombi angefordert wird, ist das
+    // Modul hinterher aus.
+    if (mod->id == modname && mod->version == version)
+      return;
+    else
+      m_delete(gmcpdata,modname);
+  }
+
+  // Das GMCP-Modul ist nur verfuegbar, wenn es zu der Kombination aus mod und
+  // version einen Handler zum Senden gibt...
+  // Der Handler ist: GMCP_<mod>_v<version>_send, aber in <mod> werden alle
+  // "." durch "_" ersetzt.
+  string replacedname = regreplace(modname, "\\.", "_", RE_GLOBAL);
+  closure sendcl = symbol_function(sprintf("GMCPmod_%s_v%d_send",
+                      replacedname, version),
+                   this_object());
+  if (!sendcl)
+    return;
+  // Diese Closure darf 0 sein. Dann findet keine Behandlung von vom Client
+  // gesendeten Kommandos statt. Was fuer die meisten Module auch in Ordnung
+  // ist, da sie dem Client keine Kommandos anbieten.
+  closure recvcl = symbol_function(sprintf("GMCPmod_%s_v%d_recv",
+                      replacedname, version),
+                   this_object());
+
+  GMCP_DEBUG("register_module()",modname+" erfolgreich registriert.",10);
+
+  mod = (<gmcp_mod_s> id: modname, version : version,
+         sendcl : sendcl, recvcl: recvcl, data: ([]) );
+  gmcpdata[modname] = mod;
+
+  // Zum schluss noch den Senden-handler mal rufen, damit der mal alle
+  // verfuegbaren daten sendet.
+  funcall(mod->sendcl, 0);
+}
+
+// Handler fuer das Core Modul von GMCP
+// Gerufen bei Empfang von Kommandos vom Client.
+protected void GMCPmod_Core_v1_recv(string cmd, mixed args)
+{
+  struct gmcp_mod_s mod = gmcpdata["Core"];
+  mapping data = mod->data;
+
+/*  if (!mod)
+  {
+    GMCPERROR("Command %s for disabled module ignored.");
+    return;
+  }
+  */
+  GMCP_DEBUG("GMCPmod_Core_v1: ", cmd, 20);
+
+  switch (cmd)
+  {
+    case "Core.Hello":
+      if (mappingp(args))
+        data["Hello"] = (["client": args["client"],
+                          "version": args["version"] ]);
+      break;
+    case "Core.Supports.Set":
+      if (pointerp(args))
+      {
+        // Alte Module abschalten/loeschen
+        foreach(string m : data["Supports"])
+          GMCP_unregister_module(m);
+        data["Supports"] = args;
+        // Versuchen, die neuen Module zu registrieren
+        foreach(string m : args)
+          GMCP_register_module(m);
+      }
+      else
+          GMCP_DEBUG("GMCPmod_Core_v1: ",
+              "Data for Core.Supports.Set is no array", 5);
+      break;
+    case "Core.Supports.Add":
+      if (!pointerp(data["Supports"]))
+        data["Supports"] = ({});
+      if (pointerp(args))
+      {
+        foreach(string m: args)
+          GMCP_register_module(m);
+        data["Supports"] += args;
+      }
+      break;
+    case "Core.Supports.Remove":
+      if (!pointerp(data["Supports"]))
+        break;
+      if (pointerp(args))
+      {
+        foreach(string m: args)
+          GMCP_unregister_module(m);
+        data["Supports"] -= args;
+      }
+      break;
+    case "Core.Supports.KeepAlive":
+      break;  // this is ignored by us.
+    case "Core.Ping":
+      if (intp(args))
+        data["Ping"] = args;
+      // send a ping back
+      GMCP_send("Core.Ping",0);
+      break;
+    case "Core.Debug":
+      if (intp(args) && args >= 0)
+        data["Debug"] = args;
+      break;
+    default:
+      GMCPERROR(sprintf("Unknown GMCP Core cmd %s with args %O",
+            cmd, args));
+      break;
+  }
+}
+
+// Handler fuer das Core Modul von GMCP
+// Gerufen, wenn Daten zu senden sind.
+protected void GMCPmod_Core_v1_send(mapping data)
+{
+  // Zur Zeit nix, spaeter mal Core.Goodbye.
+}
+
+
+// Handler fuer das MG.Char Modul
+// Gerufen bei Empfang von Kommandos vom Client.
+protected void GMCPmod_MG_char_v1_send(mapping data)
+{
+  mapping squeue = m_allocate(5,0);
+  struct gmcp_mod_s mod = gmcpdata["MG.char"];
+  // mod->data fungiert hier auch als Cache der Daten. Die muss man naemlich
+  // leider immer alle senden, nicht nur die geaenderten.
+  if (!mappingp(data))
+  {
+    // Alle verfuegbaren Informationen senden...
+    mod->data = m_allocate(6);
+    m_add(mod->data, "MG.char.base",
+              ([P_NAME: Name(WER),
+                P_GUILD: QueryProp(P_GUILD),
+                P_PRESAY: QueryProp(P_PRESAY),  // TODO
+                P_TITLE: QueryProp(P_TITLE),
+                "wizlevel": query_wiz_level(this_object()),
+                P_RACE: QueryProp(P_RACE)]) );  // TODO
+    m_add(mod->data,"MG.char.vitals", 
+              ([P_HP: QueryProp(P_HP),
+                P_SP: QueryProp(P_SP),
+                P_POISON: QueryProp(P_POISON) ]) );
+    m_add(mod->data,"MG.char.maxvitals",
+              ([P_MAX_HP: QueryProp(P_MAX_HP),
+                P_MAX_SP: QueryProp(P_MAX_SP),
+                P_MAX_POISON: QueryProp(P_MAX_POISON) ]) );
+    m_add(mod->data,"MG.char.attributes",
+              ([ A_STR: QueryAttribute(A_STR),
+                 A_INT: QueryAttribute(A_INT),
+                 A_DEX: QueryAttribute(A_DEX),
+                 A_CON: QueryAttribute(A_CON) ]) );
+    m_add(mod->data,"MG.char.info",
+              ([P_LEVEL: QueryProp(P_LEVEL),
+                P_GUILD_LEVEL: QueryProp(P_GUILD_LEVEL),
+                P_GUILD_TITLE: QueryProp(P_GUILD_TITLE) ]) );
+    m_add(mod->data,"MG.char.wimpy",
+              ([P_WIMPY: QueryProp(P_WIMPY),
+                P_WIMPY_DIRECTION: QueryProp(P_WIMPY_DIRECTION) ]) );
+    m_add(squeue,"MG.char.base");
+    m_add(squeue,"MG.char.vitals");
+    m_add(squeue,"MG.char.maxvitals");
+    m_add(squeue,"MG.char.attributes");
+    m_add(squeue,"MG.char.info");
+    m_add(squeue,"MG.char.wimpy");
+    // dies wird direkt gesendet, weil es nicht gespeichert werden muss. (wird
+    // nur beim Start des Moduls gesendet).
+    GMCP_send("MG.char.infoVars", ([
+          P_LEVEL: "Spielerstufe", P_GUILD_LEVEL: "Gildenstufe",
+          P_GUILD_TITLE: "Gildentitel" ]) );
+  }
+  else
+  {
+    // nur die in data enthaltenen senden.
+    // jetzt erstmal alles aus data so sortieren, wie es gesendet werden
+    // muss... *seufz*
+    foreach(string key, mixed val : data)
+    {
+      switch(key)
+      {
+        case P_HP:
+        case P_SP:
+        case P_POISON:
+          (mod->data)["MG.char.vitals"] += ([key: val]);
+          m_add(squeue,"MG.char.vitals");
+          break;
+        case P_MAX_HP:
+        case P_MAX_SP:
+        case P_MAX_POISON:
+          (mod->data)["MG.char.maxvitals"] += ([key: val]);
+          m_add(squeue,"MG.char.maxvitals");
+          break;
+        case P_NAME:
+          (mod->data)["MG.char.base"] += ([key: Name(WER)]);
+          m_add(squeue,"MG.char.base");
+          break;
+        case P_RACE:
+        case P_PRESAY:
+        case P_TITLE:
+        case P_GUILD:
+          (mod->data)["MG.char.base"] += ([key: val]);
+          m_add(squeue,"MG.char.base");
+          break;
+        case A_DEX:
+        case A_STR:
+        case A_CON:
+        case A_INT:
+          (mod->data)["MG.char.attributes"] += ([key: val]);
+          m_add(squeue,"MG.char.attributes");
+          break;
+        case P_LEVEL:
+        case P_GUILD_LEVEL:
+        case P_GUILD_TITLE:
+          (mod->data)["MG.char.info"] += ([key: val]);
+          m_add(squeue,"MG.char.info");
+          break;
+        case P_WIMPY:
+        case P_WIMPY_DIRECTION:
+          (mod->data)["MG.char.wimpy"] += ([key: val]);
+          m_add(squeue,"MG.char.wimpy");
+          break;
+      }
+    }
+  }
+  GMCP_DEBUG("GMCPmod_MG_char_v1_send()",
+      sprintf("Data ready: %O, Sendqueue: %O",mod->data, squeue),50);
+
+  // Jetzt die squeue senden...
+  foreach(string key : squeue)
+  {
+    GMCP_send(key, (mod->data)[key]);
+  }
+}
+
+// Handler fuer das MG.Char Modul
+// Gerufen bei Empfang von Kommandos vom Client.
+protected void GMCPmod_MG_char_v1_recv(string cmd, mixed args)
+{
+  // dieses Modul bietet dem Client keine Kommandos an, daher ignorieren.
+  GMCP_DEBUG("GMCPmod_MG_Char_v1_recv","Client-Kommando ignoriert: "+cmd,20);
+}
+
+/*
+// Handler fuer das MG.Room Modul von GMCP
+// Gerufen, wenn Daten zu senden sind.
+protected void GMCPmod_MG_Room_v1_send(mapping data)
+{
+}
+
+// Handler fuer das Room Modul von GMCP
+// Gerufen bei Empfang von Kommandos vom Client.
+protected void GMCPmod_MG_Room_v1_recv(string cmd, mixed args)
+{
+  // dieses Modul bietet dem Client keine Kommandos an, daher ignorieren.
+  GMCP_DEBUG("GMCPmod_MG_Room_v1_recv","Client-Kommando ignoriert: "+cmd,20);
+}
+*/
+
+// Recv Handler fuer das comm.channel Modul von GMCP
+// Gerufen bei Empfang von Kommandos vom Client.
+protected void GMCPmod_comm_channel_v1_recv(string cmd, mixed args)
+{
+  GMCP_DEBUG("GMCPmod_comm_channel_v1_recv",
+      "Client-Kommando ignoriert: "+cmd,20);
+}
+
+// Send Handler fuer das comm.channel Modul von GMCP
+protected void GMCPmod_comm_channel_v1_send(mapping data)
+{
+  // Ganz simpel: einfach raussenden...
+  // Core uebergibt beim Einschalten 0 als data. Dieses modul muss aber beim
+  // Eisnchalten nix machen. Also nur ignorieren.
+  if (mappingp(data))
+    GMCP_send("comm.channel", data);
+}
+
+// Recv Handler fuer das MG.room Modul von GMCP
+// Gerufen bei Empfang von Kommandos vom Client.
+protected void GMCPmod_MG_room_v1_recv(string cmd, mixed args)
+{
+  GMCP_DEBUG("GMCPmod_MG_room_v1_recv",
+      "Client-Kommando ignoriert: "+cmd,20);
+}
+
+// Send Handler fuer das comm.channel Modul von GMCP
+protected void GMCPmod_MG_room_v1_send(mapping data)
+{
+  // Bekommt immer 0 als <data> uebergeben und sucht sich die Daten aus dem
+  // Raum zusammen.
+
+  // Baeh. Warum wird das denn ohne Env gerufen. :-(
+  if (!environment())
+    return;
+
+  // Blind gibt es auch per GMCP nix.
+  if (CannotSee(1))
+    return;
+
+  int restr = environment()->QueryProp(P_MAP_RESTRICTIONS);
+
+  if (restr & MR_NOINFO)
+    return; // gar keine info senden.
+
+  // Anmerkung: int_short() waere cool. Dummerweise uebertraegt das auch
+  // sichtbare Ausgange und Objekte. Insofern: geht nicht.
+  data = ([
+      P_SHORT: process_string(environment()->QueryProp(P_INT_SHORT)||"")+".",
+      "domain": environment()->QueryProp(P_DOMAIN) || "unbekannt",
+      ]);
+
+  // sichtbare Ausgaenge ausgeben
+  mixed hide = environment()->QueryProp(P_HIDE_EXITS);
+  if (hide && !pointerp(hide))
+      data["exits"] = ({});   // alle verstecken
+  else
+  {
+      // Query() verwenden, damit sowohl normale als auch Special Exits
+      // kommen... Die Summe von beiden wuerde auch gehen, aber dann hat man
+      // zwei unnoetige Filter in den Querymethoden. Hngl.
+      mapping exits = environment()->Query(P_EXITS, F_VALUE) || ([]);
+      if (pointerp(hide))
+        data["exits"] = m_indices(exits) - hide;
+      else
+        data["exits"] = m_indices(exits);
+  }
+
+  if (restr & MR_NOUID)
+    data["id"] = "";
+  else
+    data["id"] = hash(TLS_HASH_MD5, object_name(environment()));
+
+  GMCP_send("MG.room.info", data);
+}
+
+
+// Handler fuer das "char" Modul von GMCP (Modul von Aardwolf)
+// Gerufen, wenn Daten zu senden sind.
+protected void GMCPmod_char_v1_send(mapping data)
+{
+  mapping squeue = m_allocate(4,0);
+  struct gmcp_mod_s mod = gmcpdata["char"];
+  // mod->data fungiert hier auch als Cache der Daten. Die muss man naemlich
+  // leider immer alle senden, nicht nur die geaenderten.
+  if (!mappingp(data))
+  {
+    // Alle verfuegbaren Informationen senden...
+    mod->data = m_allocate(4);
+    m_add(mod->data, "char.base", (["name": query_real_name(),
+                                 "race": QueryProp(P_RACE)]) );
+    m_add(mod->data,"char.vitals", (["hp": QueryProp(P_HP),
+                                  "mana": QueryProp(P_SP)]) );
+    m_add(mod->data,"char.stats", ([ "str": QueryAttribute(A_STR),
+                               "int": QueryAttribute(A_INT),
+                               "dex": QueryAttribute(A_DEX),
+                               "con": QueryAttribute(A_CON) ]) );
+    m_add(mod->data,"char.status", (["level": QueryProp(P_LEVEL) ]) );
+    m_add(squeue,"char.base");
+    m_add(squeue,"char.vitals");
+    m_add(squeue,"char.stats");
+    m_add(squeue,"char.status");
+  }
+  else
+  {
+    // nur die in data enthaltenen senden.
+    // jetzt erstmal alles aus data so sortieren, wie es gesendet werden
+    // muss... *seufz*
+    foreach(string key, mixed val : data)
+    {
+      switch(key)
+      {
+        case P_HP:
+          (mod->data)["char.vitals"] += (["hp": val]);
+          m_add(squeue,"char.vitals");
+          break;
+        case P_SP:
+          (mod->data)["char.vitals"] += (["mana": val]);
+          m_add(squeue,"char.vitals");
+          break;
+        case P_NAME:
+        case P_RACE:
+          (mod->data)["char.base"] += ([key: val]);
+          m_add(squeue,"char.base");
+          break;
+        case A_DEX:
+        case A_STR:
+        case A_CON:
+        case A_INT:
+          (mod->data)["char.stats"] += ([key: val]);
+          m_add(squeue,"char.stats");
+          break;
+        case P_LEVEL:
+          (mod->data)["char.status"] += ([key: val]);
+          m_add(squeue,"char.status");
+          break;
+      }
+    }
+  }
+  GMCP_DEBUG("GMCPmod_char_v1_send()",
+      sprintf("Data ready: %O, Sendqueue: %O",mod->data, squeue),50);
+
+  // Jetzt die squeue senden...
+  foreach(string key : squeue)
+  {
+    GMCP_send(key, (mod->data)[key]);
+  }
+}
+
+// Handler fuer das "char" Modul von GMCP (Modul von Aardwolf)
+// Gerufen bei Empfang von Kommandos vom Client.
+protected void GMCPmod_char_v1_recv(string cmd, mixed data)
+{
+  // dieses Modul bietet dem Client keine Kommandos an, daher ignorieren.
+  GMCP_DEBUG("GMCPmod_char_v1_recv","Client-Kommando ignoriert: "+cmd,20);
+}
+
+
+// Handler fuer das "Char" Modul von GMCP (Modul von IRE)
+// Gerufen, wenn Daten zu senden sind.
+protected void GMCPmod_Char_v1_send(mapping data)
+{
+  mapping squeue = m_allocate(4,0);
+  struct gmcp_mod_s mod = gmcpdata["Char"];
+  // mod->data fungiert hier auch als Cache der Daten. Die muss man naemlich
+  // leider immer alle senden, nicht nur die geaenderten.
+  if (!mappingp(data))
+  {
+    // Alle verfuegbaren Informationen senden...
+    mod->data = m_allocate(4);
+    m_add(mod->data,"Char.Vitals", (["hp": QueryProp(P_HP),
+                                     "mp": QueryProp(P_SP),
+                                     "maxhp": QueryProp(P_MAX_HP),
+                                     "maxmp": QueryProp(P_MAX_SP) ]) );
+    m_add(mod->data,"Char.Status", (["level": QueryProp(P_LEVEL),
+                                     "guild": QueryProp(P_GUILD) ]) );
+    m_add(squeue,"Char.Vitals");
+    m_add(squeue,"Char.Status");
+    // dies wird direkt gesendet, weil es nicht gespeichert werden muss. (wird
+    // nur beim Start des Moduls gesendet).
+    GMCP_send("Char.StatusVars", ([
+          "level": "Spielerstufe", "guild": "Gilde" ]) );
+  }
+  else
+  {
+    // nur die in data enthaltenen senden.
+    // jetzt erstmal alles aus data so sortieren, wie es gesendet werden
+    // muss... *seufz*
+    foreach(string key, mixed val : data)
+    {
+      switch(key)
+      {
+        case P_HP:
+          (mod->data)["Char.Vitals"] += (["hp": val]);
+          m_add(squeue,"Char.Vitals");
+          break;
+        case P_SP:
+          (mod->data)["Char.Vitals"] += (["mp": val]);
+          m_add(squeue,"Char.Vitals");
+          break;
+        case P_MAX_HP:
+          (mod->data)["Char.Vitals"] += (["maxhp": val]);
+          m_add(squeue,"Char.Vitals");
+          break;
+        case P_MAX_SP:
+          (mod->data)["Char.Vitals"] += (["maxmp": val]);
+          m_add(squeue,"Char.Vitals");
+          break;
+        case P_LEVEL:
+        case P_GUILD:
+          (mod->data)["Char.Status"] += ([key: val]);
+          m_add(squeue,"Char.Status");
+          break;
+      }
+    }
+  }
+  GMCP_DEBUG("GMCPmod_Char_v1_send()",
+      sprintf("Data ready: %O, Sendqueue: %O",mod->data, squeue),50);
+
+  // Jetzt die squeue senden...
+  foreach(string key : squeue)
+  {
+    GMCP_send(key, (mod->data)[key]);
+  }
+}
+
+// Handler fuer das "char" Modul von GMCP (Modul von Aardwolf)
+// Gerufen bei Empfang von Kommandos vom Client.
+protected void GMCPmod_Char_v1_recv(string cmd, mixed args)
+{
+  // dieses Modul bietet dem Client keine Kommandos an, daher ignorieren.
+  GMCP_DEBUG("GMCPmod_Char_v1_recv","Client-Kommando ignoriert: "+cmd,20);
+}
+
+
+// Handler, der von telnetneg.c gerufen wird.
+private void _std_re_handler_gmcp(struct telopt_s opt, int action,
+                                  int *optargs)
+{
+  switch(action)
+  {
+    case LOCALON:
+      // super!
+      GMCP_DEBUG("recv:", "LOCALON",10);
+      gmcpdata = ([]);
+      opt->data = gmcpdata;   // daten auch dort ablegen.
+      // Coremodule in der Version 1 registrieren (es gibt nur eine).
+      GMCP_register_module("Core 1");
+#ifdef __GMCP_DEBUG__
+      GMCPmod_Core_v1_recv("Core.Debug",30);
+#endif
+      break;
+    case LOCALOFF:
+      // alles abschalten und Daten loeschen
+      GMCP_DEBUG("recv:", "LOCALOFF",10);
+      opt->data = 0;
+      gmcpdata = 0;
+      break;
+    case REMOTEON:
+    case REMOTEOFF:
+      // Huch. Auf Clientseite ist GMCP eigentlich nie an. Ignorieren...
+      GMCP_DEBUG("recv:", "Huh? REMOTE state changed?",50);
+      break;
+    case SB:
+      // Der eigentlich interessante Fall... GMCP-Kommandos
+      if (!mappingp(gmcpdata)) return; // GMCP wohl nicht eingeschaltet...
+      string cmd;
+      mixed args;
+      string payload=to_string(optargs);
+      GMCP_DEBUG("recv", payload,10);
+      if (sscanf(payload,"%s %s", cmd, args) != 2) {
+        // ist vermutlich ein Kommando ohne daten (oder Muell)
+        cmd = payload;
+        //args = 0;
+      }
+      else
+      {
+        string err=catch(args = json_parse(args);nolog);
+        if (err)
+        {
+          printf("\nFehler beim Parsen einer GMCP-Nachricht: %s. "
+                 "Nachricht war: '%s'\n"
+                 "Befehl: '%s', Argument: '%s'\n\n",err,payload,cmd,args||"");
+          return;
+        }
+      }
+      GMCP_DEBUG("recv", sprintf("Command: %s, Data: %O", cmd, args),20);
+
+      string *cmdparts = explode(cmd, ".");
+      struct gmcp_mod_s mod;
+      string modname;
+      // versuch, ein Modul fuer das Kommando zu finden. Anfangen mit der
+      // Annahme, dass bis zum letzten Punkt der Modulname geht und dann
+      // in jedem case einen Punkt kuerzer werdend.
+      switch(sizeof(cmdparts))
+      {
+        case 4:
+          modname = implode(cmdparts[0..2],".");
+          GMCP_DEBUG("trying modname... ", modname, 20 );
+          if (member(gmcpdata, modname)) {
+            mod = gmcpdata[modname];
+            funcall(mod->recvcl, cmd, args);
+            break;
+          }
+          // Fall-through!
+        case 3:
+          modname = implode(cmdparts[0..1],".");
+          GMCP_DEBUG("trying modname... ", modname, 20);
+          if (member(gmcpdata, modname)) {
+            mod = gmcpdata[modname];
+            funcall(mod->recvcl, cmd, args);
+            break;
+          }
+          // Fall-through!
+        case 2:
+          modname = implode(cmdparts[0..0],".");
+          GMCP_DEBUG("trying modname... ", modname, 20);
+          if (member(gmcpdata, modname)) {
+            mod = gmcpdata[modname];
+            funcall(mod->recvcl, cmd, args);
+            break;
+          }
+          // Hier ists jetzt nen Fehler.
+          GMCPERROR(sprintf("Unknown GMCP module for cmd %s",cmd));
+          break;
+        default:
+          // zuviele oder zuwenig . ;-)
+          GMCPERROR(sprintf("Illegal GMCP cmd %s with args %O",
+                cmd, args));
+          break;
+      }
+      // sbdata brauchen wir eigentlich nicht mehr.
+      opt->re_wishes->sbdata = 0;
+      break;
+  } // switch (action)
+}
+
+// wird von base.c nach Konnektierung gerufen.
+// Darf aber erst gerufen werden, wenn das Spielerobjekt fertig initialisiert
+// und eingelesen ist.
+protected void startup_telnet_negs()
+{
+  // evtl. war es ein reconnect, dann steht in gmcp noch alter kram drin. Der
+  // muss weg, koennte ja auch sein, dass der Client (jetzt) kein GMCP
+  // mehr
+  // will.
+  gmcpdata = 0;
+
+  // Hack besonderer Sorte: GMCP soll lokal eingeschaltet sein. Auf
+  // Clientseiten ist es laut Protokoll nicht vorgesehen, daher duerfen
+  // (sollten?) wir kein DO an den Client senden. Wir brauchen aber einen
+  // remote handler, um die Wuensche vom Client zu verarbeiten. Daher erstmal
+  // nur den local handler binden (und gleichzeitig negotiation anstossen) und
+  // dann direkt danach den remote handler auch binden (ohne erneute
+  // negotiation zu starten). Achja und wir nehmen die gleiche Funktion als
+  // Handler fuer remote und lokal.
+  bind_telneg_handler(TELOPT_GMCP, 0, #'_std_re_handler_gmcp, 1);
+  bind_telneg_handler(TELOPT_GMCP, #'_std_re_handler_gmcp,
+                                   #'_std_re_handler_gmcp, 0);
+}
+