Zeichensatz des Spielers manuell einstellbar

Der gewuenschte Zeichensatz eines Spielers ist per
Kommando einstellbar. Dieser wird dann im
Interactive konfiguriert und in einer Prop
gespeichert.

Der manuell konfigurierte Zeichensatz wird dann
auch beim Login gesetzt, wird dann aber ggf.
spaeter durch die Aushandlung per Telnet option
geaendert.

Change-Id: I1897fa113252ac33e72b2429f662ab67355f67df
diff --git a/doc/pcmd/telnet b/doc/pcmd/telnet
index 1c04d71..11fa001 100644
--- a/doc/pcmd/telnet
+++ b/doc/pcmd/telnet
@@ -29,10 +29,15 @@
     * tls
       Zeigt an, ob die Verbindung zwischen Client und MG mit TLS
       verschluesselt ist, so dass auf dem Weg niemand mitlesen kann.
+    * charset
+      Stellt den zu nutzenden Zeichensatz ein bzw. gibt ihn aus, wenn kein
+      Argument angegeben wird.
+      Soll die Einstellung geloescht und der Default wiederhergestellt werden,
+      muss als Argument \'loeschen\' angegeben werden.
 
  SIEHE AUCH:
-    telnegs, telnet_keepalive, telnet gmcp
+    telnegs, telnet_keepalive, telnet gmcp, telnet_charset
     P_TELNET_KEEPALIVE_DELAY
 
  LETZTE AeNDERUNG:
-    26.07.2019, Zesstra
+    16.01.2020, Zesstra
diff --git a/doc/pcmd/telnet_charset b/doc/pcmd/telnet_charset
new file mode 100644
index 0000000..03b5cd0
--- /dev/null
+++ b/doc/pcmd/telnet_charset
@@ -0,0 +1,47 @@
+
+telnet charset
+----------------
+
+ KOMMANDO:
+    telnet charset [<charset> | loeschen ]
+
+ ARGUMENTE:
+
+     <charset>
+        Setzt den Zeichensatz auf <charset>
+     loeschen
+        Loescht diese Einstellung und stellt die Voreinstellung wieder her
+
+ BESCHREIBUNG:
+
+    Der Zeichensatz bestimmt, in welche Form Zeichen (Buchstaben, Ziffern,
+    Sonderzeichen) an Dich uebertragen werden. Es gibt sehr viele verschiedene
+    dieser Zeichensaetze. Uebertragt das Mud in einem anderen Zeichensatz als
+    Dein Client (oder Terminal) dies erwartet, kommt im besten Falle
+    Zeichensalat dabei heraus.
+
+    Wenn Dein Client die Telnet-Option CHARSET unterstuetzt, kann Dein Client
+    den gewuenschten Zeichensatz automatisch aushandeln. Dies tun aber nur
+    sehr wenige Clients.
+
+    Daher kann diese Einstellung auch manuell mit diesem Befehl eingestellt
+    werden. Welche das sein muss, haengt von Deinem Client (und ggf. dessen
+    Einstellungen) und/oder Deinem Terminal (und ggf. dessen Einstellungen)
+    ab.
+    Gebraeuchliche Zeichensaetze sind "ASCII", "UTF-8", "ISO8859-1" oder
+    "ISO8859-15". In fast allen Situationen wird der Zeichensatz "ASCII"
+    funktionieren, allerdings kann dieser nur sehr wenige Sonderzeichen
+    darstellen (z.B. keine deutschsprachigen Umlaute).
+
+    Wenn wir Zeichen an Dich senden muessen, welche in dem Zeichensatz, der
+    eingestellt ist, nicht dargestellt werden koennen, werden wir versuchen,
+    diese zu "transliterieren", d.h. durch eine darstellbare Entsprechung zu
+    senden.
+
+    Ohne Argumente wird der aktuelle Zustand angezeigt.
+
+ SIEHE AUCH:
+    telnegs, telnet gmcp,
+
+ LETZTE AeNDERUNG:
+    16.01.2020, Zesstra
diff --git a/doc/sphinx/props/P_TELNET_CHARSET b/doc/sphinx/props/P_TELNET_CHARSET
new file mode 100644
index 0000000..58729b3
--- /dev/null
+++ b/doc/sphinx/props/P_TELNET_CHARSET
@@ -0,0 +1,41 @@
+P_TELNET_CHARSET
+================
+
+NAME
+----
+
+    P_TELNET_CHARSET                                 "p_lib_telnet_charset"
+
+DEFINIERT IN
+------------
+
+    /secure/telnetneg.h
+
+BESCHREIBUNG
+------------
+
+    In dieser Properties steht der Zeichensatz, den der Spieler manuell
+    konfiguriert hat. Das Spielerobjekt wird so konfiguriert, dass der Driver
+    - falls notwendig - alle Eingaben des Spielers von diesem Zeichensatz nach
+    UTF-8 und alle Ausgaben an den Spieler von UTF-8 in diesen Zeichensatz
+    konvertiert.
+
+    Hierbei wird der manuell konfigurierte Zeichensatz allerdings nur dann
+    benutzt, wenn der Client des Spielers nicht mittels der Telnet-Option
+    CHARSET etwas anderes aushandelt!
+
+    Wird in dieser Property keine Einstellung vorgenommen und fuehrt der
+    Client keine Aushandlung durch, ist der Default im Morgengrauen
+    "ASCII//TRANSLIT".
+
+SIEHE AUCH
+----------
+
+    :doc:`P_TTY_COLS`, :doc:`P_TTY_ROWS`, :doc:`P_TTY_SHOW`, :doc:`P_TTY`,
+    :doc:`P_TTY_TYPE`, :doc:`P_TELNET_RTTIME`
+
+LETZTE AeNDERUNG
+----------------
+
+    16.01.2020, Zesstra
+
diff --git a/secure/telnetneg.h b/secure/telnetneg.h
index 8a84f6b..f1640ae 100644
--- a/secure/telnetneg.h
+++ b/secure/telnetneg.h
@@ -17,6 +17,7 @@
 #define P_TTY_TYPE           "tty_type"
 #define P_TTY_SHOW           "tty_show"
 #define P_TELNET_RTTIME      "p_lib_telnet_rttime"
