Added public files

Roughly added all public files. Probably missed some, though.
diff --git a/secure/questmaster.c b/secure/questmaster.c
new file mode 100644
index 0000000..314a97b
--- /dev/null
+++ b/secure/questmaster.c
@@ -0,0 +1,1174 @@
+// MorgenGrauen MUDlib
+//
+// questmaster.c -- Questmaster, verwaltet die normalen Quests und
+//                  die MiniQuests
+//
+// $Id: questmaster.c 9136 2015-02-03 21:39:10Z Zesstra $
+//
+#pragma strict_types
+#pragma no_clone
+#pragma no_shadow
+#pragma no_inherit
+#pragma verbose_errors
+#pragma combine_strings
+//#pragma pedantic
+//#pragma range_check
+#pragma warn_deprecated
+
+#include <config.h>
+#include "/secure/wizlevels.h"
+#include "/secure/questmaster.h"
+#include "/secure/lepmaster.h"
+#include "/secure/telnetneg.h" // P_TTY
+#include <living/description.h> // P_LEVEL
+#include <player/base.h> // P_TESTPLAYER
+#include <daemon.h>
+#include <ansi.h>
+#include <events.h>
+
+#define DEBUG(x) if (funcall(symbol_function('find_player),"arathorn"))\
+          tell_object(funcall(symbol_function('find_player),"arathorn"),\
+                      "QM: "+x+"\n")
+
+#define ME this_object()
+#define PL this_player()
+
+#define MQ_DATA_POINTS 0
+#define MQ_DATA_QUESTNO 1
+#define MQ_DATA_TASKDESC 2
+#define MQ_DATA_VISIBLE 3
+#define MQ_DATA_ACTIVE 4
+#define MQ_DATA_TITLE 5
+#define MQ_DATA_DIARYTEXT 6
+#define MQ_DATA_RESTRICTIONS 7
+#define MQ_DATA_ASSIGNED_DOMAIN 8
+#define MQ_DATA_QUERY_PERMITTED 9
+
+private int max_QP = 0;
+private int opt_QP = 0;
+// Die Questliste mit allen Daten
+private mapping quests = ([]);
+
+// Das Mapping mit der MQ-Liste an sich und alle zugehoerigen Daten
+private mapping miniquests = ([]);
+// Nach MQ-Nummern indizierter Cache:
+// Struktur ([int num : ({ int stupse, string mq-object }) ])
+private nosave mapping by_num = ([]);
+// Cache der Objekte, die die MQ-Listen der Regionen abfragen duerfen
+// Struktur ([string path : ({ string mq_object }) ])
+private nosave mapping mq_query_permitted = ([]);
+// Cache fuer die MQ-Punkte von Spielern, wird fuer den jeweiligen Spieler
+// beim Abfragen von dessen MQ-Punkten gefuellt. Spielername wird bei
+// Aenderungen an seinen MQ-Punkten (Bestehen einer MQ, manuelles Setzen
+// oder Loeschen einer seiner MQs) aus dem Cache ausgetragen
+private nosave mapping users_mq = ([]);
+// letzte vergebene MQ-Indexnummer. Es darf niemals eine MQ eine Indexnummer
+// kriegen, die eine andere MQ schonmal hatte, auch wenn die geloescht wurde.
+// (Zumindest nicht, ohne die entsprechenden Bits im Spieler zu loeschen, was
+// zZ nicht passiert.
+private int last_num = 0;
+
+
+void save_info() {
+  save_object(QUESTS);
+}
+
+// Caches aufbauen.
+static void make_num(string mqob_name, int stupse, int index,
+                     string taskdesc, int vis, int active, string title,
+                     string donedesc, mapping restr, string domain, 
+                     string *permitted_objs) {
+  by_num += ([ index : ({stupse, mqob_name})]);
+  foreach ( string obj: permitted_objs ) {
+    if ( member(mq_query_permitted, obj) )
+      mq_query_permitted[obj] += ({mqob_name});
+    else
+      mq_query_permitted[obj] = ({mqob_name});
+  }
+}
+
+void create() {
+  seteuid(getuid(ME));
+  if (!restore_object(QUESTS)) {
+    save_info();
+  }
+
+  walk_mapping(miniquests, #'make_num /*'*/ );
+  set_next_reset(43200); // Reset alle 12 Stunden.
+  EVENTD->RegisterEvent(EVT_LIB_QUEST_SOLVED,"HandleQuestSolved",
+      ME);
+}
+
+public int remove(int silent) {
+  save_info();
+  EVENTD->UnregisterEvent(EVT_LIB_QUEST_SOLVED, ME);
+  destruct(ME);
+  return 1;
+}
+
+// Schreibzugriff nur fuer interaktive EMs und ARCH_SECURITY.
+private int allowed_write_access() {
+  if (process_call())
+    return 0;
+  if (ARCH_SECURITY)  // prueft auch this_interactive() mit.
+    return 1;
+  return 0;
+}
+
+void reset() {
+  by_num = ([]);
+  mq_query_permitted = ([]);
+  walk_mapping(miniquests, #'make_num /*'*/ );
+  set_next_reset(43200);
+}
+
+/*
+ * (1) ABSCHNITT "NORMALE QUESTS"
+ */
+
+/* Die Quests werden in einem Mapping gespeichert. Der Schluessel ist dabei der
+   Quest-Name, die Eintraege sind Arrays der folgenden Form:
+
+   1. Element ist die Zahl der durch diese Quest zu erwerbenden Questpunkte.
+   2. Element ist die Zahl der Erfahrungspunkte, die der Spieler bekommt,
+      wenn er diese Quest loest.
+   3. Element ist ein Array mit den Filenamen der Objekte, denen es gestattet
+      ist, diese Quest beim Player als geloest zu markieren (Erzmagier duerfen
+      das aber sowieso immer).
+   4. Element ist ein String, der die Quest kurz beschreibt. Dieser String wird
+      dem Spieler vom Orakel als Hinweis gegeben.
+   5. Element ist eine Zahl zwischen -1 und 100, die den Schwierigkeitsgrad der
+      Quest angibt, nach Einschaetzung des EM fuer Quests. Das Orakel kann dann
+      evtl. sinnige Saetze wie "Diese Quest erscheint mir aber noch recht
+      schwer fuer Dich.", oder "Hm, die haettest Du ja schon viel eher loesen
+      koennen." absondern. :)
+
+      Ein Wert von -1 bedeutet eine Seherquest. Diese zaehlt nicht zu den
+      Maximalen Questpunkten, sondern zaehlt als optionale Quest
+   6. Element ist ein Integer von 0 bis 5 und gibt die "Klasse" an;
+      ausgegeben werden dort Sternchen
+   7. Element ist ein Integer, 0 oder 1.
+      0: Quest voruebergehend deaktiviert (suspendiert)
+      1: Quest aktiviert
+   8. Element ist ein String und enthaelt den Namen des Magiers, der die
+      Quest "verbrochen" hat.
+   9. Element ist ein String, der den Namen eines Magiers enthaelt, der
+      evtl. fuer die Wartung der Quest zustaendig ist.
+  10. Element ist eine Zahl von 0 bis 4, die der Quest ein Attribut
+      gibt (0 fuer keines)
+*/
+
+// geaendert:
+// 5  == diff geht nun von -1 bis 100
+// 6  == klasse geht nun von 0 bis 5
+// 10 == attribut geht nun von 0 bis 4
+
+private int RecalculateQP() {
+  int i;
+  mixed q,n;
+
+  if (!allowed_write_access())
+    return -1;
+
+  max_QP=0;
+  opt_QP=0;
+
+  n=m_indices(quests);
+  q=m_values(quests);
+    for (i=sizeof(q)-1;i>=0;i--)
+      if (q[i][Q_ACTIVE]) {
+        if (q[i][Q_DIFF]>=0)
+          max_QP+=q[i][Q_QP];
+        if (q[i][Q_DIFF]==-1)
+          opt_QP+=q[i][Q_QP];
+      }
+
+  return max_QP+opt_QP;
+}
+
+int AddQuest(string name, int questpoints, int experience,
+      string *allowedobj, string hint, int difficulty, int questclass,
+      int active, string wiz, string scndwiz, int questattribute)
+{
+  mixed *quest;
+  int i;
+
+  if (!allowed_write_access()) return 0;
+  if (!stringp(name) || sizeof(name)<5) return -1;
+  if (questpoints<1) return -2;
+  if (!intp(experience)) return -3;
+  if (!pointerp(allowedobj)) return -4;
+  for (i=sizeof(allowedobj)-1;i>=0;i--)
+    {
+      if (!stringp(allowedobj[i]) || allowedobj[i]=="") return -4;
+      allowedobj[i]=(string)"/secure/master"->_get_path(allowedobj[i],0);
+    }
+  if (!stringp(hint) || hint=="") return -5;
+  if (difficulty<-1 || difficulty>100) return -6;
+  if (questclass<0 || questclass>5) return -11;
+  if (active<0 || active>1) return -7;
+  if (!stringp(wiz) || wiz=="" ||
+      file_size("/players/"+(wiz=lower_case(wiz))) != -2) return -8;
+  if (!stringp(scndwiz))
+    scndwiz="";
+  else if (file_size("/players/"+(scndwiz=lower_case(scndwiz))) != -2)
+    return -9;
+  if (questattribute<0 || questattribute>4)
+    return -10;
+
+  if(quests[name]&&(quests[name][5]==0||quests[name][5]==1)&&quests[name][6])
+    max_QP-=quests[name][0];
+
+  quests+=([name: ({questpoints,experience,allowedobj,hint,difficulty,
+                    questclass,active,wiz, scndwiz,questattribute,
+                    ({0.0,0}) }) ]);
+  RecalculateQP();
+  save_info();
+  QMLOG(sprintf("add: %s %O (%s)",name,quests[name],
+                getuid(this_interactive())));
+  return 1;
+}
+
+int RemoveQuest(string name) {
+  mixed *quest;
+
+  if (!allowed_write_access()) return 0;
+  if (!quests[name]) return -1;
+  QMLOG(sprintf("remove: %s %O (%s)",name,quests[name],
+                getuid(this_interactive())));
+  m_delete(quests,name);
+  RecalculateQP();
+  save_info();
+  return 1;
+}
+
+int QueryNeededQP() {
+  return REQ_QP;
+}
+
+int QueryMaxQP() {
+  return max_QP;
+}
+
+int QueryOptQP() {
+  return opt_QP;
+}
+
+int QueryTotalQP() {
+  return max_QP+opt_QP;
+}
+
+mixed *QueryGroupedKeys() {
+  string *qliste;
+  mixed  *qgliste;
+  int i, j;
+
+  qgliste = allocate(sizeof(QGROUPS)+1); // letzte Gruppe sind die Seherquests
+  qliste = m_indices(quests);
+
+  for (i=sizeof(qgliste)-1;i>=0;i--)
+    qgliste[i]=({});
+
+  for (i=sizeof(qliste)-1;i>=0;i--)
+    {
+      // inaktive quest?
+      if (!quests[qliste[i]][Q_ACTIVE])
+        continue;
+      // optionale quest? also Seherquest
+        if (quests[qliste[i]][Q_DIFF]==-1)
+          qgliste[sizeof(QGROUPS)] += ({qliste[i]});
+        else {
+          // dann haben wir also eine normale Quest und daher Einordnung
+          // nach dem Schwierigkeitswert
+          for (j=sizeof(QGROUPS)-1;
+               j>=0 && QGROUPS[j]>=quests[qliste[i]][Q_DIFF];j--)
+            ;
+          qgliste[j] += ({qliste[i]});
+        }
+    }
+  return qgliste;
+}
+
+
+// folgende funk brauch ich glaube ich nicht mehr:
+int QueryDontneed(object pl) {
+  raise_error("Ich glaub, die Func QueryDontneed() braucht kein Mensch mehr. "
+  "(Zook)");
+}
+
+// Die folgende Func braucht man nicht mehr
+int QueryReadyForWiz(object player) {
+  raise_error("Die Func QueryReadyForWiz() braucht keiner mehr. (Zook)");
+}
+
+mixed *QueryQuest(string name) {
+  if(!quests[name])
+    return ({});
+  if( extern_call() )
+    return deep_copy( quests[name] );
+  return quests[name];
+}
+
+int QueryQuestPoints(string name) {
+  if( !quests[name] )
+    return -1;
+
+  return quests[name][Q_QP];
+}
+
+mixed *QueryQuests() {
+  if( extern_call() )
+    return ({m_indices(quests),map(m_values(quests),#'deep_copy /*'*/)});
+  return ({ m_indices(quests), m_values(quests) });
+}
+
+string *QueryAllKeys() {
+  return m_indices(quests);
+}
+
+int SetActive(string name, int flag) {
+  mixed *quest;
+
+  if (!allowed_write_access()) return 0;
+  if (!(quest=quests[name])) return -1;
+  switch(flag)
+    {
+    case 0:
+      if (quest[Q_ACTIVE] == flag)
+        return -2;
+      quest[Q_ACTIVE] = flag;
+      break;
+    case 1:
+      if (quest[Q_ACTIVE] == flag)
+        return -2;
+      quest[Q_ACTIVE] = flag;
+      break;
+    default:
+      return -3;
+    }
+  quests[name]=quest;
+  RecalculateQP();
+  save_info();
+  QMLOG(sprintf("%s: %s (%s)",(flag?"activate":"deactivate"),name,
+                getuid(this_interactive())));
+  return 1;
+}
+
+string name() {
+  return "<Quest>";
+}
+string Name() {
+  return "<Quest>";
+}
+
+void Channel(string msg) {
+  if(!interactive(previous_object()))
+    return;
+  catch(CHMASTER->send("Abenteuer", ME, msg);publish);
+}
+
+ /* quoted from /sys/mail.h: */
+#define MSG_FROM 0
+#define MSG_SENDER 1
+#define MSG_RECIPIENT 2
+#define MSG_CC 3
+#define MSG_BCC 4
+#define MSG_SUBJECT 5
+#define MSG_DATE 6
+#define MSG_ID 7
+#define MSG_BODY 8
+
+void SendMail(string questname, mixed *quest, object player) {
+  mixed* mail;
+  string text;
+
+  mail = allocate(9);
+
+  text =
+    "Hallo "+capitalize(getuid(player))+",\n\n"+
+    break_string("Nachdem Du gerade eben das Abenteuer '"+
+                 questname +"' ("+quest[Q_QP]+" Punkte), das "+
+                 capitalize(quest[Q_WIZ])+" fuer das "MUDNAME" entworfen hat, "
+                 "mit Erfolg bestanden hast, sind "
+                 "wir nun an Deiner Meinung dazu interessiert:", 78)+
+    "\n  Hat Dir das Abenteuer gefallen und wieso bzw. wieso nicht?\n"
+    "  Ist die Einstufung Deiner Meinung nach richtig? (AP und Stufe)\n"
+    "  Gab es Probleme oder gar Fehler?\n"
+    "  Hast Du Verbesserungsvorschlaege?\n\n";
+
+  text += break_string("Diese Nachricht wurde automatisch verschickt, "
+        "wenn Du mit dem 'r' Kommando darauf antwortest, geht die Antwort "
+        "direkt an Ark als zustaendigem Erzmagier fuer Abenteuer.\n",78);
+
+  if (quest[Q_SCNDWIZ]!="") {
+    text += break_string(
+        "Falls Du mit dem Magier sprechen willst, der zur Zeit das "
+        "Abenteuer technisch betreut, kannst Du Dich an "
+        +capitalize(quest[Q_SCNDWIZ])+ " wenden.",78);
+  }
+
+  mail[MSG_FROM] = "Ark";
+  mail[MSG_SENDER] = "Ark";
+  mail[MSG_RECIPIENT] = getuid(player);
+  mail[MSG_CC]=0;
+  mail[MSG_BCC]=0;
+  mail[MSG_SUBJECT]="Das Abenteuer: "+questname;
+  mail[MSG_DATE]=dtime(time());
+  mail[MSG_ID]=MUDNAME":"+time();
+  mail[MSG_BODY]=text;
+
+  "/secure/mailer"->DeliverMail(mail,0);
+  return;
+}
+
+static int compare (mixed *i, mixed *j) {
+  if (i[4] == j[4])
+    return i[1] > j[1];
+  else
+    return i[4] > j[4];
+}
+
+varargs string liste(mixed pl) {
+  int qgroups, i, j, qrfw;
+  mixed *qlists, *qgrouped, *qtmp;
+  string str;
+  string ja, nein, format, ueberschrift;
+
+  if(!objectp(pl))
+    if(stringp(pl))
+      pl=find_player(pl) || find_netdead(pl);
+  if(!objectp(pl))
+    pl=PL;
+  if(!objectp(pl))
+    return "Ohne Spielernamen/Spielerobjekt gibt es auch keine Liste.\n";
+
+  if ( ((string)pl->QueryProp(P_TTY)) == "ansi")
+  {
+      ja = ANSI_GREEN + "ja" + ANSI_NORMAL;
+      nein = ANSI_RED + "nein" + ANSI_NORMAL;
+  }
+  else
+  {
+      ja = "ja";
+      nein = "nein";
+  }
+
+  str = "";
+  // Festlegen des Ausgabeformates
+  format = "%=-:30s %:3d %-:6s  %-:9s %:2s/%-:3s  %-:12s %-s\n";
+  ueberschrift = sprintf("%-:30s %:3s %-:6s  %-:9s %-:6s  %-:12s %-:4s\n",
+                 "Abenteuer", "AP", "Klasse", "Attribut",
+                 "Stufe", "Autor", "Gel?");
+
+  qgroups = sizeof(QGROUPS);
+  qlists = allocate( qgroups+1 );
+  for( i=qgroups; i>=0; i-- )
+    qlists[i] = ({});
+
+  qgrouped = QueryGroupedKeys();
+
+  for (i=sizeof(qgrouped)-1;i>=0; i--)
+    for (j=sizeof(qgrouped[i])-1;j>=0; j--) {
+      qtmp = QueryQuest(qgrouped[i][j]);
+      qlists[i] += ({ ({
+        qgrouped[i][j],
+        qtmp[Q_QP],
+        QCLASS_STARS(qtmp[Q_CLASS]),
+        capitalize(QATTR_STRINGS[qtmp[Q_ATTR]]),
+        qtmp[Q_DIFF],
+        (qtmp[Q_AVERAGE][1]>10 /*&& IS_ARCH(this_player())*/
+                  ? to_string(to_int(qtmp[Q_AVERAGE][0]))
+                  : "-"),
+        capitalize(qtmp[Q_WIZ]),
+        (int)pl->QueryQuest(qgrouped[i][j]) == OK ? ja : nein
+      }) });
+    }
+
+  for( i=0; i<qgroups; i++ )
+  {
+    if (sizeof(qlists[i])) {
+      str += "\n" + ueberschrift;
+      str += sprintf("Stufen %d%s:\n",
+                     QGROUPS[i]+1,
+                     i==qgroups-1?"+":sprintf("-%d", QGROUPS[i+1]));
+      qlists[i] = sort_array( qlists[i], "compare", ME );
+      for( j=0; j<sizeof(qlists[i]); j++ ) {
+        if(qlists[i][j][Q_DIFF]>=0)
+          str += sprintf( format,
+                          qlists[i][j][0], qlists[i][j][1], qlists[i][j][2],
+                          qlists[i][j][3], sprintf("%d",qlists[i][j][4]),
+                          qlists[i][j][5],
+                          qlists[i][j][6], qlists[i][j][7]);
+      }
+      str += "\n\n";
+    }
+  }
+
+  qlists[qgroups] = sort_array(qlists[qgroups], "compare", ME);
+  i = qgroups;
+  if (sizeof(qlists[i])) {
+    str += "\n" + ueberschrift;
+    str += "Nur fuer Seher:\n";
+    for( j=0; j<sizeof(qlists[qgroups]); j++ )  {
+      if(qlists[i][j][Q_DIFF]==-1)
+        str += sprintf( format,
+                        qlists[i][j][0], qlists[i][j][1], qlists[i][j][2],
+                        qlists[i][j][3], "S", qlists[i][j][5],
+                        qlists[i][j][6],
+                        qlists[i][j][7]);
+    }
+  }
+
+  str +=
+    "\nEine Erklaerung der einzelnen Spalten findest Du unter "
+    "\"hilfe abenteuerliste\".\n";
+
+  return str;
+}
+
+
+// mitloggen, mit welchen durchschnittlichen Leveln Quests so geloest
+// werden...
+void HandleQuestSolved(string eid, object trigob, mixed data) {
+  string qname = data[E_QUESTNAME];
+
+  if (!quests[qname] || !objectp(trigob)
+      || trigob->QueryProp(P_TESTPLAYER) || IS_LEARNER(trigob))
+    return;
+
+  int lvl = (int)trigob->QueryProp(P_LEVEL);
+
+  if (lvl <= 0)
+    return;
+
+  // neuen Durchschnitt berechen.
+  mixed tmp = quests[qname][Q_AVERAGE];
+  float avg = tmp[0];
+  int count = tmp[1];
+  avg *= count;
+  avg += to_float(lvl);
+  tmp[1] = ++count;
+  tmp[0] = avg / count;
+
+  DEBUG(sprintf("%s: %f (%d)\n",qname,
+        quests[qname][Q_AVERAGE][0],
+        quests[qname][Q_AVERAGE][1]));
+}
+
+/*
+ * (2) ABSCHNITT "MINI" QUESTS
+ */
+
+int ClearUsersMQCache() {
+  if (!allowed_write_access())
+    return 0;
+
+  users_mq = ([]);
+
+  return 1;
+}
+
+mixed QueryUsersMQCache() {
+  if (!allowed_write_access())
+    return 0;
+
+  return users_mq;
+}
+
+/* Beschreibung
+ *
+ * Die MiniQuests werden in einem Mapping gespeichert.
+ *
+ * Der Key ist dabei der Name des Objektes, das die Quest im Spieler
+ * markieren darf. Die Daten zu den Miniquests stehen in den Value-Spalten
+ * des Mappings.
+ *
+ * 1. Spalte ist die Zahl der durch diese Quest zu erwerbenden Stufenpunkte
+ * 2. Spalte ist die Nummer unter der die MiniQuest gefuehrt wird.
+ * 3. Spalte ist ein String, der die Quest(aufgabe) kurz beschreibt.
+ * 4. Spalte ist ein Integer, 0 oder 1:
+ *     0 : Quest ist fuer Spieler nicht sichtbar
+ *     1 : Quest ist fuer Spieler z.B. bei einer Anschlagtafel sichtbar
+ *     Fuer Spieler unsichtbare MQs sollte es aber nicht mehr geben!
+ * 5. Spalte ist ein Integer, 0 oder 1:
+ *     0 : Quest voruebergehend deaktiviert
+ *     1 : Quest aktiviert
+ * 6. Spalte ist ein String, der den Kurztitel der Miniquest enthaelt
+ * 7. Spalte ist ein String, der eine kurze Beschreibung dessen enthaelt,
+ *     was der Spieler im Verlauf der Miniquest erlebt hat.
+ * 8. Spalte ist ein Mapping, dessen Eintraege analog zu P_RESTRICTIONS
+ *     gesetzt werden koennen, um anzugeben, welche Voraussetzungen erfuellt
+ *     sein muessen, bevor ein Spieler diese Quest beginnen kann.
+ * 9. Spalte ist die Zuordnung der MQ zu den Regionen
+ *10. Spalte ist ein Array aus Strings, das die Objekte enthaelt, die
+ *    die Daten dieser Quest abfragen duerfen, um sie an Spieler auszugeben.
+ */
+
+int DumpMiniQuests(object who) {
+  int sum_points;
+
+  if (extern_call() && !allowed_write_access())
+    return 0;
+
+  if ( !objectp(who) || !query_once_interactive(who))
+    who = this_interactive();
+
+  MQMLOG(sprintf("DumpMiniQuests: PO: %O, TI: %O", previous_object(), who));
+  rm(MQ_DUMP_FILE);
+
+  write_file(MQ_DUMP_FILE, "MINIQUESTS: ("+dtime(time())+")\n\n"+
+    "  Nr  Pkt  vis akt vergebendes Objekt\n");
+  string *msg = ({});
+
+  foreach(string obname, int stupse, int nummer, mixed descr, int vis,
+      int active /*, string title, string donedesc, mapping restrictions,
+      string domain, string *permitted_objs*/: miniquests)
+  {
+    msg += ({ sprintf("%4d %4d %4d %4d %s",
+      nummer, stupse, vis, active, obname)});
+    sum_points += stupse;
+  }
+
+  write_file(MQ_DUMP_FILE, implode(sort_array(msg, #'> /*'*/), "\n"));
+  write_file(MQ_DUMP_FILE, sprintf("\n\n"
+             "============================================================\n"
+             +"MiniQuests: %d Miniquests mit %d Punkten.\n\n",
+              sizeof(miniquests), sum_points));
+  return 1;
+}
+
+public int AddMiniQuest(int mquestpoints, string allowedobj, string descr,
+            int active, string title, string donedesc, mapping restrictions,
+            string domain, string *permitted_objs) {
+
+  if (!allowed_write_access())
+    return 0;
+
+  // Parameterpruefung: Questgeber, Restrictions, Region, Titel und
+  // zugelassene Abfrageobjekte muessen gueltig angegeben werden, alles
+  // weitere wird unten ggf. ausgenullt/korrigiert.
+  if (!stringp(allowedobj) || !sizeof(allowedobj) || !mappingp(restrictions)
+      || !stringp(domain) || !stringp(title) || !pointerp(permitted_objs))
+    return -1;
+
+  // Miniquest mit weniger als 1 Stups einzutragen ist wohl unsinnig.
+  if (mquestpoints<1)
+    return -2;
+
+  // Mindestens ein Objekt muss eingetragen werden, das Spielern Informationen
+  // ueber die Aufgabenstellung der MQ geben darf.
+  if ( !sizeof(permitted_objs) )
+    return -3;
+
+  // Pruefen, ob die als Questgeber angegebene Datei existiert.
+  if (allowedobj[<2..] == ".c")
+    allowedobj = allowedobj[0..<3];
+  allowedobj = explode(allowedobj, "#")[0];
+  allowedobj = (string)MASTER->_get_path(allowedobj,0);
+  if (file_size(allowedobj+".c") <=0)
+    return -3;
+
+  // Vergibt das angegebene Objekt schon eine MQ? Dann abbrechen.
+  if (member(miniquests,allowedobj))
+    return -4;
+
+  if (!stringp(descr) || !sizeof(descr))
+    descr = 0;
+  if (!stringp(donedesc) || !sizeof(donedesc))
+    donedesc = 0;
+
+  // Eintrag hinzufuegen, visible ist per Default immer 1.
+  // MQ-Nummer hochzaehlen
+  int nummer = last_num + 1;
+  m_add(miniquests, allowedobj, mquestpoints, nummer, descr, 1, active,
+    title, donedesc, restrictions, domain, permitted_objs);
+  // und nummer als last_num merken.
+  last_num = nummer;
+  save_info();
+  m_add(by_num, nummer, ({mquestpoints, allowedobj}));
+  MQMLOG(sprintf("AddMiniQuest: %s %O (%s)", allowedobj, miniquests[allowedobj],
+                 getuid(this_interactive())));
+
+  ClearUsersMQCache();
+  if (find_call_out(#'DumpMiniQuests) == -1)
+    call_out(#'DumpMiniQuests, 60, this_interactive());
+  return 1;
+}
+
+int RemoveMiniQuest(string name) {
+  if (!allowed_write_access())
+    return 0;
+  // Gibt es einen solchen Eintrag ueberhaupt?
+  if (!member(miniquests,name))
+    return -1;
+
+  MQMLOG(sprintf("RemoveMiniQuest: %s %O (%s)",
+    name, m_entry(miniquests, name), getuid(this_interactive())));
+
+  // MQ aus dem MQ-Indexnummern-Cache loeschen.
+  m_delete(by_num, miniquests[name,MQ_DATA_QUESTNO]);
+  // MQ aus der Miniquestliste austragen.
+  m_delete(miniquests, name);
+  save_info();
+
+  // MQ-Punkte-Cache loeschen, da nicht feststellbar ist, welcher der
+  // dort eingetragenen Spieler die gerade ausgetragene MQ geloest hatte.
+  ClearUsersMQCache();
+  if (find_call_out(#'DumpMiniQuests) == -1)
+    call_out(#'DumpMiniQuests, 60, this_interactive());
+  return 1;
+}
+
+int ChangeMiniQuest(mixed mq_obj, int param, mixed newvalue) {
+  if (!allowed_write_access())
+    return 0;
+
+  // MQ weder als Pfad, noch als Indexnummer angegeben?
+  if ( !stringp(mq_obj) && !intp(mq_obj) && !intp(param))
+    return MQ_KEY_INVALID;
+
+  // gewaehlter Parameter ungueltig?
+  if ( param < MQ_DATA_POINTS || param > MQ_DATA_QUERY_PERMITTED )
+    return MQ_KEY_INVALID;
+
+  // Indexnummer der MQ in den Pfad umwandeln
+  if ( intp(mq_obj) )
+    mq_obj = by_num[mq_obj][1];
+
+  // Vergebendes Objekt nicht gefunden? Bloed, das brauchen wir naemlich.
+  if (!stringp(mq_obj))
+    return MQ_KEY_INVALID;
+
+  if ( !member(miniquests, mq_obj) )
+    return MQ_ILLEGAL_OBJ;
+
+  switch(param) {
+    // MQ_DATA_QUESTNO ist nicht aenderbar, daher hier nicht behandelt, so
+    // dass Fallback auf default erfolgt.
+    // Stufenpunkte muessen Integers sein.
+    case MQ_DATA_POINTS:
+      if ( !intp(newvalue) || newvalue < 1 )
+        return MQ_KEY_INVALID;
+      break;
+    // Aufgabenbeschreibung, Titel, "geschafft"-Text und zugeordnete Region
+    // muessen Strings sein
+    case MQ_DATA_TASKDESC:
+    case MQ_DATA_TITLE:
+    case MQ_DATA_DIARYTEXT:
+    case MQ_DATA_ASSIGNED_DOMAIN:
+      if ( !stringp(newvalue) || !sizeof(newvalue) ) 
+        return MQ_KEY_INVALID;
+      break;
+    // das Sichtbarkeits- und das aktiv/inaktiv-Flag muessen 0/1 sein.
+    case MQ_DATA_VISIBLE:
+    case MQ_DATA_ACTIVE:
+      if ( !intp(newvalue) || newvalue < 0 || newvalue > 1 )
+        return MQ_KEY_INVALID;
+      break;
+    // Die Voraussetzungen muessen als Mapping eingetragen werden, das aber
+    // leer oder Null sein kann, wenn es keine Restriktionen gibt.
+    case MQ_DATA_RESTRICTIONS:
+      if ( !mappingp(newvalue) && newvalue != 0 )
+        return MQ_KEY_INVALID;
+      break;
+    // Regionszuordnung muss ein nicht-leeres Array sein, das nur aus Strings
+    // bestehen darf, die nicht leer sein duerfen.
+    case MQ_DATA_QUERY_PERMITTED:
+      if ( pointerp(newvalue) ) {
+        newvalue = filter(filter(newvalue, #'stringp), #'sizeof);
+        if (!sizeof(newvalue))
+          return MQ_KEY_INVALID;
+      }
+      else
+        return MQ_KEY_INVALID;
+      break;
+    default:
+      return MQ_KEY_INVALID;
+  }
+
+  mixed *altemq = m_entry(miniquests, mq_obj);
+  int nummer = miniquests[mq_obj,MQ_DATA_QUESTNO];
+  miniquests[mq_obj, param] = newvalue;
+  by_num[nummer] = ({miniquests[mq_obj,MQ_DATA_POINTS], mq_obj});
+  save_info();
+
+  MQMLOG(sprintf("ChangeMiniQuest: %s from %O to %O (%s)", mq_obj,
+    altemq, m_entry(miniquests, mq_obj), getuid(this_interactive())));
+
+  ClearUsersMQCache();
+  if (find_call_out(#'DumpMiniQuests) == -1)
+    call_out(#'DumpMiniQuests, 60, this_interactive());
+  return 1;
+}
+
+mixed QueryMiniQuestByName(string name) {
+  if (!allowed_write_access())
+    return 0;
+  return deep_copy(miniquests & ({name}));
+}
+
+mixed QueryMiniQuestByNumber(int nummer) {
+  // Zugriffsabsicherung erfolgt dort auch, daher hier unnoetig
+  return (by_num[nummer]?QueryMiniQuestByName(by_num[nummer][1]):0);
+}
+
+// Das vollstaendige MQ-Mapping nur als Kopie ausliefern.
+mixed QueryMiniQuests() {
+  return allowed_write_access() ? deep_copy(miniquests) : 0;
+}
+
+// De-/Aktivieren einer Miniquest, wirkt als Umschalter, d.h. eine aktive
+// MQ wird durch Aufruf dieser Funktion als inaktiv markiert und umgekehrt.
+int SwitchMiniQuestActive(string name) {
+  if (!allowed_write_access())
+    return -1;
+  // Haben wir eine solche MQ ueberhaupt?
+  if (!member(miniquests, name))
+    return -2;
+
+  // active-Flag invertieren
+  miniquests[name, MQ_DATA_ACTIVE] = !miniquests[name, MQ_DATA_ACTIVE];
+  save_info();
+
+  MQMLOG(sprintf("%s: %s (%s)",
+    (miniquests[name,MQ_DATA_ACTIVE]?"Activate":"Deactivate"), name,
+    getuid(this_interactive()))
+  );
+  return miniquests[name,MQ_DATA_ACTIVE];
+}
+
+int GiveMiniQuest(object winner) {
+  // Spieler muss existieren und interactive sein.
+  if (!winner ||
+      (this_interactive() && (this_interactive() != winner)) ||
+      ((this_player() == winner) && !query_once_interactive(winner)))
+    return MQ_ILLEGAL_OBJ;
+  // Gaeste koennen keine Miniquests bestehen.
+  if (winner->QueryGuest())
+    return MQ_GUEST;
+  // Aufrufendes Objekt existiert gar nicht?
+  if (!previous_object())
+    return MQ_ILLEGAL_OBJ;
+
+  string objname = load_name(previous_object());
+  // Miniquest muss existieren
+  if (!member(miniquests,objname))
+    return MQ_KEY_INVALID;
+  // Inaktive Miniquests koennen nicht vergeben werden.
+  if (!miniquests[objname, MQ_DATA_ACTIVE])
+    return MQ_IS_INACTIVE;
+
+  string mq = (MASTER->query_mq(getuid(winner)) || "");
+
+  // Spieler hat die MQ schonmal bestanden? Dann keine weiteren Aktivitaet
+  // noetig
+  if (test_bit(mq, miniquests[objname, MQ_DATA_QUESTNO]))
+    return MQ_ALREADY_SET;
+
+  catch(mq = set_bit(mq, miniquests[objname,MQ_DATA_QUESTNO]);publish);
+  MASTER->update_mq(getuid(winner), mq);
+
+  MQSOLVEDLOG(sprintf("%s: %s, (#%d), (Stupse %d)",
+    objname, geteuid(winner), miniquests[objname, MQ_DATA_QUESTNO],
+    miniquests[objname, MQ_DATA_POINTS]));
+
+  // Miniquest-Event ausloesen
+  EVENTD->TriggerEvent( EVT_LIB_MINIQUEST_SOLVED, ([
+            E_OBJECT: previous_object(),
+            E_OBNAME: objname,
+            E_PLNAME: getuid(winner),
+            E_MINIQUESTNAME: miniquests[objname, MQ_DATA_TITLE] ]) );
+
+  // Spielereintrag aus dem MQ-Punkte-Cache loeschen
+  m_delete(users_mq, getuid(winner));
+
+  return 1;
+}
+
+int QueryMiniQuestPoints(mixed pl) {
+  string spieler;
+
+  //if (!allowed_write_access())
+  //  return 0;
+
+  if (!pl)
+    return -1;
+
+  if (!objectp(pl) && !stringp(pl))
+    return -2;
+
+  if (objectp(pl) && !query_once_interactive(pl))
+    return -3;
+
+  if (objectp(pl))
+    spieler = getuid(pl);
+  else
+    spieler = pl;
+
+  if (!member(users_mq, spieler)) {
+    int mqpoints;
+    int p=-1;
+    string s = (MASTER->query_mq(spieler) || "");
+    while( (p=next_bit(s, p)) != -1) {
+      mqpoints+=by_num[p][0];
+    }
+    users_mq[spieler] = mqpoints;
+  }
+  return users_mq[spieler];
+}
+
+int HasMiniQuest(mixed pl, mixed name) {
+  string mq, spieler;
+
+  if (!pl || !name)
+    return MQ_ILLEGAL_OBJ;
+
+  if (!objectp(pl) && !stringp(pl))
+    return MQ_ILLEGAL_OBJ;
+
+  if (objectp(pl) && !query_once_interactive(pl))
+    return MQ_ILLEGAL_OBJ;
+
+  if (!objectp(name) && !stringp(name) && !intp(name))
+    return MQ_ILLEGAL_OBJ;
+
+  if (objectp(name))
+    name = explode(object_name(name), "#")[0];
+
+  if ( intp(name) )
+    name = by_num[name][1];
+
+  if (objectp(pl))
+    spieler = getuid(pl);
+  else
+    spieler = pl;
+
+  if (!member(miniquests,name))
+    return MQ_KEY_INVALID;
+
+  mq = (MASTER->query_mq(spieler) || "");
+
+  return test_bit(mq, miniquests[name, MQ_DATA_QUESTNO]);
+}
+
+// Zum Von-Hand-Setzen der MiniQuests
+int SetPlayerMiniQuest(string pl, string name) {
+  if(!allowed_write_access())
+    return 0;
+  if(!pl)
+    return MQ_ILLEGAL_OBJ;
+  if(!previous_object())
+    return MQ_ILLEGAL_OBJ;
+
+  if (!member(miniquests,name))
+    return MQ_KEY_INVALID;
+
+  string mq = (MASTER->query_mq(pl) || "");
+
+  if (test_bit(mq, miniquests[name,MQ_DATA_QUESTNO]))
+    return MQ_ALREADY_SET;
+
+  catch (mq = set_bit(mq, miniquests[name, MQ_DATA_QUESTNO]);publish);
+  MASTER->update_mq(pl, mq);
+
+  MQMLOG(sprintf("SetPlayerMiniQuest: %s %s (%s)",
+                 pl, name, getuid(this_interactive())));
+  // Spielereintrag aus dem MQ-Punkte-Cache loeschen
+  m_delete(users_mq, pl);
+  return 1;
+}
+
+int ClearPlayerMiniQuest(string pl, string name) {
+  if (!allowed_write_access())
+    return 0;
+  if (!pl)
+    return MQ_ILLEGAL_OBJ;
+  if (!previous_object())
+    return MQ_ILLEGAL_OBJ;
+
+  if (!member(miniquests,name))
+    return MQ_KEY_INVALID;
+
+  string mq = (MASTER->query_mq(pl) || "");
+
+  if (!test_bit(mq, miniquests[name, MQ_DATA_QUESTNO]))
+    return MQ_ALREADY_SET;
+
+  catch (mq = clear_bit(mq, miniquests[name, MQ_DATA_QUESTNO]);publish);
+  MASTER->update_mq(pl, mq);
+
+  MQMLOG(sprintf("ClearPlayerMiniQuest: %s %s (%s)",
+                 pl, name, getuid(this_interactive())));
+  // Spielereintrag aus dem MQ-Punkte-Cache loeschen
+  m_delete(users_mq, pl);
+  return 1;
+}
+
+// Umtragen von Miniquests von Objekt <old> auf Objekt <new>
+int MoveMiniQuest(string old_mqob, string new_mqob) {
+  if ( !allowed_write_access() )
+    return -1;
+
+  // Haben wir ueberhaupt einen solchen Eintrag?
+  if ( !member(miniquests, old_mqob) )
+    return -2;
+
+  // Pruefen, ob die als <new_mqob> angegebene Datei existiert.
+  if (new_mqob[<2..] == ".c")
+    new_mqob = new_mqob[0..<3];
+  new_mqob = explode(new_mqob, "#")[0];
+  new_mqob = (string)"/secure/master"->_get_path(new_mqob,0);
+  if (file_size(new_mqob+".c") <= 0)
+    return -3;
+  // Wenn das neue Objekt schon eine MQ vergibt, kann es keine weitere
+  // annehmen.
+  if ( member(miniquests, new_mqob) )
+    return -4;
+
+  // Der Miniquestliste einen neuen Key "new" mit den Daten des alten Keys
+  // hinzufuegen. m_entry() liefert alle Values dazu als Array, und der
+  // flatten-Operator "..." uebergibt dessen Elemente als einzelne Parameter.
+  m_add(miniquests, new_mqob, m_entry(miniquests, old_mqob)...);
+  m_delete(miniquests, old_mqob);
+  // Nummern-Index auch umtragen, sonst koennen Funktionen wie zB
+  // QueryMiniQuestByNumber() die neue nicht finden.
+  by_num[miniquests[new_mqob,MQ_DATA_QUESTNO]][1] = new_mqob;
+  return 1;
+}
+
+#define FRA_BIB "/d/ebene/miril/fraternitas/room/bibliothek"
+
+// Erlaubt die Abfrage aller MQs einer bestimmten Region fuer die Bibliothek
+// der kleinen und grossen Heldentaten in der Fraternitas.
+// Gibt ein Mapping der Form ([ indexnummer : titel; erledigt_Beschreibung ])
+// zurueck.
+mapping QuerySolvedMQsByDomain(mixed pl, string region) {
+  if ( !objectp(pl) && !stringp(pl) && 
+       load_name(previous_object())!=FRA_BIB) /*|| !allowed_write_access())*/
+    return ([:2]);
+
+  mapping res = m_allocate(30,2);   // reicht vermutlich
+  // Die angegebene Region muss in der Spalte MQ_DATA_ASSIGNED_DOMAIN
+  // enthalten sein, und das abfragende Objekt muss die Fraternitas-Bib sein
+  foreach(string mqobj, int mqp, int index, string task, int vis, int act,
+    string title, string donedesc, mapping restr, string domain:
+    miniquests) {
+      // aktive MQs der angeforderten Region zusammentragen, die der Spieler
+      // bestanden hat.
+      if ( domain == region && act && HasMiniQuest(pl, mqobj) )
+        m_add(res, index, title, donedesc);
+  }
+  //DEBUG(sprintf("%O\n",res));
+  return res;
+}
+#undef FRA_BIB
+
+// Abfrage der noch offenen MQs des angegebenen Spielers.
+// Zurueckgegeben wird ein Mapping mit den Miniquest-Nummern als Keys und
+// den Aufgabenbeschreibungen als Values, oder ein leeres Mapping, falls
+// das abfragende Objekt keine Zugriffsberechtigung hat, oder das 
+// uebergebene Spielerobjekt keine offenen Miniquests mehr hat.
+mapping QueryOpenMiniQuestsForPlayer(object spieler) {
+  // map() etwas beschleunigen
+  closure chk_restr = symbol_function("check_restrictions",
+                                      "/std/restriction_checker");
+  // Cache-Eintrag fuer das abfragende Objekt holen
+  string *list = mq_query_permitted[load_name(previous_object())];
+  mapping res = ([:2]); 
+  
+  if (!pointerp(list) || !sizeof(list))
+    return res;
+  // Liste der MQ-Objekte umwandeln in deren MQ-Nummer plus 
+  // Aufgabenbeschreibung, sofern der Spieler die MQ noch nicht bestanden
+  // hat, aber die Voraussetzungen erfuellt.
+  foreach ( string mq_obj : list ) 
+  {
+    // Nur wenn der Spieler die MQ noch nicht hat, kann er ueberhaupt einen
+    // Tip dazu bekommen.
+    if ( !HasMiniQuest(spieler, mq_obj) ) {
+      // Restriction Checker fragen, ob der Spieler statt des Hinweises
+      // eine Info bekommen muss, dass er eine Vorbedingung nicht erfuellt.
+      string restr_result = funcall(chk_restr, spieler, miniquests[mq_obj,
+        MQ_DATA_RESTRICTIONS]);
+      // Wenn so eine Info dabei rauskommt, wird diese in die Ergebnisliste
+      // uebernommen. In diesem Fall wird KEIN MQ-Hinweistext ausgeliefert.
+      if ( stringp(restr_result) )
+      {
+        m_add(res, miniquests[mq_obj,MQ_DATA_QUESTNO], 0, restr_result);
+      }
+      // Anderenfalls wird der Hinweistext uebernommen; einen Eintrag 
+      // bzgl. eines eventuellen Hinderungsgrundes gibt's dann nicht.
+      else
+      {
+        m_add(res, miniquests[mq_obj,MQ_DATA_QUESTNO], 
+          miniquests[mq_obj,MQ_DATA_TASKDESC], 0);
+      }
+    }
+  }
+  // Ergebnisliste zurueckgeben.
+  return res;
+}
+
+// Datenkonverter fuer das bisherige MQ-Mapping von
+// ([ obj : ({daten1..daten5}) ]) nach
+// ([ obj : daten1; daten2; ...; daten9 ]), wobei daten6..9 als Leerspalten
+// erzeugt werden.
+/*void ConvertMQData() {
+  if ( !allowed_write_access() )
+    return;
+
+  by_num=([]);
+  // spaltenweise aus dem miniquests-Mapping ein Array aus Arrays erzeugen
+  // Zunaechst die Keys des Mappings aufnehmen.
+  mixed *mqdata = ({m_indices(miniquests)});
+
+  // Dann das Datenarray spaltenweise dazu (jedes Array ist eine Spalte).
+  mqdata += transpose_array(m_values(miniquests));
+  // 1. Array: Keys, alle weiteren jeweils eine Wertespalte
+
+  // Array erzeugen als Array aus 5 Array-Elementen, die alle soviele
+  // Nullen enthalten, wie es Miniquests gibt,
+  // ({ ({0,0,...}), ({0,0,...}), ({0,0,...}), ({0,0,...}), ({0,0,...}) })
+  // dieses hinzufuegen. Sind die hinzukommenden 5 Spalten.
+  mqdata += allocate(5, allocate(sizeof(miniquests),0));
+
+  // Mapping erzeugen, indem mit dem flatten-Operator "..." die Einzel-
+  // Arrays des Datenpakets uebergeben werden. Erzeugt auf diese Weise
+  // ([ keys : daten1; daten2; daten3; daten4; daten5; 0; 0; 0; 0; 0,])
+  miniquests=mkmapping(mqdata...);
+}
+
+// Neue Daten einlesen, Visible-Flag bei allen MQs per Default auf 1 setzen.
+// Es gibt keine wirklich unsichtbaren MQs mehr.
+void ReadNewData() {
+  if ( !allowed_write_access() )
+    return;
+
+  string *import = explode(read_file("/players/arathorn/mqdata"),"\n")-({""});
+  string *fields;
+  foreach(string mqdata : import) {
+    fields = explode(mqdata, "#")-({""});
+    DEBUG(sprintf("%O\n", fields[0]));
+    if ( miniquests[fields[4], MQ_DATA_QUESTNO] != to_int(fields[0]) ) {
+      raise_error("MQ-Nummern stimmen nicht ueberein!\n");
+      return;
+    }
+    // fields[4] ist der MQ-Objektpfad
+    miniquests[fields[4], MQ_DATA_TITLE] = fields[7];
+    miniquests[fields[4], MQ_DATA_DIARYTEXT] = fields[6];
+    miniquests[fields[4], MQ_DATA_TASKDESC] = fields[5];
+    miniquests[fields[4], MQ_DATA_VISIBLE] = 1; // Default: visible
+    miniquests[fields[4], MQ_DATA_ASSIGNED_DOMAIN] = fields[8];
+    miniquests[fields[4], MQ_DATA_QUERY_PERMITTED] = fields[9];
+    if ( fields[3] != "0" ) {
+      miniquests[fields[4], MQ_DATA_RESTRICTIONS] =
+        restore_value(fields[3]+"\n");
+    }
+    else miniquests[fields[4], MQ_DATA_RESTRICTIONS] = 0;
+    if ( fields[9] != "0" )
+      miniquests[fields[4], MQ_DATA_QUERY_PERMITTED] =
+        restore_value(fields[9]+"\n");
+    else miniquests[fields[4], MQ_DATA_QUERY_PERMITTED] = 0;
+  }
+}
+*/