Offline-TMs integrieren

Vaults koennen beim Kobold hinterlegt werden, d.h. Spieler koennen
die Funktionalitaet einschalten, bei Abwesenheit TMs zu empfangen.
Die Vaults werden beim Login abgerufen und in den 'online'-Kobold
und die TM-History uebertragen. Von dort werden sie letztendlich
wie alle anderen Nachrichten auch angezeigt.

Change-Id: Ib8e8b21304795dd9f0be057f9357c5f5239f260c
diff --git a/std/player/comm.c b/std/player/comm.c
index 2dc0601..2dbdc75 100644
--- a/std/player/comm.c
+++ b/std/player/comm.c
@@ -65,9 +65,13 @@
 private nosave mixed *report_cache;
 
 // Puffer fuer Kobold.
-private nosave struct msg_buffer_s kobold = (<msg_buffer_s>
+private nosave struct kobold_buffer_s kobold = (<kobold_buffer_s>
                                              buf: allocate(32),
                                              index: -1,);
+// Vault fuer Offline-TMs
+// Der KOBOLD (/secure/kobold) muss immer eine Referenz auf dieses Objekt
+// halten.
+private nosave lwobject "/std/player/comm_vault" commvault;
 
 // Colourmap
 // TODO: spaeter konfigurierbar machen
@@ -83,11 +87,15 @@
 
 public varargs int ReceiveMsg(string msg, int msg_type, string msg_action,
                               string msg_prefix, object origin);
+private int check_ignores(string msg, int msg_type, string msg_action,
+                            string msg_prefix, object|string origin);
+private varargs void add_struct_tell_history(struct kobold_msg_s msg,
+                                         int sent, int recv, int flags );
 
 // erzeugt sortierte Liste an Kommunikationspartnern
 private string *sorted_commpartners(int reversed);
 
-void create()
+protected void create()
 {
   ::create();
   Set(P_ALERT, SAVE, F_MODE_AS);
@@ -98,6 +106,7 @@
   Set(P_IGNORE, ([]), F_VALUE);
   Set(P_IGNORE, SAVE, F_MODE);
   Set(P_BUFFER, SAVE, F_MODE);
+  SetProp(P_BUFFER, KOBOLD_OFFLINE);
   Set(P_MESSAGE_PREPEND, SAVE, F_MODE_AS);
   Set(P_MESSAGE_BEEP, SAVE, F_MODE_AS);
 }
@@ -147,29 +156,19 @@
   colourmap = build_colourmap(ttype);
 }
 
-// called from base.c in Reconnect()
-protected void reconnect() {
-  // Cache fuer den report zuruecksetzen, der koennte veraltet sein (insb.
-  // falls in der letzten Session GMCP benutzt wurde und jetzt nicht).
-  report_cache = 0;
-}
-
-protected void updates_after_restore(int newflag) {
-  // Colourmap aktualisieren nach Restore
-  colourmap = build_colourmap(QueryProp(P_TTY));
-
-  // Altes Ignoriere loeschen...
-  mixed ign = Query(P_IGNORE,F_VALUE);
-  if (!mappingp(ign))
+private void setup_comm_vault()
+{
+  if (!commvault && (QueryProp(P_BUFFER) & KOBOLD_OFFLINE))
   {
-    if (pointerp(ign))
-      ReceiveNotify(break_string(
-        "Deine Ignoriere-Einstellungen wurden soeben geloescht, "
-        "weil es eine Aktualisierung der Ignorierefunktion gab, "
-        "bei der eine Konversion der Daten leider nicht "
-        "moeglich war.",78), 0);
-
-    Set(P_IGNORE, ([]), F_VALUE);
+    // Schauen, ob ein Vault im KOBOLD existiert.
+    commvault = KOBOLD->RetrieveVault();
+    // Wenn nicht, aber eins gewuenscht ist, wird eins erstellt und in KOBOLD
+    // hinterlegt.
+    if (!commvault)
+    {
+      commvault = new_lwobject("/std/player/comm_vault");
+      KOBOLD->DepositVault(commvault);
+    }
   }
 }
 
@@ -501,9 +500,45 @@
   return MSG_BUFFERED;
 }
 
