Erwaehnungen markieren und aufmerksam machen.

Bei direkter Kommunikation sowie Rufen und Sagen wird eine
Benachrichtigung (aktuell Piepston) gesendet, wenn der Char
direkt mittels eines @charname erwaehnt wird (Mention).

Ausserdem werden diese Erwaehnungen farblich markiert,
sofern der Client das unterstuetzt.

Change-Id: Ifd2e87fb994edc5a76a8071ed2ff3684232250b5
diff --git a/std/npc/comm.c b/std/npc/comm.c
index c1b13af..3958915 100644
--- a/std/npc/comm.c
+++ b/std/npc/comm.c
@@ -46,6 +46,9 @@
 public varargs int ReceiveMsg(string msg, int msg_typ, string msg_action,
                               string msg_prefix, object origin)
 {
+  // etwaige Farbtags rausersetzen
+  msg = terminal_colour(msg, ([0:""]));
+
   // compatibility...
   if (msg_typ & MSG_DONT_WRAP)
     catch_tell(sprintf("%s%s", msg_prefix||"", msg));
diff --git a/std/player/base.c b/std/player/base.c
index 71591e2..e39f33a 100644
--- a/std/player/base.c
+++ b/std/player/base.c
@@ -3554,6 +3554,7 @@
 static string _set_tty(string str) {
   if(str != "dumb" && str != "vt100" && str != "ansi")
     return Query(P_TTY);
+  comm::set_colourmap(str);
   return Set(P_TTY, str);
 }
 
diff --git a/std/player/comm.c b/std/player/comm.c
index 0d5471d..7025a86 100644
--- a/std/player/comm.c
+++ b/std/player/comm.c
@@ -37,6 +37,8 @@
 #include <strings.h>
 #include <regexp.h>
 #include <interactive_info.h>
+#include <ansi.h>
+#include <assert.h>
 
 #define TELLHIST_DISABLED   0
 #define TELLHIST_NO_MESSAGE 1
@@ -68,6 +70,11 @@
                                              index: -1,);
 #define MAX_KOBOLD_LIMIT 256
 
+// Colourmap
+// TODO: spaeter konfigurierbar machen
+private mapping build_colourmap(string ttype="ansi");
+private nosave mapping colourmap = build_colourmap();
+
 varargs string name(int casus, int demonst);
 
 //local property prototypes
@@ -101,6 +108,46 @@
   set_next_reset(-1);
 }
 
+private mapping build_colourmap(string ttype)
+{
+  mapping res = ([0:""]);
+  switch(ttype)
+  {
+    case "dumb":
+      return res;
+    case "ansi":
+      res = ([ 0:ANSI_NORMAL, "normal": ANSI_NORMAL,
+               "bold": ANSI_BOLD, "underlined": ANSI_UNDERL,
+               "blink": ANSI_BLINK, "invers": ANSI_INVERS,
+               "black": ANSI_BLACK, "red": ANSI_RED,
+               "green": ANSI_GREEN, "yellow": ANSI_YELLOW,
+               "blue": ANSI_BLUE, "purple": ANSI_PURPLE,
+               "cyan": ANSI_CYAN, "white": ANSI_WHITE,
+               "bg_black": ANSI_BG_BLACK, "bg_red": ANSI_BG_RED,
+               "bg_green": ANSI_BG_GREEN, "bg_yellow": ANSI_BG_YELLOW,
+               "bg_blue": ANSI_BG_BLUE, "bg_purple": ANSI_BG_PURPLE,
+               "bg_cyan": ANSI_BG_CYAN, "bg_white": ANSI_BG_WHITE,
+               "mention": ANSI_BOLD+ANSI_BG_BLUE,
+             ]);
+      break;
+    case "vt100":
+      res += ([0:ANSI_NORMAL, "normal": ANSI_NORMAL,
+               "bold": ANSI_BOLD, "underlined": ANSI_UNDERL,
+               "blink": ANSI_BLINK, "invers": ANSI_INVERS,
+               "mention": ANSI_BOLD,
+              ]);
+      break;
+    default:
+       assert(1, "Ungueltiger Terminaltyp in build_colourmap");
+  }
+  return res;
+}
+
+protected void set_colourmap(string ttype="ansi")
+{
+  colourmap = build_colourmap(ttype);
+}
+
 // called from base.c in Reconnect()
 protected void reconnect() {
   // Cache fuer den report zuruecksetzen, der koennte veraltet sein (insb.
@@ -109,6 +156,9 @@
 }
 
 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))
@@ -469,8 +519,9 @@
       // worden, aber einige Nachrichten sind schon geloescht.
       if (!structp(msg)) continue;
       // Ausgabe via efun::tell_object(), weil die Arbeit von ReceiveMsg()