+#define P_TELNET_CHARSET     "p_lib_telnet_charset"
 
 #endif
 
diff --git a/std/player/base.c b/std/player/base.c
index 94c2a1e..8273874 100644
--- a/std/player/base.c
+++ b/std/player/base.c
@@ -12,6 +12,7 @@
 #include <sys_debug.h>
 #include <regexp.h>
 #include <input_to.h>
+#include <configuration.h>
 #include <logging.h>
 #include <werliste.h>
 #include <time.h>
@@ -254,6 +255,8 @@
   SetProp(P_NEWBIE_GUIDE,0);
   Set(P_NEWBIE_GUIDE,SAVE,F_MODE_AS);
 
+  // Daran sollte nicht jeder von aussen rumspielen.
+  Set(P_TELNET_CHARSET, PROTECTED|SAVE, F_MODE_AS);
 
   AddId("Interactive");
 
@@ -548,6 +551,30 @@
     filter( obs, "call_init", ME, logout );
 }
 
+// Zeichensatz konfigurieren.
+// Wenn es einen manuell konfigurierten Zeichensatz gibt (der jetzt ja
+// eingelesen ist), wird der erstmal eingestellt.
+private void set_manual_encoding()
+{
+  string enc = QueryProp(P_TELNET_CHARSET);
+  if (enc)
+  {
+    if (stringp(enc) &&
+        !catch(configure_interactive(ME, IC_ENCODING, enc); publish))
+    {
+      // hat geklappt fertig
+      return;
+    }
+    // wenn kein string oder nicht erfolgreich -> Prop zuruecksetzen
+    tell_object(ME, sprintf(
+          "Der von Dir eingestellte Zeichensatz \'%s\' konnte nicht "
+          "eingestellt werden. Die Voreinstellung \'%s\' wurde wieder "
+          "eingestellt.", enc,
+          interactive_info(ME, IC_ENCODING)));
+     SetProp(P_TELNET_CHARSET, 0);
+  }
+}
+
 /** Belebt einen Netztoten wieder.
   Wird im Login gerufen, wenn der Spieler netztot war. Aequivalent zu
   start_player()
@@ -565,6 +592,10 @@
 
     if ( query_once_interactive(ME) )
     {
+        // Erstmal - sofern vorhanden - den manuell konfigurierten Zeichensatz
+        // einstellen. Im folgenden wird dann versucht, die TELOP CHARSET
+        // auszuhandeln, die aendert das evtl. nochmal.
+        set_manual_encoding();
         // perform the telnet negotiations. (all that are available)
         "*"::startup_telnet_negs();
         Set( P_LAST_LOGIN, time() );
@@ -2145,7 +2176,6 @@
   * @sa start_player(), InitSkills(), FixSkills()
   */
 private void updates_after_restore(int newflag) {
- 
   // Seher duerfen die Fluchtrichtung uebermitteln lassen.
   // Eigentlich koennte es Merlin machen. Dummerweise gibt es ja auch alte
   // Seher und dann kann es gleiche fuer alle hier gemacht werden. (Ob der
@@ -2249,14 +2279,18 @@
 
     updates_after_restore(newflag);
 
-   if ( query_once_interactive(ME) )
+    if ( query_once_interactive(ME) )
     {
+      // Erstmal - sofern vorhanden - den manuell konfigurierten Zeichensatz
+      // einstellen. Im folgenden wird dann versucht, die TELOP CHARSET
+      // auszuhandeln, die aendert das evtl. nochmal.
+      set_manual_encoding();
       // Telnet-Negotiations durchfuehren, aber nur die grundlegenden aus
       // telnetneg. Alle anderen sollten erst spaeter, nach vollstaendiger
       // Initialisierung gemacht werden.
-        telnetneg::startup_telnet_negs();
-        modify_prompt();
-        Set( P_LAST_LOGIN, time() );
+      telnetneg::startup_telnet_negs();
+      modify_prompt();
+      Set( P_LAST_LOGIN, time(), F_VALUE );
     }
 
     Set( P_WANTS_TO_LEARN, 1 ); // 1 sollte der default sein !!!
@@ -4264,6 +4298,62 @@
   return 1;
 }
 