+// speichert den Inhalt vom commvault im Kobold und der TM-History
+private void process_comm_vault(lwobject "/std/player/comm_vault" vault)
+{
+  struct kobold_msg_s *buffer = vault.Retrieve();
+  if (!sizeof(buffer))
+      return;
+  foreach(struct kobold_msg_s msg: buffer)
+  {
+    // Spieler-definiertes Ignoriere? (nur typen uebergeben, keine Flags)
+    int res = check_ignores(msg.msg, msg.type, msg.action, msg.prefix,
+                            msg.sendername);
+    if (res) {
+        // Nachricht wegwerfen. Aber ggf. den Absender informieren, wenn der
+        // online ist und wir nicht Invis
+        object pl = find_player(msg.sendername);
+        if (pl &&
+            (!QueryProp(P_INVIS) || IS_LEARNER(pl)) )
+          pl->ReceiveNotify(sprintf("Deine Nachricht an %s wurde "
+                "ignoriert.",capitalize(getuid(this_object()))), MA_TELL);
+        continue;
+    }
+
+    // wenn der Puffer zu klein ist, Groesse verdoppeln.
+    // Keine Pruefung hier, weil das vault schon die Groesse beschraenkt und
+    // der Inhalt auf jeden Fall passen soll.
+    if (kobold->index >= sizeof(kobold->buf)-1)
+        kobold->buf += allocate(sizeof(kobold->buf));
+    kobold->index += 1;
+    kobold->buf[kobold->index] = msg;
+
+    // TM-History
+    add_struct_tell_history(msg, 0, 1, MSGFLAG_TELL);
+  }
+  vault.Empty();
+}
+
 private void _flush_cache(int verbose) {
   // nur mit genug Evalticks ausgeben.
-  if (get_eval_cost() < 100000) return;
+  if (get_eval_cost() < 500000) return;
   if (kobold->index >= 0)
   {
     ReceiveMsg("Ein kleiner Kobold teilt Dir folgendes mit:",
@@ -517,20 +552,34 @@
       // in der Schleife unten gab: dann ist index nicht auf -1 gesetzt
       // worden, aber einige Nachrichten sind schon geloescht.
       if (!structp(msg)) continue;
+      // Im folgenden nicht den string in der struct aendern (die wird ggf.
+      // auch noch von der TM-History gebraucht).
+      string msgstr = msg.msg;
+      // Wenn Nachricht schon laenger her ist, Uhrzeit anhaengen, aber ggf.
+      // muss ein \n abgeschnitten werden.
+      if (msg.timestamp < time() - 3600)
+      {
+        if (msgstr[<1] == '\n')
+          msgstr = msgstr[0..<2]
+                      + " [" + strftime("%d.%m.%y %T", msg.timestamp) + "]";
+        else
+          msgstr = msgstr
+                      + " [" + strftime("%d.%m.%y %T", msg.timestamp) + "]";
+      }
       // Ausgabe via efun::tell_object(), weil die Arbeit von ReceiveMsg()
       // schon getan wurde. Allerdings muessen wir uns noch um den Umbruch
       // und Farben kuemmern.
-      msg->msg = terminal_colour(msg->msg, colourmap);
+      msgstr = terminal_colour(msgstr, colourmap);
       if ((msg->type) & MSG_DONT_WRAP)
-        msg->msg = (msg->prefix ? msg->prefix : "") + msg->msg;
+        msgstr = (msg->prefix ? msg->prefix : "") + msgstr;
       else
       {
         int bsflags = msg->type & MSG_ALL_BS_FLAGS;
         if (prepend)
           bsflags |= BS_PREPEND_INDENT;
-        msg->msg = break_string(msg->msg, 78, msg->prefix, bsflags);
+        msgstr = break_string(msgstr, 78, msg->prefix, bsflags);
       }
-      efun::tell_object(this_object(), msg->msg);
+      efun::tell_object(this_object(), msgstr);
       kobold->buf[i]=0;
     }
     kobold->index=-1;
@@ -545,17 +594,48 @@
 
 varargs int cmd_kobold(string arg)
 {
+  if (!sizeof(arg))
+  {
+    _flush_cache(1);
+    return 1;
+  }
   switch(arg)
   {
     case "ein":
-      SetProp(P_BUFFER, 1);
-      printf("Der Kobold merkt sich jetzt alles!\n"); break;
+      SetProp(P_BUFFER, KOBOLD_ONLINE|KOBOLD_OFFLINE);
+      ReceiveNotify("Der Kobold merkt sich jetzt alles!");
+      break;
+    case "online":
+      SetProp(P_BUFFER, KOBOLD_ONLINE);
+      ReceiveNotify("Der Kobold merkt sich jetzt alles, "
+                    "wenn Du online bist!");
+      break;
+    case "offline":
+      SetProp(P_BUFFER, KOBOLD_OFFLINE);
+      ReceiveNotify("Der Kobold merkt sich jetzt alles, "
+                    "wenn Du offline bist!");
+      break;
     case "aus":
       SetProp(P_BUFFER, 0);
-      printf("Der Kobold wird Dich nicht stoeren!\n"); break;
-    default: if(arg) printf("Der Kobold sagt: kobold ein oder kobold aus\n");
+      ReceiveNotify("Der Kobold wird Dich nicht stoeren!");
+      break;
+    default:
+        ReceiveNotify("Der Kobold sagt: Was soll ich mir denn merken? "
+                      "('ein', 'aus', 'offline' oder 'online')");
+        return 1;
   }
-  _flush_cache(1);
+  if (QueryProp(P_BUFFER) & KOBOLD_OFFLINE)
+      setup_comm_vault();
+  else
+  {
+      // Comm-Vault entfernen. Aber zur Sicherheit nochmal abrufen und
+      // verarbeiten (sollte aber eigentlich ueberfluessig sein)
+      commvault = KOBOLD->ForgetVault();
+      if (commvault) {
+        process_comm_vault(commvault);
+        commvault = 0;
+      }
+  }
   return 1;
 }
 
@@ -629,12 +709,12 @@
   }
 }
 
