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/doc/sphinx/lfun/ReceiveMsg.rst b/doc/sphinx/lfun/ReceiveMsg.rst
index 153cc73..7fad083 100644
--- a/doc/sphinx/lfun/ReceiveMsg.rst
+++ b/doc/sphinx/lfun/ReceiveMsg.rst
@@ -60,6 +60,7 @@
     4) Speicherung im Kobold (Typ MT_COMM + aktiver Kobold)
     5) Umbrechen unter Beruecksichtigung von indent, 'msg_typ'-Flags
        fuer Umbruch und P_PREPEND_BS
+    6) Ersetzen von Farbtags entsprechendes Terminaltyps des Clients.
 
     Raeume definieren standardmaessig ebenfalls ein ReceiveMsg(), welches in
     jedem Objekt im Raum ReceiveMsg() mit den uebergebenen Argumenten aufruft.
diff --git a/doc/sphinx/wiz/farben.rst b/doc/sphinx/wiz/farben.rst
new file mode 100644
index 0000000..944811e
--- /dev/null
+++ b/doc/sphinx/wiz/farben.rst
@@ -0,0 +1,63 @@
+Farben im Mud
+=============
+
+  Farben in der Ausgabe werden im MorgenGrauen bislang eher selten verwendet und die Einfaerbung meistens den Triggern des Clients ueberlassen.
+  Moechte man allerdings direkt etwas (farblich) markieren, stehen hierfuer einige Hervorhebungs-Tags zur Verfuegung, welche in ReceiveMsg() durch die passenden Steuercodes fuer den Terminaltypen des Clients ersetzt werden.
+
+  Hierfuer muss die Ausgabe lediglich durch ReceiveMsg() und deren Wrapper erfolgen, weitere Massnahmen sind weder noetig noch empfohlen.
+
+  Benutzt werden diese Tags, indem die in den auszugebenden Text zwischen zwei "%^" gesetzt werden: ```%^tag%^markierter Text%^endetag%^```. Hierbei sollte dann ReceiveMsg() allerdings auch der Zeilenumbruch ueberlassen werden.
+
+  Zu beachten ist, dass je nach Client bzw. seinem Terminaltyp nicht alle Tags benutzbar sind. Falls z.B. der Client kein ANSI untertuetzt und die Spielerin dieses abgeschaltet hat, werden diese Tags nicht gesendet oder als Tag "normal" behandelt.
+
+  Technisch erfolgt die Ersetzung mittels terminal_colour(), welches man nutzen koennte, um Farben in eine Ausgabe zu bringen, ohne ReceiveMsg & Co zu nutzen. Allerdings ist aktuell die Farbtabelle des Spielerobjekts nicht abfragbar.
+
+
+Tag-Liste
+---------
+
+Logische Hervorhebungen
+________________________
+
+  Zusaetzlich zu den direkt physischen Hervorhebungen (s.u.) gibt es noch logische Hervorhebungen, welche zu einer vom Spieler konfigurierbaren (TODO) Darstellung fuehren:
+
+  mention
+    Erwaehnungen eines Charnamens mit @Charname
+
+  normal
+    Normale Darstellung des Clients
+
+Physische Hervorhebungen
+________________________
+
+  Diese Hervorhebungen stellen direkt eine bestimmte Farbe oder Darstellung ein.
+
+  Vordergrundfarben (ANSI)
+    black, red, green, yellow, blue, purple, cyan, white
+
+  Hintergrundfarben (ANSI)
+    bg_black, bg_red, bg_green, bg_yellow, bg_blue, bg_purple, bg_cyan, bg_white
+
+  Sonstiges (VT100, ANSI)
+    bold (fettgedruckt), underlined (unterstrichen), blink (blinkend), invers (Vorder- und Hintergrundfarbe invertiert)
+
+
+Beispiel
+--------
+
+  .. code-block:: pike
+
+  this_player().ReceiveMsg(
+      "Zesstra wirft Dir einen %^red%^roten Eimer%^normal%^ an den Kopf.",
+      MT_LOOK, MA_EMOTE);
+
+
+SIEHE AUCH
+----------
+
+  :doc:`../lfun/ReceiveMsg`,
+  :doc:`../efun/terminal_colour`,
+  :doc:`../props/P_TTY`, :doc:`../props/P_TTY_TYPE`
+
+Letzte Aenderung: 10.22.2022
+
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)
diff --git a/sys/ansi.h b/sys/ansi.h
index 4e4d244..0f37a8e 100644
--- a/sys/ansi.h
+++ b/sys/ansi.h
@@ -7,11 +7,15 @@
 #ifndef _ANSI_
 #define _ANSI_
 
+// the first 5 are also supported by VT100
 #define ANSI_BOLD ""
 #define ANSI_UNDERL ""
 #define ANSI_BLINK ""
 #define ANSI_INVERS ""
 
+#define ANSI_NORMAL ""
+
+// From here on only supported by ANSI
 #define ANSI_BLACK ""
 #define ANSI_RED ""
 #define ANSI_GREEN ""
@@ -30,6 +34,4 @@
 #define ANSI_BG_CYAN ""
 #define ANSI_BG_WHITE ""
 
-#define ANSI_NORMAL ""
-
 #endif
diff --git a/sys/player/comm.h b/sys/player/comm.h
index a0e7b38..281881e 100644
--- a/sys/player/comm.h
+++ b/sys/player/comm.h
@@ -36,9 +36,10 @@
 #define MB_SAY     4096
 #define MB_CHANNEL 8192
 #define MB_SHOUT   16384
+#define MB_MENTION 0x8000
 #define MB_MISC    0x10000  // alle anderen Aktionen ohne eigenen Filter
 // Flags < 1048576 (2^20) reserviert fuer Flags
-#define MB_ALL     (MB_SAY | MB_TELL | MB_CHANNEL | MB_SHOUT | MB_MISC)
+#define MB_ALL     (MB_SAY | MB_TELL | MB_CHANNEL | MB_SHOUT | MB_MENTION | MB_MISC)
 
 // definitions for Message()