+//TODO: beim manuellen Setzen sollte - sofern TELOPT CHARSET ausgehandelt
+//TODO::wurde, versucht werden, diesen neu mit dem Client zu verhandeln...
+private int set_telnet_charset(string enc) {
+  // Wenn es "loeschen" ist, wird die Prop genullt und wir stellen den Default
+  // ein.
+  if (!sizeof(enc))
+  {
+    tell_object(ME, break_string(sprintf(
+          "Zur Zeit ist der Zeichensatz \'%s\' aktiv. "
+          "Alle Ausgaben an Dich werden in diesem Zeichensatz gesendet "
+          "und wir erwarten alle Eingaben von Dir in diesem Zeichensatz. "
+          "Moeglicherweise hat Dein Client diesen Zeichensatz automatisch "
+          "ausgehandelt.", interactive_info(ME, IC_ENCODING)), 78));
+  }
+  else if (lower_case(enc) == "loeschen")
+  {
+    SetProp(P_TELNET_CHARSET, 0);
+    configure_interactive(ME, IC_ENCODING, interactive_info(0,IC_ENCODING));
+    tell_object(ME, break_string(sprintf(
+          "Der Default \'%s\' wurde wieder hergestellt. "
+          "Alle Ausgaben an Dich werden in diesem Zeichensatz gesendet "
+          "und wir erwarten alle Eingaben von Dir in diesem Zeichensatz. "
+          "Sollte Dein Client die Telnet-Option CHARSET unterstuetzen, kann "
+          "dieser allerdings direkt einen Zeichensatz aushandeln, der dann "
+          "stattdessen gilt.",
+          interactive_info(ME, IC_ENCODING)), 78));
+  }
+  else
+  {
+    // Wenn der Zeichensatz keine //TRANSLIT-Variante ist, machen wir den zu
+    // einer. Das verhindert letztlich eine Menge Laufzeitfehler, wenn ein
+    // Zeichen mal nicht darstellbar ist.
+    if (strstr(enc, "//TRANSLIT") == -1)
+      enc += "//TRANSLIT";
+    if (catch(configure_interactive(ME, IC_ENCODING, enc); nolog))
+    {
+      tell_object(ME, break_string(sprintf(
+            "Der Zeichensatz \'%s\' ist nicht gueltig oder zumindest auf "
+            "diesem System nicht verwendbar.", enc),78));
+    }
+    else
+    {
+      SetProp(P_TELNET_CHARSET, interactive_info(ME, IC_ENCODING));
+      tell_object(ME, break_string(sprintf(
+            "Der Zeichensatz \'%s\' wurde eingestellt. Alle Ausgaben an "
+            "Dich werden in diesem Zeichensatz gesendet und wir erwarten "
+            "alle Eingaben von Dir in diesem Zeichensatz. Sollte Dein "
+            "Client die Telnet-Option CHARSET unterstuetzen, kann "
+            "dieser allerdings direkt einen Zeichensatz aushandeln, der "
+            "dann stattdessen gilt.",
+            interactive_info(ME, IC_ENCODING)),78));
+    }
+  }
+  return 1;
+}
+
 int telnet_cmd(string str) {
   if (!str) return 0;
   string *args = explode(str, " ");
@@ -4279,6 +4369,8 @@
       return set_keep_alive(newargs);
     case "rttime":
       return print_telnet_rttime();
+    case "charset":
+      return set_telnet_charset(newargs);
     case "tls":
       if (tls_query_connection_state(ME) > 0)
         tell_object(ME,