-private varargs void add_to_tell_history( string uid, int sent, int recv,
-                                 string message, string indent, int flags )
+private varargs void add_struct_tell_history(struct kobold_msg_s msg,
+                                         int sent, int recv, int flags )
 {
   /* tell_history ist ein Mapping mit UIDs der Gespraechspartner als Key.
      Als Wert ist eine Strukur vom Typ chat_s eingetragen.
-     Strukturen chat_s und stored_msg_s sind in /std/player/comm_structs.c
+     Strukturen chat_s und kobold_msg_s sind in /std/player/comm_structs.c
      definiert.
      TODO fuer spaeter, gerade keine Zeit fuer:
      Als Wert ist ein Array von chat_s enthalten, wobei das 0. Element das
@@ -643,31 +723,22 @@
      Element ist aeltestes Gespraech).
      */
 
-  //TODO: Entfernen, wenn das nicht mehr passiert.
-  if (!stringp(uid))
-  {
-    ReceiveMsg(sprintf(
-      "\nadd_to_tell_history(): got bad uid argument %O."
-      "sent: %d, recv: %d, flags: %d, msg: %s", 
-      uid, sent, recv, flags, message),MT_DEBUG|MSG_BS_LEAVE_LFS,0,0,ME);
-  }
-
   // Gespraechspartner fuer erwidere auch ohne tmhist speichern.
   if (flags & (MSGFLAG_TELL|MSGFLAG_RTELL))
-    last_comm_partner = uid;
+    last_comm_partner = msg.sendername;
 
   // ist ein sortiertes Array von max. MAX_SAVED_CHATS Groesse, welches die
   // Spieler enthaelt, denen man schon was mitgeteilt hat. Aktuellste am
   // Anfang.
   if (sent) {
     if (!sizeof(commreceivers))
-      commreceivers = ({uid});
-    else if (commreceivers[0] != uid) {
+      commreceivers = ({msg.sendername});
+    else if (commreceivers[0] != msg.sendername) {
       // nur wenn der aktuelle Partner nicht am Anfang steht, muss man hier was
       // tun. Comm-Partner an den Anfang stellen und ggf. alten Eintrag
       // entfernen.
       // TODO: Effizienter gestalten.
-      commreceivers = ({uid}) + (commreceivers-({uid}));
+      commreceivers = ({msg.sendername}) + (commreceivers-({msg.sendername}));
       // ggf. kuerzen. (wenn !tell_history_enabled, wird es ggf. unten
       // gemacht, denn die Hist muss min. alle UID enthalten, die auch in
       // commreceivers drin sind.)
@@ -680,12 +751,12 @@
   if (!tell_history_enabled)
     return;
 
-  if (!indent && message[<1] == 10)
-      message = message[..<2];
+  if (msg.msg[<1] == '\n')
+      msg.msg = msg.msg[..<2];
 
   struct chat_s chat;
   // Gespraechspartner unbekannt?
-  if (!member(tell_history, uid)) {
+  if (!member(tell_history, msg.sendername)) {
     // zuviele Gespraeche in Hist? >= ist Absicht weil ja gleich noch eins
     // dazu kommt.
     if (sizeof(tell_history) >= MAX_SAVED_CHATS) {
@@ -704,16 +775,16 @@
         commreceivers-=({deluid});
     }
     // neues Gespraech anlegen
-    chat = (<chat_s> uid: uid, time_first_msg: time(), 
-               time_last_msg: time(),
+    chat = (<chat_s> uid: msg.sendername, time_first_msg: msg.timestamp,
+               time_last_msg: msg.timestamp,
          sentcount: sent, recvcount: recv,
          msgbuf: 0, ptr: 0 );
-    tell_history[uid] = chat;
+    tell_history[msg.sendername] = chat;
   }
   else {
     // Gespraechspartner bekannt, altes Gespraech weiterbenutzen
-    chat = tell_history[uid];
-    chat->time_last_msg = time();
+    chat = tell_history[msg.sendername];
+    chat->time_last_msg = msg.timestamp;
     chat->sentcount += sent;
     chat->recvcount += recv;
   }
@@ -726,22 +797,27 @@
   if (!pointerp(chat->msgbuf))
     chat->msgbuf = allocate(MAX_SAVED_MESSAGES);
 
-  // Message-Struktur ermitteln oder neu anlegen
-  struct stored_msg_s msg;
-  if (!structp(chat->msgbuf[chat->ptr])) {
-    // neue Struct ins Array schreiben
-    chat->msgbuf[chat->ptr] = msg = (<stored_msg_s>);
-  }
-  else {
-    // alte Struct ueberschreiben
-    msg = chat->msgbuf[chat->ptr];
-  }
+  // neue Struct ins Array schreiben
+  chat->msgbuf[chat->ptr] = msg;
   // Index auf naechste Messagestruktur ermitteln
   chat->ptr = (chat->ptr + 1) % MAX_SAVED_MESSAGES;
-  // Message speichern
-  msg->msg = message;
-  msg->prefix = indent;
-  msg->timestamp = time();
+}
+
+private varargs void add_to_tell_history( string uid, int sent, int recv,
+                                 string message, string indent, int flags )
+{
+  //TODO: Entfernen, wenn das nicht mehr passiert.
+  if (!stringp(uid))
+  {
+    ReceiveMsg(sprintf(
+      "\nadd_to_tell_history(): got bad uid argument %O."
+      "sent: %d, recv: %d, flags: %d, msg: %s", 
+      uid, sent, recv, flags, message),MT_DEBUG|MSG_BS_LEAVE_LFS,0,0,ME);
+  }
+  // Message-Struktur anlegen
+  struct kobold_msg_s msg = (<kobold_msg_s> msg: message, prefix: indent,
+                             sendername: uid, timestamp: time());
+  add_struct_tell_history(msg, sent, recv, flags);
 }
 
 protected void clear_tell_history(int force)
@@ -989,7 +1065,7 @@
   msg = break_string(msg, 78, indent,
     (QueryProp(P_MESSAGE_PREPEND) ? BS_PREPEND_INDENT : 0) | BS_LEAVE_MY_LFS);
 
-  if(QueryProp(P_BUFFER) &&
+  if((QueryProp(P_BUFFER) & KOBOLD_ONLINE) &&
      (deaf ||
       query_editing(this_object()) ||
       query_input_pending(this_object())))
@@ -1369,7 +1445,7 @@
         return 1;
       case -2:
         // check KOBOLD
-        ob = find_object(KOBOLD);
+        ob = load_object(KOBOLD);
         lname = ({string|int})ob->find_player(lower_case(who));
         if (lname == -1) {
           ReceiveNotify("Das war nicht eindeutig!",MA_TELL);
@@ -1831,11 +1907,11 @@
 
     More(sprintf("%@s", map(data[ptr..MAX_SAVED_MESSAGES-1] +
                               data[0..ptr-1],
-         function string (struct stored_msg_s msg) {
+         function string (struct kobold_msg_s msg) {
              if (!structp(msg)) return "";
                return break_string(terminal_colour(msg->msg, colourmap)
-                 + " <"
-                 + strftime("%H:%M:%S",msg->timestamp) + ">", 78,
+                 + " ["
+                 + strftime("%H:%M:%S",msg->timestamp) + "]", 78,
                  msg->prefix || "", msg->prefix ? BS_LEAVE_MY_LFS : 0);
          } ) ) );
     return 1;
@@ -1986,7 +2062,7 @@
 // Typen. 
 // Rueckgabe: 0 oder MSG_IGNORED | MSG_VERB_IGN | MSG_MUD_IGN
 private int check_ignores(string msg, int msg_type, string msg_action,
-                            string msg_prefix, object origin)
+                            string msg_prefix, object|string origin)
 {
   // Einige Dinge lassen sich nicht ignorieren.
   if (msg_type & (MT_NEWS|MT_NOTIFICATION))
@@ -1996,9 +2072,14 @@
   // eine ignorierbare msg_action geben.
   else if (stringp(msg_action) && origin && origin != ME)
   {
-    string srcname =
+    string srcname;
+    if (objectp(origin))
+      srcname =
       (query_once_interactive(origin) ? origin->query_real_name()
                                       : origin->name(WER) || "");
+    else
+      srcname = origin;
+
     mapping ign = Query(P_IGNORE, F_VALUE);
 
     if (member(ign, srcname))
@@ -2178,7 +2259,7 @@
         || QueryProp(P_EARMUFFS))
     {
       if (!(flags & MSG_DONT_BUFFER)
-            && QueryProp(P_BUFFER))
+            && (QueryProp(P_BUFFER) & KOBOLD_ONLINE))
       {
         // Nachricht soll im Kobold gespeichert werden.
         return add_to_kobold(msg, msg_type, msg_action, msg_prefix, origin);
@@ -2213,3 +2294,36 @@
 
   return MSG_DELIVERED;
 }
+
+// called from base.c in Reconnect()
+protected void reconnect() {
+  // Cache fuer den report zuruecksetzen, der koennte veraltet sein (insb.
+  // falls in der letzten Session GMCP benutzt wurde und jetzt nicht).
+  report_cache = 0;
+}
+
+protected void updates_after_restore(int newflag) {
+  // Colourmap aktualisieren nach Restore
+  colourmap = build_colourmap(QueryProp(P_TTY));
+
+  // Altes Ignoriere loeschen...
+  mixed ign = Query(P_IGNORE,F_VALUE);
+  if (!mappingp(ign))
+  {
+    if (pointerp(ign))
+      ReceiveNotify(break_string(
+        "Deine Ignoriere-Einstellungen wurden soeben geloescht, "
+        "weil es eine Aktualisierung der Ignorierefunktion gab, "
+        "bei der eine Konversion der Daten leider nicht "
+        "moeglich war.",78), 0);
+
+    Set(P_IGNORE, ([]), F_VALUE);
+  }
+  // ggf. Comm-Vault abrufen oder neu erstellen.
+  setup_comm_vault();
+  // Wenn es eins gibt, den Inhalt zu unserem internen Koboldpuffer
+  // hinzufuegen, von wo es spaeter angezeigt wird.
+  if (commvault)
+      process_comm_vault(commvault);
+}
+
diff --git a/std/player/comm_structs.c b/std/player/comm_structs.c
index 03c132a..c0b3322 100644
--- a/std/player/comm_structs.c
+++ b/std/player/comm_structs.c
@@ -11,32 +11,29 @@
 
 inherit "/std/living/comm_structs";
 
+// Struct fuer im Kobold, im Comm-Vault und in der TM-History gespeicherte
+// Nachrichten.
 // Basiert auf allgemeiner msg_s Struktur aus living/comm-structs.c
 struct kobold_msg_s (msg_s) {
   string action;    // Messageaction fuer ReceiveMsg
   string sendername;// Ursprung der Nachricht
-};
-
-// Fuer gespeicherte Nachrichten (in der comm-History von Spielern) wird die
-// kobold_msg verwendet, aber es ist noch ein zusaetzlicher Zeitstempel
-// noetig.
-struct stored_msg_s (kobold_msg_s) {
   int timestamp;    // Zeitstempel der Nachricht
 };
 
-struct msg_buffer_s {
-  //struct msg_s *buf;
-  mixed *buf;
+// Buffer fuer den Kobold im Spielerobjekt
+struct kobold_buffer_s {
+  struct kobold_msg_s *buf;
   int index;
 };
 
+// Struktur fuer Gespraeche in der TM-History
 struct chat_s {
   string uid;           // UID des Gespraechspartners
   int time_first_msg;   // Zeit der ersten Nachricht
   int time_last_msg;    // Zeit der letzen Nachricht
   int sentcount;        // Anzahl gesendeter Nachrichten
   int recvcount;        // Anzahl empfangener Nachrichten
-  mixed msgbuf;         // Array von msg_s (Art Ringpuffer)
+  struct kobold_msg_s *msgbuf;  // Array von kobold_msg_s (Art Ringpuffer)
   int ptr;              // Pointer auf die naechste zu ueberschreibende msg_s
                         // in msgbuf
 };
diff --git a/std/player/comm_vault.c b/std/player/comm_vault.c
index 81541d6..af983ec 100644
--- a/std/player/comm_vault.c
+++ b/std/player/comm_vault.c
@@ -5,10 +5,10 @@
 
 inherit "/std/player/comm_structs";
 
-nosave string uuid;
+private nosave string uuid;
 // nosave ist wichtig - niemand soll den buffer mit save_value() auslesen
 // koennen!
-nosave struct stored_msg_s *buffer = ({});
+private nosave struct kobold_msg_s *buffer = ({});
 
 protected void create_lw()
 {
@@ -20,9 +20,9 @@
     return uuid;
 }
 
-public struct stored_msg_s *Retrieve()
+public struct kobold_msg_s *Retrieve()
 {
-//    if (getuid(this_object()) == getuid(previous_object()))
+    if (uuid == getuuid(previous_object()))
     {
         return buffer;
     }
@@ -31,7 +31,7 @@
 
 public void Empty()
 {
-//    if (getuid(this_object()) == getuid(previous_object()))
+    if (uuid == getuuid(previous_object()))
     {
         buffer = ({});
     }
@@ -48,7 +48,7 @@
     if (sizeof(buffer) >= MAX_KOBOLD_LIMIT)
         return MSG_BUFFER_FULL;
 
-    buffer += ({ (<stored_msg_s> msg:msg, type:msg_type,
+    buffer += ({ (<kobold_msg_s> msg:msg, type:msg_type,
                   prefix: msg_prefix, action: msg_action,
                   sendername: ({string})origin.query_real_name(),
                   timestamp: time())