-      // schon getan wurde. Allerdings muessen wir uns noch um den UMbruch
-      // kuemmern.
+      // schon getan wurde. Allerdings muessen wir uns noch um den Umbruch
+      // und Farben kuemmern.
+      msg->msg = terminal_colour(msg->msg, colourmap);
       if ((msg->type) & MSG_DONT_WRAP)
         msg->msg = (msg->prefix ? msg->prefix : "") + msg->msg;
       else
@@ -532,7 +583,12 @@
      (name == ignore[0] && member(ignore[1..], verb) != -1));
 }
 
-private void comm_beep(string msg_action)
+// Die Nachricht kommt mit einem Alert oder Mention. comm_beep() prueft, ob
+// ein Piepston ausgegeben werden soll (langfristig andere Benachrichtigungen
+// geplant!).
+// Ueblicherweise werden nur alle <P_MESSAGE_BEEP> Sekunden Toene
+// uebermittelt.
+private void comm_beep(string msg_action, int mention=0)
 {
   // Wenn Alerts komplett abgeschaltet sind, gibts nix.
   int alert_config = ({int})QueryProp(P_ALERT);
@@ -563,6 +619,9 @@
         required |= MB_MISC;
         break;
   }
+  if (mention)
+      required |= MB_MENTION;
+
   // wenn in alert min. ein notwendiges Flag drin ist darf gepiepst werden
   if (required & alert_config)
   {
@@ -1031,6 +1090,7 @@
         "sag": MB_SAY, "sage": MB_SAY,
         "ebene": MB_CHANNEL, "ebenen": MB_CHANNEL,
         "ruf": MB_SHOUT, "rufe": MB_SHOUT,
+        "erwaehnung": MB_MENTION, "erwaehnungen": MB_MENTION,
         "sonstige": MB_MISC, "sonstiges": MB_MISC,
         "alle": MB_ALL, "alles": MB_ALL,
         ]);
@@ -1064,6 +1124,7 @@
     MB_SAY: "sage",
     MB_TELL: "teile mit",
     MB_CHANNEL: "Ebenenmeldungen",
+    MB_MENTION: "Erwaehnungen",
     MB_SHOUT: "rufe",
     MB_MISC: "sonstige",]);
   string types = CountUp(map(filter(m_indices(text), #'_alert_filter_flags), text));
@@ -1702,7 +1763,8 @@
                               data[0..ptr-1],
          function string (struct stored_msg_s msg) {
              if (!structp(msg)) return "";
-               return break_string( msg->msg + " <"
+               return break_string(terminal_colour(msg->msg, colourmap)
+                 + " <"
                  + strftime("%H:%M:%S",msg->timestamp) + ">", 78,
                  msg->prefix || "", msg->prefix ? BS_LEAVE_MY_LFS : 0);
          } ) ) );
@@ -1982,6 +2044,34 @@
     if (res) return res;
   }
 
+  // Mentions in der Form @Charname werden in Kommunikation oder beim Hoeren
+  // von Rufen und Sagen markiert und gegebenfalls gibt es ein Pieps dafuer
+  int mention;
+  if ((type & MT_COMM)
+      || ((type & MT_LISTEN) && msg_action in ({MA_SAY, MA_SHOUT}))
+      )
+  {
+    // Um den Charnamen Tags fuer terminal_colour() einfuegen.
+    // Lookahead und Lookbehind assertions um die Whitespaces um das Wort
+    // nicht in den Match einzuschliessen (und zu ersetzen).
+    string tmp = regreplace(msg,
+                    sprintf("(?<=\\s)@%s(?=\\s*)",getuid(ME)),
+                    sprintf("%%^mention%%^@%s%%^normal%%^",
+                            capitalize(getuid(ME))),
+                    RE_PCRE|RE_GLOBAL|RE_CASELESS);
+    send_debug(ME, tmp);
+    // Der Vergleich ist weniger schlimm als es aussieht, weil die Strings
+    // unterschiedlich gross sein werden und daher nicht zeichenweise
+    // verglichen werden muessen. Es ist dann jedenfalls schneller als
+    // getrennt mit regmatch oder strstr zu schauen, ob @charname
+    // vorkommt.
+    if (tmp != msg)
+    {
+        msg = tmp;
+        mention = 1;
+    }
+  }
+
   // Fuer MT_COMM gibt es ein paar Sonderdinge zu machen.
   if ((type & MT_COMM))
   {
@@ -2032,8 +2122,12 @@
     }
   }
 
-  if ((msg_type & MSG_ALERT))
-    comm_beep(msg_action);
+  // Farbtags ersetzen
+  msg = terminal_colour(msg, colourmap);
+
+  // Alertton senden?
+  if ((msg_type & MSG_ALERT) || mention)
+    comm_beep(msg_action, mention);
 
   // Ausgabenachricht bauen und an den Spieler senden.
   if (flags & MSG_DONT_WRAP)