Added public files

Roughly added all public files. Probably missed some, though.
diff --git a/secure/PlayerDeleter.c b/secure/PlayerDeleter.c
new file mode 100644
index 0000000..1b1ce2e
--- /dev/null
+++ b/secure/PlayerDeleter.c
@@ -0,0 +1,87 @@
+// MorgenGrauen MUDlib
+//
+// master.c -- master object
+//
+// $Id: master.c 7336 2009-11-19 20:37:31Z Zesstra $
+#pragma strong_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 <files.h>
+#include <wizlevels.h>
+#include <events.h>
+#include <config.h>
+#include <properties.h>
+
+protected void create() {
+  seteuid(getuid(this_object()));
+}
+
+public varargs int Delete(string plname, int nobanish, int noclone) {
+  // definitiv nur fuer EM+ und nur Objekt mit Level 66+ in der Callchain.
+  if (!ARCH_SECURITY || process_call())
+    return -1;
+
+  // existiert der Spieler?
+  if (!stringp(plname)
+      || !master()->find_userinfo(plname))
+    return -2;
+  // gibt es das Spielerobjekt?
+  object plob = find_player(plname) || find_netdead(plname);
+  // wenn kein Objekt: Dummyobjekt erzeugen
+  if (!objectp(plob) && !noclone) {
+    catch(plob = __create_player_dummy(plname));
+  }
+
+  int wlevel = query_wiz_level(plname);
+  string part_filename="/"+plname[0..0]+"/"+plname+".o";
+
+  // ggf. banishen
+  if (!nobanish && wlevel >= LEARNER_LVL)
+    master()->BanishName(plname, "So hiess mal ein Magier hier", 1);
+  else if (!nobanish && wlevel >= SEER_LVL)
+    master()->BanishName(plname, "So hiess mal ein Seher hier", 1);
+
+  // Spielpausen aufheben
+  master()->TBanishName(plname, 0);
+
+  // loggen
+  log_file("ARCH/ADMIN_USERDELETE",
+      sprintf("%s: %s geloescht durch %s (%s)\n",
+             strftime("%y%m%d-%H%M%S"),
+             plname,
+             secure_euid(),
+             to_string(query_ip_number(this_interactive())) ));
+
+
+  // Loesch-Event ausloesen
+  if (plob) {
+    EVENTD->TriggerEvent(EVT_LIB_PLAYER_DELETION, ([
+            E_PLNAME: plname,
+            E_ENVIRONMENT: environment(plob),
+            E_GUILDNAME: plob->QueryProp(P_GUILD) ]) );
+
+    // Spielerobjekt zerstoeren
+    plob->move("/room/void", M_NOCHECK);
+    plob->remove(1);
+  }
+
+  // Files loeschen
+  if (file_size("/"SECUREDIR"/save"+part_filename) > FSIZE_NOFILE)
+    rm("/"SECUREDIR"/save"+part_filename);
+  if (file_size("/"LIBSAVEDIR + part_filename) > FSIZE_NOFILE)
+    rm("/"LIBSAVEDIR + part_filename);
+  if (file_size("/"MAILDIR + part_filename) > FSIZE_NOFILE)
+    rm("/"MAILDIR + part_filename);
+
+  master()->RemoveFromCache(plname);
+
+  return 1;
+}
+
diff --git a/secure/awmaster.c b/secure/awmaster.c
new file mode 100644
index 0000000..cccdb1d
--- /dev/null
+++ b/secure/awmaster.c
@@ -0,0 +1,433 @@
+// MorgenGrauen MUDlib
+//
+// awmaster.c -- Armours- and Weapons-Master
+//
+#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 <combat.h>
+#include <language.h>
+#include <properties.h>
+#include <wizlevels.h>
+
+#define SAVEFILE  "/secure/ARCH/awmaster"
+#define ADUMPFILE "/log/ARCH/RUESTUNGEN"
+#define DDUMPFILE "/log/ARCH/DAMAGERS"
+#define WDUMPFILE "/log/ARCH/WAFFEN"
+
+#define AWF_PUTON  1
+#define AWF_PUTOFF 2
+#define AWF_BOOST  4
+#define AWF_ZAUBER 8
+#define AWF_RESIST 16
+
+#define AWM_TYPE      0
+#define AWM_CLASS     1
+#define AWM_EFF_CLASS 2
+#define AWM_FLAGS     3
+#define AWM_WEIGHT    4
+#define AWM_VALUE     5
+#define AWM_HANDS     6
+#define AWM_D_TYPE    7
+#define AWM_X_CLASS   8
+#define AWM_TIME      9
+
+mapping armours,weapons,damagers;
+private int save_me_soon;
+
+private int allowed()
+{
+  if (previous_object() && geteuid(previous_object())==ROOTID)
+    return 1;
+  if (!process_call() && previous_object() && this_interactive() && ARCH_SECURITY)
+    return 1;
+  return 0;
+}
+
+void create()
+{
+    seteuid(getuid(this_object()));
+
+    if (!restore_object(SAVEFILE))
+    {
+        armours = ([]);
+        weapons = ([]);
+        damagers = ([]);
+    }
+    if (widthof(damagers) == 1) {
+      mapping tmp = damagers;
+      damagers = m_allocate(sizeof(tmp),2);
+      foreach(string r, int flag: tmp) {
+        damagers[r,0]=flag;
+      }
+    }
+}
+
+void save_me(int now)
+{
+  if (now)
+    save_object(SAVEFILE);
+  else
+    save_me_soon=1;
+}
+
+void reset() {
+  if (save_me_soon)
+  {
+    save_me_soon=0;
+    save_me(1);
+  }
+}
+
+public varargs int remove(int silent) {
+  save_me(1);
+  destruct(this_object());
+  return 1;
+}
+
+int xflags(object ob){
+    int re;
+    mapping m;
+    if (!ob || !objectp(ob))
+        return 0;
+    re=0;
+    if (((object)ob->QueryProp(P_WEAR_FUNC))==ob  ||
+        ((object)ob->QueryProp(P_WIELD_FUNC))==ob )
+        re += AWF_PUTON;
+    if (((object)ob->QueryProp(P_REMOVE_FUNC))==ob  ||
+        ((object)ob->QueryProp(P_UNWIELD_FUNC))==ob )
+        re += AWF_PUTOFF;
+    if (((object)ob->QueryProp(P_DEFEND_FUNC))==ob ||
+        ((object)ob->QueryProp(P_HIT_FUNC))==ob    )
+        re += AWF_BOOST;
+    // ists nen Mapping und nicht leer?
+    if (mappingp(m=(mapping)ob->QueryProp(P_RESISTANCE_STRENGTHS))
+        && sizeof(m))
+        re += AWF_RESIST;
+    return re;
+}
+
+void RegisterArmour()
+{   object  ob;
+    string  id;
+    int     flag,h;
+
+    if (!objectp(ob=previous_object()) || 
+      member(inherit_list(ob),"/std/armour.c")==-1)
+      return;
+    id = explode(object_name(ob),"#")[0];
+    if (member(armours,id))
+    {
+        armours[id][AWM_TIME]=time();
+        flag=0;
+        if ((h=(int)ob->QueryProp(P_AC)) > armours[id][AWM_CLASS])
+        {
+            armours[id][AWM_CLASS]=h;
+            flag=1;
+        }
+        if ((h=(int)ob->QueryProp(P_EFFECTIVE_AC)) > armours[id][AWM_EFF_CLASS])
+        {
+            armours[id][AWM_EFF_CLASS]=h;
+            flag=1;
+        }
+        if ((h=(int)ob->QueryProp(P_NR_HANDS)) < armours[id][AWM_HANDS])
+        {
+            armours[id][AWM_HANDS]=h;
+            flag=1;
+        }
+        if ((h=xflags(ob)) != armours[id][AWM_FLAGS])
+        {
+            armours[id][AWM_FLAGS]=h;
+            flag=1;
+        }
+    }
+    else
+    {
+      armours += ([ id : 
+        ([
+            AWM_TYPE      : ob->QueryProp(P_ARMOUR_TYPE) ,
+            AWM_CLASS     : ob->QueryProp(P_AC) ,
+            AWM_EFF_CLASS : ob->QueryProp(P_EFFECTIVE_AC) ,
+            AWM_FLAGS     : xflags(ob),
+            AWM_WEIGHT    : ob->QueryProp(P_WEIGHT) ,
+            AWM_VALUE     : ob->QueryProp(P_VALUE) ,
+            AWM_HANDS     : ob->QueryProp(P_NR_HANDS) , // Fuer Schilde
+            AWM_D_TYPE    : ob->QueryProp(P_DAM_TYPE) ,
+            AWM_X_CLASS   : ob->QueryProp(P_EFFECTIVE_WC) ||
+                            ob->QueryProp(P_WC),
+            AWM_TIME      : time()
+        ]) ]);
+    }
+    save_me(0);
+}
+
+void RegisterWeapon()
+{   object  ob;
+    string  id;
+    int     flag,h;
+
+    if (!objectp(ob=previous_object()) ||
+      member(inherit_list(ob),"/std/weapon.c")==-1)
+      return;
+    id = explode(object_name(ob),"#")[0];
+    if (member(weapons,id))
+    {
+        weapons[id][AWM_TIME] = time();
+        flag=0;
+        if ((h=(int)ob->QueryProp(P_WC)) > weapons[id][AWM_CLASS])
+        {
+            weapons[id][AWM_CLASS]=h;
+            flag=1;
+        }
+        if ((h=(int)ob->QueryProp(P_EFFECTIVE_WC)) > weapons[id][AWM_EFF_CLASS])
+        {
+            weapons[id][AWM_EFF_CLASS]=h;
+            flag=1;
+        }
+        if ((h=(int)ob->QueryProp(P_NR_HANDS)) < weapons[id][AWM_HANDS])
+        {
+            weapons[id][AWM_HANDS]=h;
+            flag=1;
+        }
+        if ((h=xflags(ob)) != weapons[id][AWM_FLAGS])
+        {
+            weapons[id][AWM_FLAGS]=h;
+            flag=1;
+        }
+    }
+    else
+    {
+      weapons += ([ id :
+        ([
+            AWM_TYPE      : ob->QueryProp(P_WEAPON_TYPE) ,
+            AWM_CLASS     : ob->QueryProp(P_WC) ,
+            AWM_EFF_CLASS : ob->QueryProp(P_EFFECTIVE_WC) ,
+            AWM_FLAGS     : xflags(ob),
+            AWM_WEIGHT    : ob->QueryProp(P_WEIGHT) ,
+            AWM_VALUE     : ob->QueryProp(P_VALUE) ,
+            AWM_HANDS     : ob->QueryProp(P_NR_HANDS) ,
+            AWM_D_TYPE    : ob->QueryProp(P_DAM_TYPE) ,
+            AWM_X_CLASS   : ob->QueryProp(P_EFFECTIVE_AC) || 
+                            ob->QueryProp(P_AC),
+            AWM_TIME      : time()
+        ]) ]);
+    }
+    save_me(0);
+}
+
+void RegisterDamager(object dam_ob,int old_dam, int new_dam)
+{   object ob;
+    int flag;
+    string fn;
+
+    if (!objectp(ob=previous_object()) ||
+      (member(inherit_list(ob),"/std/weapon.c")==-1 &&
+       member(inherit_list(ob),"/std/armour.c")==-1 ))
+      return;
+    if (old_dam>new_dam) // Repair
+        flag=2;
+    else if (new_dam>old_dam) // Damage
+        flag=1;
+    else
+        return;
+    if (!objectp(dam_ob))
+        return;
+    if (!(fn=old_explode(object_name(dam_ob),"#")[0]) || !stringp(fn))
+        return;
+    damagers[fn,0]=damagers[fn,0]|flag;
+    damagers[fn,1]=time(); 
+    save_me(0);
+}
+
+string dtdump(mixed arg)
+{   string re;
+    int i,w;
+
+    if (stringp(arg))
+        return capitalize(arg);
+    if (!pointerp(arg) || !stringp(arg[0]))
+        return "<NONE>";
+    if ((i=sizeof(arg))==1)
+        return capitalize(arg[0]);
+    w = (31-i)/i;
+    if (w--<1)
+        return "<MANY>";
+    for (re="",--i;i>=0;i--)
+    {
+        if (!stringp(arg[i]))
+            re += "-";
+        else
+            re += capitalize(arg[i][0..w]);
+        if (i)
+            re += "|";
+    }
+    return re;
+}
+
+int Dump(mixed what, int sortidx)
+{   string  file,*ind;
+    mapping dump;
+
+    if (!allowed())
+        return -1;
+
+    if (!what)
+    {
+        write("Nimm doch mal einen richtigen Parameter!\n");
+        return 0;
+    }
+    if (stringp(what) && sizeof(what)>0)
+    {
+        what==what[0..0];
+        if (what=="a")
+            what=1;
+        else if (what=="w")
+            what=2;
+        else if (what=="d")
+            what=3;
+        else
+        {
+            write("Nimm doch mal einen richtigen Parameter!\n");
+            return 0;
+        }
+    }
+    if (!intp(what) || what<1 || what>3)
+    {
+        write("Nimm doch mal einen richtigen Parameter!\n");
+        return 0;
+    }
+    if (what==3) // Die 'damagers' haben ein anderes Ausgabeformat
+    {
+        printf("AWM: Dumping 'damagers' to '%s'\n",DDUMPFILE);
+        if (sizeof(damagers) < 1) {
+            write("AWM: Dump aborted, mapping empty.\n");
+            return 1;
+        }
+        if (file_size(DDUMPFILE)>1)
+            rm(DDUMPFILE);
+        
+        // nach letzter Aktualisierung sortieren.
+        mixed sorted = sort_array(m_indices(damagers),
+            function int (string a, string b) {
+                return damagers[a,1] < damagers[b,1];
+            } );
+
+        string ausgabe = sprintf(
+            "--- Damagers-Dump --- %s --- %s ---\n\n"+
+            "%:15s D R [Filename]\n",
+            dtime(time()),capitalize(getuid(this_interactive())),
+            "Datum/Zeit");
+        foreach(string rue : sorted) {
+            ausgabe += sprintf("%:15s %1s %1s %s\n",
+                strftime("%y%m%d-%H:%M:%S",damagers[rue,1]),
+                (damagers[rue,0]&1?"+":"-"),
+                (damagers[rue,0]&2?"+":"-"),
+                rue);
+        }
+        
+        write_file(DDUMPFILE, ausgabe);
+        return 1;
+    }
+    if (what==2)
+        what=0;
+    file=(what?ADUMPFILE:WDUMPFILE);
+
+    printf("AWM: Dumping '%s' to '%s'\n",
+        (what?"armours":"weapons"),file);
+    
+    dump=(what?armours:weapons);
+    
+    if (sortidx) {
+      ind = sort_array(m_indices(dump),
+          function int (string a, string b)
+          {return dump[a][sortidx] < dump[b][sortidx];} );
+    }
+    else
+      ind = sort_array(m_indices(dump),#'>);
+    
+    if (sizeof(ind) < 1)
+    {
+        write("AWM: Dump aborted, mapping empty.\n");
+        return 1;
+    }
+
+    if (file_size(file)>1)
+        rm(file);
+    
+    string ausgabe = sprintf(
+        "--- %s-Dump --- %s --- %s ---\n\n"+
+        "[Filename], Datum/Zeit\n"+
+        "    ____Typ___ CLS ECL XCL NFBR WGHT. VALUE H %30.30'_'|s\n",
+        (what?"Ruestungs":"Waffen"),dtime(time()),
+        capitalize(getuid(this_interactive())),"DamType(s)");
+
+    foreach(string index : ind)
+    {
+        ausgabe += sprintf(
+            "[%s] %s\n    %10s %3d %3d %3d %1s%1s%1s%1s %5d %5d %1d %-30.30s\n",
+            index, strftime("%y%m%d-%H:%M:%S",dump[index][AWM_TIME]),
+            dump[index][AWM_TYPE],
+            dump[index][AWM_CLASS],
+            dump[index][AWM_EFF_CLASS],
+            dump[index][AWM_X_CLASS],
+           (dump[index][AWM_FLAGS]&AWF_PUTON?"+":"-"),
+           (dump[index][AWM_FLAGS]&AWF_PUTOFF?"+":"-"),
+           (dump[index][AWM_FLAGS]&AWF_BOOST?"+":"-"),
+           (dump[index][AWM_FLAGS]&AWF_RESIST?"+":"-"),
+            dump[index][AWM_WEIGHT],
+            dump[index][AWM_VALUE],
+            dump[index][AWM_HANDS],
+            dtdump(dump[index][AWM_D_TYPE]) );
+    }
+    write_file(file,ausgabe);
+    write("AWM: Done.\n");
+    return 1;
+}
+
+int Unregister(string what)
+{
+    if (!allowed())
+        return -1;
+    if (!what)
+    {
+        write("Du solltest schon einen Filenamen angeben!\n");
+        return 0;
+    } 
+    if (member(armours,what))
+    {
+        m_delete(armours,what);
+        write("Unregistered "+what+" from 'armours'.\n");
+        return 1;
+    }
+    if (member(weapons,what))
+    {
+        m_delete(weapons,what);
+        write("Unregistered "+what+" from 'weapons'.\n");
+        return 1;
+    }
+    if (member(damagers,what))
+    {
+        m_delete(damagers,what);
+        write("Unregistered "+what+" from 'damagers'.\n");
+        return 1;
+    }
+    save_me(0);
+    return 0;
+}
+
+int ResetDamagers()
+{
+    if (!allowed())
+        return -1;
+    damagers = m_allocate(0,2);
+    save_me(1);
+    return 1;
+}
+
diff --git a/secure/bbmaster.c b/secure/bbmaster.c
new file mode 100644
index 0000000..9677f7f
--- /dev/null
+++ b/secure/bbmaster.c
@@ -0,0 +1,393 @@
+#pragma strict_types
+#pragma no_clone
+#pragma no_shadow
+#pragma no_inherit
+//#pragma pedantic
+//#pragma range_check
+#pragma warn_deprecated
+
+#include <wizlevels.h>
+#include <daemon.h>
+#include <events.h>
+#include <strings.h>
+#include <files.h>
+
+#define FTPSAVE "/secure/ARCH/ftpd"
+
+#define MAXLOGSIZE 2000000
+#define SMALLLOGSIZE 200000
+#define LOGTIME 120
+#define MAXBUFFSIZE 2000
+
+#define DEBUG(x)  if (find_player("zesstra"))\
+            tell_object(find_player("zesstra"),\
+                                      "EDBG: "+x+"\n")
+
+// fuer FTP.
+private mapping monitored;
+
+
+#define D_LOGTIME    0  // Logendezeit, <int>
+#define D_FLAGS      1  // Flags
+#define D_ERSTIE     2  // Name des Ersties.
+#define D_IPSTRING   3
+#define D_INDEX      4  // Index fuer D_LOG, naechster freier index.
+#define D_LOG        5  // Logdaten, <mixed> (Array)
+/* Datenstruktur von D_LOG:
+   ({ ({<zeit>, <kommando>, <environment>}), .... })
+   */
+#define DL_TIME  0  // int
+#define DL_CMD   1  // string
+#define DL_ENV   2  // string
+private nosave mapping ldata = m_allocate(0,D_LOG);
+
+// Flags:
+#define FL_PERMANENT    1 // 'permanent', d.h. nicht nur kurz nach Einloggen
+#define FL_SYNC         2 // nicht puffern, synchron auf Platte schreiben.
+
+// Ja. Macht das bloss nicht nach.
+#define P_SECOND "second"
+
+public int query_bb();
+public void writebb( string msg );
+public varargs void BBWrite(string msg, int catmode);
+
+public int add( string user, int timeout );
+public int sub( string user );
+public void ftpbb( string user, string msg );
+
+private void scan_bb_opfer();
+private void DumpData(string uid, string erstie, string ip, int flags, mixed logdata);
+private void ProcessBuffer(string uid);
+private void RemoveTemporaryPlayer(string uid);
+
+public void create()
+{
+    seteuid( getuid(this_object()) );
+    restore_object( FTPSAVE );
+    scan_bb_opfer();
+    EVENTD->RegisterEvent(EVT_LIB_LOGIN, "Eventhandler", this_object());
+    EVENTD->RegisterEvent(EVT_LIB_LOGOUT, "Eventhandler", this_object());
+    log_file("ARCH/bbmaster.log", strftime("%c: bbmaster wurde geladen.\n"));
+}
+
+// Auf den asynchronen Logout-event zu warten ermoeglicht es theoretisch, 1-2s
+// das Log zu umgehen. Andererseits geht das ohnehin nur dann, wenn die
+// Logzeit eh abgelaufen ist.
+public void Eventhandler(string eid, object trigob, mixed data) {
+  if (previous_object() == find_object(EVENTD)) {
+    string uid;
+    if (objectp(trigob)
+        && strstr(load_name(trigob),"/std/shells/") == 0
+        && !trigob->QueryGuest()) {
+      // Bei Login und Logout den BBMode einschalten (weil der Loginevent ja
+      // erst 1-2s nach Einloggen abgearbeitet wird.
+      trigob->__set_bb(1);
+      uid=getuid(trigob);
+    }
+    else {
+      // kein Objekt mehr da. Vermutlich hat ein Spieler 'ende' gemacht, aber
+      // es koennte auch sein, dass jemand mit nem Selbstzerstoerer den Event
+      // gefakt hat. Aber selbst wenn, viel kann man damit nicht erreichen.
+      uid = data[E_PLNAME];
+      if (!stringp(uid)) return;
+      // Pruefung auf nicht-Anwesenheit von uid waere noch moeglich, hat aber
+      // Probleme, wenn ein Spieler sehr schnell wieder einloggt.
+    }
+
+    if (eid == EVT_LIB_LOGOUT && member(ldata,uid)) {
+      // Wenn Logout und es gibt Daten im Puffer, koennte man die evtl.
+      // wegschreiben oder loeschen.
+      ProcessBuffer(uid);
+      // auf jeden Fall temporaere Spieler entfernen. (Wichtig!)
+      RemoveTemporaryPlayer(uid);
+    }
+  }
+}
+
+// schreibt alle Puffer synchron, ohne Callout... Kann laggen.
+public int ProcessAllBuffers() {
+  
+    if (extern_call() && !ARCH_SECURITY)
+    return -1;
+
+  foreach(string uid, int logtime, int flags, string erstie, string ip,
+          int index, mixed data: ldata) {
+    if (index) {
+      DumpData(uid, erstie, ip, flags, data);
+      ldata[uid,D_LOG]=({});
+      ldata[uid,D_INDEX]=0;
+    }
+  }
+  return 1;
+}
+
+private void ProcessBuffer(string uid) {
+    
+  if (time() <= ldata[uid,D_LOGTIME]
+      && ldata[uid,D_INDEX]) {
+    // Daten wegschreiben, wenn Logzeit nicht abgelaufen. Sonst nicht.
+    call_out(#'DumpData, 2, uid, ldata[uid,D_ERSTIE], ldata[uid,D_IPSTRING],
+	                    ldata[uid,D_FLAGS], ldata[uid,D_LOG]);
+  }
+  ldata[uid,D_LOG] = ({});
+  ldata[uid,D_INDEX] = 0;
+}
+
+private void DumpData(string uid, string erstie, string ip, int flags, mixed logdata) {
+  string res = sprintf("\n%s%s, IP: %s\n", capitalize(uid),
+                      (stringp(erstie) ? " ("+capitalize(erstie)+")" : ""),
+		      (stringp(ip) ? ip : "Unbekannt"));
+  logdata-=({0});
+  foreach(mixed arr : logdata) {
+    res+=sprintf("%O: %O [%s]\n", 
+        strftime("%y%m%d-%H%M%S",arr[DL_TIME]),
+        arr[DL_CMD], arr[DL_ENV] || "<unbekannt>");
+  }
+
+  //DEBUG("DumpData: "+res);
+  if (flags & FL_PERMANENT)
+    catch(log_file("ARCH/bb."+uid, res, MAXLOGSIZE));
+  else if (file_size(LIBLOGDIR"/ARCH/bbmaster") == FSIZE_DIR)
+    catch(log_file("ARCH/bbmaster/"+uid, res, SMALLLOGSIZE));
+  // kein else, in anderen Faellen werden die Daten verworfen.
+}
+
+private void AddTemporaryPlayer(string uid) {
+    // natuerlich nur, wenn noch nix eingetragen.
+    if (!member(ldata, uid)) {
+      object ob = find_player(uid) || find_netdead(uid);
+      
+      mixed erstie;
+      if (ob)
+        erstie = (string)ob->QueryProp(P_SECOND);
+
+      ldata += ([uid: time() + LOGTIME + random(LOGTIME/2); 
+	              0;
+		      (stringp(erstie) ? erstie : 0);
+		      query_ip_number(ob);
+		      0; ({})
+	        ]);
+    }
+}
+
+private void RemoveTemporaryPlayer(string uid) { 
+  if (!(ldata[uid,D_FLAGS] & FL_PERMANENT)) {
+    m_delete(ldata, uid);
+  }
+}
+
+
+// Vom Spielererobjekt bei Erschaffung in InitPlayer() gerufen.
+public int query_bb()
+{
+    
+    if (load_name(previous_object())[0..11] != "/std/shells/")
+        return 0;
+
+    // in jedem Fall wird nun (temporaer) der BB-Modus aktiviert.
+    if (!previous_object()->QueryGuest())
+      previous_object()->__set_bb(1);
+
+    // nur fuer 'permanente' auch 1 zurueckgeben.
+    return ldata[getuid(previous_object()),D_FLAGS] & FL_PERMANENT;
+}
+
+
+
+// neue Funktion. Kriegt nur Kommandosstring uebergegen, werden ggf. gepuffert
+// und dann weggeschrieben.
+public varargs void BBWrite(string msg, int catmode) {
+  
+  if ( !this_interactive() ||
+      (extern_call() && 
+       strstr(load_name(previous_object()), "/std/shells/") != 0 ) )
+    return;
+
+  string uid = getuid(this_interactive());
+
+  if (!member(ldata, uid))
+    AddTemporaryPlayer(uid);  
+  else if (ldata[uid,D_LOGTIME] < time()) {
+    // Logzeit abgelaufen. -> ENDE.
+    if (ldata[uid,D_INDEX]) {
+      this_interactive()->__set_bb(0);
+      // es kann vorkommen, dass hier nen ProcessBuffer mit anderer uid
+      // drinhaengt. Ist aber egal, dann wird der Puffer halt naechstesmal
+      // geschrieben.
+      if (find_call_out(#'ProcessBuffer) == -1)
+          call_out(#'ProcessBuffer, 2, uid);
+    }
+    return;
+  }
+
+  // im synchronen Modus direkt auf Platte schreiben.
+  //DEBUG("BBWrite: Flags von +"+uid+": "+to_string(ldata[uid,D_FLAGS])
+  //    +"\n");
+  if (ldata[uid,D_FLAGS] & FL_SYNC) {
+    //DEBUG("BBWrite: Syncmodus\n");
+    if (!catmode) {
+      msg = sprintf("%s: %s [%O]\n", strftime("%y%m%d-%H%M%S"),
+	  msg, environment(this_interactive()));
+    }
+    else
+      msg = msg + "\n";
+    log_file( "ARCH/bb."+uid, msg, MAXLOGSIZE );
+    return;
+  }
+
+  // alle anderen werden erstmal gepuffert. 
+
+  // wenn catmode und nen Index > 0 wird der Kram an den vorherigen Eintragen
+  // angehaengt.
+  int index = ldata[uid,D_INDEX];
+  if (catmode && index > 0) {
+    --index;
+    ldata[uid,D_LOG][index][DL_CMD] += msg;
+  }
+  else {
+    // Puffer vergroessern?
+    if (index >= sizeof(ldata[uid,D_LOG]))
+      ldata[uid,D_LOG]+=allocate(100);
+    ldata[uid,D_LOG][index] = ({ time(), msg, 
+	                         object_name(environment(this_interactive())) 
+                               });
+    ldata[uid,D_INDEX]++;
+    // es kann vorkommen, dass hier nen ProcessBuffer mit anderer uid
+    // drinhaengt. Ist aber egal, dann wird der Puffer halt naechstesmal
+    // geschrieben.
+    if (index > MAXBUFFSIZE 
+        && find_call_out(#'ProcessBuffer) == -1)
+      call_out(#'ProcessBuffer, 2, uid);
+  }
+}
+
+// Alte Funktion, kriegt Strings, teilweise mit Datum, teilweise ohne,
+// schrieb frueher nur weg. msg faengt entweder mit einem Datum/Zeit-String
+// oder mit einem "->" an.
+public void writebb( string msg )
+{
+  int catmode; 
+ 
+  if ( !this_interactive() ||
+      (extern_call() && 
+       strstr(load_name(previous_object()), "/std/shells/") != 0 ) )
+    return;
+ 
+  // erstmal String bereinigen.
+  msg = trim(msg,TRIM_RIGHT,"\n");
+  if (strstr(msg,"->") == 0) {
+    catmode=1;
+    msg= " -> " + msg[2..];
+  }
+  else {
+    // faengt mit Datumsstring an, erstes Leerzeichen ab dem zehnten Zeichen
+    // suchen und von dort abschneiden.
+    msg = msg[strstr(msg," ",10)+1 ..];
+  }
+
+  // Dann weitergeben
+  BBWrite(msg, catmode);
+}
+
+private void scan_bb_opfer()
+{
+    string* lines;
+    object pl;
+    string uid;
+
+    // diese user werden 'permanent' ueberwacht, nicht nur direkt nach dem
+    // Einloggen.
+    lines = explode( lower_case( read_file("/secure/ARCH/BB_OPFER.dump")
+                                       || "" ), "\n" )[2..];
+    
+    foreach(string line : lines) {
+        if( sizeof(line) && line[0] != '#' ) {
+	    uid=line[0 .. member(line,' ')-1];
+	    AddTemporaryPlayer(uid);
+	    ldata[uid,D_LOGTIME] = __INT_MAX__;
+	    ldata[uid,D_FLAGS] = FL_PERMANENT;
+	    pl = find_player(uid) || find_netdead(uid);
+	    if (pl)
+	      pl->__set_bb(1);
+	}
+    }
+}
+
+// Neuladen ist kein grosses Problem, weil der bbmaster ja automatisch
+// neugeladen wird. Nebeneffekt ist lediglich, dass fuer alle laufenden Logs
+// die Logzeit wieder von vorne anfaengt. Da das Schreiben der Puffer aber Lag
+// verursachen kann, duerfen es nur EM+.
+public varargs int remove(int silent) {
+ 
+  if (!ARCH_SECURITY)
+    return 0;
+
+  log_file("ARCH/bbmaster.log", strftime("%c: remove() called.\n"));
+
+  // alle Puffer leeren...
+  // ProcessAllBuffers() wird hierbei _ohne_ Limits aufgerufen! Kann fieses
+  // Lag erzeugen, aber sonst wurde der Kram evtl. nicht ordentlich
+  // geschrieben.
+  limited(#'ProcessAllBuffers);
+  destruct(this_object());
+  return 1;
+}
+
+
+// Alles ab hier nur zum Ueberwachen von FTP-Aktivitaeten.
+private int player_exists( string user )
+{
+    if ( !stringp( user ) || sizeof( user ) < 2 )
+        return 0;
+    
+  return file_size( "/save/" + user[0..0] + "/" + user + ".o" ) > 0;
+}
+
+public int add( string user, int timeout )
+{
+    if ( !ARCH_SECURITY || process_call() )
+        return -1;
+    
+    if( !stringp(user) || !player_exists(lower_case(user)) || !intp(timeout) ||
+        !timeout )
+        return -2;
+    
+    monitored[lower_case(user)] = timeout;
+    save_object( FTPSAVE );
+    
+    return 1;
+}
+
+
+public int sub( string user )
+{
+    if ( !ARCH_SECURITY || process_call() )
+        return -1;
+    
+    if( !stringp(user) || !member( monitored, lower_case(user) ) )
+        return -2;
+    
+    m_delete( monitored, lower_case(user) );
+    save_object( FTPSAVE );
+    
+    return 1;
+}
+
+
+public void ftpbb( string user, string msg )
+{
+    if( getuid(previous_object()) != ROOTID )
+        return;
+
+    if ( ldata[user,D_FLAGS] & FL_PERMANENT )
+        log_file( "ARCH/bb."+user, msg, 2000000 );
+
+    if ( monitored[user] ){
+        if ( monitored[user] > 0 && monitored[user] < time() )
+            sub( user );
+        else
+            CHMASTER->send( "FTP", capitalize(user), msg );
+    }
+}
+
diff --git a/secure/combat.c b/secure/combat.c
new file mode 100644
index 0000000..c6031a4
--- /dev/null
+++ b/secure/combat.c
@@ -0,0 +1,87 @@
+/*
+ * secure/combat.c
+ *
+ * the combat master object. It defines some useful functions to be
+ * used by weapons, armour and livings.
+ */
+#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 <combat.h>
+
+int default_weapon_class(string type) {
+	switch(type) {
+		case "knife":
+			return 50;
+		case "club":
+			return 70;
+		case "sword":
+			return 100;
+		case "axe":
+			return 90;
+	}
+	return 30;
+}
+
+int default_weapon_weight(string type) {
+	switch(type) {
+		case "knife":
+			return 100;
+		case "club":
+			return 1500;
+		case "sword":
+			return 2000;
+		case "axe":
+			return 1500;
+	}
+	return 1000;
+}
+
+int default_weapon_value(string type) {
+	switch(type) {
+		case "knife":
+			return 10;
+		case "club":
+			return 50;
+		case "sword":
+			return 500;
+		case "axe":
+			return 300;
+	}
+	return(0);
+}
+
+int valid_weapon_type(mixed t) {
+	if (member(({WT_SWORD, WT_AXE, WT_CLUB, WT_SPEAR, WT_STAFF,
+               WT_KNIFE}), t ) != -1) {
+		 return 1;
+	}
+	else {
+		log_file("COMBAT","Invalid weapon type: "+t+", object: "+
+				object_name(previous_object())+"\n");
+		return 0;
+	}
+}
+
+
+int valid_armour_type(mixed t) {
+	if (VALID_ARMOUR_TYPE(t)) {
+		 return 1;
+	}
+	else {
+		log_file("COMBAT","Invalid armour type: "+t+", object: "+
+				object_name(previous_object())+"\n");
+		return 0;
+	}
+}
+
+
+int query_prevent_shadow() { return 1; }
+
diff --git a/secure/config.h b/secure/config.h
new file mode 100644
index 0000000..a70c8da
--- /dev/null
+++ b/secure/config.h
@@ -0,0 +1,151 @@
+#ifndef _CONFIG_
+#define _CONFIG_
+
+/*
+ * config.h
+ *
+ * general configuration is done here, the selection of gamedriver mode
+ * and the language to use.
+ */
+
+#define MUDHOST "mg"
+#define FTPD_IP "87.79.24.60"
+#define UDPSERV "87.79.24.60"
+
+// Guess a MUDNAME if none is set.
+#ifndef MUDNAME
+// set mudname to MorgenGrauen if Mud started on the MG server
+#  if MUDHOST == __HOST_NAME__ && !defined(__TESTMUD__)
+#    define MUDNAME "MorgenGrauen"
+#  else
+#    define MUDNAME "MG-Homemud"
+#  endif
+#endif
+
+#if MUDNAME == "MorgenGrauen"
+#  define SSLPORT 4712
+#else
+#  define SSLPORT 4714
+#endif
+
+// undef to disable MSSP support.
+#define MSSP_SUPPORT "MSSP-Plaintext"
+
+// undef to disable support for pure-ftpd virtual users
+#define _PUREFTPD_
+
+#define _MUDLIB_NAME_ "MorgenGrauen"
+#define _MUDLIB_VERSION_ "3.3.5"
+
+/* define general pathnames */
+#define MASTER          "secure/master"
+#define MAILPATH        "mail/"
+#define SAVEPATH        "save/"
+#define NEWSPATH        "news/"
+#define NEWSSERVER      "secure/news"
+#define SECURESAVEPATH  "secure/save/"
+#define COMBAT_MASTER   "secure/combat"
+
+#define WIZARDDIR       "players"
+#define DOMAINDIR       "d"
+#define PROJECTDIR      "p"
+#define DOCDIR          "doc"
+#define GUILDDIR        "gilden"
+#define SPELLBOOKDIR    "spellbooks"
+#define MAILDIR         "mail"
+#define LIBSAVEDIR      "save"
+#define FTPDIR          "open"
+#define TMPDIR          "tmp"
+#define STDDIR          "std"
+#define SYSDIR          "sys"
+#define LIBOBJDIR       "obj"
+#define LIBROOMDIR      "room"
+#define ETCDIR          "etc"
+#define LIBLOGDIR       "log"
+#define NEWSDIR         "news"
+#define SECUREDIR       "secure"
+#define LIBDATADIR      "data"
+#define LIBITEMDIR      "items"
+
+#define NETDEAD_ROOM "/room/netztot"
+#define NETDEAD_CHECK_TIME 5
+
+/* define special userids */ 
+#define BACKBONEID " S T D "
+#define ROOTID     " R O O T "			/* uppercase !! */
+#define MAILID     " M A I L "
+#define NEWSID     " N E W S "
+#define NOBODY     "NOBODY"
+#define ROOMID     "room"
+#define POLIZEIID  "polizei"
+#define DOCID      "DOC"
+#define GUILDID    "GUILD"
+#define ITEMID     "ITEMS"
+
+// "Besondere" Magierlevel
+#define WIZLVLS ([ ROOTID: 100,\
+                   ROOMID: 21,\
+                   POLIZEIID: 21,\
+                   "alle": 25,\
+                   NOBODY: 0,\
+                   DOCID: 0,\
+                   GUILDID: 30,\
+                   ITEMID: 0,\
+                 ])
+
+#define MAX_LOG_SIZE 50000
+
+#ifndef TESTMUD
+#  define CALL_OUT_HARD 1200
+#  define CALL_OUT_SOFT 1000
+#else // !TESTMUD
+#  ifdef MIN_CALL_OUT
+#    define CALL_OUT_HARD 120
+#    define CALL_OUT_SOFT 100
+#  else
+#    define CALL_OUT_HARD 120000
+#    define CALL_OUT_SOFT 100000
+#  endif
+#endif // TESTMUD
+
+#define BACKBONE_WIZINFO_SIZE 8
+#define LIVING_NAME 3
+#define NAME_LIVING 4
+#define MEMORY_BUFF 5
+#define NETDEAD_MAP 6
+#define IP_NAMES    7
+
+
+#ifndef SIMUL_EFUN_FILE
+#define SIMUL_EFUN_FILE       "secure/simul_efun/simul_efun"
+#endif
+#ifndef SPARE_SIMUL_EFUN_FILE
+#define SPARE_SIMUL_EFUN_FILE "secure/simul_efun/spare/simul_efun"
+#endif
+
+#define MAX_MAILS_PER_HOUR 200
+
+//max. groesse von Mappings und Arrays sollten vom Driver oder der
+//Kommandozeile vordefiniert sein. Wenn nicht:
+#ifndef __MAX_MAPPING_KEYS__
+#define __MAX_MAPPING_KEYS__ 30000
+#endif
+#ifndef __MAX_MAPPING_SIZE__
+#define __MAX_MAPPING_SIZE__ 60000
+#endif
+#ifndef __MAX_ARRAY_SIZE__
+#define __MAX_ARRAY_SIZE__ 10000
+#endif
+
+// Haben wir einen Fehlerdaemonen zu Speicher der Daten? Wenn ja, welchen?
+// Der normale braucht Support fuer sqlite. Wenn der nicht existiert, lassen
+// wir das mit dem Errord sein.
+#ifdef __SQLITE__
+#define ERRORD "/secure/errord.c"
+#endif
+
+// Savefile-Version
+#define __LIB__SAVE_FORMAT_VERSION__ 1
+
+#endif // _CONFIG_
+
diff --git a/secure/debug.c b/secure/debug.c
new file mode 100644
index 0000000..f5a07ec
--- /dev/null
+++ b/secure/debug.c
@@ -0,0 +1,86 @@
+// MorgenGrauen MUDlib
+/** \file /file.c
+* Kurzbeschreibung.
+* Langbeschreibung...
+* \author <Autor>
+* \date <date>
+* \version $Id$
+*/
+/* Changelog:
+*/
+#pragma strict_types,save_types,rtt_checks
+#pragma no_clone
+#pragma no_inherit
+#pragma no_shadow
+#pragma pedantic
+#pragma range_check
+
+#include <functionlist.h>
+#include <lpctypes.h>
+
+#include <defines.h>
+#include <wizlevels.h>
+
+/** \def DEBUG
+  Outputs debug message to Maintainer, if Mainteiner is logged in. 
+*/
+#ifndef DEBUG
+#define DEBUG(x)  if (find_player("zesstra"))\
+            tell_object(find_player("zesstra"),\
+                                      "DDBG: "+x+"\n")
+#endif
+
+/** \fn set_object_next_reset(ob,zeit)
+  \brief setzt den naechsten Reset auf 'zeit'
+  \details setzt in Objekten den naechsten Reset - nur fuer EM+
+  \param[in] ob
+  (object) Objekt des Reset geaendert wird.
+  \param[in] zeit
+  (int) Zeit bis zum naechsten Reset.
+  \return (string) Gibt die uebergebene Zeit bis zum bisherigen Reset.
+  \author Zesstra
+  \date 06.10.2007
+  \sa set_object_heart_beat()
+*/
+// * Reset eines Objektes ein/ausschalten
+int set_object_next_reset(mixed ob, int zeit) {
+
+  if (stringp(ob))
+    ob=find_object(ob);
+
+  if (objectp(ob) && ELDER_SECURITY)
+  //if (objectp(ob) && SPECIAL_SECURITY && !clonep(ob))
+      return funcall(bind_lambda(#'efun::set_next_reset,ob),zeit);
+
+  return -2;
+}
+
+mixed query_variable(object ob, string var)
+{
+  if (!previous_object() || !IS_ARCH(geteuid(previous_object())) 
+      || !this_interactive() || !IS_ARCH(this_interactive())
+      || getuid(ob)==ROOTID )
+  {
+    write("Du bist kein EM oder Gott!\n");
+    return 0;
+  }
+
+  log_file("ARCH/QV", sprintf("%s: %O inquires var %s in %O\n",
+                              ctime(time()),this_interactive(),var,ob));
+  
+  mixed res = variable_list(ob, RETURN_FUNCTION_NAME|RETURN_FUNCTION_FLAGS|
+                                RETURN_FUNCTION_TYPE|RETURN_VARIABLE_VALUE);
+  int index = member(res,var);
+  if (index > -1)
+  {
+    return ({res[index],res[index+1],res[index+2],res[index+3]});
+  }
+
+  return 0;
+}
+
+protected void create() {
+  // secure_level() in *_SECURITY() prueft auf die EUID
+  seteuid(getuid(ME));
+}
+
diff --git a/secure/dropmaster.c b/secure/dropmaster.c
new file mode 100644
index 0000000..dcd09c1
--- /dev/null
+++ b/secure/dropmaster.c
@@ -0,0 +1,202 @@
+#pragma strict_types,save_types,rtt_checks
+#pragma pedantic,range_check
+#pragma no_inherit,no_clone
+#pragma no_shadow
+
+#define _NEED_DROPMASTER_IMPLEMENTATION_
+#include <dropmaster.h>
+#include <defines.h>
+#include <wizlevels.h>
+
+struct random_event_s {
+  string *names;
+  int reduction;
+  int *rnd_values;
+  int *default_rnd_values;
+  string creator;
+};
+
+// Mapping mit einem random_event_s als Wert pro Schluessel
+// Schluessel: 0: globaler Eintrag, ansonsten BP-Namen oder Teile von Pfaden
+// (/d/<eben>/<magier>/<gebiet>/)
+private mapping randomEvents;
+
+private struct random_event_s SanitizeRE(struct random_event_s re)
+{
+    // rnd_values, default_rnd_values und names muessen gleich lang sein -> auf
+    // das kuerzeste kuerzen. Achtung: macht implizit eine Kopie - WICHTIG!
+    int size = sizeof(re->rnd_values);
+    if (pointerp(re->names))
+        size = min(size, sizeof(re->names));
+    if (pointerp(re->default_rnd_values)) {
+      size = min(size, sizeof(re->default_rnd_values));
+      re->default_rnd_values = re->default_rnd_values[0..size-1];
+    }
+    if (pointerp(re->names))
+      re->names = re->names[0..size-1];
+
+    re->rnd_values = re->rnd_values[0..size-1];
+
+    return re;
+}
+
+// key==0 signifies the global key
+public varargs void CreateRandomEvent(string key, int *rnd_val, 
+                                      string *rnd_names, int red)
+{
+  struct random_event_s re = (<random_event_s>);
+  
+  if (!member(randomEvents,key)) {
+    re->rnd_values = copy(rnd_val) || DFLT_RND_VALUES;
+    // wenn rnd_val == 0 ist, ist das OK, dann wird spaeter immer
+    // DFLT_RND_VALUES genommen.
+    re->default_rnd_values = copy(rnd_val);
+    // names darf auch 0 sein, wenn Defaultwerte genommen werden sollen.
+    re->names = copy(rnd_names);
+    re->reduction = red || RND_REDUCTION;
+    re->creator = object_name(extern_call() ? previous_object() : this_object());
+
+    randomEvents[key] = SanitizeRE(re);
+  }
+}
+
+public varargs int ChangeRandomEvent(string key, int *rnd_val, int *rnd_val_dflt, 
+                                      string *rnd_names, int red)
+{
+  if (process_call()) return -1;
+  if (!member(randomEvents, key)) return -2;
+  struct random_event_s re = randomEvents[key];
+  // Schreibzugriff erlauben? Nur fuer Erschafferobjekte und EM.
+  // Bemerkung: RM+ haben Zugriff, weil sie Schreibzugriff auf das
+  // Erschafferobjekt haben.
+  if (extern_call()
+      && re->creator != object_name(previous_object())
+      && !ARCH_SECURITY)
+    return -1;
+
+  if (pointerp(rnd_val))
+    re->rnd_values = rnd_val;
+  if (pointerp(rnd_val_dflt))
+    re->default_rnd_values = rnd_val_dflt;
+  if (pointerp(rnd_names))
+    re->names = rnd_names;
+  if (red)
+    re->reduction = red;
+
+  SanitizeRE(re);
+
+  return 1;
+}
+
+public int DeleteRandomEvent(string key)
+{
+  if (process_call()) return -1;
+  if (!member(randomEvents, key)) return -2;
+  struct random_event_s re = randomEvents[key];
+  // Schreibzugriff erlauben? Nur fuer Erschafferobjekte und EM.
+  // Bemerkung: RMs haben Zugriff, weil sie Schreibzugriff auf das
+  // Erschafferobjekt haben.
+  if (extern_call()
+      && re->creator != object_name(previous_object())
+      && !ARCH_SECURITY)
+    return -1;
+
+  m_delete(randomEvents, key);
+  return 1;
+}
+
+void create()
+{
+  seteuid(getuid(this_object()));
+  restore_object(DROPSAVE);
+  if(!randomEvents || !mappingp(randomEvents))
+  {
+    randomEvents = ([]);
+  }
+  if (!member(randomEvents, GLOBAL_KEY))
+    CreateRandomEvent(GLOBAL_KEY, DFLT_RND_VALUES, DFLT_RND_NAMES, RND_REDUCTION);
+  set_next_reset(3600 + random(10800));
+}
+
+void saveme(){
+  save_object(DROPSAVE);
+}
+
+varargs int remove()
+{
+  saveme();
+  destruct(ME);
+  return 1;
+}
+
+public varargs int dropRare(int rarelevel, string key) {
+  if (!member(randomEvents, key))
+    return 0;
+  
+  struct random_event_s re = randomEvents[key];
+  if (rarelevel < 0 || rarelevel >= sizeof(re->rnd_values))
+    return 0;
+  if (random(re->rnd_values[rarelevel]) == 0)
+  {
+    // Defaultwert wiederherstellen.
+    if (pointerp(re->default_rnd_values))
+      re->rnd_values[rarelevel] = re->default_rnd_values[rarelevel];
+    else
+      re->rnd_values[rarelevel] = DFLT_RND_VALUES[rarelevel];
+    // und droppen.
+    return 1;
+  }
+  else
+  {
+    // Wahrscheinlichkeit erhoehen, indem rnd_values reduziert wird.
+    re->rnd_values[rarelevel] -= re->reduction;
+    // negative rnd_values sind ok, random() ist dann immer 0.
+  }
+  // fall-through
+  return 0;
+}
+
+// Droppt genau 1 oder 0 Items aus dem gegebenen Set. Benutzt hierbei das
+// mittels <key> definierte Randomevent und den zur jeweiligen ID gegebenen
+// <rarelevel>.
+// <set> muss in der Form
+// { {ID,RARELEVEL}, {ID,RARELEVEL}, ... }
+// gegeben werden. <ID> muss dabei != 0 sein und wird im Erfolgsfall
+// zurueckgeben.
+mixed dropSetItem(mixed set, string key) {
+  
+  if (pointerp(set))
+  {
+    set=filter(set, function int (mixed el) {
+      return pointerp(el) && sizeof(el)>=2 && intp(el[1]);
+    } );
+    set=sort_array(set, function int (mixed a, mixed b) {
+      return a[1]>b[1];
+    });
+    
+    foreach(mixed el: set) {
+      if(dropRare(el[1], key)){
+        return el[0];
+      }
+    }      
+  }
+  
+  return 0;
+}
+
+void reset()
+{
+  set_next_reset(3600*72);
+  // ein wenig aufraeumen
+
+  // Alle Events rauswerfen, deren Erschafferobjekt nicht mehr exisiert
+  // (Nachteil: nicht-geladene VC-Objekte werden nicht beruecksichtigt).
+  foreach(string key, struct random_event_s re: randomEvents) {
+    if (!find_object(re->creator)
+        && file_size(re->creator + ".c") <= 0)
+      m_delete(randomEvents, key);
+  }
+
+  saveme();
+}
+
diff --git a/secure/dropmaster.h b/secure/dropmaster.h
new file mode 100644
index 0000000..3eb2bff
--- /dev/null
+++ b/secure/dropmaster.h
@@ -0,0 +1,28 @@
+#ifndef _DROPMASTER_
+#define _DROPMASTER_
+
+// path to dropmaster
+#define DROPMASTER "/secure/dropmaster"
+#define DROPSAVE "/secure/ARCH/DROPMASTER"
+
+// rarelevels
+#define COMMON					      0
+#define UNCOMMON				      1
+#define RARE					        2
+#define VERY_RARE				      3
+#define ULTRA_RARE				    4
+#define RATHER_NOT_PROBABLE		5
+#define DFLT_RND_NAMES	({"gewoehnlich","ungewoehnlich",\
+                          "selten","sehr selten","aussergewoehnlich selten",\
+                          "praktisch nie"})
+
+#if defined(_NEED_DROPMASTER_IMPLEMENTATION_) && !defined(_DROPMASTER_IMPLEMENTATION_)
+#define _DROPMASTER_IMPLEMENTATION_
+// random helper
+#define GLOBAL_KEY	0
+#define DFLT_RND_VALUES  ({100,1000,10000,100000,200000,1000000})
+#define RND_REDUCTION	10
+
+#endif // _DROPMASTER_IMPLEMENTATION_
+
+#endif // _DROPMASTER_
diff --git a/secure/errord-structs.c b/secure/errord-structs.c
new file mode 100644
index 0000000..185da99
--- /dev/null
+++ b/secure/errord-structs.c
@@ -0,0 +1,62 @@
+#pragma strong_types,rtt_checks,save_types
+
+struct frame_s {
+  int    id;
+  int    type;
+  string name;
+  string prog;
+  string obj;
+  int    loc;
+  int    ticks;
+};
+
+struct note_s {
+  int    id;
+  int    time;
+  string user;
+  string txt;
+};
+
+struct base_issue_s {
+  int    id;
+  string hashkey;
+  string uid;
+  int    type;
+  int    mtime;
+  int    ctime;
+  int    atime;
+  int    count;
+  int    deleted;
+  int    resolved;
+  int    locked;
+  string locked_by;
+  int    locked_time;
+  string resolver;
+  string message;
+  string loadname;
+};
+
+struct ctissue_s (base_issue_s) {
+};
+
+struct userissue_s (ctissue_s) {
+  string obj;
+  string prog;
+  int    loc;
+  string titp;
+  string tienv;
+};
+
+struct rtissue_s (userissue_s) {
+  string hbobj;
+  int    caught;
+  string command;
+  string verb;
+};
+
+// all possible data including stack trace and notes.
+struct fullissue_s (rtissue_s) {
+  mixed * notes; //struct note_s *
+  mixed * stack; //struct frame_s *
+};
+
diff --git a/secure/errord.c b/secure/errord.c
new file mode 100644
index 0000000..0c9c4e8
--- /dev/null
+++ b/secure/errord.c
@@ -0,0 +1,1482 @@
+/*  MorgenGrauen MUDlib
+    /p/daemon/errord.c
+    speichert Fehler und Warnungen
+    Autor: Zesstra
+    $Id: errord.c 9439 2016-01-20 09:48:28Z Zesstra $
+    ggf. Changelog:
+*/
+
+#pragma strict_types,save_types,rtt_checks
+#pragma no_clone
+#pragma no_shadow
+#pragma no_inherit
+#pragma pedantic
+#pragma range_check
+#pragma warn_deprecated
+
+#include <config.h>
+#include <wizlevels.h>
+#include <defines.h>
+#include <debug_info.h>
+#include <commands.h>
+#include <wizlevels.h>
+#include <mail.h>
+#include <tls.h>
+#include <events.h>
+
+inherit "/secure/errord-structs";
+
+#define __NEED_IMPLEMENTATION__
+#include "errord.h"
+#undef __NEED_IMPLEMENTATION__
+
+#define SAVEFILE  (__DIR__+"ARCH/errord")
+
+#define TI this_interactive()
+
+private int       access_check(string uid,int mode);
+private varargs int set_lock(int issueid, int lock, string note);
+private varargs int set_resolution(int issueid, int resolution, string note);
+
+private int versende_mail(struct fullissue_s fehler);
+
+nosave mapping lasterror;   // die letzen 5 jeder Art.
+
+
+/* ******************* Helfer **************************** */
+
+public int getErrorID(string hashkey)
+{
+  int** row=sl_exec("SELECT id from issues WHERE hashkey=?1;",
+                    hashkey);
+  //DEBUG(sprintf("getEID: %s: %O\n",hashkey,row));
+  if (pointerp(row))
+  {
+    return row[0][0];
+  }
+  return -1;
+}
+
+// note->id muss auf einen Eintrag in issues verweisen, es erfolgt keine
+// Pruefung.
+int db_add_note(struct note_s note)
+{
+  sl_exec("INSERT INTO notes(issueid,time,user,txt) "
+          "VALUES(?1,?2,?3,?4);",
+          to_array(note)...);
+  return sl_insert_id();
+}
+
+private struct frame_s* db_get_stack(int issueid)
+{
+  mixed rows = sl_exec("SELECT * FROM stacktraces WHERE issueid=?1 "
+                       "ORDER BY rowid;", issueid);
+  if (pointerp(rows))
+  {
+    struct frame_s* stack = allocate(sizeof(rows));
+    int i;
+    foreach(mixed row : rows)
+    {
+      stack[i] = to_struct(row, (<frame_s>));
+      ++i;
+    }
+    return stack;
+  }
+  return 0;
+}
+
+private struct note_s* db_get_notes(int issueid)
+{
+  mixed rows = sl_exec("SELECT * FROM notes WHERE issueid=?1 "
+                       "ORDER BY rowid;", issueid);
+  if (pointerp(rows))
+  {
+    struct note_s* notes = allocate(sizeof(rows));
+    int i;
+    foreach(mixed row : rows)
+    {
+      notes[i] = to_struct(row, (<note_s>));
+      ++i;
+    }
+    return notes;
+  }
+  return 0;
+}
+
+// einen durch id oder hashkey bezeichneten Eintrag als fullissue_s liefern.
+private struct fullissue_s db_get_issue(int issueid, string hashkey)
+{
+  mixed rows = sl_exec("SELECT * FROM issues WHERE id=?1 OR hashkey=?2;",
+                       issueid, hashkey);
+  if (pointerp(rows))
+  {
+    // Einfachster Weg - funktioniert aber nur, solange die Felder in der DB
+    // die gleiche Reihenfolge wie in der struct haben! Entweder immer
+    // sicherstellen oder Ergebnisreihenfolge oben im select festlegen!
+    struct fullissue_s issue = to_struct( rows[0], (<fullissue_s>) );
+    if (issue->type == T_RTERROR)
+        issue->stack = db_get_stack(issue->id);
+    issue->notes = db_get_notes(issue->id);
+    return issue;
+  }
+  return 0;
+}
+
+private struct fullissue_s filter_private(struct fullissue_s issue)
+{
+    //momentan wird F_CLI, also die Spielereingabe vor dem Fehler
+    //ausgefiltert, wenn TI kein EM oder man in process_string() ist.
+
+    //Wenn EM und nicht in process_string() oder die Spielereingabe gar nicht
+    //im Fehlereintrag drinsteht: ungefiltert zurueck
+    if (!issue->command ||
+        (!process_call() && ARCH_SECURITY) )
+        return issue;
+
+    //sonst F_CLI rausfiltern, also Kopie und in der Kopie aendern.
+    issue->command="Bitte EM fragen";
+    return issue;
+}
+
+// setzt oder loescht die Loeschsperre.
+// Prueft, ob <issueid> existiert und aendert den Zustand nur, wenn noetig.
+// Rueckgabe: -1, wenn Issue nicht existiert, -2 wenn bereits resolved, -3
+//            wenn keine Aenderung noetig, sonst den neuen Sperrzustand
+int db_set_lock(int issueid, int lockstate, string note)
+{
+  int** rows = sl_exec("SELECT locked,resolved FROM issues WHERE id=?1;",
+                       issueid);
+  if (!rows)
+    return -1;  // nicht vorhanden.
+
+  if (rows[0][1])
+      return -2; // bereits resolved -> Sperre nicht moeglich.
+
+
+  if (lockstate && !rows[0][0])
+  {
+    // Sperren
+//    sl_exec("BEGIN TRANSACTION;");
+    sl_exec("UPDATE issues SET locked=1,locked_by=?2,locked_time=?3,mtime=?3 "
+            "WHERE id=?1;",
+            issueid, getuid(TI), time());
+    db_add_note( (<note_s> id: issueid, time: time(), user: getuid(TI),
+                           txt: sprintf("Lock gesetzt: %s", 
+                                        note ? note : "<kein Kommentar>")) );
+//    sl_exec("COMMIT;");
+    return 1;
+  }
+  else if (!lockstate && rows[0][0])
+  {
+    // entsperren
+//    sl_exec("BEGIN TRANSACTION;");
+    sl_exec("UPDATE issues SET locked=0,locked_by=0,locked_time=0,mtime=?2 "
+            "WHERE id=?1;", issueid, time());
+    db_add_note( (<note_s> id: issueid, time: time(), user: getuid(TI),
+                           txt: sprintf("Lock geloescht: %s", 
+                                        note ? note : "<kein Kommentar>")) );
+//    sl_exec("COMMIT;");
+    return 0;
+  }
+  // nix aendern.
+  return -3;
+}
+
+// markiert ein Issue als gefixt oder nicht gefixt.
+// Prueft, ob <issueid> existiert und aendert den Zustand nur, wenn noetig.
+// Rueckgabe: -1, wenn Issue nicht existiert, -3 wenn keine Aenderung noetig,
+//            sonst den neuen Sperrzustand
+int db_set_resolution(int issueid, int resolved, string note)
+{
+  int** rows = sl_exec("SELECT resolved FROM issues WHERE id=?1;",
+                       issueid);
+  if (!rows)
+    return -1;  // nicht vorhanden.
+
+  if (resolved && !rows[0][0])
+  {
+    // Als gefixt markieren.
+//    sl_exec("BEGIN TRANSACTION;");
+    sl_exec("UPDATE issues SET resolved=1,resolver=?2,mtime=?3 "
+            "WHERE id=?1;",
+            issueid, getuid(TI),time());
+    db_add_note( (<note_s> id: issueid, time: time(), user: getuid(TI),
+                           txt: sprintf("Fehler gefixt: %s", 
+                                        note ? note : "<kein Kommentar>")) );
+//    sl_exec("COMMIT;");
+    return 1;
+  }
+  else if (!resolved && rows[0][0])
+  {
+    // als nicht gefixt markieren.
+//    sl_exec("BEGIN TRANSACTION;");
+    sl_exec("UPDATE issues SET resolved=0,resolver=0,mtime=?2 "
+            "WHERE id=?1;", issueid, time());
+    db_add_note( (<note_s> id: issueid, time: time(), user: getuid(TI),
+                           txt: sprintf("Fix zurueckgezogen: %s", 
+                                        note ? note : "<kein Kommentar>")) );
+//    sl_exec("COMMIT;");
+    return 0;
+  }
+  // nix aendern.
+  return -3;
+
+}
+
+// Transferiert ein Issue zu einer neuen zustaendigen UID
+// Prueft, ob <issueid> existiert und aendert den Zustand nur, wenn noetig.
+// Rueckgabe: -1, wenn Issue nicht existiert, -3 wenn keine Aenderung noetig,
+//            1, wenn erfolgreich neu zugewiesen
+int db_reassign_issue(int issueid, string newuid, string note)
+{
+  string** rows = sl_exec("SELECT uid FROM issues WHERE id=?1;",
+                       issueid);
+  if (!rows)
+    return -1;  // nicht vorhanden.
+
+  if (!stringp(newuid))
+    return(-2);
+
+  if (newuid != rows[0][0])
+  {
+//    sl_exec("BEGIN TRANSACTION;");
+    sl_exec("UPDATE issues SET uid=?2,mtime=?3 WHERE id=?1;",
+            issueid, newuid,time());
+    db_add_note( (<note_s> id: issueid, time: time(), user: getuid(TI),
+                           txt: sprintf("Fehler von %s an %s uebertragen. (%s)",
+                                        rows[0][0], newuid,
+                                        note ? note : "<kein Kommentar>")) );
+//    sl_exec("COMMIT;");
+    return 1;
+  }
+
+  return -3;
+}
+
+// inkrementiert count und aktualisiert mtime, atime.
+// Ausserdem wird ggf. das Loeschflag genullt - ein erneut aufgetretener
+// Fehler sollte anschliessend nicht mehr geloescht sein. Geloeste
+// (resolved) Eintraege werden NICHT auf ungeloest gesetzt. Vermutlich trat
+// der Fehler in einem alten Objekte auf...
+// Issue muss in der DB existieren.
+int db_countup_issue(int issueid)
+{
+  sl_exec("UPDATE issues SET count=count+1,mtime=?2,atime=?2,deleted=0 WHERE id=?1;",
+          issueid,time());
+  return 1;
+}
+
+// Das Issue wird ggf. ent-loescht und als nicht resvolved markiert.
+// Sind pl und msg != 0, wird eine Notiz angehaengt.
+// aktualisiert mtime, atime.
+// Issue muss in der DB existieren.
+int db_reopen_issue(int issueid, string pl, string msg)
+{
+  int** row=sl_exec("SELECT deleted,resolved from issues WHERE id=?1;",
+                    issueid);
+  if (pointerp(row)
+      && (row[0][0] || row[0][1]) )
+  {
+//    sl_exec("BEGIN TRANSACTION;");
+    if (pl && msg)
+    {
+      db_add_note( (<note_s> id: issueid,
+                             time: time(),
+                             user: pl,
+                             txt: msg) );
+    }
+    sl_exec("UPDATE issues SET "
+        "deleted=0,resolved=0,resolver=0,mtime=?2,atime=?2 WHERE id=?1;",
+        issueid,time());
+//    sl_exec("COMMIT;");
+  }
+  return 1;
+}
+
+int db_insert_issue(struct fullissue_s issue)
+{
+  //DEBUG(sprintf("db_insert: %O\n", issue));
+
+  mixed row=sl_exec("SELECT id from issues WHERE hashkey=?1;",
+                    issue->hashkey);
+  //DEBUG(sprintf("insert: %s: %O\n",issue->hashkey,row));
+  if (pointerp(row))
+  {
+    issue->id=row[0][0];
+    return db_countup_issue(issue->id);
+  }
+//  sl_exec("BEGIN TRANSACTION;");
+  sl_exec("INSERT INTO issues(hashkey,uid,type,mtime,ctime,atime,count,"
+          "deleted,resolved,locked,locked_by,locked_time,resolver,message,"
+          "loadname,obj,prog,loc,titp,tienv,hbobj,caught,command,verb) "
+          "VALUES(?1,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11,?12,?13,?14,?15,"
+          "?16,?17,?18,?19,?20,?21,?22,?23,?24);",
+          (to_array(issue)[1..24])...); 
+  issue->id=sl_insert_id();
+
+  if (pointerp(issue->stack))
+  {
+    foreach(struct frame_s entry : issue->stack)
+    {
+      entry->id = issue->id;
+      sl_exec("INSERT INTO stacktraces(issueid,type,name,prog,obj,loc,ticks) "
+              "VALUES(?1,?2,?3,?4,?5,?6,?7);",
+              to_array(entry)...);
+    }
+  }
+  if (pointerp(issue->notes))
+  {
+    foreach(struct note_s entry : issue->notes)
+    {
+      entry->id = issue->id;
+      sl_exec("INSERT INTO notes(issueid,time,user,txt) "
+              "VALUES(?1,?2,?3,?4);",
+              to_array(entry)...);
+    }
+  }
+//  sl_exec("COMMIT;");
+
+  return issue->id;
+}
+
+
+// loggt einen T_REPORTED_ERR, T_REPORTED_IDEA, T_REPORTED_TYPO, T_REPORTED_MD
+public string LogReportedError(mapping err)
+{
+    //darf nur von Spielershells oder Fehlerteufel gerufen werden.
+    if (extern_call() && !previous_object()
+        || (strstr(load_name(previous_object()),"/std/shells/") == -1
+           && load_name(previous_object()) != "/obj/tools/fehlerteufel"))
+        return 0;
+
+    //DEBUG("LogReportedError\n");
+    // DEBUG(sprintf("%O\n",err));
+    string uid = (string)master()->creator_file(err[F_OBJ]);
+
+    // default-Typ
+    if (!member(err, F_TYPE)) err[F_TYPE] = T_REPORTED_ERR;
+
+    // div. Keys duerfen nicht gesetzt sein.
+    err -= ([F_STATE, F_READSTAMP, F_CAUGHT, F_STACK, F_CLI, F_VERB,
+             F_LOCK, F_RESOLVER, F_NOTES]);
+
+    // Errormapping in issue-struct umwandeln und befuellen.
+    struct fullissue_s issue = (<fullissue_s>);
+    issue->type = err[F_TYPE];
+    issue->uid = uid;
+    issue->mtime = issue->ctime = issue->atime = time();
+    issue->count=1;
+    issue->loadname = load_name(err[F_OBJ]);
+    issue->message = err[F_MSG];
+    issue->obj = object_name(err[F_OBJ]);
+    // Normalisieren auf fuehrenden / und kein .c
+    if (err[F_PROG]!="unbekannt")
+        issue->prog = load_name(err[F_PROG]);
+    else
+        issue->prog = "unbekannt";
+    issue->titp = getuid(this_interactive() || this_player());
+    if (objectp(err[F_OBJ]))
+      issue->tienv = object_name(environment(err[F_OBJ]));
+
+    //DEBUG(sprintf("%O\n",issue));
+    issue->hashkey = hash(TLS_HASH_MD5,
+        sprintf("%d%s%s", issue->type, issue->loadname, issue->message));
+
+    // ggf. vorhandenen Fehler suchen - zugegeben: sollte bei von Spielern
+    // gemeldeten Dingen vermutlich nie vorkommen...
+    int oldid = getErrorID(issue->hashkey);
+    if (oldid >= 0)
+    {
+      // ggf. sicherstellen, dass er wieder eroeffnet wird.
+      db_reopen_issue(oldid, "<ErrorD>",
+                      "Automatisch wiedereroeffnet wegen erneutem Auftreten.");
+      db_countup_issue(oldid);
+      return issue->hashkey;
+    }
+
+    // sonst fuegen wir einen neuen Eintrag hinzu
+    // Spielergemeldete Bugs werden erstmal vor automatischem Loeschen
+    // geschuetzt, bis ein zustaendiger Magier ihn zur Kenntnis nimmt und
+    // entsperrt.
+    issue->locked = 1;
+    issue->locked_by = getuid(TI || PL);
+    issue->locked_time = time();
+
+    // In DB eintragen.
+    issue->id = db_insert_issue(issue);
+
+    lasterror[issue->type]=issue->id;
+
+    // Event triggern, aber nur eine Teilmenge der Daten direkt mitliefern.
+    EVENTD->TriggerEvent(EVT_LIB_NEW_ERROR,
+              ([F_TYPE: issue->type, F_HASHKEY:issue->hashkey,
+               F_UID:issue->uid, F_ID: issue->id]));
+
+    DEBUG(sprintf("LogReportedError: %s\n",issue->hashkey));
+
+    return issue->hashkey;
+}
+
+//Fehler registrieren
+//Diese Funktion darf nicht mehr als 200k Ticks verbrauchen und wird nur vom
+//Master gerufen!
+public int LogError(string msg,string prg,string curobj,int line,mixed culprit,
+    int caught)
+{
+    //DEBUG(sprintf("LogError: Prog: %O, Obj: %O,\n",prg,curobj));
+
+    //darf nur vom Master gerufen werden
+    if (!extern_call() || 
+        (previous_object() && previous_object() != master()))
+        return 0;
+
+    struct fullissue_s issue = (<fullissue_s>);
+
+    //UID bestimmen
+    issue->uid=(string)master()->creator_file(curobj);
+    //DEBUG(sprintf("LogError: UID: %s\n",uid));
+
+    //Loadname (besser als BP, falls rename_object() benutzt wurde) bestimmen
+    if (!stringp(curobj) || !sizeof(curobj))
+        issue->loadname = curobj = "<Unbekannt>";
+    else
+    {
+        //load_name nimmt Strings und Objects und konstruiert den loadname,
+        //wie er sein sollte, wenn das Objekt nicht mehr existiert.
+        issue->loadname=load_name(curobj);
+    }
+    if (!stringp(issue->loadname))
+    {
+        //hier kommt man rein, falls curobj ein 'kaputter' Name ist,
+        //d.h. load_name() 0 liefert.
+        issue->loadname="<Illegal object name>";
+    }
+
+    // Wenn curobj in /players/ liegt, es einen TI gibt, welcher ein Magier
+    // ist und dieser die Prop P_DONT_LOG_ERRORS gesetzt hat, wird der FEhler
+    // nicht gespeichert.
+    if (this_interactive() && IS_LEARNER(this_interactive())
+        && strstr(issue->loadname,WIZARDDIR)==0
+        && this_interactive()->QueryProp(P_DONT_LOG_ERRORS))
+    {
+        return 0;
+    }
+
+    // prg und curobj auf fuehrenden / und ohne .c am Ende normieren.
+    if (stringp(prg))
+        issue->prog = load_name(prg);
+    if (stringp(curobj) && curobj[0]!='/')
+    {
+      curobj="/"+curobj;
+    }
+
+    issue->obj = curobj;
+    issue->loc = line;
+    issue->message = msg;
+    issue->ctime = issue->mtime = issue->atime = time();
+    issue->type = T_RTERROR;
+    issue->count = 1;
+    issue->caught = caught;
+
+    //Hashkey bestimmen: Typ, Name der Blueprint des buggenden Objekts,
+    //Programmname, Zeilennr., Fehlermeldung
+    //TODO: evtl. sha1() statt md5()?
+    issue->hashkey=hash(TLS_HASH_MD5,
+        sprintf("%d%s%s%d%s", T_RTERROR, issue->loadname||"",
+                            issue->prog || "", issue->loc,
+                            issue->message||"<No error message given.>"));
+    DEBUG(sprintf("LogError: Hashkey: %s", issue->hashkey));
+
+    // ggf. vorhandenen Fehler suchen
+    int oldid = getErrorID(issue->hashkey);
+    if (oldid >= 0)
+    {
+      db_reopen_issue(oldid, "<ErrorD>",
+                      "Automatisch wiedereroeffnet wegen erneutem Auftreten.");
+      db_countup_issue(oldid);
+      return oldid;
+    }
+
+    //sonst fuegen wir einen neuen Eintrag hinzu
+    //DEBUG(sprintf("LogError: OBJ: %s, BP: %s",curobj,loadname));
+    // Wenn Fehler im HB, Objektnamen ermitteln
+    if (objectp(culprit))
+        issue->hbobj = object_name(culprit);
+
+    //gibt es einen TI/TP? Name mit erfassen
+    mixed tienv;
+    if(objectp(this_interactive()))
+    {
+        issue->titp=getuid(this_interactive());
+        tienv=environment(this_interactive());
+    }
+    else if (objectp(PL) && query_once_interactive(PL))
+    {
+        issue->titp=getuid(PL);
+        tienv=environment(PL);
+    }
+    else if (objectp(PL))
+    {
+        issue->titp=object_name(PL);
+        tienv=environment(PL);
+    }
+    if (objectp(tienv))
+        issue->tienv=object_name(tienv);
+
+    // Mal schauen, ob der Commandstack auch was fuer uns hat. ;-)
+    mixed cli;
+    if (pointerp(cli=command_stack()) && sizeof(cli))
+    {
+        issue->verb=cli[0][CMD_VERB];
+        issue->command=cli[0][CMD_TEXT];
+    }
+
+    //stacktrace holen
+    mixed stacktrace;
+    if (caught)
+        stacktrace=debug_info(DINFO_TRACE,DIT_ERROR);
+    else
+        stacktrace=debug_info(DINFO_TRACE,DIT_UNCAUGHT_ERROR);
+    // gueltige Stacktraces haben min. 2 Elemente.
+    // (leerer Trace: ({"No trace."}))
+    if (sizeof(stacktrace) > 1)
+    {
+      int i;
+      issue->stack = allocate(sizeof(stacktrace)-1);
+      // erstes Element ist 0 oder HB-Objekt: kein frame, daher ueberspringen
+      foreach(mixed entry : stacktrace[1..])
+      {
+        // frame->id will be set later by db_insert_issue().
+        struct frame_s frame = (<frame_s> type : entry[TRACE_TYPE],
+                                          name:  entry[TRACE_NAME],
+                                          prog:  entry[TRACE_PROGRAM],
+                                          obj:   entry[TRACE_OBJECT],
+                                          loc:   entry[TRACE_LOC],
+                                          ticks: entry[TRACE_EVALCOST]);
+        issue->stack[i] = frame;
+        ++i;
+      }
+    }
+
+    issue->id = db_insert_issue(issue);
+
+    lasterror[T_RTERROR]=issue->id;
+
+    // Event triggern, aber nur eine Teilmenge der Daten direkt mitliefern.
+    EVENTD->TriggerEvent(EVT_LIB_NEW_ERROR,
+              ([ F_TYPE: T_RTERROR, F_HASHKEY: issue->hashkey, F_UID:
+               issue->uid, F_ID: issue->id ]));
+
+//    DEBUG(sprintf("LogError: Fehlereintrag:\n%O\n",
+//          errors[uid][hashkey]));
+//    DEBUG(sprintf("LogError: Verbrauchte Ticks: %d\n",
+//          200000-get_eval_cost()));
+    return issue->id;
+}
+
+//Warnungen registrieren
+//Diese Funktion darf nicht mehr als 200k Ticks verbrauchen und wird nur vom
+//Master gerufen!
+public int LogWarning(string msg,string prg,string curobj,int line, int in_catch)
+{
+    //DEBUG(sprintf("LogWarning: Prog: %O, Obj: %O,\n",prg,curobj));
+
+    //darf nur vom Master gerufen werden
+    if (!extern_call() || 
+        (previous_object() && previous_object() != master()))
+        return 0;
+
+    struct fullissue_s issue = (<fullissue_s>);
+
+    //UID bestimmen
+    issue->uid=(string)master()->creator_file(curobj);
+    //DEBUG(sprintf("LogWarning UID: %s\n",uid));
+
+    //Loadname (besser als BP, falls rename_object() benutzt wurde) bestimmen
+    if (!stringp(curobj) || !sizeof(curobj))
+        issue->loadname = curobj = "<Unbekannt>";
+    else
+    {
+        //load_name nimmt Strings und Objects und konstruiert den loadname,
+        //wie er sein sollte, wenn das Objekt nicht mehr existiert.
+        issue->loadname=load_name(curobj);
+    }
+
+    if (!stringp(issue->loadname))
+        //hier sollte man reinkommen, falls curobj ein 'kaputter' Name ist,
+        //d.h. load_name() 0 liefert.
+        issue->loadname="<Illegal object name>";
+
+    // prg und curobj auf abs. Pfade normalisieren.
+    if (stringp(prg))
+        issue->prog=load_name(prg);
+    if (stringp(curobj) && curobj[0]!='/')
+    {
+      curobj="/"+curobj;
+    }
+
+    //DEBUG(sprintf("LogWarning: OBJ: %s, BP: %s\n",curobj,blue));
+
+    // Wenn curobj in /players/ liegt, es einen TI gibt, welcher ein Magier
+    // ist und dieser die Prop P_DONT_LOG_ERRORS gesetzt hat, wird der FEhler
+    // nicht gespeichert.
+    if (this_interactive() && IS_LEARNER(this_interactive())
+        && strstr(issue->loadname,WIZARDDIR)==0
+        && this_interactive()->QueryProp(P_DONT_LOG_ERRORS)) {
+        return 0;
+    }
+
+    //Hashkey bestimmen, Typ, Name der Blueprint des buggenden Objekts, Programm
+    //Zeilennr., Warnungsmeldung
+    issue->hashkey=hash(TLS_HASH_MD5,
+        sprintf("%d%s%s%d%s", T_RTWARN, issue->loadname, issue->prog, line,
+                           msg));
+    //DEBUG(sprintf("LogWarning: Hashkey: %s",hashkey));
+
+
+    // ggf. vorhandenen Fehler suchen
+    int oldid = getErrorID(issue->hashkey);
+    if (oldid >= 0)
+    {
+      db_reopen_issue(oldid, "<ErrorD>",
+                      "Automatisch wiedereroeffnet wegen erneutem Auftreten.");
+      db_countup_issue(oldid);
+      return oldid;
+    }
+
+    //sonst fuegen wir einen neuen Eintrag hinzu
+    // erstmal vervollstaendigen
+    issue->obj = curobj;
+    issue->message = msg;
+    issue->ctime = issue->mtime = issue->atime = time();
+    issue->loc = line;
+    issue->count = 1;
+    issue->type = T_RTWARN;
+    issue->caught = in_catch;
+
+    //gibt es einen TI/TP? Name mit erfassen
+    mixed tienv;
+    if(objectp(this_interactive()))
+    {
+        issue->titp=getuid(this_interactive());
+        tienv=environment(this_interactive());
+    }
+    else if (objectp(PL) && query_once_interactive(PL))
+    {
+        issue->titp=getuid(PL);
+        tienv=environment(PL);
+    }
+    else if (objectp(PL))
+    {
+        issue->titp=object_name(PL);
+        tienv=environment(PL);
+    }
+    if (objectp(tienv))
+        issue->tienv=object_name(tienv);
+
+    // Mal schauen, ob der Commandstack auch was fuer uns hat. ;-)
+    mixed cli;
+    if (pointerp(cli=command_stack()) && sizeof(cli))
+    {
+        issue->verb=cli[0][CMD_VERB];
+        issue->command=cli[0][CMD_TEXT];
+    }
+
+    issue->id = db_insert_issue(issue);
+
+    lasterror[T_RTWARN]=issue->id;
+    // Event triggern, aber nur eine Teilmenge der Daten direkt mitliefern.
+    EVENTD->TriggerEvent(EVT_LIB_NEW_ERROR,
+        ([F_TYPE: issue->type, F_ID: issue->id,
+          F_UID: issue->uid, F_HASHKEY: issue->hashkey]) );
+
+//    DEBUG(sprintf("LogWarning: Warnungseintrag:\n%O\n",
+//          warnings[uid][hashkey]));
+//    DEBUG(sprintf("LogWarning: Verbrauchte Ticks: %d\n",
+//          200000-get_eval_cost()));
+    return issue->id;
+}
+
+//Warnungen und Fehler beim Kompilieren  registrieren
+//Diese Funktion darf nicht mehr als 200k Ticks verbrauchen und wird nur vom
+//Master gerufen!
+public int LogCompileProblem(string file,string msg,int warn) {
+
+    //DEBUG(sprintf("LogCompileProblem: Prog: %O, Obj: %O,\n",file,msg));
+
+    //darf nur vom Master gerufen werden
+    if (!extern_call() || 
+        (previous_object() && previous_object() != master()))
+        return 0;
+
+    struct fullissue_s issue = (<fullissue_s>);
+
+    //UID bestimmen
+    issue->uid=(string)master()->creator_file(file);
+    //DEBUG(sprintf("LogCompileProblem UID: %s\n",uid));
+
+    // An File a) fuehrenden / anhaengen und b) endendes .c abschneiden. Macht
+    // beides netterweise load_name().
+    issue->loadname = load_name(file);
+    issue->type = warn ? T_CTWARN : T_CTERROR;
+
+    //loggen wir fuer das File ueberhaupt?
+    if (member(BLACKLIST,explode(issue->loadname,"/")[<1])>=0)
+        return 0;
+
+    //Hashkey bestimmen, in diesem Fall einfach, wir koennen die
+    //Fehlermeldunge selber nehmen.
+    issue->hashkey=hash(TLS_HASH_MD5,sprintf(
+          "%d%s%s",issue->type,issue->loadname, msg));
+    //DEBUG(sprintf("LogCompileProblem: Hashkey: %s",hashkey));
+
+    // ggf. vorhandenen Fehler suchen
+    int oldid = getErrorID(issue->hashkey);
+    if (oldid >= 0)
+    {
+      db_reopen_issue(oldid, "<ErrorD>",
+                      "Automatisch wiedereroeffnet wegen erneutem Auftreten.");
+      db_countup_issue(oldid);
+      return oldid;
+    }
+
+    // neuen Eintrag
+    issue->message = msg;
+    issue->count = 1;
+    issue->ctime = issue->mtime = issue->atime = time();
+
+    issue->id = db_insert_issue(issue);
+
+    if (warn) lasterror[T_CTWARN]=issue->id;
+    else lasterror[T_CTERROR]=issue->id;
+
+//    DEBUG(sprintf("LogCompileProblem: Eintrag:\n%O\n",
+//          (warn ? ctwarnings[uid][hashkey] : cterrors[uid][hashkey])));
+//   DEBUG(sprintf("LogCompileProblem: Verbrauchte Ticks: %d\n",
+//          200000-get_eval_cost()));
+   return issue->id;
+}
+
+/* ****************  Public Interface ****************** */
+
+//Einen bestimmten Fehler nach Hashkey suchen und als fullissue_s inkl. Notes
+//und Stacktrace liefern.
+struct fullissue_s QueryIssueByHash(string hashkey)
+{
+  struct fullissue_s issue = db_get_issue(0, hashkey);
+  if (structp(issue))
+    return filter_private(issue);
+  return 0;
+}
+//Einen bestimmten Fehler nach ID suchen und als fullissue_s inkl. Notes
+//und Stacktrace liefern.
+struct fullissue_s QueryIssueByID(int issueid)
+{
+  struct fullissue_s issue = db_get_issue(issueid, 0);
+  if (structp(issue))
+    return filter_private(issue);
+  return 0;
+}
+
+// den letzten Eintrag den jeweiligen Typ liefern.
+struct fullissue_s QueryLastIssue(int type)
+{
+    if (!member(lasterror,type))
+        return 0;
+    //einfach den kompletten letzten Eintrag zurueckliefern
+    return(QueryIssueByID(lasterror[type]));
+}
+
+// Liefert alle Issues, deren obj,prog oder loadname gleich <file> ist.
+public struct fullissue_s* QueryIssuesByFile(string file, int type)
+{
+  mixed rows = sl_exec("SELECT * FROM issues "
+                       "WHERE (loadname=?1 OR prog=?1 OR obj=?1) "
+                       "AND deleted=0 AND resolved=0 AND type=?2"
+                       "ORDER BY type,mtime;", file, type);
+  if (pointerp(rows))
+  {
+    struct fullissue_s* ilist = allocate(sizeof(rows));
+    int i;
+    foreach(mixed row : rows)
+    {
+      // Einfachster Weg - funktioniert aber nur, solange die Felder in der DB
+      // die gleiche Reihenfolge wie in der struct haben! Entweder immer
+      // sicherstellen oder Ergebnisreihenfolge oben im select festlegen!
+      struct fullissue_s issue = to_struct( row, (<fullissue_s>) );
+      if (issue->type == T_RTERROR)
+          issue->stack = db_get_stack(issue->id);
+      issue->notes = db_get_notes(issue->id);
+      ilist[i] = filter_private(issue);
+      ++i;
+    }
+    return ilist;
+  }
+  return 0;
+}
+
+// Liefert eine Liste von allen IDs, Loadnames, UIDs und Typen fuer die
+// angebenen <type> und <uid>.
+varargs < <int|string>* >* QueryIssueListByFile(string file)
+{
+  mixed rows = sl_exec("SELECT id,loadname,obj,prog,loc FROM issues "
+                       "WHERE (loadname=?1 OR prog=?1 OR obj=?1) "
+                       "AND deleted=0 AND resolved=0 "
+                       "ORDER BY type,mtime;", file);
+  return rows;
+}
+
+// Liefert eine Liste von allen IDs, Loadnames, UIDs und Typen fuer die
+// angebenen <type> und <uid>.
+varargs < <int|string>* >* QueryIssueListByLoadname(string file, int type)
+{
+  mixed rows;
+  if (type && file)
+  {
+     rows = sl_exec("SELECT id,loadname,uid,type FROM issues "
+                       "WHERE loadname=?1 AND type=?2 AND deleted=0 "
+                       "AND resolved=0 "
+                       "ORDER BY type,mtime;", file, type);
+  }
+  else if (type)
+  {
+     rows = sl_exec("SELECT id,loadname,uid,type FROM issues "
+                       "WHERE type=?1 AND deleted=0 AND resolved=0 "
+                       "ORDER BY type,mtime;", type);
+  }
+  else if (file)
+  {
+    rows = sl_exec("SELECT id,loadname,uid,type FROM issues "
+                       "WHERE loadname=?1 AND deleted=0 AND resolved=0 "
+                       "ORDER BY type,mtime;", file);
+  }
+  return rows;
+}
+
+
+// Liefert eine Liste von allen IDs, Loadnames, UIDs und Typen fuer die
+// angebenen <type> und <uid>.
+varargs < <int|string>* >* QueryIssueList(int type, string uid)
+{
+  mixed rows;
+  if (type && uid)
+  {
+    rows = sl_exec("SELECT id,loadname,uid,type FROM issues "
+                   "WHERE type=?1 AND uid=?2 AND deleted=0 "
+                   "AND resolved=0 "
+                   "ORDER BY type,rowid;", type,uid);
+  }
+  else if (type)
+  {
+    rows = sl_exec("SELECT id,loadname,uid,type FROM issues "
+                   "WHERE type=?1 AND deleted=0 AND resolved=0 "
+                   "ORDER BY type,rowid;", type);
+  }
+  else if (uid)
+  {
+    rows = sl_exec("SELECT id,loadname,uid,type FROM issues "
+                   "WHERE uid=?1 AND deleted=0 AND resolved=0 "
+                   "ORDER BY type,rowid;", uid);
+  }
+  return rows;
+}
+
+varargs string* QueryUIDsForType(int type) {
+    //liefert alle UIDs fuer einen Fehlertyp oder fuer alle Fehlertypen
+    string** rows;
+
+    if (type)
+    {
+      rows = sl_exec("SELECT uid FROM issues "
+                     "WHERE type=?1 AND deleted=0 AND resvoled=0;", type);
+    }
+    else
+      rows = sl_exec("SELECT uid FROM issues WHERE deleted=0 "
+                     "AND resolved=0;");
+
+    return map(rows, function string (string* item)
+                     {return item[0];} );
+}
+
+//Wieviele unterschiedliche Fehler in diesem Typ?
+varargs int QueryUniqueIssueCount(int type, string uid)
+{
+  int** rows;
+
+  if (type && uid)
+  {
+    rows = sl_exec("SELECT count(*) FROM issues "
+                   "WHERE type=?1 AND uid=?2 AND deleted=0 AND resolved=0;",
+                   type, uid);
+  }
+  else if (type)
+  {
+    rows = sl_exec("SELECT count(*) FROM issues "
+                   "WHERE type=?1 AND deleted=0 AND resolved=0;",
+                   type);
+  }
+  else if (uid)
+  {
+    rows = sl_exec("SELECT count(*) FROM issues "
+                   "WHERE uid=?1 AND deleted=0 AND resolved=0;",
+                   uid);
+  }
+  else
+    rows = sl_exec("SELECT count(*) FROM issues "
+                   "WHERE deleted=0 AND resolved=0;");
+
+  return rows[0][0];
+}
+
+//Einen bestimmten Fehler loeschen
+varargs int ToggleDeleteError(int issueid, string note)
+{
+  mixed rows = sl_exec("SELECT uid,deleted from issues WHERE id=?1;",
+                       issueid);
+  if (!pointerp(rows))
+    return -1;
+  
+  if (!access_check(rows[0][0], M_DELETE))
+    //zugriff zum Schreiben nicht gestattet
+    return -10;
+
+//  sl_exec("BEGIN TRANSACTION;");
+  if (rows[0][1])
+  {
+    // was deleted -> undelete it
+    sl_exec("UPDATE issues SET deleted=0,mtime=?2 WHERE id=?1;",
+            issueid,time());
+    db_add_note((<note_s> id: issueid, time: time(), user: getuid(TI),
+                          txt: sprintf("Loeschmarkierung entfernt. (%s)",
+                                       note ? note: "<kein Kommentar>")
+                ));
+  }
+  else
+  {
+    // was not deleted -> delete it.
+    sl_exec("UPDATE issues SET deleted=1,mtime=?2 WHERE id=?1;",
+            issueid, time());
+    db_add_note((<note_s> id: issueid, time: time(), user: getuid(TI),
+                          txt: sprintf("Loeschmarkierung gesetzt. (%s)",
+                                       note ? note: "<kein Kommentar>")
+                ));
+  }
+//  sl_exec("COMMIT;");
+  return !rows[0][1];
+}
+
+
+// sperrt den Eintrag oder entsperrt ihn.
+// Sperre heisst hier, dass der Fehler vom Expire nicht automatisch geloescht
+// wird.
+varargs int LockIssue(int issueid, string note) {
+    return set_lock(issueid, 1, note);
+}
+
+varargs int UnlockIssue(int issueid, string note) {
+    return set_lock(issueid, 0, note);
+}
+
+// einen Fehler als gefixt markieren
+varargs int ResolveIssue(int issueid, string note) {
+    return set_resolution(issueid, 1, note);
+}
+// einen Fehler als nicht gefixt markieren
+varargs int ReOpenIssue(int issueid, string note) {
+    return set_resolution(issueid, 0, note);
+}
+
+varargs int AddNote(int issueid, string note)
+{
+
+    if (!stringp(note) || !sizeof(note))
+      return(-3);
+
+    // existiert die ID in der DB?
+    struct fullissue_s issue = db_get_issue(issueid,0);
+    if (!issue)
+      return -1;
+
+    if (!access_check(issue->uid, M_WRITE))
+        //zugriff zum Schreiben nicht gestattet
+        return(-10);
+
+    return db_add_note((<note_s> id: issueid, time: time(), user: getuid(TI),
+                                 txt: note));
+}
+
+//Einen bestimmten Fehler einer anderen UID zuweisen.
+//Hashkey ist zwar eindeutig, aber Angabe der
+//der UID ist deutlich schneller. Weglassen des Typs nochmal langsamer. ;-)
+//Potentiell also sehr teuer, wenn man UID oder UID+Typ weglaesst.
+varargs int ReassignIssue(int issueid, string newuid, string note)
+{
+    struct fullissue_s issue = db_get_issue(issueid,0);
+    if (!issue)
+      return -1;
+
+    if (!access_check(issue->uid, M_REASSIGN))
+        //zugriff zum Schreiben nicht gestattet
+        return(-10);
+
+    return db_reassign_issue(issueid, newuid, note);
+}
+
+/* *********** Eher fuer Debug-Zwecke *********************** */
+/*
+mixed QueryAll(int type) {
+    //das koennte ein sehr sehr grosses Mapping sein, ausserdem wird keine
+    //Kopie zurueckgegeben, daher erstmal nur ich...
+    if (!this_interactive() || 
+        member(MAINTAINER,getuid(this_interactive()))<0)
+        return(-1);
+    if (process_call()) return(-2);
+    if (!type) return(-3);
+    return(errors[type]);
+}
+
+mixed QueryResolved()
+{
+    //das koennte ein sehr sehr grosses Mapping sein, ausserdem wird keine
+    //Kopie zurueckgegeben, daher erstmal nur ich...
+    if (!this_interactive() || 
+        member(MAINTAINER,getuid(this_interactive()))<0)
+        return(-1);
+    if (process_call()) return(-2);
+    return(resolved);
+}
+*/
+
+/* *****************  Internal Stuff   ******************** */
+void create() {
+    seteuid(getuid(ME));
+
+    if (sl_open("/secure/ARCH/errord.sqlite") != 1)
+    //if (sl_open("/errord.sqlite") != 1)
+    {
+      raise_error("Datenbank konnte nicht geoeffnet werden.\n");
+    }
+    sl_exec("PRAGMA foreign_keys = ON; PRAGMA temp_store = 2; ");
+    //string* res=sl_exec("PRAGMA quick_check(N);");
+    //if (pointerp(res))
+    //{
+    //  raise_error("");
+    //}
+    //sl_exec("CREATE TABLE issue(id INTEGER,haskey TEXT);");
+    foreach (string cmd :
+        explode(read_file("/secure/ARCH/errord.sql.init"),";\n"))
+    {
+      if (sizeof(cmd) && cmd != "\n")
+      {
+        sl_exec(cmd);
+      }
+    }
+    sl_exec("ANALYZE main;");
+
+    lasterror=([]);
+}
+
+string name() {return("<Error-Daemon>");}
+
+void save_me(int now) {
+  if (now)
+    save_object(SAVEFILE);
+  else if (find_call_out(#'save_object)==-1) {
+    call_out(#'save_object, 30, SAVEFILE);
+  }
+}
+
+varargs int remove(int silent) {
+    save_me(1);
+    destruct(ME);
+    return(1);
+}
+
+
+// sperrt den Eintrag (lock!=0) oder entsperrt ihn (lock==0). 
+// Sperre heisst hier, dass der Fehler vom Expire nicht automatisch geloescht
+// wird.
+// liefert <0 im Fehlerfall, sonst Array mit Lockdaten
+private varargs int set_lock(int issueid, int lock, string note)
+{
+    struct fullissue_s issue = db_get_issue(issueid,0);
+    if (!issue)
+      return -1;
+
+    if (!access_check(issue->uid, M_WRITE))
+        //zugriff zum Schreiben nicht gestattet
+        return(-10);
+
+    return db_set_lock(issueid, lock, note);
+}
+
+//markiert einen Fehler als gefixt, mit 'note' als Bemerkung (res!=0) oder
+//markiert einen Fehler wieder als nicht-gefixt (resolution==0)
+//liefert < 0 im Fehlerfall, sonst den neuen Zustand.
+private varargs int set_resolution(int issueid, int resolution, string note)
+{
+    struct fullissue_s issue = db_get_issue(issueid,0);
+    if (!issue)
+      return -1;
+
+    // Fixen duerfen nur zustaendige
+    if (resolution
+        && !access_check(issue->uid, M_FIX))
+      return -10;
+    // ggf. Fix zurueckziehen darf jeder mit M_WRITE
+    if (!resolution &&
+        !access_check(issue->uid, M_WRITE))
+      return -10;
+
+    int res = db_set_resolution(issueid, resolution, note);
+
+    if (res == 1)
+    {
+      // Fehler jetzt gefixt.
+      versende_mail(db_get_issue(issueid,0));  // per Mail verschicken
+    }
+
+    return res;
+}
+
+//ist der Zugriff auf uid erlaubt? Geprueft wird TI (wenn kein TI, auch kein
+//Schreibzugriff)
+//mode gibt an, ob lesend oder schreibend
+private int access_check(string uid, int mode) {
+
+    if (mode==M_READ)
+        return LEARNER_SECURITY;  //lesen darf jeder Magier
+
+    // In process_string() schonmal gar nicht.
+    if (process_call()) return(0);
+    // EM+ duerfen alles loeschen.
+    if (ARCH_SECURITY) return(1);
+    // eigene UIDs darf man auch vollumfaenglich bearbeiten.
+    if (secure_euid()==uid) return(1);
+
+    // alles andere wird speziell geprueft
+    switch(mode)
+    {
+      // M_WRITE ist zur zeit eigentlich immer nen Append - das duerfen zur
+      // Zeit alle ab Vollmagier
+      case M_WRITE:
+        return LEARNER_SECURITY;
+        break;
+      // Loeschen und Fixen duerfen zur Zeit nur Zustaendige.
+      case M_DELETE:
+      case M_REASSIGN:
+      case M_FIX:
+        // Master nach UIDs fragen, fuer die der jew. Magier
+        // zustaendig ist.
+        if (member((string *)master()->QueryUIDsForWizard(secure_euid()),uid) >= 0)
+          return 1;
+
+        break;
+    }
+
+    return(0);        //Fall-through, neinRNER_SECURITY
+}
+
+public string format_stacktrace(struct frame_s* stacktrace) {
+    string *lines;
+
+    if (!pointerp(stacktrace) || !sizeof(stacktrace))
+        return("");
+
+    lines=allocate(sizeof(stacktrace));
+    int i;
+    foreach(struct frame_s frame: stacktrace)
+    {
+      lines[i]=sprintf("Fun: %.20O in Prog: %.40s\n"
+                       "   Zeile: %.8d [%.50s]\n"
+                       "   Evalcosts: %d",
+                       frame->name,frame->prog,
+                       frame->loc,frame->obj,frame->ticks);
+      ++i;
+    }
+    return(implode(lines,"\n"));
+}
+
+public string format_notes(struct note_s* notes)
+{
+  int i;
+  string text="";
+  foreach(struct note_s note: notes)
+  {
+    text+=sprintf("Notiz %d von %.10s am %.30s\n%s",
+        ++i,capitalize(note->user),dtime(note->time),
+        break_string(note->txt, 78,"  "));
+  }
+  return text;
+}
+
+public string format_error_spieler(struct fullissue_s issue)
+{
+  string txt;
+  string *label;
+
+  if (!issue)
+    return 0;
+
+  switch(issue->type)
+  {
+    case T_RTERROR:
+      label=({"Laufzeitfehler","Dieser Laufzeitfehler"});
+      break;
+    case T_REPORTED_ERR:
+      label=({"Fehlerhinweis","Dieser Fehlerhinweis"});
+      break;
+    case T_REPORTED_TYPO:
+      label=({"Typo","Dieser Typo"});
+      break;
+    case T_REPORTED_IDEA:
+      label=({"Idee","Diese Idee"});
+      break;
+    case T_REPORTED_MD:
+      label=({"Fehlendes Detail","Dieses fehlende Detail"});
+      break;
+    case T_RTWARN:
+      label=({"Laufzeitwarnung","Diese Laufzeitwarnung"});
+      break;
+    case T_CTWARN:
+      label=({"Ladezeitwarnung","Diese Ladezeitwarnung"});
+      break;
+    case T_CTERROR:
+      label=({"Ladezeitfehler","Dieser Ladezeitfehler"});
+      break;
+    default: return 0;
+  }
+
+  txt=sprintf( "\nDaten fuer %s mit ID '%s':\n"
+               "Zeit: %25s\n",
+               label[0], issue->hashkey,
+               dtime(issue->ctime)
+              );
+
+  txt+=sprintf("%s",break_string(issue->message,78,
+                  "Meldung:    ",BS_INDENT_ONCE));
+
+  if (pointerp(issue->notes))
+      txt+="Bemerkungen:\n"+format_notes(issue->notes)+"\n";
+
+  return txt;
+}
+
+public string format_error(struct fullissue_s issue, int only_essential)
+{
+  string txt;
+  string *label;
+
+  if (!issue)
+    return 0;
+
+  switch(issue->type)
+  {
+    case T_RTERROR:
+      label=({"Laufzeitfehler","Dieser Laufzeitfehler"});
+      break;
+    case T_REPORTED_ERR:
+      label=({"Fehlerhinweis","Dieser Fehlerhinweis"});
+      break;
+    case T_REPORTED_TYPO:
+      label=({"Typo","Dieser Typo"});
+      break;
+    case T_REPORTED_IDEA:
+      label=({"Idee","Diese Idee"});
+      break;
+    case T_REPORTED_MD:
+      label=({"Fehlendes Detail","Dieses fehlende Detail"});
+      break;
+    case T_RTWARN:
+      label=({"Laufzeitwarnung","Diese Laufzeitwarnung"});
+      break;
+    case T_CTWARN:
+      label=({"Ladezeitwarnung","Diese Ladezeitwarnung"});
+      break;
+    case T_CTERROR:
+      label=({"Ladezeitfehler","Dieser Ladezeitfehler"});
+      break;
+    default: return 0;
+  }
+
+  txt=sprintf( "\nDaten fuer %s mit ID %d:\n"
+               "Hashkey: %s\n"
+               "Zeit: %25s (Erstmalig: %25s)\n",
+               label[0], issue->id, issue->hashkey,
+               dtime(abs(issue->mtime)),dtime(issue->ctime)
+              );
+
+  if (stringp(issue->prog))
+      txt += sprintf(
+               "Programm:   %.60s\n"
+               "Zeile:      %.60d\n",
+               issue->prog, issue->loc
+               );
+
+  if (stringp(issue->obj))
+      txt+=sprintf("Objekt:     %.60s\n",
+               issue->obj);
+
+  txt += sprintf("Loadname:   %.60s\n"
+                 "UID:        %.60s\n",
+                 issue->loadname, issue->uid);
+
+  txt+=sprintf("%s",break_string(issue->message,78,
+                  "Meldung:    ",BS_INDENT_ONCE));
+  if (stringp(issue->hbobj))
+      txt+=sprintf(
+               "HB-Obj:     %.60s\n",issue->hbobj);
+
+  if (stringp(issue->titp)) {
+      txt+=sprintf(
+               "TI/TP:      %.60s\n",issue->titp);
+      if (stringp(issue->tienv))
+          txt+=sprintf(
+               "Environm.:  %.60s\n",issue->tienv);
+  }
+
+  if (!stringp(issue->command) ||
+      !ARCH_SECURITY || process_call())
+  {
+      // Kommandoeingabe ist Privatsphaere und darf nicht von jedem einsehbar
+      // sein.
+      // in diesem Fall aber zumindest das Verb ausgeben, so vorhanden
+      if (issue->verb)
+          txt+=sprintf(
+              "Verb:        %.60s\n",issue->verb);
+  }
+  // !process_call() && ARCH_SECURITY erfuellt...
+  else if (stringp(issue->command))
+      txt+=sprintf(
+          "Befehl:     %.60s\n",issue->command);
+
+  if (issue->caught)
+      txt+=label[1]+" trat in einem 'catch()' auf.\n";
+
+  if (!only_essential)
+  {
+    if (issue->deleted)
+        txt+=label[1]+" wurde als geloescht markiert.\n";
+
+    if (issue->locked)
+        txt+=break_string(
+            sprintf("%s wurde von %s am %s vor automatischem Loeschen "
+            "geschuetzt (locked).\n",
+            label[1],issue->locked_by, dtime(issue->locked_time)),78);
+    if (issue->resolved)
+        txt+=label[1]+" wurde als erledigt markiert.\n";
+  }
+
+  txt+=sprintf("%s trat bisher %d Mal auf.\n",
+               label[1],issue->count);
+
+  if (pointerp(issue->stack))
+      txt+="Stacktrace:\n"+format_stacktrace(issue->stack)+"\n";
+
+  if (pointerp(issue->notes))
+      txt+="Bemerkungen:\n"+format_notes(issue->notes)+"\n";
+
+  return txt;
+}
+
+// letzter Aenderung eines Spieler-/Magiersavefiles. Naeherung fuer letzten
+// Logout ohne das Savefile einzulesen und P_LAST_LOGOUT zu pruefen.
+private int recent_lastlogout(string nam, int validtime)
+{
+  if (!nam || sizeof(nam)<2) return 0;
+  return file_time("/"LIBSAVEDIR"/" + nam[0..0] + "/" + nam + ".o") >= validtime;
+}
+
+// Versendet Mail an zustaendigen Magier und ggf. Spieler, der den Eintrag
+// erstellt hat.
+// ACHTUNG: loescht issue->command.
+private int versende_mail(struct fullissue_s issue)
+{
+  // Versendet eine mail mit dem gefixten Fehler.
+  mixed *mail;
+  string text, *empf;
+  int res = -1;
+
+  mail = allocate(9);
+  mail[MSG_FROM] = "<Fehler-Daemon>";
+  mail[MSG_SENDER] = getuid(TI);
+  mail[MSG_BCC] = 0;
+  mail[MSG_SUBJECT] = sprintf("Fehler/Warnung in %s behoben", issue->loadname);
+  mail[MSG_DATE] = dtime(time());
+
+  // auch wenn ein EM fixt, sollen die Empfaenger nicht automatisch die
+  // Spielereingabe erhalten, also command loeschen.
+  issue->command = 0;
+
+  // erstmal eine Mail an zustaendige Magier.
+  empf = (string*)master()->QueryWizardsForUID(issue->uid);
+  // lang (180 Tage) nicht eingeloggte Magier ausfiltern
+  empf = filter(empf, #'recent_lastlogout, time() - 15552000);
+  if (sizeof(empf))
+  {
+
+    text = break_string(
+      sprintf(STANDARDMAILTEXT,capitalize(getuid(TI)))
+      +format_error(issue, 1),78,"",BS_LEAVE_MY_LFS);
+
+    mail[MSG_RECIPIENT] = empf[0];
+    if (sizeof(empf)>1)
+      mail[MSG_CC] = empf[1..];
+    else
+      mail[MSG_CC] = 0;
+    mail[MSG_BODY]=text;
+    mail[MSG_ID]=sprintf(MUDNAME": %d.%d",time(),random(__INT_MAX__));
+
+    if (!sizeof("/secure/mailer"->DeliverMail(mail,0)))
+      res = -1; // an niemanden erfolgreich zugestellt. :-(
+    else
+      res = 1;
+  }
+
+  // Bei von Spielern gemeldeten Fehler werden Spieler bei
+  // Erledigung informiert, wenn deren letzter Logout weniger als 180 Tage her
+  // ist.
+  if ( (issue->type &
+        (T_REPORTED_ERR|T_REPORTED_TYPO|T_REPORTED_IDEA|T_REPORTED_MD))
+      && issue->titp
+      && recent_lastlogout(issue->titp, time() - 15552000) )
+  {
+    text = break_string(
+      sprintf(STANDARDMAILTEXT_ERRORHINT,
+        capitalize(issue->titp), capitalize(getuid(TI)))
+      +format_error_spieler(issue), 78,"",BS_LEAVE_MY_LFS);
+
+    mail[MSG_ID]=sprintf(MUDNAME": %d.%d",time(),random(__INT_MAX__));
+    mail[MSG_RECIPIENT] = issue->titp;
+    mail[MSG_CC] = 0;
+    mail[MSG_SUBJECT] = sprintf("Fehler/Idee/Typo wurde von %s behoben",
+                                getuid(TI));
+    mail[MSG_BODY] = text;
+
+    if (!sizeof("/secure/mailer"->DeliverMail(mail,0)))
+      res |= -1;
+    else
+      res |= 1;
+  }
+
+  return res;
+}
+
+void reset()
+{
+  // geloeschte Issues sofort, gefixte 30 Tage nach letzter Aenderung
+  // loeschen.
+  sl_exec("DELETE FROM issues WHERE deleted=1;");
+  sl_exec("DELETE FROM issues WHERE resolved=1 AND mtime<?1;",
+          time()-30*24*3600);
+  set_next_reset(3600*24);
+}
+
+// Nicht jeder Magier muss den ErrorD direkt zerstoeren koennen.
+public string NotifyDestruct(object caller) {
+  if( (caller!=this_object() && !ARCH_SECURITY) || process_call() ) {
+    return "Du darfst den Error-Daemon nicht zerstoeren!\n";
+  }
+  return 0;
+}
+
diff --git a/secure/errord.h b/secure/errord.h
new file mode 100644
index 0000000..bd82301
--- /dev/null
+++ b/secure/errord.h
@@ -0,0 +1,112 @@
+/*  MorgenGrauen MUDlib
+    /sys/errord.h
+    Headerfile fuer den Error-Daemonen
+    Autor: Zesstra
+    $Id: errord.h 9439 2016-01-20 09:48:28Z Zesstra $
+    ggf. Changelog:
+*/
+
+#ifndef __ERRORD_H__
+#define __ERRORD_H__
+
+// Typen
+#define T_RTERROR   1	   //Runtime Error / Laufzeitfehler
+#define T_RTWARN    2    //Runtime Warning / Laufzeitwarnung
+#define T_CTERROR   4    //Compiletime Error / Fehler beim Uebersetzen
+#define T_CTWARN    8    //Compiletime Warning / Warnung beim Uebersetzen
+#define T_REPORTED_ERR   16   // von Spielern gemeldete Bugs
+#define T_REPORTED_TYPO  32   // von Spielern gemeldete Typos
+#define T_REPORTED_IDEA  64   // von Spielern gemeldete Ideen 
+#define T_REPORTED_MD    128  // von Spielern gemeldete fehlende Details
+#define ALL_ERR_TYPES ({T_RTERROR, T_RTWARN, T_CTERROR, T_CTWARN, \
+                        T_REPORTED_ERR, T_REPORTED_TYPO, T_REPORTED_IDEA, \
+                        T_REPORTED_MD })
+
+// Status
+#define STAT_DELETED  0x1
+#define STAT_LOCKED   0x2
+#define STAT_RESOLVED 0x4
+
+// Achtung: viele Keys koennen nicht vorhanden oder Werte 0 sein!
+#define F_ID          "id"          //int: (row) ID in der DB
+#define F_TYPE        "type"        //int: Typ-Werte, s.o.
+#define F_HASHKEY     "hashkey"     //string: Hashkey des Fehlers
+#define F_UID         "uid"         //string: "UID des Fehlers"
+#define F_STATE       "state"       //int: Status-Werte, s.o.
+#define F_MODSTAMP    "mtime"       //int
+#define F_CREATESTAMP "ctime"       //int
+#define F_READSTAMP   "atime"       //int 
+#define F_PROG        "prog"        //string == Bluename, falls kein replace_program()
+#define F_OBJ         "obj"         //string
+#define F_LOADNAME    "loadname"    //string == Bluename, falls kein rename_object()
+#define F_LINE        "loc"         //int
+#define F_MSG         "message"     //string
+#define F_HB_OBJ      "hbobj"       //string
+#define F_CAUGHT      "caught"      //int
+#define F_TITP        "titp"        //string
+#define F_STACK       "stack"       //Array von mixed (mixed)
+#define F_CLI         "command"     //string (Spielereingabe)
+#define F_VERB        "verb"        //string (Kommandoverb)
+#define F_COUNT       "count"       //int
+#define F_TIENV       "tienv"       //string, object_name() vom Env von TI/TP
+#define F_LOCK        "locked"      //mixed (Array von 2 Elementen)
+#define F_RESOLVER    "resolver"    //string (wer hat gefixt?)
+#define F_NOTES       "notes"       //Bemerkungen, Array von 3-elementigen Arrays
+
+// Prop, welche in /players/ das Loggen von Fehlern im errord unterbindet.
+#define P_DONT_LOG_ERRORS    "p_lib_errord_dont_log"
+
+#ifdef __NEED_IMPLEMENTATION__
+
+#ifdef DEBUG
+#undef DEBUG
+#endif
+//#define DEBUG(x)
+#define DEBUG(x)  if (funcall(symbol_function('find_player),"zesstra"))\
+        tell_object(funcall(symbol_function('find_player),"zesstra"),\
+        "EDBG: "+x+"\n")
+
+//Wer pflegt das Ding hier gerade?
+#define MAINTAINER ({"zesstra"})
+
+//Stanard-Expire
+#define STDEXPIRE 2678400 //31 Tage
+
+//Blacklist fuer Files, die nicht erfasst werden sollen (nur
+//Uebersetzungsprobleme momentan)
+#define BLACKLIST ({".tool.lpc",".xtool.h"})
+
+//Zugriffsarten
+#define M_READ   1
+#define M_WRITE  2  // actually: append-only
+#define M_FIX    4
+#define M_REASSIGN 8
+#define M_DELETE 16
+
+// Changelog
+#define CHANGELOG "/log/CHANGELOG"
+
+// Log
+//#define MAILLOG       "sent.log"
+
+// Standardmailtext: TODO 
+//#define STANDARDTEXT  HOME("mailtext.txt")
+
+#define STANDARDMAILTEXT "Huhu lieber Mitmagier,\n\n" \
+    "der unten angegebene Fehler in einem Objekt, fuer welches Du " \
+    "(als programmierender Magier oder RM) zustaendig bist, wurde soeben " \
+    "von %s als gefixt markiert. Bitte beachte ggf. die unten angebenen " \
+    "Bemerkungen zum Fix.\n\n--- Fehler-Daten ---\n"
+
+#define STANDARDMAILTEXT_ERRORHINT "Huhu %s,\n\n" \
+    "ein von Dir abgesetzter Fehler (s.u.) wurde von %s bearbeitet und als " \
+    "erledigt markiert. Bei Fragen wende Dich Dich bitte an den " \
+    "bearbeitenden Magier.\n" \
+    "Vielen Dank fuer Deine Mithilfe!\n\n" \
+    "--- Fehler-Daten ---\n"
+
+
+#endif // __NEED_IMPLEMENTATION__
+
+#endif // __ERRORD_H__
+
diff --git a/secure/exploration.h b/secure/exploration.h
new file mode 100644
index 0000000..d7afb40
--- /dev/null
+++ b/secure/exploration.h
@@ -0,0 +1,75 @@
+// MorgenGrauen MUDlib
+//
+// exploration.h -- Definitionen fuer den explorationmaster
+//
+// $Id: exploration.h 8835 2014-06-09 20:24:00Z Zesstra $
+
+#ifndef __EXPLORATION_H__
+#define __EXPLORATION_H__
+
+/* Dateinamen */
+#define EPMASTER    "/secure/explorationmaster"
+#define EPSAVEFILE  "/secure/ARCH/exploration"
+#define DUMPFILE    "/secure/ARCH/EPROOMS.dump"
+#define MAKE_EPSTAT "/secure/ARCH/epstat"
+#define EPSTAT_INFO "/secure/ARCH/epstat.o"
+
+/* Ziele von ChangeEPObject() */
+#define CHANGE_OB   0 // das Objekt selbst
+#define CHANGE_KEY  1 // Schluessel
+#define CHANGE_TYPE 2 // der Typ
+#define CHANGE_BONUS 3 // ist es ein Bonus-EP?
+
+/* Positionen im Mapping */
+#define MPOS_KEY  0   // Die Schluessel
+#define MPOS_NUM  1   // Nummer des EP
+#define MPOS_TYPE 2   // Typ (s.u.)
+
+/* Typen von EP-Spendern */
+#define EP_DETAIL 0   // Details, SpecialDetails
+#define EP_EXIT   1   // Exits und SpecialExits
+#define EP_CMD	  2   // AddCmd()-Kommandos
+#define EP_INFO   3   // Infos von NPCs
+#define EP_MISC   4   // eigene GiveExplorationPoint()-Aufrufe
+#define EP_RDET   5   // ReadDetails
+#define EP_PUB    6   // Speisen und Getraenke in Kneipen
+#define EP_SMELL  7   // Gerueche
+#define EP_SOUND  8   // Geraeusche
+#define EP_TOUCH  9   // ertastbare Details
+#define EP_MAX    9   // max. Anzahl von Typen
+
+#define EP_TYPES ({ "det ", "exit", "cmd ", "info", "misc",\
+                    "rdet", "pub ", "sme ", "sond", "tastdet" })
+
+/* Fehler bei Funktionsaufrufen */
+#define EPERR_NOT_ARCH	  -1  // this_interactive() war kein Erzmagier
+#define EPERR_INVALID_OB  -2  // ungueltiges Objekt (zB. VC-Raum)
+#define EPERR_NO_ENTRY	  -3  // Eintrag nicht gefunden
+#define EPERR_INVALID_ARG -4  // Ungueltiges Argument
+
+/* und hier was fuer die Statistik... */
+#define MIN_EP  10
+
+/* FP-Scripte... */
+#define LF_LOG "ARCH/FPS_TOO_FAST"
+#define LF_TIME 900
+#define LF_WARN 3
+
+/* FP-Logfile */
+#define FP_LOG "ARCH/FPS_FOUND"
+
+#endif
+
+
+#ifdef NEED_PROTOTYPES
+
+#ifndef __EXPLORATION_H_PROTO__
+#define __EXPLORATION_H_PROTO__
+
+/* prototypes */
+static void GiveEP(int type, string key); // in /std/thing/description.c
+
+#endif // __EXPLORATION_H_PROTO__
+
+#endif	// NEED_PROTOYPES
+
diff --git a/secure/explorationmaster.c b/secure/explorationmaster.c
new file mode 100644
index 0000000..ad609d4
--- /dev/null
+++ b/secure/explorationmaster.c
@@ -0,0 +1,1024 @@
+// MorgenGrauen MUDlib
+//
+// explorationmaster.c -- Verwaltung der ExplorationPoints (FP)
+//
+// $Id: explorationmaster.c 9569 2016-06-05 21:28:23Z Zesstra $
+//
+#pragma strict_types,rtt_checks
+#pragma no_clone,no_shadow,no_inherit
+//#pragma pedantic
+//#pragma range_check
+#pragma warn_deprecated
+
+#define BY_EP "/secure/ARCH/EPSTAT_BY_EP"
+#define BY_PL "/secure/ARCH/EPSTAT_BY_PL"
+#define BY_PN "/secure/ARCH/EPSTAT_BY_PN"
+#define AVGLOG "/secure/ARCH/EPSTAT_AVG"
+
+inherit "/std/util/pl_iterator";
+
+#include <config.h>
+#include <wizlevels.h>
+#include <userinfo.h>
+#include <exploration.h>
+#include <properties.h>
+#include <new_skills.h>
+
+#define DOMAIN_INFO 1
+
+#include <living/comm.h>
+#define ZDEBUG(x) if (find_player("zesstra")) \
+  find_player("zesstra")->ReceiveMsg(x,MT_DEBUG,0,object_name()+":",this_object())
+//#define ZDEBUG(x)
+
+// Struktur: ([ string filename : string* action, int number, int type ]) 
+private mapping obs;
+private int epcount;  // Anzahl FP
+private int epavg;    // Durchschnittliche FP/Spieler
+// Bitstrings fuer normale (alloc) und Bonus-FP (bonus)
+private string alloc,bonus;
+
+private nosave string vc_ob;
+private nosave string output;
+
+private nosave int changed;
+private nosave int dumping;
+private nosave mapping dumpMap, lastfound, querytime;
+
+private nosave int stat_done;
+
+protected void create()
+{
+  seteuid(getuid(this_object()));
+  if (!restore_object(EPSAVEFILE)) {
+    obs = ([]);
+    epcount = epavg = 0;
+    alloc = " ";
+    bonus = " ";
+    save_object(EPSAVEFILE);
+  }
+  if (!bonus) bonus=" ";
+  lastfound=([]);
+  querytime=([]);
+}
+
+public varargs int remove(int silent)
+{
+  save_object(EPSAVEFILE);
+  destruct(this_object());
+  return 1;
+}
+
+// Statistik erstellen
+private int SetAverage(int newavg)
+{
+  if (epavg != newavg)
+  {
+    epavg = newavg;
+    write_file(AVGLOG, sprintf("%s : %d\n",dtime(time()),newavg));
+    save_object(EPSAVEFILE);
+  }
+  return epavg;
+}
+
+private void check_player(string pl, mixed extra)
+{
+  int *fp_found = extra[0];
+  mapping plstat = extra[1];
+  mapping pnstat = extra[2];
+  object pldata = extra[3];
+  if (!pldata)
+  {
+    pldata=clone_object("/obj/playerdata");
+    extra[3]=pldata;
+  }
+  // Letzte Loginzeit ermitteln, wenn laenger als 90 Tage her und nicht
+  // eingeloggt, wird der Spieler uebersprungen.
+  // Der playerdata braucht eine UID, mit der er das Spielersavefile laden
+  // kann. Hierzu setzen wir ihm die des zu ladenden Spielers...
+  pldata->ReleasePlayer();
+  seteuid(pl);
+  efun::export_uid(pldata);
+  seteuid(getuid(this_object()));
+  (int)pldata->LoadPlayer(pl);
+  // Testspieler ausnehmen, Spieler, die 90 Tage nicht da waren.
+  if ( ((int)pldata->QueryProp(P_LAST_LOGIN) < time() - 7776000
+        && !find_player(pl))
+      || (mixed)pldata->QueryProp(P_TESTPLAYER))
+    return;
+  // Wenn kein SPieler/Seher, sondern Magier: auch ueberspringen
+  mixed* uinfo = (mixed*)master()->get_userinfo(pl);
+  if (uinfo[USER_LEVEL+1] >= LEARNER_LVL)
+    return;
+
+  string eps=(string)master()->query_ep(pl) || "";
+  int p=-1;
+  int count, avgcount;
+  while ((p=next_bit(eps,p)) != -1)
+  {
+    if (p<sizeof(fp_found))
+    {
+      ++count;
+      ++fp_found[p];
+      // es tragen nur normale EPs zum Durchschnitt bei
+      if (!test_bit(bonus,p))
+        ++avgcount;
+    }
+  }
+
+  // Spieler mit weniger als MIN_EP ignorieren.
+  if (avgcount >= MIN_EP)
+  {
+    extra[4] += avgcount;   // Summe der gefundenen FP
+    ++extra[5];          // Anzahl beruecksichtigter Spieler
+  }
+  plstat += ([ pl : count ]);
+  if (!member(pnstat, count))
+    pnstat += ([ count : ({ pl }) ]);
+  else
+    pnstat[count] = ({ pl })+pnstat[count];
+}
+
+// Mit allen Spielern fertig, aufraeumen.
+#define BAR "************************************************************"
+private void plcheck_finished(mixed extra)
+{
+  ZDEBUG("plcheck_finished entry\n");
+  if (get_eval_cost() < 1000000)
+  {
+    call_out(#'plcheck_finished, 4+random(6), extra);
+    return;
+  }
+
+  int *fp_found = extra[0];
+  mapping plstat = extra[1];
+  mapping pnstat = extra[2];
+  object pldata = extra[3];
+  int avgcount = extra[4];
+  int avgspieler = extra[5];
+
+  if (objectp(pldata))
+    pldata->remove(1);
+
+  if (file_size(BY_EP) >= 0)
+    rm(BY_EP);
+  if (file_size(BY_PL) >= 0)
+    rm(BY_PL);
+  if (file_size(BY_PN) >= 0)
+    rm(BY_PN);
+
+  // Neuen Durchschnittswert fuer gefundene FP setzen.
+  if (avgspieler)
+  {
+    SetAverage(to_int(avgcount/avgspieler));
+  }
+
+  // Histogramm ueber alle FP schreiben: wie oft wurde jeder gefunden?
+  int maxval = max(fp_found);
+  int fp_index;
+  foreach(int found : fp_found)
+  {
+    write_file(BY_EP, sprintf("%5d:%5d %s\n", fp_index, found,
+               BAR[0..(60*found)/maxval]));
+    ++fp_index;
+  }
+  // sortierte Liste der Spieler (sortiert nach gefundenen FP) erzeugen
+  foreach(int fp : sort_array(m_indices(pnstat),#'<) )
+  {
+    foreach(string pl : pnstat[fp])
+      write_file(BY_PN, sprintf("%-14s: %5d\n", pl, fp));
+  }
+  // alphabetisch sortierte Liste der Spieler erzeugen
+  foreach(string pl : sort_array(m_indices(plstat),#'>) )
+  {
+    write_file(BY_PL, sprintf("%-14s: %5d\n", pl, plstat[pl]));
+  }
+}
+#undef BAR
+
+private void make_stat()
+{
+  stat_done = time();
+  // Leider laesst sich epcount nicht nutzen Beim Loeschen von FP wird
+  // <epcount> dekrementiert, so dass hier nicht mehr alle FPs ausgewertet
+  // werden. Daher muss stattdessen bis zur Gesamtgroesse von <obs> gezaehlt
+  // werden.
+  int* fp_found = allocate(sizeof(obs));
+  mapping plstat = m_allocate(500);
+  mapping pnstat = m_allocate(500);
+  start_player_check(#'check_player, #'plcheck_finished, 1200000,
+                     ({fp_found,plstat,pnstat,
+                       0, 0, 0}) );
+}
+
+void reset()
+{
+  if (changed && !dumping)
+  {
+    catch(rm(DUMPFILE);publish);
+    dumping = 1;
+    call_out("dumpEPObjects", 0, sort_array(m_indices(obs),#'> /*'*/));
+    changed = 0;
+  }
+  // nur einmal am tag statistiken erstellen
+  if (time()%86400 < 4000
+      && stat_done < time()-80000)
+    make_stat();
+}
+
+private string strArr(string *s)
+{
+  string ret;
+  int i;
+
+  ret = ("\""+s[<1]+"\"");
+  for (i=sizeof(s)-2; i>=0; i--)
+    ret += (", \""+s[i]+"\"");
+
+  return ret;
+}
+
+static void dumpEPObjects(string *doit)
+{
+  string *toGo, id;
+  int i,j;
+
+  if (!mappingp(dumpMap))
+    dumpMap = ([]);
+
+  toGo = 0;
+  if (sizeof(doit) > 200) {
+    toGo = doit[200..];
+    doit = doit[0..199];
+  }
+
+  j = sizeof(doit);
+
+  for (i=0; i<j; i++) {
+    id = (string)master()->creator_file(doit[i]);
+    if (member(dumpMap, id))
+      dumpMap[id] += ({ doit[i] });
+    else
+      dumpMap += ([ id : ({ doit[i] }) ]);
+  }
+  if (toGo)
+    call_out("dumpEPObjects", 1, toGo);
+  else {
+    int step;
+
+    step = 0;
+    id = "";
+    toGo = sort_array(m_indices(dumpMap),#'> /*'*/ );
+    for (i=0, j=sizeof(toGo); i<j; i++) {
+      int k,l;
+      doit = dumpMap[toGo[i]];
+      id += sprintf("### %s %s\n", toGo[i], "#########################"[sizeof(toGo[i])..]);
+      for (k=0, l=sizeof(doit); k<l; k++) {
+        id += sprintf("%s %4d %s %s.c ({ %s })\n",
+                      EP_TYPES[obs[doit[k], MPOS_TYPE]],
+                      obs[doit[k], MPOS_NUM],
+                      test_bit(bonus,obs[doit[k], MPOS_NUM])?"b":"n",
+                      doit[k],
+                      strArr(obs[doit[k]]));
+        if (!(++step % 50)) {
+          write_file(DUMPFILE, id);
+          id = "";
+        }
+      }
+      id += "\n";
+    }
+    write_file(DUMPFILE,id);
+    if (dumping == 2)
+      write("Fertig! Anfrage bitte wiederholen.\n");
+    dumping = 0;
+    changed = 0;
+    dumpMap = 0;
+  }
+}
+
+private string validOb(mixed ob)
+{
+  string fn, fpart;
+
+  if (!objectp(ob))
+    return 0;
+
+  fn = old_explode(object_name(ob),"#")[0];
+  fpart = old_explode(fn,"/")[<1];
+ /*
+  if (query_once_interactive(ob))
+    return 0;
+
+  if ((file_size(fn+".c") <= 0) &&
+      this_player() &&
+      (strstr(fpart, getuid(this_player())) >= 0))
+    return 0;
+ */
+  return fn;
+}
+
+private int allowed()
+{
+  if (previous_object() && geteuid(previous_object())==ROOTID)
+    return 1;
+  if ( !process_call() && previous_object() && this_interactive()
+        && ARCH_SECURITY )
+    return 1;
+
+  return 0;
+}
+
+nomask varargs int AddEPObject(object ob, mixed keys, int type, int bonusflag)
+{
+  string fn;
+
+  if (!allowed())
+    return EPERR_NOT_ARCH;
+
+  if (!(fn = validOb(ob)))
+    return EPERR_INVALID_OB;
+
+  if (stringp(keys))
+    keys = ({ keys });
+
+  if (member(obs, fn)) {
+    if (type == obs[fn, MPOS_TYPE])
+      obs[fn] = keys;
+    else
+      obs += ([ fn : keys; obs[fn, MPOS_NUM]; type ]);
+    if (bonusflag) bonus = set_bit(bonus, obs[fn, MPOS_NUM]);
+       else bonus = clear_bit(bonus, obs[fn, MPOS_NUM]);
+  }
+  else {
+    int nr, i;
+
+    nr = 0;
+    while (test_bit(alloc,nr))
+      nr++;
+
+    obs += ([ fn : keys; nr; type ]);
+    ++epcount;
+    alloc = set_bit(alloc,nr);
+    if (bonusflag) bonus = set_bit(bonus,nr);
+       else bonus = clear_bit(bonus,nr);
+  }
+
+  changed = 1;
+  save_object(EPSAVEFILE);
+  return epcount;
+}
+
+nomask int RemoveEPObject(string|object ob)
+{
+  string fn;
+
+  if (!allowed())
+    return EPERR_NOT_ARCH;
+  
+  if (objectp(ob))
+    ob=object_name(ob);
+
+  fn = explode(ob,"#")[0];
+  if (!member(obs,fn))
+    return EPERR_NO_ENTRY;
+
+  alloc = clear_bit(alloc, obs[fn, MPOS_NUM]);
+  bonus = clear_bit(bonus, obs[fn, MPOS_NUM]);
+
+  m_delete(obs, fn);
+
+  changed = 1;
+  --epcount;
+  save_object(EPSAVEFILE);
+  return epcount;
+}
+
+nomask int ChangeEPObject(object ob, int what, mixed new)
+{
+  string fn, fn2;
+  mapping entry;
+
+  if (!allowed())
+    return EPERR_NOT_ARCH;
+
+  if (!(fn = validOb(ob)))
+    return EPERR_INVALID_OB;
+
+  if (!member(obs,fn))
+    return EPERR_NO_ENTRY;
+
+  switch(what) {
+  case CHANGE_OB:
+    if (!(fn2 = validOb(new)))
+      return EPERR_INVALID_ARG;
+    entry = ([ fn2: obs[fn]; obs[fn,MPOS_NUM]; obs[fn,MPOS_TYPE] ]);
+    obs = m_copy_delete(obs, fn);
+    obs += entry;
+    break;
+  case CHANGE_BONUS:
+    if (!(intp(new)))
+      return EPERR_INVALID_ARG;
+    if (new) bonus=set_bit(bonus,obs[fn,MPOS_NUM]);
+       else bonus=clear_bit(bonus,obs[fn,MPOS_NUM]);
+    break;
+  case CHANGE_KEY:
+    if (!stringp(new) && !pointerp(new))
+      return EPERR_INVALID_ARG;
+       
+    if (stringp(new))
+      new = ({ new });
+
+    obs[fn] = new;
+    break;
+  case CHANGE_TYPE:
+    if (!intp(new) || new < 0 || new > EP_MAX)
+      return EPERR_INVALID_ARG;
+
+    obs[fn, MPOS_TYPE] = new;
+    break;
+  default:
+    return EPERR_INVALID_ARG;
+  }
+  changed = 1;
+  save_object(EPSAVEFILE);
+  return 1;
+}
+
+nomask mixed QueryEPObject(object ob)
+{
+  string fn;
+
+  if (!allowed())
+    return EPERR_NOT_ARCH;
+
+  if (!(fn = validOb(ob)))
+    return EPERR_INVALID_OB;
+
+  if (!member(obs, fn))
+    return 0;
+
+  return ({ obs[fn], obs[fn,MPOS_NUM], obs[fn, MPOS_TYPE], test_bit(bonus,obs[fn, MPOS_NUM]) });
+}
+
+private string getMatch(string m)
+{
+  string *res;
+  int i;
+
+  res = regexp(sort_array(m_indices(obs), #'> /*'*/), m);
+  for (i=sizeof(res)-1; i>=0; i--)
+    res[i] = sprintf("%s %s %s.c ({ %s })",
+                     EP_TYPES[obs[res[i], MPOS_TYPE]],
+                     test_bit(bonus,obs[res[i], MPOS_NUM])?"b":"n",
+                     res[i],
+                     strArr(obs[res[i]]));
+  return implode(res, "\n");
+}
+
+private string getMatchArch(string m)
+{
+  string *res;
+  int i;
+
+  res = regexp(sort_array(m_indices(obs), #'> /*'*/), m);
+  for (i=sizeof(res)-1; i>=0; i--)
+    res[i] = sprintf("%s %4d %s %s.c ({ %s })",
+                     EP_TYPES[obs[res[i], MPOS_TYPE]],
+                     obs[res[i], MPOS_NUM],
+                     test_bit(bonus,obs[res[i], MPOS_NUM])?"b":"n",
+                     res[i],
+                     strArr(obs[res[i]]));
+  return implode(res, "\n");
+}
+
+/*
+ *  Anleitung fuer >=EMs:
+ *    ShowEPObjects() zeigt das gesamte Dumpfile an
+ *    ShowEPObjects("dump") erzeugt ein neues Dumpfile
+ *    ShowEPObjects("string") liefert alle EPs, die "/string/"
+ *                            im Filenamen enthalten
+ *    ShowEPObjects("string1","string2") liefert alle EPs, die
+ *                            "/string1/string2/" im Filenamen enthalten
+ *  Anleitung fuer RMs:
+ *    ShowEPObjects() liefert all Deine EPs
+ *    ShowEPObjects("domainname") liefert Deine EPs in Deiner Domanin
+ *    ShowEPObjects("domainname","magiername") liefert die EPs des
+ *                                Mitarbeiters in Deiner Region
+ *
+ *  Anleitung fuer <RMs:
+ *    ShowEPObjects() liefert Deine EPs
+ *
+ *  Alle EPObjects einer ganzen Domain kann man nicht mehr auf einmal
+ *  ziehen und sollte man auch nicht. Alle Zugriffe auf diese Funktion
+ *  werden geloggt. Auch die der EMs!
+ *
+ *                                             Rikus
+ */
+
+nomask varargs void ShowEPObjects(string what, string magname)
+{
+  if (allowed()) {
+    if (what == "dump") {
+      if (!dumping) {
+        dumping = 2;
+        catch(rm(DUMPFILE);publish);
+        call_out("dumpEPObjects", 0, sort_array(m_indices(obs),#'>/*'*/));
+      }
+      printf("Liste wird erstellt und in '%s' abgelegt!\n", DUMPFILE);
+      return;
+    }
+    if (!what || what == "") {
+      this_interactive()->More(DUMPFILE, 1);
+      log_file("ARCH/EPZugriffe", ctime(time())+": "+
+        capitalize(getuid(this_interactive()))+" schaute sich das DUMPFILE an.\n");
+      return;
+    }
+    what="/"+what+"/";
+    if (magname) what+=magname+"/";
+  }
+  else
+#ifdef DOMAIN_INFO
+    if (IS_LORD(this_interactive())) {
+      if (!what || what == "")
+       what = "/"+getuid(this_interactive())+"/";
+      else {
+       if (!master()->domain_master(getuid(this_interactive()), what)) {
+         write("Sorry, Du kannst nur Objekte in Deiner eigenen Region abfragen!\n");
+         return;
+       }
+       if (!magname || magname=="")
+         magname = getuid(this_interactive());
+//        if (!master()->domain_member(magname, what)) {
+//         write(capitalize(magname)+" ist gar kein Mitarbeiter in Deiner Region!\n");
+//          return;
+//       }
+       what = "/d/"+what+"/"+magname+"/";
+      }
+    }
+    else
+#endif
+      {
+       if (!what || what == "")
+         what = getuid(this_interactive());
+       else if (what != getuid(this_interactive())) {
+         write("Sorry, Du kannst nur Deine eigenen Objekte abfragen!\n");
+         return;
+       }
+       what="/"+what+"/";
+      }
+  if (allowed())
+    this_interactive()->More(getMatchArch(what));
+  else
+    this_interactive()->More(getMatch(what));
+  log_file("ARCH/EPZugriffe", ctime(time())+": "+
+    capitalize(getuid(this_interactive()))+" schaute sich "+what+" an.\n");
+  return;
+}
+
+nomask void PrepareVCQuery(string file)
+{
+  string path, *parts;
+
+  vc_ob = 0;
+
+  if (!previous_object() || !stringp(file))
+    return;
+
+  parts = explode(object_name(previous_object()),"/");
+
+  if (parts[<1] == "virtual_compiler") {
+    path = implode(parts[0..<2]+({ file }), "/");
+    if (file_size(path+".c") < 0)
+      vc_ob = path;
+  }
+}
+
+nomask mixed *QueryExplore()
+{
+  string fn;
+
+  if (!previous_object())
+    return 0;
+
+  if (!member(obs, fn = old_explode(object_name(previous_object()),"#")[0]))
+    if (!vc_ob || !member(obs, fn = vc_ob)) {
+      vc_ob = 0;
+      return 0;
+    }
+
+  vc_ob = 0;
+  return ({ obs[fn, MPOS_TYPE], obs[fn] });
+}
+
+nomask int QueryMaxEP()
+{
+  return (epcount||1);
+}
+
+nomask int QueryAverage()
+{
+  return (epavg||1);
+}
+
+static int check_arch(object u)
+{
+   return query_wiz_level(u)>=ARCH_LVL;
+}
+
+private int check_to_fast(string name, string fn, int gesetzt)
+{
+    if (gesetzt) return 1; // Rikus, sonst arger scroll :)
+
+    log_file(FP_LOG,sprintf("%s : %s : %s\n",name,fn,dtime(time())),500000);
+
+    if ( !member( lastfound, name ) )
+        lastfound += ([ name: time(); 1; ({ fn + "#*#" + dtime(time()) + "#*#" +gesetzt }) ]);
+    else if ( time() <= lastfound[name, 0] + LF_TIME ){
+        lastfound[name, 1]++;
+        lastfound[name, 2] = ({ fn + "#*#" + dtime(time()) + "#*#" +gesetzt })
+            + lastfound[name,2];
+    }
+    else {
+        lastfound[name, 1] = 1;
+        lastfound[name, 2] = ({ fn + "#*#" + dtime(time()) + "#*#" +gesetzt });
+    }
+
+    lastfound[name, 0] = time();
+
+    if ( lastfound[name, 1] >= LF_WARN ){
+        object *u;
+        int i;
+        string *tmp;
+
+//        u = filter( users(), "check_arch" );
+//        map( u, #'tell_object/*'*/, "**** FP-Liste/Script " +
+//                   capitalize(name) + " (" + dtime(time()) + ") ****\n" );
+
+        for ( i = sizeof(lastfound[name, 2]); i--; ){
+            tmp = explode( lastfound[name, 2][i], "#*#" );
+            log_file( LF_LOG, sprintf( "%s : %s : %s : %s\n",
+                                         tmp[1], name, tmp[2], tmp[0] ), 500000 );
+        }
+
+        lastfound[name, 2] = ({});
+    }
+    return 1;
+}
+
+nomask int GiveExplorationPoint(string key)
+{
+  string fn;
+  string ep;
+  int gesetzt;
+
+  if (!previous_object() || !this_interactive() || !this_player() ||
+       this_player() != this_interactive() ||
+       this_player()->QueryProp(P_KILLS) ||
+       this_player()->QueryGuest()       )
+    return 0;
+
+  fn = old_explode(object_name(previous_object()), "#")[0];
+
+  if (!member(obs, fn))
+    return 0;
+
+  if (member(obs[fn],key) < 0)
+    return 0;
+
+  ep = (MASTER->query_ep(getuid(this_interactive())) || "");
+
+  gesetzt=test_bit(ep,obs[fn,1]);
+  check_to_fast(getuid(this_player()),fn,gesetzt);
+  if (gesetzt) return 0;
+  
+  catch(ep = set_bit(ep, obs[fn,1]));
+
+  return (int)MASTER->update_ep(getuid(this_interactive()),ep);
+}
+
+nomask int GiveExplorationPointObject(string key, object ob)
+{
+  string fn;
+  string ep;
+  int gesetzt;
+  
+  if (!objectp(ob) || ob->QueryProp(P_KILLS ))
+    return 0;
+
+  fn = old_explode(object_name(previous_object()), "#")[0];
+
+  if (!member(obs, fn))
+    return 0;
+
+  if (member(obs[fn],key) < 0)
+    return 0;
+
+  ep = (MASTER->query_ep(getuid(ob)) || "");
+
+  gesetzt=test_bit(ep,obs[fn,1]);
+  check_to_fast(getuid(this_player()),fn,gesetzt);
+  if (gesetzt) return 0;
+
+  catch(ep = set_bit(ep, obs[fn,1]));
+
+  return (int)MASTER->update_ep(getuid(ob),ep);
+}
+
+
+private int QueryRealExplorationPoints(string pl)
+{
+  return count_bits(MASTER->query_ep(pl) || " ");
+}
+
+nomask int QueryExplorationPoints(mixed pl)
+{
+  mixed val;
+  int ep;
+
+  if (!stringp(pl)) pl=getuid(pl);
+  ep=QueryRealExplorationPoints(pl);
+
+  if (allowed() || !ep) return ep;
+
+  val=querytime[pl];
+
+  if (!pointerp(val) || sizeof(val)<2)
+    val=({0,time()});
+
+  if (time()>=val[1]) {
+    val = ({ ep + random(6)-3, time()+300+random(300) });
+    if (val[0]<0) val[0]=0;
+    querytime+=([pl:val]);
+  }
+  return val[0];
+}
+
+private int remove_fp(int num, string pl)
+{
+  int i,j,k,t,maxEP;
+  string ep;
+  ep = (MASTER->query_ep(pl) || "");
+
+  maxEP = QueryMaxEP();
+  for( i=0; i<num; i++)
+  {
+    t = random(maxEP);
+    for( j=0; j<maxEP; j++ ) {
+      if( test_bit(ep, k=(t+j)%maxEP ) ) break;
+    }
+    if( j==maxEP ) break;
+    ep = clear_bit(ep, k);
+  }
+  MASTER->update_ep(pl,ep);
+  return i;
+}
+
+/* */
+
+// 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
+
+nomask int RemoveFP(int num, string pl, string grund)
+{
+  int i;
+  string text;
+  mixed* mail;
+
+  if (!allowed()) return -1;
+  if( num<0 ) return -3;
+  if (!grund || grund=="") grund="<unbekannt>";
+  if (!pl) return -2;
+  i=remove_fp(num, pl);
+  log_file("ARCH/fp_strafen", ctime(time())+": "
+    +this_interactive()->Name(WER)+" loescht "+pl+" "+i
+    +" FPs\nGrund:"+grund+"\n");
+  if( i>0 ) {
+     text =
+     "Hallo "+capitalize(pl)+",\n\n"+
+     break_string(
+      this_interactive()->Name(WER)+" hat soeben veranlasst, dass Dir "+i
+      +" FPs abgezogen wurden.\nGrund:"+grund+"\n", 78 );
+
+     mail = allocate(9);
+     mail[MSG_FROM] = getuid(this_interactive());
+     mail[MSG_SENDER] = MUDNAME;
+     mail[MSG_RECIPIENT] = pl;
+     mail[MSG_CC]=0;
+     mail[MSG_BCC]=0;
+     mail[MSG_SUBJECT]="FP-Reduktion";
+     mail[MSG_DATE]=dtime(time());
+     mail[MSG_ID]=MUDNAME":"+time();
+     mail[MSG_BODY]=text;
+
+     "/secure/mailer"->DeliverMail(mail,1);
+  }
+  return i;
+}
+/* */
+
+private int add_fp(int num, string pl)
+{
+  int i,j,k,t,maxEP;
+  string ep;
+  ep = (MASTER->query_ep(pl) || "");
+
+  maxEP = QueryMaxEP();
+  for( i=0; i<num; i++)
+  {
+    t = random(maxEP);
+    for( j=0; j<maxEP; j++ ) {
+      if( !test_bit(ep, k=(t+j)%maxEP ) ) break;
+    }
+    if( j==maxEP ) break;
+    ep = set_bit(ep, k);
+  }
+  MASTER->update_ep(pl,ep);
+  return i;
+}
+
+nomask int AddFP(int num, string pl)
+{
+  int i;
+  if (!allowed()) return -1;
+  if ( num<0 ) return -3;
+  if (!pl) return -2;
+  i=add_fp(num, pl);
+  log_file("ARCH/fp_strafen", ctime(time())+": "
+    +this_interactive()->Name(WER)+" gibt "+pl+" "+i
+    +" FPs\n");
+
+  return i;
+}
+
+nomask int SetFP(int num, string pl)
+{
+  int maxEP;
+  string ep;
+  if (!allowed()) return -1;
+  if ( num<0 ) return -3;
+  if (!pl) return -2;
+  ep = (MASTER->query_ep(pl) || "");
+
+  maxEP = QueryMaxEP();
+  if (num<0 || num>=maxEP) return -4;
+  ep = set_bit(ep, num);
+  MASTER->update_ep(pl,ep);
+  return num;
+}
+
+nomask int ClearFP(int num, string pl)
+{
+  int maxEP;
+  string ep;
+  if (!allowed()) return -1;
+  if ( num<0 ) return -3;
+  if (!pl) return -2;
+  ep = (MASTER->query_ep(pl) || "");
+
+  maxEP = QueryMaxEP();
+  if (num<0 || num>=maxEP) return -4;
+  ep = clear_bit(ep, num);
+  MASTER->update_ep(pl,ep);
+  return num;
+}
+
+private void printep( int nr, string key, int kind, string* det )
+{
+  output+=sprintf("%4d %s %s.c %s ({ %s })\n",nr,test_bit(bonus,nr)?"b":"n",key,EP_TYPES[kind],
+                strArr(det));
+}
+
+nomask varargs int ShowPlayerEPs(string pl,string pattern)
+{
+  string ep,teststring;
+  if (!allowed()) return -1;
+  ep = (MASTER->query_ep(pl) || "");
+
+  output="";
+  if (!pattern || pattern=="")
+    teststring="%s";
+  else teststring="%s"+pattern+"%s";
+  walk_mapping( obs, lambda( ({ 'key, 'v1, 'v2, 'v3 }),
+                          // v1 -- details, v2 -- Nummer, v3 -- art
+                          // key -- Filename
+                          ({ #'if, ({ #'test_bit, ep, 'v2 }),
+                          ({#'if,({#'sscanf,'key,teststring,'v4,'v5}),
+                          ({ #'printep, 'v2, 'key, 'v3, 'v1 })
+                          }) }) ));
+  this_interactive()->More(output);
+  return 1;
+}
+
+// Hier kommen Funktionen fuer die Levelpunkte
+// Funktion fuer Levelpunkte steht im LEPMASTER!
+
+nomask int QueryLEP(int lep) {
+    raise_error("Bitte QueryLEP() im LEPMASTER abfragen, nicht im "
+       "EPMASTER!");
+    return(-1); //never reached
+}
+
+string QueryForschung()
+{
+  int my;
+  string ret;
+
+  if ((my=QueryRealExplorationPoints(getuid(previous_object()))) < MIN_EP)
+    return "Du kennst Dich im "MUDNAME" so gut wie gar nicht aus.\n";
+
+  my *= 100;
+  int absolute = my/QueryMaxEP();
+  int relative = my/QueryAverage();
+
+  ret = "Verglichen mit Deinen Mitspielern, kennst Du Dich im "MUDNAME" ";
+  switch(relative) {
+  case 0..10:
+    ret += "kaum";
+    break;
+  case 11..40:
+    ret += "aeusserst schlecht";
+    break;
+  case 41..56:
+    ret += "sehr schlecht";
+    break;
+  case 57..72:
+    ret += "schlecht";
+    break;
+  case 73..93:
+    ret += "unterdurchschnittlich";
+    break;
+  case 94..109:
+    ret += "durchschnittlich gut";
+    break;
+  case 110..125:
+    ret += "besser als der Durchschnitt";
+    break;
+  case 126..145:
+    ret += "recht gut";
+    break;
+  case 146..170:
+    ret += "ziemlich gut";
+    break;
+  case 171..210:
+    ret += "gut";
+    break;
+  case 211..300:
+    ret += "sehr gut";
+    break;
+  case 301..400:
+    ret += "ausserordentlich gut";
+    break;
+  case 401..500:
+    ret += "unheimlich gut";
+    break;
+  default:
+    ret += "einfach hervorragend";
+    break;
+  }
+  ret += " aus.\nAbsolut gesehen ";
+
+  switch(absolute) {
+  case 0..5:
+    ret += "kennst Du nur wenig vom "MUDNAME".";
+    break;
+  case 6..10:
+    ret += "solltest Du Dich vielleicht noch genauer umsehen.";
+    break;
+  case 11..17:
+    ret += "bist Du durchaus schon herumgekommen.";
+    break;
+  case 18..25:
+    ret += "hast Du schon einiges gesehen.";
+    break;
+  case 26..35:
+    ret += "bist Du schon weit herumgekommen.";
+    break;
+  case 36..50:
+    ret += "koenntest Du eigentlich einen Reisefuehrer herausbringen.";
+    break;
+  case 51..75:
+    ret += "hast Du schon sehr viel gesehen.";
+    break;
+  default:
+    ret += "besitzt Du eine hervorragende Ortskenntnis.";
+  }
+  return break_string(ret, 78, 0, 1);
+}
+
+// Nicht jeder Magier muss den EPMASTER entsorgen koennen.
+string NotifyDestruct(object caller) {
+  if( (caller!=this_object() && !ARCH_SECURITY) || process_call() )
+  {
+    return "Du darfst den Exploration Master nicht zerstoeren!\n";
+  }
+  return 0;
+}
+
diff --git a/secure/ftpd.c b/secure/ftpd.c
new file mode 100644
index 0000000..749cf03
--- /dev/null
+++ b/secure/ftpd.c
@@ -0,0 +1,178 @@
+/**************************************************************************
+** ftpd.c
+** Rumata@mg
+** 13.6.1999
+**
+** Ermittlung der Zugriffsrechte aus den Userdaten.
+**
+** Dieses Objekt wird vom ftpimp aus aufgerufen, der wiederum vom master
+** aufgerufen wird, um die Zugriffsrechte zu pruefen.
+**
+** Fuktionsweise:
+** + Im Master wird vom UNIX-ftpd aus FtpAccess (/secure/master/network)
+**   aufgerufen.
+** + Dieses ruft dann, sobald ein Zugriff erkannt wird
+**   QueryRead, QueryWrite oder QueryDir auf.
+** + Diese fragen den Master, ob der Zugriff legal ist.
+** + QueryDir liefert auch gleich das Ergebnis mit.
+**
+** Um ftp-zugriffe überwachen zu können, kann der ftpd eine liste der
+** ueberwachten spieler liefern und deren Zugriffe auf dem FTP Kanal ausgeben.
+**
+** add( name, zeit ) - FTP Zugriffe des Spielers 'name' werden protokolliert.
+**                     Nach 'zeit' wird dieser Zugriff automatisch beendet.
+**                     Ist 'zeit' <= 0, wir auf ewig ueberwacht. 
+** sub( name )       - FTP Snoop fuer Spieler 'name' direkt aufheben.
+** list()            - Mapping der ueberwachten Spieler zurueckgeben.
+**
+** FILES:
+**   /secure/master/network.c
+**   /secure/ftpd.c
+**   /std/shells/filesys/ftpimp.c
+**   /secure/ARCH/ftpd.o
+**   /p/daemon/channeld.init
+**
+***************************************************************************/
+
+#include "/secure/wizlevels.h"
+#include "/sys/daemon.h"
+
+#define FTPDSAVE "/secure/ARCH/ftpd"
+#define FTPD_CH "FTP"
+#define BBMASTER "/secure/bbmaster"
+
+mapping monitored;
+// Zu jedem Spielernamen wird vermerkt, wie lange die Ueberwachung dauern soll.
+// Zahlen <= 0 bedeuten fuer immer.
+
+nomask void create() {
+  if( clonep(this_object()) ) {
+    destruct( this_object() );
+    return;
+  }
+  seteuid( getuid() );
+  if( !restore_object(FTPDSAVE) )
+    monitored = ([]);
+}
+
+nomask int player_exists( string user ) {
+  user = lower_case(user);
+  if( !stringp( user ) || sizeof( user ) < 1 ) return 0;
+  return file_size( "/save/"+user[0..0]+"/"+user+".o" ) > 0;
+}
+
+nomask varargs int add( string user, int timeout ) {
+  if( !ARCH_SECURITY ) return -1;
+  if( !player_exists(user) ) return -2;
+  monitored[user] = timeout;
+  save_object( FTPDSAVE );
+  return 0;
+}
+
+nomask int sub( string user ) {
+  if( !ARCH_SECURITY ) return -1;
+  if( !player_exists(user) ) return -2;
+  monitored -= ([ user ]);
+  save_object( FTPDSAVE );
+  return 0;
+}
+
+nomask mixed list() {
+  if( !ARCH_SECURITY ) return -1;
+  return deep_copy(monitored);
+}
+
+#define NEWIMP "yes"
+
+private object findFtpImpFor( string user ) {
+#ifdef NEWIMP
+  return "/secure/impfetch"->impFor( lower_case(user) );
+#else
+  object imp;
+  string fname;
+
+  user = lower_case( user );
+  fname = "/ftpimp:" + user;
+  imp = find_object( fname );
+  if( !objectp(imp) ) {
+    imp = clone_object( "secure/ftpimp" );
+    imp->SetUser(user);
+    rename_object( imp, fname );
+  }
+  return imp;
+#endif
+}
+
+private nomask void msg( string user, string m ) {
+  int timeout;
+  object r;
+
+  if( !stringp(user) ) return;
+  m += "\n";
+  if( (r=find_player("rumata")) && user=="atamur" ) {
+     timeout = CHMASTER->send(FTPD_CH,findFtpImpFor(user), m );
+     tell_object( r, sprintf("%O\n", findFtpImpFor(user) ) );
+  }
+  BBMASTER->ftpbb(user,m);
+  if( !member(monitored,user) ) return;
+  timeout = monitored[user];
+  if( timeout > 0 && timeout < time() ) {
+    sub( user );
+    return;
+  }
+  CHMASTER->send(FTPD_CH,findFtpImpFor(user), m );
+}
+
+nomask int secure() {
+  return previous_object()==find_object(MASTER);
+}
+
+nomask mixed QueryRead( string user, string file ) {
+  if( !secure() ) return -1;
+  if( (IS_WIZARD(user) || file[0..4]=="/open")
+      && file[0] == '/'
+      && MASTER->valid_read(file, user, "read_file", 0)
+      ) {
+    msg( user, "read " + file );
+    return "OK";
+  } else {
+    return "FAIL";
+  }
+}
+
+nomask mixed QueryWrite( string user, string file ) {
+  if( !secure() ) return -1;
+  if( file[0] == '/'
+      && MASTER->valid_write( file, user, "write_file", 0)
+      ) {
+    msg( user, "write " + file );
+    return "OK";
+  } else {
+    return "FAIL";
+  }
+}
+
+#define DBG(x) if(find_player("rumata")){tell_object(find_player("rumata"),"FTPD:"+x+"\n");}
+
+//nomask mixed QueryDir( string user, string file ) {
+//  string reply;
+//  object imp;
+//
+//  if( !secure() ) return -1;
+//  reply = QueryRead( user, file );
+//  if( reply=="FAIL" ) return -1;
+//
+//  imp = findFtpImpFor( user );
+//  if( !objectp(imp) ) return -1; // should never happen
+//
+//  return imp->GetDir( file );
+//}
+
+nomask mixed QueryDir( string user, string file ) {
+  if( !secure() ) return -1;
+  if( file[0] == '/'
+      && MASTER->valid_read( file+"/*", user, "get_dir", 0))
+      return "OK";
+  else 
+    return "FAIL";
+}
diff --git a/secure/ftpimp.c b/secure/ftpimp.c
new file mode 100644
index 0000000..daee34c
--- /dev/null
+++ b/secure/ftpimp.c
@@ -0,0 +1,75 @@
+// MorgenGrauen MUDlib
+//
+// master/ftpimp.c -- FTP Avatar
+//
+// $Id: ftpimp.c 6440 2007-08-18 23:00:49Z Zesstra $
+//
+// siehe /secure/ftpd
+
+#pragma strong_types
+
+#include "/secure/master.h"
+#define FTPD "/secure/ftpd"
+
+//private inherit "/std/shells/filesys/filesys";
+private inherit "/secure/misc/filesys/filesys";
+
+static string user; // Name des zugehoerigen Users
+
+nomask private int secure_impcall() {
+  return
+    getuid(previous_object()) == ROOTID &&
+    geteuid(previous_object()) == ROOTID;
+
+}
+
+// Nach dem Aufruf von SetUser wird der Filename geaednert, sodass
+// das normale clonep in get_dir nicht mehr geht. Hier ein ersatz.
+private nomask int my_is_clone() {
+  return member(object_name(this_object()),':')>=0;
+}
+
+nomask void create() {
+  if( !clonep(this_object()) ) {
+    set_next_reset( -1 ); // blueprint soll nicht sterben
+    return;
+  }
+  //if( secure_impcall() ) ::create();
+}
+
+nomask int SetUser( string name ) {
+  if( !secure_impcall() || !clonep(this_object()) ) return -2;
+  if( stringp(user) && user != "" ) {
+    //write( "User="+user+"\n" );
+    return -1; // schon ein name da!
+  }
+  user = lower_case(name);
+  seteuid( user );
+  set_next_reset( 1800 );
+}
+
+// Gibt einen negativen Wert zurück, wenn die Datei nicht gelesen werden
+// kann oder einen string, der die gesamten Daten des Verzeichnisses
+// enthält.
+nomask mixed GetDir(string dir)
+{
+  mixed ret; ret = "";
+  if( !secure_impcall() || !my_is_clone()) return -2;
+  if( !stringp(user) || user=="" ) return -1;
+  _ls_output( dir, &ret, user, 0x83 );
+  set_next_reset( 1800 );
+  return ret;
+}
+
+nomask void reset() {
+  destruct(this_object());
+}
+
+varargs mixed move() {
+  return -1;
+}
+
+// Fuer die Meldung auf FTP
+nomask string name() {
+  return capitalize(user);
+}
diff --git a/secure/gildenmaster.c b/secure/gildenmaster.c
new file mode 100644
index 0000000..56c5e3d
--- /dev/null
+++ b/secure/gildenmaster.c
@@ -0,0 +1,165 @@
+#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
+
+#define NEED_PROTOTYPES
+#include <thing/properties.h>
+#include <properties.h>
+#include <wizlevels.h>
+#include <new_skills.h>
+#include <events.h>
+
+string *valid_guilds = ({});
+
+void create() {
+  seteuid(getuid(this_object()));
+  restore_object(GUILD_SAVEFILE);
+  if (!pointerp(valid_guilds)) 
+    valid_guilds=({});
+}
+
+//noetig, da nun valid_guilds eine Variable ist
+public mixed QueryProp( string name )
+{
+  if (name==P_VALID_GUILDS) 
+      return copy(valid_guilds);
+  return 0;
+}
+
+nomask int beitreten() {
+  object pl,gilde;
+  string gname,ogname;
+
+  if (!(pl=this_player()) || !(gilde=previous_object()))
+      return 0;
+  // Gilden sind Blueprints.
+  gname=object_name(gilde);
+
+  if ((GUILD_DIR+ogname)==gname) {
+    write("Du bist schon in dieser Gilde.\n");
+    return -4;
+  }
+
+  if ((ogname=(string)pl->QueryProp(P_GUILD)) && 
+      (ogname!=(((string)pl->QueryProp(P_DEFAULT_GUILD)) || DEFAULT_GUILD))) {
+    write("Du bist noch in einer anderen Gilde.\n");
+    return -1;
+  }
+  if (gname[0..7]!=GUILD_DIR ||
+        member(valid_guilds,(gname=gname[8..]))<0) {
+      write("Diese Gilde ist leider nicht zugelassen.\n"+
+              "Bitte verstaendige einen Erzmagier.\n");
+      return -2;
+  }
+  pl->SetProp(P_GUILD,gname);
+  // Event Gildenwechsel triggern
+  EVENTD->TriggerEvent(EVT_GUILD_CHANGE, ([
+      E_OBJECT: pl, E_PLNAME: getuid(pl),
+      E_ENVIRONMENT: environment(pl),
+      E_GUILDNAME: gname,
+      E_LAST_GUILDNAME: ogname ]) );
+
+  return 1;
+}
+
+static nomask void loose_ability(mixed key, mixed dat, int loss) {
+  mixed val;
+
+  val=mappingp(dat)?dat[SI_SKILLABILITY]:dat;
+  if (!intp(val) || val<=0) return;
+  val-=(val*loss)/100;if (val<=0) val=1;
+  if (mappingp(dat))
+      dat[SI_SKILLABILITY]=val;
+  else
+      dat=val;
+}
+
+varargs nomask int austreten(int loss) {
+  object pl,gilde;
+  string gname;
+  mapping skills;
+
+  if (!(pl=this_player()) || !(gilde=previous_object()))
+      return 0;
+  // Gilden muessen Blueprints sein, so. ;-)
+  gname=object_name(gilde);
+  if (gname[0..7]!=GUILD_DIR ||
+      ((string)pl->QueryProp(P_GUILD))!=gname[8..]) {
+      write("Du kannst hier nicht aus einer anderen Gilde austreten.\n");
+      return -1;
+  }
+  if (gname[8..]==(pl->QueryProp(P_DEFAULT_GUILD)||DEFAULT_GUILD))
+  {
+        write("Aus dieser Gilde kannst Du nicht austreten.\n");
+        return -1;
+  }
+  if (loss<=0) loss=20;
+  skills=(mapping)pl->QueryProp(P_NEWSKILLS);
+  walk_mapping(skills,"loose_ability",this_object(),loss);
+  pl->SetProp(P_NEWSKILLS,skills);
+  pl->SetProp(P_GUILD,0);
+  // Event Gildenwechsel triggern
+  EVENTD->TriggerEvent(EVT_GUILD_CHANGE, ([
+      E_OBJECT: pl, E_PLNAME: getuid(pl),
+      E_ENVIRONMENT: environment(pl),
+      E_GUILDNAME: pl->QueryProp(P_GUILD),
+      E_LAST_GUILDNAME: gname ]) );
+
+  // Defaultgilde ggf. neuen Titel setzen lassen.
+  gname = (string)pl->QueryProp(P_GUILD);
+  (GUILD_DIR+"/"+gname)->adjust_title(pl);
+
+  return 1;
+}
+
+nomask static int security_check()
+{
+  if (previous_object() && geteuid(previous_object())==ROOTID)
+    return 1;
+  if (!process_call() && previous_object() && ARCH_SECURITY)
+    return 1;
+  return 0;
+}
+
+nomask int AddGuild(string gildob) {
+  object tp;
+
+  if (!stringp(gildob) || !sizeof(gildob) || !security_check() 
+      || file_size(GUILD_DIR+gildob+".c")<0)
+      return 0;
+  if (member(valid_guilds, gildob) != -1)
+      return 0;
+  valid_guilds+=({gildob});
+  save_object(GUILD_SAVEFILE);
+  return 1;
+}
+
+nomask int RemoveGuild(string gildob) {
+  object tp;
+
+  if (!stringp(gildob) || !sizeof(gildob) 
+      || !security_check())
+      return 0;
+  if (member(valid_guilds, gildob) == -1)
+      return 0;
+  valid_guilds-=({gildob});
+  
+  save_object(GUILD_SAVEFILE);
+  return 1;
+}
+
+nomask int ValidGuild(string gildenob) {
+  if (!stringp(gildenob)) 
+      return 0;
+
+  return (member(valid_guilds,gildenob)>=0);
+}
+
+nomask string *_query_valid_guilds() {return copy(valid_guilds); }
+
diff --git a/secure/guestmaster.c b/secure/guestmaster.c
new file mode 100644
index 0000000..0304d8d
--- /dev/null
+++ b/secure/guestmaster.c
@@ -0,0 +1,60 @@
+// MorgenGrauen MUDlib
+//
+// guestmaster.c -- Verwalter von Gast-Logins
+//
+// $Id: guestmaster.c 8909 2014-08-20 21:33:55Z Zesstra $
+#pragma no_clone,no_inherit,no_shadow
+#pragma strict_types,save_types,rtt_checks
+
+#include <wizlevels.h>
+
+nosave int max_guests = 10; /* Max no. of guests. -1 is 'unlimited' */
+nosave object *guests = ({});
+nosave int    *ltime = ({});
+
+protected void create()
+{
+  seteuid(getuid());
+}
+
+nomask int new_guest () {
+  int ix;
+
+  if (!max_guests) return 0;
+  if (load_name(previous_object()) != "/secure/login")
+    return 0;
+ 
+  if ((ix = member(guests,0)) == -1) {
+    ix = sizeof(guests);
+    if (max_guests < 0 || ix < max_guests) 
+    {
+      guests += ({ 0 }), ltime += ({ 0 });
+    }
+    else {
+      int mintime, minix;
+      mintime = time();
+      for (ix = 0; ix < sizeof(guests); ix++) {
+        if (guests[ix] && ltime[ix] < mintime) {
+          mintime=ltime[ix];
+          minix=ix;
+        }
+      }
+      ix = minix;
+    }
+  }
+  return ix+1;
+}
+
+nomask void set_guest (int ix, object pl) {
+  if (load_name(previous_object()) != "/secure/login") return;
+  guests[ix-1] = pl;
+  ltime[ix-1] = time();
+}
+
+nomask int query_max_guests() { return max_guests; }
+nomask int set_max_guests(int v) {
+  if (!process_call() && LORD_SECURITY)
+    max_guests = v;
+  return max_guests;
+}
+
diff --git a/secure/impfetch.c b/secure/impfetch.c
new file mode 100644
index 0000000..b7556bf
--- /dev/null
+++ b/secure/impfetch.c
@@ -0,0 +1,24 @@
+// Imp Fetcher
+
+#define FTPD "secure/ftpd"
+#define FTPIMP "std/shells/filesys/ftpimp"
+
+#define D(msg) if(find_player("rumata"))tell_object(find_player("rumata"),msg)
+
+void create() {}
+
+nomask mixed impFor( string name ) {
+    object imp;
+    string fname;
+
+    fname = "/ftpimp:" + name;
+    imp = find_object( fname );
+    if( !objectp(imp) ) {
+	seteuid( name );
+	imp = clone_object( FTPIMP );
+	imp->SetUser( name );
+	rename_object( imp, fname );
+        destruct( this_object() ); // next imp must have rootid again
+    }
+    return imp;
+}
diff --git a/secure/inetd.c b/secure/inetd.c
new file mode 100644
index 0000000..93b9145
--- /dev/null
+++ b/secure/inetd.c
@@ -0,0 +1,808 @@
+/*
+ * UDP port handling code. Version 0.7a
+ * Written by Nostradamus for Zebedee.
+ * Developed from an original concept by Alvin@Sushi.
+ */
+
+#pragma strict_types
+#pragma no_clone
+#pragma no_shadow
+#pragma no_inherit
+#pragma verbose_errors
+//#pragma pedantic
+#pragma no_range_check
+#pragma no_warn_deprecated
+
+
+#include <living/comm.h>
+#define ZDEBUG(x)        if (find_player("zesstra"))\
+            find_player("zesstra")->ReceiveMsg(x, \
+                MT_DEBUG|MSG_DONT_STORE,0,"IM: ",this_object())
+
+//#define ZDEBUG(x)
+
+#include <udp.h>
+
+/* --- Configurable definitions. --- */
+
+/* CD muds will probably need these include file. */
+/* #include <std.h> */
+/* #include "/config/sys/local.h" */
+
+/* Public commands which will be accessible to any unlisted muds.
+ * PING, QUERY and REPLY are included by default. */
+#define COMMANDS \
+({ "channel", "finger", "tell", "who", "mail", "www", "htmlwho", "locate" })
+
+//({ "channel", "finger", "ftp", "locate", "man", "tell", "who" })
+
+/* Define this to the object that receives incoming packets and passes
+ * them to the inetd. Undefine for no _receive_udp() security.
+ * NOTE: The string must be of the format that object_name() returns. */
+#define UDP_MASTER                __MASTER_OBJECT__
+
+/* How to set the euid for this object if using native mode.
+ * Ensure that it can read the INETD_HOSTS file. */
+#define SET_EUID                seteuid(getuid())
+
+/* Define this if you are running another intermud package concurrently. */
+/* #define RECEIVE_UDP_COMPAT(sender, packet) \
+                UDP_MANAGER->incoming_udp(sender, packet)        // CD */
+
+/* Define this if you are running another intermud package concurrently and
+ * have a compatability module for it. */
+/* #define SEND_UDP_COMPAT(mudname, data, expect_reply) \
+                "/secure/udp_compat"->_send_udp(mudname, data, expect_reply) */
+
+/* The maximum number of characters we can send in one packet.
+ * You may need to reduce this, but 512 should be safe. */
+#define MAX_PACKET_LEN        1024
+
+/* You shouldn't need to change anything below. */
+
+#define USE_OLD_DELIMITER
+//#define DELIMITER_COMPAT
+#define USE_OLD_DATA_FORMAT
+
+#ifdef ZEBEDEE
+#include <defs.h>
+#endif
+
+#ifndef DATE
+#define DATE                ctime(time())[4..15]
+#endif
+
+/* --- End of Config. Do not alter anything below. --- */
+
+#define UNKNOWN                0
+#define UP                time()
+#define DOWN                (-time())
+
+#define NEW_DELIMITER        "\n"
+#ifdef USE_OLD_DELIMITER
+#define DELIMITER        "|"
+#else
+#define DELIMITER        "\n"
+#endif
+#define OLD_DELIMITER        "|"
+#define HOSTFILE_DEL        ":"
+#define HOSTFILE_DEL2        ","
+#define RETRY                "_RETRY"
+
+private nosave mapping hosts, pending_data, incoming_packets;
+private nosave mapping static_host_list;
+private nosave string *received_ids;
+private nosave int packet_id;
+
+void set_host_list();
+varargs string _send_udp(string mudname, mapping data, int expect_reply);
+
+#define DEBUG(msg)
+
+void my_create() {
+#ifndef COMPAT_FLAG
+    SET_EUID;
+#endif
+    packet_id = 0;
+    pending_data = ([ ]);
+    incoming_packets = ([ ]);
+    hosts = ([ ]);
+    received_ids = ({ });
+    set_host_list();
+    if (!this_player())
+        call_out("startup", 1, 0);
+}
+
+#ifdef CREATE_FUN
+CREATE_FUN() {
+#elif !defined(COMPAT_FLAG) || defined(ZEBEDEE)
+void create() {
+#else
+void reset(arg) {
+    if (arg)
+        return;
+#endif
+    my_create();
+}
+
+status check_system_field(mapping data, string field) {
+    return data[SYSTEM] && member(data[SYSTEM], field) != -1;
+}
+
+mapping add_system_field(mapping data, string field) {
+    if (data[SYSTEM]) {
+        if (!check_system_field(data, field))
+            data[SYSTEM] += ({ field });
+    }
+    else
+        data[SYSTEM] = ({ field });
+    return data;
+}
+
+private mapping read_host_list(string file) {
+
+  mixed data = (read_file(file));
+  if (!data) {
+    log_file(INETD_LOG_FILE, "*Error in reading host file: "
+        + file +"\n\n");
+    return 0;
+  }
+
+  data = old_explode(data, "\n");
+  mapping new_hosts = m_allocate(sizeof(data),1);
+
+  foreach(string line: data) {
+    if (line == "" || line[0] == '#')
+      continue;
+    mixed fields = old_explode(line, HOSTFILE_DEL);
+    if (sizeof(fields) < 5) {
+      log_file(INETD_LOG_FILE, sprintf(
+            "*Parse error in hosts file: %s, Line: %s\n", file, line));
+      continue;
+    }
+
+    string name = lower_case(fields[HOST_NAME]);
+    if (member(new_hosts, name))
+      continue; // mud already in list
+
+    string *local_cmds = old_explode(fields[LOCAL_COMMANDS],HOSTFILE_DEL2);
+    if (member(local_cmds,"*") != -1)
+        local_cmds = local_cmds - ({ "*" }) + COMMANDS;
+    
+    new_hosts[name] = ({ capitalize(fields[HOST_NAME]),
+                     fields[HOST_IP],
+                     to_int(fields[HOST_UDP_PORT]),
+                     local_cmds,
+                     old_explode(fields[HOST_COMMANDS], HOSTFILE_DEL2),
+                     UNKNOWN
+                   });
+    /*
+     * Get existing host status from current active host lost as long as the
+     * IP and UDP ports are the same.
+     */
+    if (hosts[name] &&
+        hosts[name][HOST_IP] == new_hosts[name][HOST_IP] &&
+        hosts[name][HOST_UDP_PORT] == new_hosts[name][HOST_UDP_PORT])
+        new_hosts[name][HOST_STATUS] = hosts[name][HOST_STATUS];
+  }
+  return new_hosts;
+}
+
+/*
+ * Read the INETD_HOSTS file and set the "hosts" mapping from it.
+ * Retain existing HOST_STATUS fields.
+ * Reads INETD_HOSTSS and INETD_HOSTS.dump
+ * Hosts from INETD_HOSTS are included in the static host list.
+ */
+void set_host_list() {
+
+  // read the static host file and the last host file dump.
+  mapping static_hosts = read_host_list(HOST_FILE);
+  if (!static_hosts)
+      return; // retain the old list(s)
+
+  // remember the static hosts
+  static_host_list = m_reallocate(static_hosts,0);
+  
+  // read the last host file dump and add the static hosts. Then the static
+  // hosts have precedence over the ones from the dump.
+  hosts = (read_host_list(HOST_FILE".dump") || ([])) + static_hosts;
+
+}
+
+mixed get_hosts() {return copy(hosts);}
+
+int  dump_hosts_list() {
+  
+  write_file(HOST_FILE+".dump", sprintf(
+    "#Hostlist dump from "MUDNAME", created %s\n", strftime()), 1);
+
+  foreach(string mudname, mixed tmp : hosts) {
+    // skip muds we did not hear from for 2 days.
+//    if (tmp[HOST_STATUS] + 172800 < time())
+//      continue;
+    write_file(HOST_FILE+".dump",
+        sprintf("%s:%s:%d:%s:%s\n",tmp[0],tmp[1],tmp[2],implode(tmp[3],","),
+          implode(tmp[4],",")));
+  }
+  return 1;
+}
+
+/*
+ * Make a PING request to all muds in the "hosts" mapping to set
+ * HOST_STATUS information.
+ * But don't ping all muds at once, because that may overflow the callout
+ * table during mud startup, when hundreds of objects make callouts.
+ */
+void startup(string *muds) {
+
+    if (!pointerp(muds))
+      muds=m_indices(hosts);
+    if (!sizeof(muds))
+      return;
+    string *part;
+    if (sizeof(muds) > 9)
+      part=muds[0..9];
+    else
+      part=muds;
+    foreach(string mud: part) 
+      _send_udp(mud, ([ REQUEST: PING ]), 1);
+    muds -= part;
+    if (sizeof(muds))
+      call_out(#'startup, 4, muds);
+}
+
+/*
+ * Remove a buffered packet from the "incoming_packets" mapping.
+ */
+void remove_incoming(string id) {
+    m_delete(incoming_packets, id);
+}
+
+/*
+ * Decode a string from a UDP packet.
+ * Returns:   The actual value of the argument (either int or string)
+ */
+mixed decode(string arg) {
+
+    if (sizeof(arg) && arg[0] == '$')
+        return arg[1..];
+#ifdef RESTRICTED_CASTS
+    if (to_string(to_int(arg)) == arg)
+        return to_int(arg);
+#else
+    if ((string)((int)arg) == arg)
+        return (int)arg;
+#endif
+    return arg;
+}
+
+/*
+ * Decode a UDP packet.
+ * Arguments: UDP packet as a string.
+ * Returns:   The decoded information as a mapping, 1 for succes but no
+ *            output (buffered packet), or 0 on error.
+ */
+mixed decode_packet(string packet, string delimiter) {
+    string *data;
+    mapping ret;
+    string info, tmp;
+    mixed class;
+    int i, id, n;
+
+    /* If this packet has been split, handle buffering. */
+    if (packet[0..sizeof(PACKET)] == PACKET + ":") {
+        if (sscanf(packet, PACKET + ":%s:%d:%d/%d" + delimiter + "%s",
+        class, id, i, n, tmp) != 5)
+            return 0;
+        class = lower_case(class) + ":" + id;
+        if (pointerp(incoming_packets[class])) {
+            incoming_packets[class][i-1] = tmp;
+            if (member(incoming_packets[class], 0) == -1) {
+                ret =
+                decode_packet(implode(incoming_packets[class], ""), delimiter);
+                remove_incoming(class);
+                return ret;
+            }
+        } else {
+            incoming_packets[class] = allocate(n);
+            incoming_packets[class][i-1] = tmp;
+/* Is it possible to already have a timeout running here ?!? */
+            /* If no timeout is running then start one. */
+            if (!pending_data[class]) {
+                call_out("remove_incoming",
+                REPLY_TIME_OUT + REPLY_TIME_OUT * RETRIES, class);
+            } else {
+                DEBUG("\nINETD: *** Buffered packet Timeout already running! ***\n");
+            }
+        }
+        return 1;
+    }
+    ret = ([ ]);
+    for(i = 0, n = sizeof(data = old_explode(packet, delimiter)); i < n; i++) {
+        /* DATA fields can be denoted by a preceeding blank field. */
+        if (data[i] == "") {
+            tmp = DATA;
+            /* Test for illegal packet length (no DATA) */
+            if (++i >= n)
+                return 0;
+            info = data[i];
+        }
+        else if (sscanf(data[i], "%s:%s", tmp, info) != 2)
+            return 0;
+        switch((string)(class = decode(tmp))) {
+            case DATA:
+#ifdef USE_EXTRACT
+                return ret + ([ DATA: decode(implode(
+                    ({ info }) + extract(data, i+1), delimiter)) ]);
+#else
+                return ret + ([ DATA: decode(implode(
+                    ({ info }) + data[i+1..], delimiter)) ]);
+#endif
+            case SYSTEM:
+                ret[class] = old_explode(info, ":");
+                continue;
+            default:
+                ret[class] = decode(info);
+                continue;
+        }
+    }
+    return ret;
+}
+
+/* Check wether a UDP packet was valid.
+ * Logs are made and "host" information is updated as appropriate.
+ * Arguments: Decoded UDP packet (mapping)
+ * Returns:   0 for valid packets, an error string otherwise.
+ */
+string valid_request(mapping data) {
+    mixed host_data;
+    string *muds;
+    string req;
+    int i;
+    
+    if (!data[NAME] || !data[UDP_PORT])
+        return DATE + ": Illegal packet.\n";
+    if (host_data = hosts[lower_case(data[NAME])]) {
+        if (data[HOST] != host_data[HOST_IP]) {
+            if (data[NAME] == LOCAL_NAME)
+                return DATE + ": *** FAKE MUD ***\n";
+            log_file(INETD_LOG_FILE, DATE + ": Host change:\n" +
+            host_data[HOST_NAME] + ": " + host_data[HOST_IP] + " -> " +
+            data[HOST] + "\n\n");
+            host_data[HOST_IP] = data[HOST];
+        }
+        if (data[UDP_PORT] != host_data[HOST_UDP_PORT]) {
+            if (data[NAME] == LOCAL_NAME)
+                return DATE + ": *** FAKE MUD ***\n";
+            log_file(INETD_LOG_FILE, DATE + ": Port change:\n" +
+                host_data[HOST_NAME] + " (" + host_data[HOST_IP] + "): " +
+                host_data[HOST_UDP_PORT] + " -> " + data[UDP_PORT] + "\n\n");
+            host_data[HOST_UDP_PORT] = data[UDP_PORT];
+        }
+    } else {
+        if (lower_case(data[NAME]) == lower_case(LOCAL_NAME))
+            return DATE + ": *** FAKE MUD ***\n";
+        for(i = sizeof(muds = m_indices(hosts)); i--; ) {
+            host_data = hosts[muds[i]];
+            if (data[HOST] == host_data[HOST_IP] &&
+            data[UDP_PORT] == host_data[HOST_UDP_PORT]) {
+                log_file(INETD_LOG_FILE, DATE + ": Name change:\n" +
+                    host_data[HOST_NAME] + " (" + host_data[HOST_IP] +
+                    ") -> " + data[NAME] + "\n\n");
+                host_data[HOST_NAME] = data[NAME];
+                hosts[lower_case(data[NAME])] = host_data;
+                m_delete(hosts, muds[i]);
+                i = -2;
+                break;
+            }
+        }
+        if (i != -2) {
+            host_data = hosts[lower_case(data[NAME])] = ({
+                data[NAME],
+                data[HOST],
+                data[UDP_PORT],
+                COMMANDS,
+                ({ "*" }),
+                UP
+            });
+            log_file(INETD_LOG_FILE, DATE + ": New mud.\n" + data[NAME] + ":" +
+            data[HOST] + ":" + data[UDP_PORT] + "\n\n");
+        }
+    }
+    if (!(req = data[REQUEST]))
+        return DATE + ": System message.\n";
+    if (req != PING &&
+        req != QUERY &&
+        req != REPLY &&
+        member(host_data[LOCAL_COMMANDS], req) == -1)
+    {
+        /* This should probably send a system message too. */
+        _send_udp(host_data[HOST_NAME], ([
+            REQUEST: REPLY,
+            RECIPIENT: data[SENDER],
+            ID: data[ID],
+            DATA: "Invalid request @" + LOCAL_NAME + ": " +
+                capitalize(data[REQUEST]) + "\n"
+        ]) );
+        return DATE + ": Invalid request.\n";
+    }
+    return 0;
+}
+
+/*
+ * Incoming UDP packets are sent to this function to be interpretted.
+ * The packet is decoded, checked for validity, HOST_STATUS is updated
+ * and the appropriate udp module called.
+ * Arguments: Senders IP address (string)
+ *            UDP packet (string)
+ */
+void _receive_udp(string sender, string packet) {
+    mapping data;
+    string req, err, id;
+
+#ifdef UDP_MASTER
+    if (!previous_object() ||
+    object_name(previous_object()) != UDP_MASTER) {
+        log_file(INETD_LOG_FILE, DATE + ": Illegal call of _receive_udp() by " +
+        object_name(previous_object()) + "\n\n");
+        return;
+    }
+#endif
+
+    ZDEBUG(sprintf("%O -> %.500O\n",sender, packet));
+    if (
+#ifdef DELIMITER_COMPAT
+#ifdef USE_EXTRACT
+        (!(data = decode_packet(packet, NEW_DELIMITER))
+#else
+        (!(data = decode_packet(packet, NEW_DELIMITER))
+#endif
+        || (data[HOST] = sender) && (err = valid_request(data))) &&
+        (!(data = decode_packet(packet, OLD_DELIMITER)) ||
+        (data[HOST] = sender) && (err = valid_request(data)))
+#else
+#ifdef USE_EXTRACT
+        !(data = decode_packet(packet, DELIMITER))
+#else
+        !(data = decode_packet(packet, DELIMITER))
+#endif
+#endif
+    ) {
+        if (!data)
+#ifdef RECEIVE_UDP_COMPAT
+            RECEIVE_UDP_COMPAT(sender, packet);
+#else
+            log_file(INETD_LOG_FILE, DATE + ": Received invalid packet.\nSender: " +
+            sender + "\nPacket:\n" + packet + "\n\n");
+#endif
+        return;
+    }
+#ifdef DELIMITER_COMPAT
+    if (!mappingp(data))
+        return;
+    if (err)
+#else
+          if (!mappingp(data))
+                  return;
+    data[HOST] = sender;
+    if (err = valid_request(data))
+#endif
+    {
+        log_file(INETD_LOG_FILE, err + "Sender: " + sender + "\nPacket:\n" +
+        packet + "\n\n");
+        return;
+    }
+    hosts[lower_case(data[NAME])][HOST_STATUS] = UP;
+    if ((req = data[REQUEST]) == REPLY) {
+            mapping pending;
+
+        /* If we can't find the reply in the pending list then bin it. */
+        if (!(pending = pending_data[lower_case(data[NAME]) + ":" + data[ID]]))
+            return;
+        /* Set data[REQUEST] correctly, but still send to (req = REPLY) */
+        data[REQUEST] = pending[REQUEST];
+#ifdef INETD_DIAGNOSTICS
+        data[PACKET_LOSS] = pending[PACKET_LOSS];
+        data[RESPONSE_TIME] = time() - pending[RESPONSE_TIME] + 1;
+#endif
+#if 0
+/* channel replies may not include a recipient, and shouldn't have one set */
+        /* Restore the RECIPIENT in replies if none given and it is known. */
+        if (!data[RECIPIENT] && pending[SENDER])
+            data[RECIPIENT] = pending[SENDER];
+#endif
+        m_delete(pending_data, lower_case(data[NAME]) + ":" + data[ID]);
+    }
+    else if (data[ID]) {
+      id = lower_case(data[NAME]) + ":" + data[ID];
+      if (member(received_ids, id) == -1)
+      {
+        received_ids += ({ id });
+        call_out("remove_received_id",
+              REPLY_TIME_OUT + REPLY_TIME_OUT * (RETRIES + 1), id);
+      }
+      else
+            add_system_field(data, REPEAT);
+    }
+    if (err = catch(
+        call_other(UDP_CMD_DIR + req, "udp_" + req, deep_copy(data));publish))
+    {
+        _send_udp(data[NAME], ([
+            REQUEST: REPLY,
+            RECIPIENT: data[SENDER],
+            ID: data[ID],
+            DATA: capitalize(req)+ " request failed @" + LOCAL_NAME + ".\n"
+        ]) );
+        log_file(INETD_LOG_FILE, DATE + ": " + data[REQUEST] + " from " +
+        data[NAME] + " failed.\n" + err + packet + "\n\n");
+    }
+}
+
+int do_match(string mudname, string match_str) {
+    return mudname[0..sizeof(match_str)-1] == match_str;
+}
+
+#ifdef NO_CLOSURES
+
+status greater(mixed a, mixed b) {
+    return a > b;
+}
+
+string *expand_mud_name(string name) {
+    return sort_array(
+        filter(m_indices(hosts), "do_match", this_object(), name),
+        "greater",
+        this_object()
+    );
+}
+
+#else
+
+string *expand_mud_name(string name) {
+    return sort_array(
+        filter(m_indices(hosts), #'do_match, name),
+        #'>
+    );
+}
+
+#endif
+
+string encode(mixed arg) {
+    if (objectp(arg))
+        return object_name(arg);
+    if (stringp(arg) && sizeof(arg) &&
+        (arg[0] == '$' ||
+#ifdef RESTRICTED_CASTS
+    to_string(to_int(arg)) == (string)arg))
+#else
+    (string)to_int(arg) == (string)arg))
+#endif
+        return "$" + arg;
+    return to_string(arg);
+}
+
+string encode_packet(mapping data) {
+    int i;
+    mixed indices;
+    string header, body, t1, t2;
+    string *ret;
+    status data_flag;
+
+    for(ret = ({ }), i = sizeof(indices = m_indices(data)); i--; ) {
+        if (indices[i] == DATA) {
+            data_flag = 1;
+            continue;
+        }
+        header = encode(indices[i]);
+        body = encode(data[indices[i]]);
+        if (sscanf(header, "%s:%s", t1, t2) ||
+            sscanf(header + body, "%s" + DELIMITER + "%s", t1, t2)
+        )
+            return 0;
+        
+        ret += ({ header + ":" + body });
+    }
+    if (data_flag)
+#ifdef USE_OLD_DATA_FORMAT
+        ret += ({ DATA + ":" + encode(data[DATA]) });
+#else
+        ret += ({ "", encode(data[DATA]) });
+#endif
+    return implode(ret, DELIMITER);
+}
+
+// Funktion explode_
+// Die Funktion zerlegt den String packet in gleich lange Teilstrings
+// der Laenge len und gibt die Teilstrings als Array zurueck. Der letzte
+// Teilstring kann kuerzer sein als die anderen.
+string *explode_packet(string packet, int len) {
+
+  int ptr, m_ptr,size;
+  string *result;
+
+  // Variablen initialisieren
+  m_ptr=ptr=0;
+  size=sizeof(packet);
+  result=({});
+
+  // Um Arrayadditionen zu vermeiden wird vorher allokiert. Die Division 
+  // durch 0 ist nicht abgefangen, da bei len=0 was im Aufruf falch ist.
+  result=allocate((size/len+1));
+
+  while (ptr<size) {
+    result[m_ptr] = // Aktuellen Teilstring speichern
+#ifdef USE_EXTRACT
+            extract(packet,ptr,ptr+len-1);
+#else
+            packet[ptr..ptr+len-1]; 
+#endif
+    ptr+=len;// Neuen Pointer im String berechnen
+    m_ptr++; // Neuen Pointer im Mapping berechnen. Hier nutze ich eine
+             // Variable mehr als noetig, weil das billiger ist, als jedes
+             // mal ptr=m_ptr*len auszurechnen. Lieber zwei Additionen als 
+             // eine Multiplikation und eine Addtion.
+  }
+
+  // Wenn size/len aufgeht, ist das Result-Feld zu gross. Dann bleibt 
+  // ein Element leer, das wird hier gestrippt. Das ist billiger als
+  // jedesmal auszurechnen, ob size/len aufgeht.
+  return result-({0});
+ 
+}
+
+varargs string _send_udp(string mudname, mapping data, int expect_reply) {
+    mixed host_data;
+    string *packet_arr;
+    string packet;
+    int i;
+
+    mudname = lower_case(mudname);
+    if (!(host_data = hosts[mudname])) {
+        string *names;
+
+        if (sizeof(names = expand_mud_name(mudname)) == 1)
+            host_data = hosts[mudname = names[0]];
+        else
+#ifdef SEND_UDP_COMPAT
+            return (string)SEND_UDP_COMPAT(mudname, data, expect_reply);
+#else
+        if (!sizeof(names))
+            return "Unbekannter Mudname: " + capitalize(mudname) + "\n";
+        else
+            return break_string("Mudname ("+capitalize(mudname)+
+            ") nicht eindeutig, es passen: "+implode(map(names,#'capitalize),", ")+
+            ".\n",78);
+#endif
+    }
+    
+    if (data[REQUEST] != PING &&
+        data[REQUEST] != QUERY &&
+        data[REQUEST] != REPLY &&
+        member(host_data[HOST_COMMANDS], "*") == -1 &&
+        member(host_data[HOST_COMMANDS], data[REQUEST]) == -1)
+        return capitalize(data[REQUEST]) + ": Command unavailable @" +
+                   host_data[HOST_NAME] + "\n";
+    
+    data[NAME] = LOCAL_NAME;
+    data[UDP_PORT] = LOCAL_UDP_PORT;
+    
+    if (expect_reply) {
+        /* Don't use zero. */
+        data[ID] = ++packet_id;
+        /* Don't need deep_copy() as we are changing the mapping size. */
+        pending_data[mudname + ":" + packet_id] =
+#ifdef INETD_DIAGNOSTICS
+        data + ([ NAME: host_data[HOST_NAME], RESPONSE_TIME: time() ]);
+#else
+        data + ([ NAME: host_data[HOST_NAME] ]);
+#endif
+    }
+    if (!(packet = encode_packet(data))) {
+        if (expect_reply)
+            pending_data = m_copy_delete(pending_data, mudname + ":" + packet_id);
+        log_file(INETD_LOG_FILE, DATE + ": Illegal packet sent by " +
+        object_name(previous_object()) + "\n\n");
+        return "inetd: Illegal packet.\n";
+    }
+    if (expect_reply)
+        call_out("reply_time_out", REPLY_TIME_OUT, mudname + ":" + packet_id);
+
+    if (sizeof(packet) <= MAX_PACKET_LEN)
+        packet_arr = ({ packet });
+    else {
+        string header;
+        int max;
+
+        /* Be careful with the ID.  data[ID] could have been set up by RETRY */
+        header =
+            PACKET + ":" + lower_case(LOCAL_NAME) + ":" +
+            ((expect_reply || data[REQUEST] != REPLY)&& data[ID] ?
+            data[ID] : ++packet_id) + ":";
+        
+        /* Allow 8 extra chars: 3 digits + "/" + 3 digits + DELIMITER */
+        packet_arr = explode_packet(packet,
+            MAX_PACKET_LEN - (sizeof(header) + 8));
+
+        for(i = max = sizeof(packet_arr); i--; )
+            packet_arr[i] =
+            header + (i+1) + "/" + max + DELIMITER + packet_arr[i];
+    }
+    
+    for(i = sizeof(packet_arr); i--; )
+    {
+      ZDEBUG(sprintf("%O <- %.500O\n",host_data[HOST_IP], packet_arr[i]));
+      if (!send_udp(
+        host_data[HOST_IP], host_data[HOST_UDP_PORT], packet_arr[i]))
+            return "inetd: Error in sending packet.\n";
+    }
+    return 0;
+}
+
+void reply_time_out(string id) {
+    mapping data;
+
+    if (data = pending_data[id]) {
+        object ob;
+
+#ifdef INETD_DIAGNOSTICS
+        data[PACKET_LOSS]++;
+#endif
+        if (data[RETRY] < RETRIES) {
+            mapping send;
+
+            data[RETRY]++;
+            /* We must use a copy so the NAME field in pending_data[id]
+             * isn't corrupted by _send_udp(). */
+            send = deep_copy(data);
+            send = m_copy_delete(send, RETRY);
+#ifdef INETD_DIAGNOSTICS
+            send = m_copy_delete(send, PACKET_LOSS);
+            send = m_copy_delete(send, RESPONSE_TIME);
+#endif
+            call_out("reply_time_out", REPLY_TIME_OUT, id);
+            _send_udp(data[NAME], send);
+            return;
+        }
+        data = m_copy_delete(data, RETRY);
+#ifdef INETD_DIAGNOSTICS
+        data = m_copy_delete(data, RESPONSE_TIME);
+#endif
+        catch(call_other(UDP_CMD_DIR + REPLY, "udp_" + REPLY,
+        add_system_field(data, TIME_OUT));publish);
+        /* It's just possible this was removed from the host list. */
+        if (hosts[lower_case(data[NAME])])
+            hosts[lower_case(data[NAME])][HOST_STATUS] = DOWN;
+        remove_incoming(lower_case(data[NAME]) + ":" + id);
+    }
+    pending_data = m_copy_delete(pending_data, id);
+}
+
+void remove_received_id(string id) {
+    received_ids -= ({ id });
+}
+
+varargs mixed query(string what, mixed extra1, mixed extra2) {
+    mixed data;
+
+    switch(what) {
+        case "commands":
+            return COMMANDS;
+        case "hosts":
+            return deep_copy(hosts);
+        case "pending":
+            return deep_copy(pending_data);
+        case "incoming":
+            return deep_copy(incoming_packets);
+        case "received":
+            return ({ }) + received_ids;
+        /* args: "valid_request", request, mudname */
+        case "valid_request":
+            if (data = hosts[extra2])
+                return member(data[HOST_COMMANDS], "*") != -1 ||
+                        member(data[HOST_COMMANDS], extra1) != -1;
+            return 0;
+    }
+    return(0);
+}
+
diff --git a/secure/krautmaster.c b/secure/krautmaster.c
new file mode 100644
index 0000000..0d0ab06
--- /dev/null
+++ b/secure/krautmaster.c
@@ -0,0 +1,1289 @@
+#pragma strong_types, save_types, rtt_checks
+#pragma no_inherit, no_clone, no_shadow
+
+#include <defines.h>
+#include <properties.h>
+#include <wizlevels.h>
+#include <items/kraeuter/kraeuter.h>
+#include <items/kraeuter/trankattribute.h>
+#include <living/comm.h>
+
+
+#ifndef BS
+#define BS(x)             break_string(x, 78)
+#endif
+
+// Weiter unten beim Parsen des Datenfiles werden SetProp() und Name()
+// verwendet, daher erben wir thing.
+inherit "/std/secure_thing";
+
+// Liste aller moeglichen Zutaten, gemappt ueber einen key
+// AN: irritierender Name, denn es sind nicht direkt die Zutaten enthalten
+// sondern die ueber einen Key identifizierten Kraeutergruppen, die die
+// Wirkungen/Funktionen festlegen.
+// Format:
+// ([ key : ({ attr_pos, attr_neg1, attr_neg2, Haltbark., Wirkdauer, 
+//             Unterstuetzung, Stoerung, Haeufigkeit  }) ])
+// Beispiel: [( "a7":({({2,400}),({11,150}),0,172800,0,({"n*"}),0,1}) ])
+// HU: War ein Mapping, bleibt ein Mapping, und neu: Die Eintraege werden 
+// auch Mappings!
+private mapping ingredients;
+
+// Mapping von id zum Key der Zutaten im ingredients-mapping: id2key[id]=key
+// Die beiden Listen werden benoetigt, um zu den Kraut-IDs die jeweiligen
+// Keys zu finden.
+// HU: KEIN MAPPING. Ein Array von Strings. Aber das geht natuerlich.
+private string *id2key;
+
+// Mapping von key zu Kraeuter-ID der Zutaten.
+private mapping key2id;
+
+// Eigenschaften der Kraeuter:
+// krautprops[id]=({ID, Demon, Gender, Name, Adjektiv(e), P_LONG, Raumdetail})
+// krautprops[0] = 0; ID 0 darf nicht existieren
+private mixed *krautprops;
+
+// hier wird gespeichert welche Version einer Zutat fuer einen Spieler ist.
+// AN/TODO: Wenn das fuer Nichtseher nutzbar sein soll, sollte hier besser
+// mit getuuid() gearbeitet werden, statt getuid() zu verwenden.
+// player enthaelt fuer jede Spieler-UID einen Bitstring. Gesetztes Bit
+// bedeutet, dass der Spieler ueber das Kraut mit dieser ID etwas ueber
+// die Verwendung und Wirkung weiss. Es gibt jedoch noch keine Lernmoeglich-
+// keit und INSBESONDERE keine Funktionalitaet im Master, ueber die man 
+// die Bits setzen koennte.
+private mapping player;
+
+// verstuemmeltes mapping fuer den VC in service (Save-Dummy)
+private mapping map_ldfied;
+
+// rooms enthaelt die Teileintraege des map_ldfied mapping vom VC
+private mapping rooms;
+
+// Enthaelt Daten zu den Raeumen, in denen das Trocknen von Kraeutern
+// moeglich ist.
+private mapping drying_data = ([:4]);
+
+string build_plantname(mixed *props);
+
+// struct-Templat fuer Trankattribute
+// Fuer das SQL-Log MUSS die Reihenfolge der Trankattribute hier genau die
+// sein, wie die Spalten in der Tabelle.
+/* Currently not used.
+struct trank_attrib_s {
+    int car;
+    int da;
+    int dm;
+    int du;
+    int dn;
+    int flt;
+    int fro;
+    int hI;
+    int hP;
+    int hK;
+    int hL;
+    int pa;
+    int pm;
+    int pu;
+    int ss;
+    int sp;
+    int sd;
+};
+*/
+
+#define allowed() (!process_call() && \
+        IS_ARCH(RPL) && IS_ARCH(this_interactive()) )
+
+#define POTIONFILES ({ TRANKITEM })
+
+// eigenschaften im krautprops-array
+#define PROP_ID           INGREDIENT_ID
+#define PROP_DEMON        INGREDIENT_DEMON
+#define PROP_GENDER       INGREDIENT_GENDER
+#define PROP_NAME         INGREDIENT_NAME
+#define PROP_ADJ          INGREDIENT_ADJ
+#define PROP_LONG         INGREDIENT_LONG
+#define PROP_ROOMDETAIL   INGREDIENT_ROOMDETAIL
+
+// ATTR_ is immer ein array ({ attribut, ability })
+#define ATTR_ARR_ATTRIB      0
+#define ATTR_ARR_ABILITY     1
+
+// maximal erlaubter Wert fuer eine Eigenschaft
+#define MAX_ATTR_ABILITY      999
+
+void debug(string str)
+{
+  //write("debug: "+str+"\n");
+  if (this_player() && IS_ARCH(this_player()))
+    this_player()->ReceiveMsg(str,536870912);
+}
+
+protected void create()
+{
+   seteuid(getuid());
+   // AN/TODO: Variablen oben direkt initialisieren. Pruefen, ob davon
+   // irgendwas nosave sein kann.
+   if (!restore_object(__DIR__"/ARCH/krautmaster"))
+   {
+      ingredients=([]);
+      id2key=({});
+      key2id=([]);
+      krautprops=({});
+      player=([]);
+      map_ldfied=([]);
+      rooms=([]);
+   }
+   if (sl_open("/log/ARCH/krauttrank.sqlite") != 1)
+   {
+     raise_error("Datenbank konnte nicht geoeffnet werden.\n");
+   }
+   sl_exec("CREATE TABLE IF NOT EXISTS traenke(id INTEGER PRIMARY KEY, "
+           "uid TEXT NOT NULL, rnd INTEGER, "
+           "time INTEGER DEFAULT CURRENT_TIMESTAMP, "
+           "receipe TEXT NOT NULL, "
+           "quality TEXT NOT NULL, "
+           "car INTEGER, "
+           "da INTEGER, "
+           "dm INTEGER, "
+           "du INTEGER, "
+           "dn INTEGER, "
+           "flt INTEGER, "
+           "fro INTEGER, "
+           "hI INTEGER, "
+           "hP INTEGER, "
+           "hK INTEGER, "
+           "hL INTEGER, "
+           "pa INTEGER, "
+           "pm INTEGER, "
+           "pu INTEGER, "
+           "ss INTEGER, "
+           "sp INTEGER, "
+           "sd INTEGER);"
+           );
+   sl_exec("CREATE INDEX IF NOT EXISTS idx_uid ON traenke(uid);");
+   sl_exec("CREATE INDEX IF NOT EXISTS idx_receipe ON traenke(receipe);");
+   sl_exec("CREATE TABLE IF NOT EXISTS rohdaten(id INTEGER PRIMARY KEY, "
+           "uid TEXT NOT NULL, rnd INTEGER, "
+           "time INTEGER DEFAULT CURRENT_TIMESTAMP, "
+           "receipe TEXT NOT NULL, "
+           "quality TEXT NOT NULL, "
+           "car INTEGER, "
+           "da INTEGER, "
+           "dm INTEGER, "
+           "du INTEGER, "
+           "dn INTEGER, "
+           "flt INTEGER, "
+           "fro INTEGER, "
+           "hI INTEGER, "
+           "hP INTEGER, "
+           "hK INTEGER, "
+           "hL INTEGER, "
+           "pa INTEGER, "
+           "pm INTEGER, "
+           "pu INTEGER, "
+           "ss INTEGER, "
+           "sp INTEGER, "
+           "sd INTEGER);"
+           );
+   sl_exec("CREATE INDEX IF NOT EXISTS idx_uid_r ON rohdaten(uid);");
+}
+
+public string QueryPlantFile(int id)
+{
+  if (member(krautprops, id))
+    return build_plantname(krautprops[id]);
+  return 0;
+}
+
+// AN: Funktion ermittelt, ob ein Spieler pl das Kraut mit der ID id
+// verwenden kann. Laut Kommentar unten muss man dafuer wohl was ueber das
+// Kraut gelernt haben, von wem ist mir gerade noch nicht klar.
+// AN/TODO: Es ist bisher keine Funktionalitaet vorhanden, um die IDs fuer
+// den Spieler freizuschalten. Die Funktionsweise muss aus dem Beispielcode
+// unten fuer das Learn-On-Demand abgelesen werden.
+nomask int CanUseIngredient(object pl, int id)
+{
+  // Ich mach mal den harten Weg. -- Humni
+  return 1;
+  // Bitstring des Spielers aus der Liste auslesen.
+   string str=player[getuid(pl)];
+   if (!stringp(str)) str="";
+   if (test_bit(str, id))
+      return 1; // make the common case fast
+   else {
+     // letztenendes habe ich mich entschlossen einzubauen, das man nur die
+     // Kraeuter verwenden kann, ueber die man schonmal etwas gelesen/gelernt
+     // hat, aus diesem Grund, ist folgender Block auskommentiert.
+     // Dieser Block bedeutet quasi ein "auto learning on demand" d.h.
+     // wird ein Kraut verwendet wird geprueft ob fuer diese Gruppe bereits
+     // eine ID freigeschaltet wurde - ansonsten wird eine freigeschaltet.
+     /*
+         // pruefen ob fuer den Key bereits ein Bit gesetzt ist, ggf. setzen
+         if (id>=sizeof(id2key)) return 0; // illegale Id
+         int idlist=key2id[id2key[id]];
+         int i;
+         for (i=sizeof(idlist)-1; i>=0; i--) {
+           if (test_bit(str, idlist[i])) return 0; // Key bereits vorhanden
+         }
+         // Zufaellig ein Bit fuer den Key setzen
+         i=random(sizeof(idlist));
+         player[getuid(pl)]=set_bit(str, idlist[i]);
+         save_object(object_name());
+         return (i==id);
+     */
+     return 0;
+   }
+}
+
+// Diese Funktion wird vom Metzelorakel aufgerufen, um die Belohnungen zu
+// erzeugen, die man dort fuer erfolgreich absolvierte Metzelauftraege erhaelt
+#define ALLOWED_CALLER ({ "/d/ebene/arathorn/orakel/secure/schamane" })
+object get_plant_by_category(int npc_class)
+{
+  if ( member(ALLOWED_CALLER, load_name(previous_object()))<0 )
+    raise_error("unauthorised call to get_plant_by_category()\n");
+
+  // Uebergebene NPC-Klasse in Kraut-Kategorie umsetzen.
+  // Kategorie 7 wird als 7-9 interpretiert (siehe unten).
+  int category;
+  switch(npc_class) {
+    case 1: case 2:         category=4; break;
+    case 3: case 4:         category=5; break;
+    case 5: case 6: case 7: category=6; break;
+    default:                category=7; break;
+  }
+
+  // Alle Kraeuter der ermittelten Kategorie raussuchen. Bei Kategorie 7
+  // werden auch alle aus 8 und 9 dazugenommen.
+  int *eligible_plant_ids=({});
+  foreach( string grp, mapping grpdata : ingredients ) {
+    if ( category == 7 && grpdata[T_ABUNDANCE]>=7 || 
+          category == grpdata[T_ABUNDANCE] )
+      eligible_plant_ids += key2id[grp];
+  }
+
+  // Krautnamen zu den Kraut-IDs ermitteln.
+  string *plantfiles=map(eligible_plant_ids, function string (int plantid) {
+    return build_plantname(krautprops[plantid]);});
+
+  // Ein Kraut zufaellig auswaehlen, clonen und das Objekt zurueckgeben.
+  object plant=clone_object(PLANTDIR+plantfiles[random(sizeof(plantfiles))]);
+  plant->DryPlant(80+random(11));
+  // Aufschreiben, wer welches Kraut mit welcher Qualitaet rausbekommt.
+  log_file("ARCH/plant_by_category",
+    sprintf("%s %-12s %-s Qual %d Kat %d Class %d\n", 
+      strftime("%x %X",time()), getuid(PL),
+      object_name(plant)[sizeof(PLANTDIR)..],
+      plant->QueryProp(P_QUALITY), category, npc_class));
+// sprintf("%24s: call from %O, player: %s (PL: %O), kategory: %d\n", ctime(),
+// previous_object(), getuid(player), PL, kategory))
+  return plant;
+}
+
+private nosave object simul_efun, plantvc;
+
+// fuer SIMUL_EFUN_FILE
+#include <config.h>
+
+// AN/TODO: Klaeren, warum hier eine eigene Funktion get_cloner() existiert,
+// wo doch der Kraeuter-VC schon eine hat.
+nomask private string get_cloner()
+{
+   int i;
+   object po;
+   if (!simul_efun) {
+      if (!(simul_efun=find_object(SIMUL_EFUN_FILE)))
+         simul_efun=find_object(SPARE_SIMUL_EFUN_FILE);
+   }
+   // wenn sie jetzt nicht existiert - auch gut, dann gibt es halt keine
+   // sefuns.
+
+   if (!plantvc) plantvc=find_object(KRAEUTERVC);
+   
+   for (i=0; po=previous_object(i); i++) {
+      if (po==master() || po==simul_efun || po==ME ||
+          po==previous_object() || po==plantvc)
+         continue;
+      else return object_name(po);
+   }
+   return 0;
+}
+
+// AN: 
+nomask string CheckPlant(int id)
+{
+   if (id<=0 || id>=sizeof(id2key)) return 0;
+   if (!stringp(id2key[id])) return 0;
+   return get_cloner();
+}
+
+// ueber diese Funktion kann die Liste der Id's updatet werden
+// es wird <filename> eingelesen und durchgeparst.
+// Diese Datei muss in jeder Zeile folgendes Format einhalten und darf keine
+// leerzeilen enthalten! > Id,key,Gender,Name,Adjektiv
+// AN: Das ist Quatsch. Das Format muss so aussehen:
+// String-ID;Kraut-ID;demon;gender;P_NAME;adjektiv;P_LONG;roomdetail;
+// HU: Diese Funktion lass ich so. Harhar.
+// Update: Na gut. Fast.
+nomask private int LoadIdList(string filename)
+{
+   int i, id, si, demon;
+   string *lines, file;
+   mixed *data;
+   file=read_file(filename);
+   if (!stringp(file)) raise_error("Missing File: "+filename+"\n");
+   lines=explode(read_file(filename), "\n");
+   si=sizeof(lines)-1;
+   // AN/TODO: Warum verwendet man hier nicht auch einfach explode()?
+   // Wenn "##ende##" als Trennzeichen enthalten ist, liefert explode()
+   // als letzten Eintrag einen Leerstring, darauf kann man pruefen.
+   // Allerdings muesste das vor dem explode() zur Zeilentrennung passieren.
+   for ( ;si>=0; si--) 
+   {
+     string lili=lines[si];
+     if (strstr(lines[si],"##ende##")>=0) break;
+   }
+   si--;
+   if (si<0) raise_error("##ende## not found in id-list.\n");
+   id2key=allocate(si+2);
+   krautprops=allocate(si+2);
+   // AN: Fuer das Auslesen der Werte aus dem Array "data" muessen die 
+   // Indizierungs-Defines jeweils +1 verwendet werden, weil im Datenfile
+   // schlauerweise ein Datenfeld *vorne* angefuegt wurde.
+   // AN/TODO: vielleicht sollte man besser Element 0 zuerst auslesen und
+   // dann das Array arr um ein Element kuerzen: arr = arr[1..];
+   for (i=si; i>=0; i--) {
+      data=explode(lines[i], ";");
+      if (sizeof(data)!=8) raise_error("Wrong id-list format in line "+(i+1)+"\n");
+      id=to_int(data[PROP_ID+1]);
+      data[PROP_ID+1]=id;
+      if (id>si+1) raise_error(sprintf("Line %d: id %d greater than array size %d.\n", i, id, si));
+      id2key[id]=data[0];
+      // AN: Ich sehe noch nicht so ganz, warum man von dem Datenfeld
+      // PROP_DEMON nur das letzte Zeichen als Literal pruefen sollte.
+      // Komplett geht doch genausogut?
+      switch(data[PROP_DEMON+1][<1]) {
+        case 'R': data[PROP_DEMON+1]=RAW; break;
+        case '0': data[PROP_DEMON+1]=0;   break;
+        case '1': data[PROP_DEMON+1]=1;   break;
+        case '2': data[PROP_DEMON+1]=2;   break;
+        default: raise_error("Unknown demonformat '"+data[PROP_DEMON+1]+
+                   "' in idlist line "+(i+1)+"\n");
+      }
+      switch(data[PROP_GENDER+1][<1]) {
+        case 'N': data[PROP_GENDER+1]=NEUTER; break;
+        case 'F': data[PROP_GENDER+1]=FEMALE; break;
+        case 'M': data[PROP_GENDER+1]=MALE;   break;
+        default: raise_error("Unknown genderformat '"+data[PROP_GENDER+1]+
+                   "' in idlist line "+(i+1)+"\n");
+      }
+      SetProp(P_GENDER, data[PROP_GENDER+1]);
+      SetProp(P_NAME, data[PROP_NAME+1]);
+      // AN/TODO: data[PROP_ADJ] muss man nicht unbedingt vorher auf 0 setzen
+      if (!sizeof(data[PROP_ADJ+1])) data[PROP_ADJ+1]=0;
+      if (data[PROP_ADJ+1])
+         SetProp(P_NAME_ADJ, ({"ganz normal", data[PROP_ADJ+1]}));
+      else SetProp(P_NAME_ADJ, "ganz normal");
+      SetProp(P_ARTICLE, data[PROP_DEMON+1]!=RAW);
+      demon=(data[PROP_DEMON+1]==RAW ? 0 : data[PROP_DEMON+1]);
+      // AN: Wenn keine Langbeschreibung hinterlegt wurde, wird der Name
+      // des Krautes als solche verwendet. Ebenso fuer das Raumdetail, das
+      // beim Betrachten des Krautes im Raum ausgegeben wird.
+      if (!sizeof(data[PROP_LONG+1])) {
+         data[PROP_LONG+1] = Name(WER, demon)+".\n";
+      }
+      else data[PROP_LONG+1] = BS(data[PROP_LONG+1]);
+// Humni: Offenbar kommen am Zeilenende manchmal Zeichen dazu. Ich gehe davon
+// aus, dass keine Beschreibung kuerzer als 2 Zeichen ist.
+      if (sizeof(data[PROP_ROOMDETAIL+1])<2) {
+         data[PROP_ROOMDETAIL+1] = Name(WER, demon)+".\n";
+      }
+      else data[PROP_ROOMDETAIL+1] = BS(data[PROP_ROOMDETAIL+1]);
+      krautprops[id]=data[1..];
+   }
+   SetProp(P_NAME, 0);
+   SetProp(P_NAME_ADJ, "");
+   
+   // key2id-Cache neu aufbauen.
+   key2id=([]);
+   for (i=sizeof(id2key)-1; i>=0; i--) {
+      if (member(key2id, id2key[i]))
+         key2id[id2key[i]]+=({ i });
+      else key2id[id2key[i]]=({ i });
+   }
+   return 1;
+}
+
+// Hilfsfunktion wird zum einparsen benötigt
+// wandelt z.B. den string von "h 500" in ({ 3, 500 }) um
+private nomask <string|int>* buildAttrArr(string attr)
+{
+   if (!attr) return 0;
+   attr=trim(attr);
+   <string|int>* res=explode(attr, " ")-({""});
+   //debug(sprintf(" build Attr %O",res));
+   if (sizeof(res)!=2) raise_error("Wrong attrib format!\n");
+   //debug(sprintf("%O",T_KRAUT_MAP));
+   return ({T_KRAUT_MAP[res[0]],(int)res[1]});
+}
+
+// AN: Hilfsfunktion zum Einlesen der Traenkeliste.
+nomask private void LoadIndex(string filename)
+{
+   int i, j, si;
+   string *lines, file;
+   mixed  *data;
+
+   file=read_file(filename);
+   if (!stringp(file)) raise_error("Missing File: "+filename+"\n");
+   
+   // AN/TODO: Auch hier waere wieder die Verwendung von explode() zu
+   // erwaegen.
+   lines=explode(file, "\n")-({""});
+   si=sizeof(lines);
+   for (i=0; i<si; i++) if (lines[i]=="##start##") break;
+   i++;
+   if (i>=si) raise_error("missing ##start## in Index.\n");
+   ingredients=([]);
+   for (; i<si; i++) { // alle zeilen durchlaufen...
+     // AN/TODO: Tabulatoren als Trennzeichen finde ich irgendwie bloed.
+     // HU: Darum nun Semikolons
+     // debug("Zeile "+lines[i]);
+     data=old_explode(lines[i], ";"); // an Semikolons trennen...
+     // debug(sprintf("%O",data));
+     if (sizeof(data)!=9) 
+       {
+         //debug(sprintf("%O",data));
+         raise_error("Wrong indexlist format in line "+(i+1)+"\n");
+       }
+     for (j=8; j>=0; j--) {
+       // AN/TODO: if(data[j]=="" || data[j]=="-") data[j]=0;
+       // es sind ohnehin ueberall Strings enthalten.
+       // Wir machen aus "-" oder "0" eine echte int-Null.
+       if (sizeof(data[j])==0 || data[j][0]=='-') data[j]=0;
+     }
+     // HU: Ab hier bau ich mal neu. Den Rest pack ich auskommentiert darunter, wenn jemand den alten Code
+     // nachschauen will.
+     // Ich will ein Mapping von dieser Kraeutergruppe.
+     mapping mk=([]);
+     // Dieses Mapping soll die Eintraege nun enthalten.
+     mk[T_EFFECT_DURATION]=to_int(data[5]);
+     mk[T_ABUNDANCE]=to_int(data[8]);
+     // positive Effekte aufteilen
+     //debug(sprintf("Vorposis %O - %O",mk,data));
+     if (stringp(data[1]))
+       {
+         string* posis=explode(data[1],",");
+         //debug(sprintf("%O - %O",mk,posis));
+         foreach (string q:posis) {
+           //debug(q);
+           <string|int>* arr=buildAttrArr(q);
+           //debug(sprintf("%O",arr));
+           mk[arr[0]]=mk[arr[0]]+arr[1];
+         }
+       }
+     //debug(sprintf("%O",mk));
+     // Erster Negativer Effekt
+     if (data[2]!=0)
+       {
+         <string|int>* arr=buildAttrArr(data[2]);
+         mk[arr[0]]=mk[arr[0]]-arr[1];
+       }
+     //debug(sprintf("vorneg %O",mk));
+     // Zeiter negativer Effekt
+     if (data[3]!=0)
+       {
+         <string|int>* arr=buildAttrArr(data[3]);
+         mk[arr[0]]=mk[arr[0]]-arr[1];
+       }
+     // Haltbarkeit wird umgerechnet
+     string* sti=explode(data[4]," ")-({""});
+     //debug("haltbar "+sti[0]);
+     string stt=trim(sti[0]);
+     int dur;
+     if (stt[<1..]=="d") // Tage
+       {
+         //debug("Tage");
+         // Der erste Teil ist die Dauer in Tagen.
+         dur=to_int(sti[0][..<2]);
+         dur=dur*24*60*60; // Sekunden
+         mk[T_EXPIRE]=dur;
+       }
+     if (stt[<1..]=="h") // Stunden
+       {
+         //debug("Stunden");
+         // Sonst ist es halt die Dauer in Stunden.
+         dur=to_int(sti[0][..<2]);
+         dur=dur*60*60;
+         mk[T_EXPIRE]=dur;
+       }
+     //debug("ergibt "+dur);
+     // Nun die lustigen Unterstuetzungen. Dazu benutzen wir unseren lustigen Glueckshasen.
+     // Und ein Reh.
+     string* glueckshase;
+     string reh;
+     // Alle Leerzeichen raus!
+     //debug("Rehe");
+     //debug(sprintf("Data: %O",data));
+     if (stringp(data[6]))
+       {
+         reh=(explode(data[6]," ")-({""}))[0];
+     
+         glueckshase=explode(reh,",")-({});
+         mk[T_SUPPORTER]=glueckshase;
+       }
+     else
+       {
+         mk[T_SUPPORTER]=0;
+       }
+     // Nun machen wir genauso die Blockaden.
+     // Das tolle ist: Reh und Glueckshase koennen wir wiederverwenden! Das freut.
+     if (stringp(data[7]))
+       {
+         reh=(explode(data[7]," ")-({""}))[0];
+         glueckshase=explode(reh,",")-({});
+         mk[T_BLOCKING]=glueckshase;
+       }
+     else
+       {
+         mk[T_BLOCKING]=0;
+       }
+     ingredients[trim(data[0])]=mk;
+   }
+   //debug("Wuff");
+   //debug(sprintf("%O",ingredients));
+}
+
+nomask private void save()
+{
+  save_object(__DIR__"/ARCH/krautmaster");
+}
+
+// AN: erzeugt aus Namen + Adjektiv der Pflanzendaten den Objektnamen,
+// z.B. waldrebe_gemein oder ackerklee_gelb, wobei Bindestriche auch
+// durch Unterstriche ersetzt werden (acker_rettich).
+string build_plantname(mixed *props)
+{
+   string key;
+   // AN/TODO: erst PROP_NAME in key schreiben, dann ggf. PROP_ADJ dazu
+   if (sizeof(props[PROP_ADJ])>0)
+      key=lowerstring(props[PROP_NAME]+"_"+props[PROP_ADJ]);
+   else key=lowerstring(props[PROP_NAME]);
+   // AN/TODO: ersetzen durch regreplace();
+   key=implode(old_explode(key, " "), "_");
+   key=implode(old_explode(key, "-"), "_");
+   return key;
+}
+
+public void UpdateVC()
+{
+  KRAEUTERVC->update(map_ldfied);
+}
+
+// AN: Daten neu parsen
+// Nach dem Schreiben des Savefiles mittels save() wird auch das
+// Kraeuter-Headerfile vom Kraeuter-VC neu geschrieben.
+int _refresh()
+{
+   int i;
+   string key;
+   if (extern_call() && !allowed())
+     return 0;
+   
+   LoadIdList(__DIR__"ARCH/kraeuterliste.dump");
+   LoadIndex(__DIR__"ARCH/kraeuterindex.dump");
+   map_ldfied=([]);
+   for (i=sizeof(krautprops)-1; i>=0; i--)
+   {
+      if (sizeof(krautprops[i])<=PROP_ROOMDETAIL) continue;
+      key = build_plantname(krautprops[i]);
+      map_ldfied[key]=({ krautprops[i], rooms[key]||([]) });
+   }
+   save();
+   UpdateVC();
+   
+   // Update Headerfile mit Kraeuterliste
+   string *keys = sort_array(m_indices(map_ldfied), #'<);
+   string headerfile =
+     "// Automatisch generiertes File, nicht von Hand editieren!\n"
+     "// Erzeugendes File: "+object_name()+"\n\n"
+     "#define PLANTCOUNT "+to_string(sizeof(keys))+"\n\n"
+     +"#define PLANT(x) \"/items/kraeuter/\"+x\n\n";
+   foreach(key: keys)
+   {
+     headerfile += sprintf("#define %-30s PLANT(\"%s\")\n",
+                     upperstring(key), key);
+   }
+   write_file(KRAEUTERLISTE, headerfile, 1);
+
+   write("Inputfiles parsed. Save & Headerfiles updated!\n");
+   return 1;
+}
+
+int _cloneplant(string str)
+{
+  if (allowed())
+  {
+    if (to_string(to_int(str)) == str)
+      str = QueryPlantFile(to_int(str));
+    clone_object(PLANTDIR+str)->move(this_player(), M_NOCHECK);
+    write("Kraut " + str + " geclont.\n");
+    return 1;
+  }
+  return 0;
+}
+
+#define MAX_ROOMS 10  /* kein Kraut ist in mehr als 10 Raeumen */
+// AN: Ausgabe der Kategorienliste ueber das Planttool.
+int _showplant(string str)
+{
+   int i, si, kat, secure;
+   string *list, key;
+   mixed *res, *arr;
+   mapping props;
+
+   secure=allowed();
+   notify_fail("Es gibt folgende Kraeuterkategorien:\n"
+    +" 0 - haeufig und an vielen Stellen im Mud anzufinden\n"
+    +" 1 - etwas seltener zu finden, aber immer noch leicht\n"
+    +" 2 - an wenigen gut versteckten Stellen in abgelegenen Gebieten\n"
+    +" 3 - dito, jedoch muss das Kraut durch einen NPC (XP >= 500000) bewacht sein.\n"
+    +" 4 - aeusserst selten und XP >= 1 mio\n"
+    +" 5 - aeusserst selten und XP >= 2 mio\n"
+    +" 6 - aeusserst selten und NPC bringt >= 5  Stupse\n"
+    +" 7 - aeusserst selten und NPC bringt >= 10 Stupse\n"
+    +" 8 - aeusserst selten und NPC bringt >= 20 Stupse\n"
+    +" 9 - aeusserst selten und NPC bringt >= 50 Stupse\n"
+    +"\nSyntax: showplant <kategorie>.\n");
+   kat=to_int(str);
+   if (kat<0 || kat>9) return 0;
+   if (to_string(kat)!=str) return 0;
+   list=m_indices(map_ldfied);
+   // AN: *grummel*
+   // res = allocate(MAX_ROOMS, ({}));
+   res=map(allocate(MAX_ROOMS), #'allocate); // ({ ({}) ... ({}) })
+   for (i=sizeof(list)-1; i>=0; i--) {
+      arr=map_ldfied[list[i]];
+      if (sizeof(arr)!=2) raise_error("Wrong map_ldfied-Format by "+list[i]+"\n");
+      key=id2key[arr[0][PROP_ID]];
+      if (!key) raise_error("Missing Key for id "+arr[0][PROP_ID]+"\n");
+      props=ingredients[key];
+      //if (!pointerp(props)) continue; // noch nicht eingetragen
+      //if (sizeof(props)!=8)
+      // printf("Warning: Wrong ingredient-content by "+key+"\n");
+      //debug(sprintf("%O",props));
+      if (props==0)
+      {
+        debug("Falscher Key: "+key);
+      }
+      else
+      {
+        if (props[T_ABUNDANCE]==kat)
+        {
+          si=sizeof(arr[1]);
+          if (si<MAX_ROOMS) {
+            if (stringp(arr[0][PROP_ADJ])) {
+              SetProp(P_ARTICLE, 0);
+              SetProp(P_NAME, arr[0][PROP_NAME]);
+              SetProp(P_NAME_ADJ, arr[0][PROP_ADJ]);
+              SetProp(P_GENDER, arr[0][PROP_GENDER]);
+              key=Name(WER);
+            }
+            else key=arr[0][PROP_NAME];
+            if (secure)
+              res[si]+=({ sprintf("%3d %-40s: %d\n", arr[0][PROP_ID], key, si) });
+            else res[si]+=({ sprintf("%-40s: %d\n", key, si) });
+          }
+        }
+      }
+   }
+   for (i=0; i<MAX_ROOMS; i++) {
+      sort_array(res[i], #'>);
+      filter(res[i], #'write);
+   }
+   return 1;
+}
+
+// AN: Ausgabe der Raeume, in denen die Kraeuter zu finden sind.
+// angesteuert ueber das Planttool.
+int _showrooms(string str)
+{
+   int i, j, id;
+   string *list, dummy;
+   mixed *arr;
+   if (!allowed()) return 0;
+   notify_fail("Syntax: showrooms <id> oder showrooms all\n");
+   if (str!="all") {
+     id=to_int(str);
+     if (to_string(id)!=str) return 0;
+   }
+   else id=-1;
+   list=m_indices(map_ldfied);
+   for (i=sizeof(list)-1; i>=0; i--) {
+      arr=map_ldfied[list[i]];
+      if (sizeof(arr)!=2) raise_error("Wrong map_ldfied-Format by "+list[i]+"\n");
+      if (arr[0][PROP_ID]==id || id<0) {
+         if (!sizeof(m_indices(arr[1]))) {
+            if (id>=0) write("Fuer diese Id sind keine Raeume eingetragen.\n");
+         }
+         else if (id>=0) {
+            write("Folgende Raeume sind fuer "+arr[0][PROP_ID]+" eingetragen.\n");
+            filter(map(m_indices(arr[1]), #'+, "\n"), #'write);
+            return 1;
+         }
+         else filter(map(m_indices(arr[1]), #'+, ": "+arr[0][PROP_ID]+", "+arr[0][PROP_NAME]+"\n"), #'write);
+         if (id>=0) return 1;
+      }
+   }
+   write("Fuer diese Id sind bisher keine Kraeuter eingetragen.\n");
+   return 1;
+}
+
+// Nutzung der Kraeuter in  Gebieten liefert nur dann gueltige Kraeuter,
+// wenn der Raum eingetragen ist.
+int _addroom(string str)
+{
+   int id, i;
+   string *list, vc;
+
+   if (!allowed()) {
+      write("Fuer das Eintragen der Raeume wende Dich doch bitte "
+            "an einen EM.\n");
+      return 1;
+   }
+   notify_fail("Syntax: addroom <krautnummer> <filename>\n");
+   str=PL->_unparsed_args();
+   if (!str || sscanf(str, "%d %s", id, str)!=2) return 0;
+   if (str=="hier" || str=="here")
+   {
+     if (!this_player())
+     {
+       notify_fail("Kein Spielerobjekt, kann "
+           "Raum nicht identifizieren.\n");
+       return 0;
+     }
+     str=to_string(environment(this_player()));
+   }
+   if (str[<2..]==".c") str=str[0..<3]; // .c abschneiden
+   if (file_size(str+".c")<=0) {
+      list=explode(str, "/");
+      vc=implode(list[0..<2], "/")+"/virtual_compiler.c";
+      if (file_size(vc)<=0 || !call_other(vc, "Validate", list[<1])) {
+         write("No such file \""+str+"\".\n");
+         return 1;
+      }
+   }
+   if (id<=0 || id>=sizeof(id2key)) {
+      write("Illegal plantid "+id+".\n");
+      return 1;
+   }
+   list=m_indices(map_ldfied);
+   for (i=sizeof(list)-1; i>=0; i--) {
+      if (map_ldfied[list[i]][0][PROP_ID]==id) {
+         if (!member(map_ldfied[list[i]][1], str)) {
+            map_ldfied[list[i]][1]+=([ str ]);
+            rooms[list[i]]=(rooms[list[i]]||([]))+([ str ]);
+            write("Raum Erfolgreich eingetragen!\n");
+         }
+         else write("Raum bereits eingetragen.\n");
+         save();
+         _refresh();
+         return 1;
+      }
+   }
+   write("Kraut mit id "+id+" nicht gefunden.\n");
+   return 1;
+}
+
+int _delroom(string str)
+{
+   int i, done;
+   string *list;
+
+   if (!allowed()) {
+      write("Fuer das Loeschen von Raeumen wende Dich doch bitte "
+            "an einen EM.\n");
+      return 1;
+   }
+   notify_fail("Syntax: delroom <filename>.\n");
+   str=PL->_unparsed_args();
+   if (!str) return 0;
+   if (str[<2..]==".c") str=str[0..<3];
+   list=m_indices(map_ldfied); done=0;
+   for (i=sizeof(list)-1; i>=0; i--)
+   {
+      if (member(map_ldfied[list[i]][1], str)) {
+         m_delete(map_ldfied[list[i]][1], str);
+         m_delete(rooms[list[i]], str);
+         write("Raum bei id "+map_ldfied[list[i]][0][PROP_ID]
+               +" ausgetragen.\n");
+         done=1;
+      }
+   }
+   if (!done) {
+      if (file_size(str+".c")<0)
+         write("No such file \""+str+"\".\n");
+      else write("Fuer "+str+" sind keine Kraeuter eingetragen!\n");
+   }
+   else {
+      save();
+      _refresh();
+   }
+   return 1;
+}
+
+// Veranlasst den Kraeuter-VC, eine phys. Datei aus den Kraeuterdaten eines
+// Krautes zu erzeugen, falls man diese ausbeschreiben will.
+int _createfile(string str)
+{
+  int id;
+  if (!allowed()) {
+    write("Diese Funktion wurde fuer Dich nicht freigegeben!\n");
+    return 1;
+  }
+  id=to_int(str);
+  if (to_string(id)!=str || id<=0 || id>=sizeof(id2key)) {
+    write("Illegal plantid '"+str+"'.\n");
+    return 1;
+  }
+  notify_fail("Unknown Function im kraeuterVC: _createfile()\n");
+  return call_other(KRAEUTERVC, "_createfile", build_plantname(krautprops[id]));
+}
+
+// AN: Hilfsfunktionen, derzeit ebenfalls deaktiviert.
+// i = 0..7, Position des Krautes, fuer das der Aufruf erfolgt, in der 
+// Liste der in den Kessel einfuellten Kraeuter.
+// keyLst ist die Liste der Kraeutergruppen, der die Kraeuter zugeordnet
+// sind.
+// An dieser Stelle kann also die Wirkung von Kraeutergruppen abgehaengt
+// werden. Unklar ist mir aktuell nur, warum diese Funktion den Parameter
+// "i" benoetigen wuerde.
+// Idee: Es soll die Entscheidung davon abhaengig gemacht werden koennen,
+// wie die Gesamtkombination aussieht, und zusaetzlich davon, aus welcher
+// Gruppe das einzelne Kraut stammt.
+nomask private int IsBlocked(int i, string *keyLst)
+{
+  return 0;
+}
+
+// AN: Diese Funktion muesste nach dem Code in make_potion() zu urteilen
+// 0 oder 1 zurueckgeben, dann wird der Eigenschaftswert der Kraeutergruppe
+// um den Faktor 1.5 verstaerkt.
+nomask private int IsBoosted(int i, string *keyLst)
+{
+  return 0;
+}
+
+#define PRNG "/std/util/rand-glfsr"
+// Individuelle Boni/Mali fuer Spieler. ploffset soll via Referenz uebergeben
+// werden und wird von der Funktion gesetzt.
+int calculate_mod(int krautindex, string plname, int ploffset)
+{
+  // Startoffset zufaellig ermittelt, aber immer gleich
+  // fuer jeden Spielernamen
+  PRNG->InitWithUUID(plname);
+  ploffset = PRNG->random(16);
+  // Jedes Kraut hat auch einen iOffset (der konstant bleibt und sich nicht
+  // aendert). Der wird auch addiert. Bei Ueberschreiten von 30 wird nach 0
+  // gewrappt.
+  // Der Offset ist dann (spieleroffset + krautindex) % 16, d.h. alle Modifikatoren werden
+  // der Reihe nach durchlaufen. So kriegt jeder Spieler - fast egal, bei welchem
+  // Offset er startet - auch der Reihe nach alle Boni+Mali.
+  int offset = ((ploffset + krautindex) % 16) * 2;
+  // Am Ende wird das ganze noch nach 85 bis 115 verlegt.
+  return offset + 85;
+}
+
+#define ZWEITIES "/secure/zweities"
+
+mapping calculate_potion(int* plantids, int* qualities, string familie)
+{
+  // Man sollte ohne die Kraeuter nicht so einfach Wirkungen beliebig
+  // berechnen koennen.
+  if (extern_call() && !ARCH_SECURITY)
+    return 0;
+
+  // Hier speichern wir unser Ergebnis bzw. unser Zwischenergebnis.
+  mapping attrib;
+  // Hier speichern wir die Wirkgruppen, die aus den Plants gezogen werden.
+  mapping* wirkungen=({});
+  // Hier speichern wir gleich schon beim Erzeugen die wichtigsten Blockaden.
+  string* unterstuetzungen=({});
+  string* blockaden=({});
+  int zufall;
+  // Die Sortierung nach PlantID ist nur fuer das Tranklog wichtig.
+  plantids = sort_array(plantids, #'<=);
+
+  // PASS 1: Pflanzen durch Wirkungen ersetzen, dabei Unterstuetzer
+  // und Blocker merken.
+  foreach (int id, int qual : mkmapping(plantids,qualities))
+    {
+      //debug(sprintf("Gehe durch Plant: %d",id));
+      string key=id2key[id];
+      // Wirkungen dieses Krauts kopieren
+      mapping ing=copy(ingredients[key]);
+      //debug(sprintf("%O",ing));
+      // Zu den Wirkungen noch den Key hinzufuegen.
+      ing["key"]=key;
+      // Die Qualitaet des Krautes wird noch mit dem spielerindividuellen
+      // Modifikator skaliert.
+      ing["quality"]=(qual * calculate_mod(id, familie, &zufall)) / 100;
+      wirkungen+=({ing});
+      if (pointerp(ing[T_SUPPORTER]))
+        {
+          foreach (string pi:ing[T_SUPPORTER])
+            {
+              unterstuetzungen+=({pi});
+            }
+        }
+      if (pointerp(ing[T_BLOCKING]))
+        {
+          foreach (string pi:ing[T_BLOCKING])
+            {
+              blockaden+=({pi});
+            }
+        }
+      debug(sprintf("Kraut %s ergibt Werte %O.",key,wirkungen));
+    }
+  // PASS 2: Jetzt die Unterstuetzungen auswerten
+  foreach (mapping mar:wirkungen)
+    {
+      foreach (string pi:unterstuetzungen)
+        {
+          // Haben wir eine Unterstuetzung?
+          if (mar["key"]==pi || ((pi[1]=='*') && (pi[0]==mar["key"][0])))
+            {
+              debug (sprintf("mar=%O, pi=%O",mar,pi));
+              // ALLE ZAHLEN mal 1.5 nehmen. Mir ist klar, dass das nun auch
+              // mit irgendwelchen Haeufigkeiten passiert, aber mal ehrlich,
+              // das ist zur Berechnung egal.
+              foreach (string kk, mixed val : &mar)
+                {
+                  if (intp(val))
+                    {
+                      val=15*val/10;
+                    }
+                }
+            }
+        }
+    }
+  // PASS 3: Jetzt die Blockaden auswerten
+  foreach (mapping mar:wirkungen)
+    {
+      foreach (string pi:blockaden)
+        {
+          // Haben wir eine Blockade?
+          if (mar["key"]==pi || ((pi[1]=='*') && (pi[0]==mar["key"][0])))
+            {
+              debug (sprintf("mar=%O, pi=%O",mar,pi));
+              // Hier werden alle Zahlen auf Null gesetzt.
+              foreach (string kk, mixed val : &mar)
+                {
+                  if (intp(val))
+                    {
+                      val=0;
+                    }
+                }
+            }
+        }
+    }
+
+  // PASS 3.5: Qualitaet der Kraeuter skalieren.
+  foreach (mapping mar:wirkungen)
+    {
+      foreach (string kk, mixed val : &mar)
+        {
+          if (intp(val) && kk!="quality")
+            {
+              val=val*mar["quality"]/100;
+            }
+        }
+    }
+
+  // PASS 4: Nun addieren wir alles auf in das Mapping attrib.
+  attrib=([]);
+  foreach (mapping mar:wirkungen)
+    {
+      foreach (string kk:mar)
+        {
+          if (intp(mar[kk]))
+            {
+              attrib[kk]=attrib[kk]+mar[kk];
+            }
+        }
+    }
+
+  // Die Wirkungsdauer ist der Durchschnitt der Wirkungsdauern
+  attrib[T_EFFECT_DURATION] /= sizeof(plantids);
+  debug(sprintf("Duration: %d\n",attrib[T_EFFECT_DURATION]));
+
+  // Die Haltbarkeit des Tranks ist die Haltbarkeit des kleinsten Krautes.
+  int dur=10000000;
+  foreach (mapping mar:wirkungen)
+    {
+      if (mar[T_EXPIRE]>0 && dur>mar[T_EXPIRE])
+        {
+          dur=mar[T_EXPIRE];
+        }
+    }
+  if (dur==10000000)
+    {
+      dur=0;
+    }
+  attrib[T_EXPIRE]=dur;
+  debug(sprintf("Expire: %d\n",dur));
+
+  int maximum=0;
+  // Effekte rausrechnen, die nicht maximal sind
+  foreach (string kk, mixed val:attrib)
+  {
+    if (member(T_KRAUT_EFFECTS,kk)>=0)
+    {
+      if (val>0 && maximum<val)
+      {
+        maximum=val;
+      }
+    }
+  }
+  // Logeintrag erstellen.
+  sl_exec("INSERT INTO rohdaten(uid, rnd, receipe, quality, car, da, dm, du, "
+          "dn, flt, fro, hI, hP, hK, hL, pa, pm, pu, ss, sp, sd) "
+          "VALUES(?1,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11, ?12, ?13, ?14, "
+          "?15, ?16, ?17, ?18, ?19, ?20, ?21);",
+          this_player() ? getuid(this_player()) : "<unknown>",
+          zufall,
+          implode(map(plantids, #'to_string), ", "),
+          implode(map(qualities, #'to_string), ", "),
+          attrib[T_CARRY], attrib[T_DAMAGE_ANIMALS],
+          attrib[T_DAMAGE_MAGIC], attrib[T_DAMAGE_UNDEAD],
+          attrib[T_EFFECT_DURATION], attrib[T_FLEE_TPORT],
+          attrib[T_FROG], attrib[T_HEAL_DISEASE],
+          attrib[T_HEAL_POISON], attrib[T_HEAL_SP],
+          attrib[T_HEAL_LP], attrib[T_PROTECTION_ANIMALS],
+          attrib[T_PROTECTION_MAGIC], attrib[T_PROTECTION_UNDEAD],
+          attrib[T_SA_SPEED], attrib[T_SA_SPELL_PENETRATION],
+          attrib[T_SA_DURATION]);
+
+  // Maximal zwei positive Effekte.
+  int cteff=0;
+  foreach (string kk, mixed val : &attrib)
+  {
+      if (member(T_KRAUT_EFFECTS,kk)>=0)
+      {
+        // Nur die 2 staerksten positiven Wirkungen bleiben ueber (dazu
+        // muessen sie wirklich den gleichen Zahlenwert haben, kann bei
+        // Heilungen vorkommen, sonst eher unwahrscheinlich).
+        if (val>0 && maximum>val)
+        {
+          val=0;
+        }
+        // Thresholds. Zu zu grosse Wirkungen haben die Grenze als
+        // Auswirkung. Negative Wirkungen, die -T_MINIMUM_THRESHOLD nicht
+        // ueberschreiben, fallen weg. Bei den positiven bleibt ja ohnehin nur
+        // die staerkste Wirkung ueber, da gibt es erstmal keine
+        // Mindestgroesse mehr.
+        if (val>T_MAXIMUM_THRESHOLD)
+        {
+          val=T_MAXIMUM_THRESHOLD;
+        }
+        if (val < 0 && val > -T_MINIMUM_THRESHOLD)
+        {
+          val=0;
+        }
+        if (maximum==val && val>0)
+        {
+          cteff++;
+          // Voellig willkuerlich, was hier getroffen wird. Ob reproduzierbar,
+          // vermutlich ja, aber haengt mit der Mappingstruktur zusammen.
+          // Harhar.
+          if (cteff>2)
+          {
+            val=0;
+          }
+        }
+      }
+  }
+  debug(sprintf(" TRANKERGEBNIS: %O",attrib));
+  // Logeintrag erstellen.
+  sl_exec("INSERT INTO traenke(uid, rnd, receipe, quality, car, da, dm, du, "
+          "dn, flt, fro, hI, hP, hK, hL, pa, pm, pu, ss, sp, sd) "
+          "VALUES(?1,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11, ?12, ?13, ?14, "
+          "?15, ?16, ?17, ?18, ?19, ?20, ?21);",
+          this_player() ? getuid(this_player()) : "<unknown>",
+          zufall,
+          implode(map(plantids, #'to_string), ", "),
+          implode(map(qualities, #'to_string), ", "),
+          attrib[T_CARRY], attrib[T_DAMAGE_ANIMALS],
+          attrib[T_DAMAGE_MAGIC], attrib[T_DAMAGE_UNDEAD],
+          attrib[T_EFFECT_DURATION], attrib[T_FLEE_TPORT],
+          attrib[T_FROG], attrib[T_HEAL_DISEASE],
+          attrib[T_HEAL_POISON], attrib[T_HEAL_SP],
+          attrib[T_HEAL_LP], attrib[T_PROTECTION_ANIMALS],
+          attrib[T_PROTECTION_MAGIC], attrib[T_PROTECTION_UNDEAD],
+          attrib[T_SA_SPEED], attrib[T_SA_SPELL_PENETRATION],
+          attrib[T_SA_DURATION]);
+
+  return attrib;
+}
+
+mapping make_potion(object* plants)
+{
+  // -> mappt nicht-Objekt zu 0, aber 0 ist auch ne gueltige PlantID. Daher
+  // müssen zerstoerten Objekte vorher raus.
+  // TODO: drauf verlassen, dass nur intakte Objekt enthalten sind?
+  if (member(plants, 0) >= 0)
+    raise_error(sprintf("make_potion() got invalid object in plant array "
+                        "%.50O\n",plants));
+
+  int* plantids = (int*)plants->QueryPlantId();
+  int* qualities = (int*)plants->QueryProp(P_QUALITY);
+
+  return calculate_potion(plantids, qualities,
+                          ZWEITIES->QueryFamilie(this_player()));
+}
+
+// AN: Sucht alle Pflanzen raus, in deren Namen der Suchbegriff "str"
+// vorkommt und listet diese auf. Laeuft allerdings momentan noch in einen
+// Fehler "index out of bounds", aber man muesste hier (TODO) sowieso mal
+// von explode() auf strstr() umbauen, denke ich.
+string _findplant(string str) {
+  int i, k;
+  string *ind, *tmp;
+
+  if(!str) return "";
+  write("Suche nach '"+str+"':\n\n");
+  ind = m_indices(map_ldfied);
+  for(i=0;i<sizeof(ind);i++) {
+    tmp = map_ldfied[ind[i]][0];
+    if( stringp(tmp[3]) && 
+        old_explode(lower_case(tmp[3]),str)[0] != lower_case(tmp[3]) 
+        ||
+        stringp(tmp[4]) && 
+        old_explode(lower_case(tmp[4]),str)[0] != lower_case(tmp[4]))
+      write(" -  "+tmp[3]+
+          (stringp(tmp[4])?" ("+tmp[4]+")":"")+"       - "+tmp[0]+"\n");
+  }
+
+  return "";
+}
+
+// AN: Funktion liefert das Ergebnisarray aus make_potion() fuer eine Liste
+// von Kraeutern, die als ", "-getrennte Kraut-IDs uebergeben werden muessen.
+mixed _checkTrank(string str)
+{
+  if (extern_call() && !allowed())
+    return 0;
+
+  string *ind, *args, name;
+  object *objs;
+  int k, l;
+
+  objs = ({});
+  if(!str) return "Keine Kraeuter uebergeben!";
+  ind = old_explode(str,",");
+//  ind = ({"180","11","53"});
+  for(int i=0;i<sizeof(ind);i++)
+  {
+    name = build_plantname(krautprops[to_int(ind[i])]);
+    write("Input: '"+name+"' ("+ind[i]+")\n");
+    objs += ({clone_object(PLANTDIR+name)});
+  }
+  mapping ragtest = make_potion(objs);
+  objs->remove();
+/*  name="";
+  for(int i=0;i<sizeof(ragtest);i++)
+       name = name + ragtest[i]+",";
+  write("Result: ({ "+name+" })\n");*/
+  return sprintf("%O\n",ragtest);
+}
+
+#define QUAL_BASE  0
+#define QUAL_RND   1
+#define DELAY_BASE 2
+#define DELAY_RND  3
+
+#define ALLOWED_DRIER "/items/kraeuter/trockner"
+
+// Liefert die Trocknungsdaten eines Raumes aus, mit denen der Kraeuter-
+// trockner dann das Kraut bearbeiten kann.
+int *QueryDryingData() {
+  // Es muss allerdings das aufrufende Objekt ein Trockner-Clone sein, 
+  // der in einem der zugelassenen Raeume stehen muss.
+  // Wenn das nicht der Fall ist, wird der Trockner das leere Array, das 
+  // zurueckgegeben wird, als Indiz werten, dass er im falschen Raum steht.
+  if ( objectp(previous_object()) &&
+       load_name(previous_object()) == ALLOWED_DRIER &&
+       member(drying_data, load_name(environment(previous_object()))) &&
+       clonep(previous_object()) )
+  {
+    // Raum ermitteln, Delay/Quali errechnen, Ergebnisarray zurueckgeben.
+    string where = load_name(environment(previous_object()));
+    int delay = drying_data[where,DELAY_BASE]+
+                random(drying_data[where,DELAY_RND]);
+    int qual  = drying_data[where,QUAL_BASE]+
+                random(drying_data[where,QUAL_RND]);
+    return ({ delay, qual });
+  }
+  return ({});
+}
+
+// Modifizieren der Trocknungsdaten.
+// <room> muss der volle Dateiname des Raumes sein, ohne .c am Ende.
+// <values> enthaelt die vier Parameter zu dem Raum in folgender Reihenfolge:
+// ({ Quali-Basis, Quali-Zufallsanteil, Delay-Basis, Delay-Zufallsanteil })
+// Wenn <values> nicht angeben wird oder 0 ist, werden die Daten zu <room>
+// geloescht.
+int|mapping SetDryingData(string room, int* values) 
+{
+  // keine Zugriffsrechte
+  if ( !allowed() )
+    return -1;
+  
+  // <values> wurde nicht uebergeben? Dann Daten loeschen.
+  if ( !values ) 
+  {
+    m_delete(drying_data, room);
+    return 1;
+  }
+
+  // Ansonsten muessen 4 Integer-Werte als <values> uebergeben werden.
+  if ( sizeof(values) != 4 ) 
+    return -2;
+
+  if ( room[<2..<1] == ".c" )
+    room = room[..<3];
+
+  // Uebergebene Daten aendern direkt das Mapping der Trocknungsdaten.
+  m_add(drying_data, room, values...);
+  save();
+  return ([ room : drying_data[room,0]; drying_data[room,1]; 
+                   drying_data[room,2]; drying_data[room,3]]);
+}
+
+varargs mapping QueryDrying()
+{
+  return (allowed() ? drying_data : ([]) );
+}
+
+varargs int remove(int silent) 
+{
+  save();
+  return ::remove(silent);
+}
+
+/*
+#define DRYINGDATA "/secure/ARCH/kraeutertrocknungsdaten"
+
+private void ReadDryingData() 
+{
+  mixed data = explode(read_file(DRYINGDATA), "\n")-({""});
+  foreach(string line : data) 
+  {
+    if ( line[0] == '#' )
+      continue;
+    string *fields = explode(line,";");
+    fields[1..] = map(fields[1..], #'to_int);
+    m_add(tmp_drying_data, fields...);
+  }
+}*/
+
diff --git a/secure/lepmaster.c b/secure/lepmaster.c
new file mode 100644
index 0000000..3d7cb79
--- /dev/null
+++ b/secure/lepmaster.c
@@ -0,0 +1,408 @@
+// MorgenGrauen MUDlib
+//
+// lepmaster.c -- Master, welcher aus scoremaster, explorationmaster,
+//                questmaster und dem Spielerobjekt Informationen
+//                ausliest und aus diesen die Levelpunkte des Spieler
+//                berechnet
+//                Weiters derjenige, der die Seheranforderungen 
+//                prueft. 
+//
+// $Id: lepmaster.c 9168 2015-03-05 00:04:22Z Zesstra $
+
+// Hier kommen Funktionen fuer die Levelpunkte
+#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 <properties.h>
+#include <new_skills.h>
+#include <exploration.h>
+#include <questmaster.h>
+#include <scoremaster.h>
+#include <wizlevels.h>
+
+#include "lepmaster.h"
+
+// Das Programm zur Foerderung der kleinen Level aktivieren. ;-)
+#define __PFOERKL__ 1
+
+// die ersten 8 Level, d.h. Level 1 bis 8 entsprechen diesen LEPs.
+int *LOW_LEVEL_LEP_LIST = ({100,120,140,180,220,300,360,420}); // 9. Level: 500 LEP
+
+#define REQ_TEXT1  ([0:"unglaublich viel", \
+                  1:"unglaublich viel", \
+		  2:"enorm viel", \
+		  3:"sehr viel", \
+		  4:"viel", \
+		  5:"einiges", \
+		  6:"etwas", \
+		  7:"wenig", \
+                  8:"sehr wenig", \
+                  9:"kaum etwas" ])
+
+#define REQ_TEXT2  ([0:"unglaublich viele", \
+                  1:"unglaublich viele", \
+		  2:"enorm viele", \
+		  3:"sehr viele", \
+		  4:"viele", \
+		  5:"einige", \
+		  6:"ein paar", \
+		  7:"wenige", \
+                  8:"sehr wenige", \
+                  9:"kaum" ])
+
+#define DEBUG(x,y)
+//#define DEBUG(x,y) if (find_player("zook")) tell_object(find_player("zook"),sprintf(x,y))
+
+
+void create()
+{
+  seteuid(getuid());
+}
+
+int QueryLEPForPlayer(object pl)
+{
+  int ret, val, i, l;
+
+  if (extern_call() && !IS_ARCH(geteuid(previous_object())))
+    return -1;
+  if (!pl || !query_once_interactive(pl))
+    return -2;
+
+  //  Grundoffset 100, da man mit Level 1 statt 0 startet
+  ret = 100;
+
+  // Beitrag A: Stupse von NPC-Erstkills
+  ret += (int)SCOREMASTER->QueryKillPoints(pl);
+
+  DEBUG("Nach KP: ret = %d\n", ret);
+
+  // Beitrag B: Stupse von geloesten Miniquests
+  ret += (int)QM->QueryMiniQuestPoints(pl);
+
+  DEBUG("Nach MQP: ret = %d\n", ret);
+
+  // Beitrag C: Questpunkte
+  //  werden 1:1 uebernommen;
+  ret += (int)pl->QueryProp(P_QP);
+
+  DEBUG("Nach QP: ret = %d\n", ret);
+
+  // Beitrag D: Erfahrungspunkte
+  //  Stupse = XPs ^ 0.32
+  val = (int)pl->QueryProp(P_XP);
+
+  if (val<1) l=0;
+  else l=to_int(exp(log(to_float(val))*0.32));
+
+  ret += l;
+
+  DEBUG("Nach XP: ret = %d\n", ret);
+
+  // Beitrag E: Zaubertraenke
+  //  Gefundene Traenke geben 5 LEP
+  //  Die Heiltraenke geben zusaetzlich 10+20+30+40 LEP
+  i = 80 - (val = sizeof((int *)pl->QueryProp(P_POTIONROOMS)));
+
+  ret += 5*i + ([ 0: 100, 1: 60, 2: 30, 3: 10])[val];
+
+  // Beitrag F: Forscherpunkte
+  //  Pro FP gibt es 6 Stufenpunkte
+  ret += 6 * (int)EPMASTER->QueryExplorationPoints(pl);
+
+  DEBUG("Nach FP: ret = %d\n", ret);
+
+  // Beitrag G: Gildeneinstufung
+  //  Maximale Gildeneinstufung (10000) entspricht vier Leveln
+  ret += ((int)pl->QueryProp(P_GUILD_RATING))/25;
+
+  DEBUG("Nach GR: ret = %d\n", ret);
+
+  // Ausgabe gibt es nur in 20er-Schritten
+  ret -= ret%20;
+
+  return (ret > 100) ? ret : 100;
+
+}
+
+nomask int QueryLEP()
+{
+  if (!previous_object())
+    return 0;
+  return QueryLEPForPlayer(previous_object());
+}
+
+nomask int QueryLevel(int lep) {
+  if (lep<=0)
+    lep=QueryLEP();
+
+  if (lep<=0)
+    return 0;
+
+#ifdef __PFOERKL__
+  // normale Level alle 100 LEP
+  if (lep >= 500)
+    return lep/100 + 4;
+  else {
+    // level mit weniger LEP am Anfang (1-8)
+    // Level aus der LOW_LEVEL_LEP_LIST raussuchen.
+    int lev = sizeof(LOW_LEVEL_LEP_LIST) - 1;
+    for ( ; lev >= 0; --lev) {
+      if (LOW_LEVEL_LEP_LIST[lev] <= lep)
+        break;  // gefunden
+    }
+    return lev+1;
+  }
+#else
+  return lep/100;
+#endif // __PFOERKL__
+  return 0;
+}
+
+// Wieviele LEP fehlen zum naechsten Level?
+nomask int QueryNextLevelLEP(int lvl, int lep) {
+  int needed;
+
+  if (QueryLevel(lep) > lvl)
+    return 0;
+
+#ifdef __PFOERKL__
+  if (lvl < sizeof(LOW_LEVEL_LEP_LIST))
+    needed = LOW_LEVEL_LEP_LIST[lvl];
+  else
+    needed = (lvl-3) * 100; // (lvl + 1 - 4) * 100
+#else
+  needed = (lvl+1) * 100;
+#endif // __PFOERKL__
+
+  // needed sind jetzt die insgesamt benoetigten LEP. Vorhandene abziehen.
+  needed -= lep;
+  // nix < 0 zurueckliefern
+  return max(needed,0);
+}
+
+string QueryForschung()
+{
+  int max, my, avg;
+  string ret;
+
+  if ((my=(int)EPMASTER->QueryExplorationPoints(getuid(previous_object()))) < MIN_EP)
+    return "Du kennst Dich im "MUDNAME" so gut wie gar nicht aus.\n";
+
+  my *= 100;
+  max = my/(int)EPMASTER->QueryMaxEP();
+  avg = my/(int)EPMASTER->QueryAverage();
+
+  ret = "Verglichen mit Deinen Mitspielern, kennst Du Dich im "MUDNAME" ";
+  switch(avg) {
+  case 0..10:
+    ret += "kaum";
+    break;
+  case 11..40:
+    ret += "aeusserst schlecht";
+    break;
+  case 41..56:
+    ret += "sehr schlecht";
+    break;
+  case 57..72:
+    ret += "schlecht";
+    break;
+  case 73..93:
+    ret += "unterdurchschnittlich";
+    break;
+  case 94..109:
+    ret += "durchschnittlich gut";
+    break;
+  case 110..125:
+    ret += "besser als der Durchschnitt";
+    break;
+  case 126..145:
+    ret += "recht gut";
+    break;
+  case 146..170:
+    ret += "ziemlich gut";
+    break;
+  case 171..210:
+    ret += "gut";
+    break;
+  case 211..300:
+    ret += "sehr gut";
+    break;
+  case 301..400:
+    ret += "ausserordentlich gut";
+    break;
+  case 401..500:
+    ret += "unheimlich gut";
+    break;
+  default:
+    ret += "einfach hervorragend";
+    break;
+  }
+  ret += " aus.\nAbsolut gesehen ";
+
+  switch(max) {
+  case 0..5:
+    ret += "kennst Du nur wenig vom "MUDNAME".";
+    break;
+  case 6..10:
+    ret += "solltest Du Dich vielleicht noch genauer umsehen.";
+    break;
+  case 11..17:
+    ret += "bist Du durchaus schon herumgekommen.";
+    break;
+  case 18..25:
+    ret += "hast Du schon einiges gesehen.";
+    break;
+  case 26..35:
+    ret += "bist Du schon weit herumgekommen.";
+    break;
+  case 36..50:
+    ret += "koenntest Du eigentlich einen Reisefuehrer herausbringen.";
+    break;
+  case 51..75:
+    ret += "hast Du schon sehr viel gesehen.";
+    break;
+  default:
+    ret += "besitzt Du eine hervorragende Ortskenntnis.";
+  }
+  return break_string(ret, 78, 0, 1);
+}
+
+
+nomask mixed QueryWizardRequirements(object player)
+{
+  // Diese Funktion gibt ein 2-elementiges Array zurueck, in dem im ersten
+  // Element steht, ob der Spieler Seher werden kann (1) oder
+  // nicht (0) und im zweiten Element steht, was genau ihm noch 
+  // fehlt. 
+  // Fehlercode ist ({-1, ""})
+
+  // Die Umrechnungsfaktoren wurden einfach aus QueryLEP uebernommen; ggf.
+  // kann man das einmal in Unterfunktionen auslagern. 
+
+  mixed ret;
+  string s; 
+  int i,z,val; 
+
+  ret = ({-1, "Hier ist etwas schief gelaufen. Bitte einen Erzmagier\n"
+	    +"benachrichtigen.\n"});
+  s = "";
+  i = 0; 
+
+  if(!player || !objectp(player))
+    player=(this_player()?this_player():this_interactive());
+
+  if(!player)
+    return ({-1,""});
+
+  DEBUG("Es geht um: %O\n", player);
+
+  // Abenteuerpunkte
+  DEBUG("Abenteuerpunkte: %d ("+REQ_QP+")\n", player->QueryProp(P_QP));
+  if (player->QueryProp(P_QP) < REQ_QP) {
+    s += sprintf(" * Dir fehlen noch mindestens %d Abenteuerpunkte.\n", 
+		 REQ_QP - (int)player->QueryProp(P_QP));
+    i--;
+  }
+
+  // Forscherpunkte
+  z = 6 * (int)EPMASTER->QueryExplorationPoints(player);
+  DEBUG("Forscherpunkte: %d ("+REQ_EP+")\n", z);
+  if (z < REQ_EP) {
+    s += sprintf(" * Du kennst Dich im "MUDNAME" noch nicht genug aus, "
+		 +"genau genommen\n   musst Du Dir noch %s ansehen.\n", 
+		 REQ_TEXT1[(z*10/REQ_EP)] );
+    i--;
+  }
+
+  // Zaubertraenke
+  z = 80 - (val = sizeof((int*)player->QueryProp(P_POTIONROOMS)));
+  z = z*5 + ([0:100, 1:60, 2:30, 3:10])[val];
+  DEBUG("Zaubertraenke: %d ("+REQ_P+")\n", z);
+  if (z < REQ_P) {
+    s += sprintf(" * Du musst noch einige Zaubertraenke (ca. %d) suchen.\n",
+		 (REQ_P - z)/5 );
+    i--;
+  }
+
+  // Erstkills
+  z = (int)SCOREMASTER->QueryKillPoints(player);
+  DEBUG("Erstkills: %d ("+REQ_K+")\n", z);
+  if (z < REQ_K) {
+        s += sprintf(" * Du hast noch nicht genuegend wuerdige Gegner erlegt, genau "
+    	 +"genommen\n   musst Du noch %s wuerdige Monster toeten.\n",
+    	 REQ_TEXT2[(z*10/REQ_K)] );;
+    i--;
+  }
+
+  int minlevel = QueryLevel(REQ_LEP);
+
+  // Restliche Stufenpunkte 
+  DEBUG("Stufenpunkte: %d ("+REQ_LEP+")\n", player->QueryProp(P_LEP));
+  if ((int)(player->QueryProp(P_LEP)) < REQ_LEP) {
+    s += sprintf(" * Du musst mindestens %d Stufenpunkte, entspricht Stufe %d, "
+        "erreichen.\n", REQ_LEP, minlevel);
+    i--;
+  }
+  
+  // Demnach mindestens REQ/100-Level 
+  DEBUG("Level: %d ("+REQ_LEP/100+")\n", player->QueryProp(P_LEVEL));
+  if ((int)player->QueryProp(P_LEVEL) < minlevel) {
+    s += sprintf(" * Du musst mindestens Stufe %d erreichen.\n", minlevel);
+    i--;
+  }
+  
+  if(i<0) {
+    ret = ({-1, 
+	 sprintf("Du hast noch nicht alle Seher-Anforderungen erfuellt.\n"
+		+"Im einzelnen fehlt Dir folgendes:\n\n%s\n"
+		 +break_string("Falls Du Dir nun dennoch unsicher bist, "
+  +"welche Anforderungen Du erfuellen musst, dann "
+  +"schaue bei 'hilfe seher' und 'hilfe stufenpunkte' doch einfach noch "
+  +"einmal nach. Sind dann "
+  +"immer noch Dinge offen oder unklar, so sprich einfach einen "
+				  +"der Erzmagier an.", 78,0,1),s) });
+  } 
+    
+  if (i==0) {
+    ret = ({1, break_string(
+	       "Du hast alle Seher-Anforderungen erfuellt. Wende Dich doch "
+	       +"einmal an Merlin und frage ihn, ob er Dich befoerdert.", 
+	       78,0,1) });
+  }
+
+  return ret; 
+}
+
+nomask int QueryReadyForWiz(object player)
+{
+  mixed r;
+  
+  r = QueryWizardRequirements(player);
+  
+  if (!pointerp(r) && sizeof(r)!=2 && !intp(r[0]))
+    return -1;
+
+  return r[0];
+}
+
+nomask string QueryReadyForWizText(object player)
+{
+  mixed r;
+
+  r = QueryWizardRequirements(player);
+
+  if (!pointerp(r) && sizeof(r)!=2 && !stringp(r[1]))
+    return "Hier ist etwas schief gegangen, bitte verstaendige einen "
+      +"Erzmagier.";
+
+  return r[1];
+}
+
diff --git a/secure/lepmaster.h b/secure/lepmaster.h
new file mode 100644
index 0000000..31e7247
--- /dev/null
+++ b/secure/lepmaster.h
@@ -0,0 +1,22 @@
+// MorgenGrauen MUDlib
+//
+// lepmaster.h -- Definitionen fuer den lepmaster
+//
+// $Id: lepmaster.h,v 1.3 2006/02/22 08:57:48 Zook Exp $
+
+#ifndef __LEPMASTER_H__
+#define __LEPMASTER_H__
+
+/* Dateinamen */
+
+// to change:
+#define LEPMASTER   "/secure/lepmaster"
+
+// Anforderungen: 
+#define REQ_QP  1610     // Stupse aus Quests
+#define REQ_EP   600     // Stupse aus Forscherpunkten
+#define REQ_P    200     // Stupse aus Zaubertraenken
+#define REQ_K    150     // Stupse aus Erstkills
+#define REQ_LEP 3800     // Mindestens 3800 Stupse und Lvl. 38
+
+#endif
diff --git a/secure/login.c b/secure/login.c
new file mode 100644
index 0000000..ce14646
--- /dev/null
+++ b/secure/login.c
@@ -0,0 +1,1138 @@
+// MorgenGrauen MUDlib
+//
+// login.c -- Object for players just logging in
+//
+// $Id: login.c 9245 2015-06-04 13:04:39Z Arathorn $
+
+ /*
+ * secure/login.c
+ *
+ * This object is cloned for every user trying to log in
+ * We are still running root.
+ *
+ * login.c looks up the username in the secure/PASSWD file. If it is
+ * found, the password is checked. If the user is already logged in,
+ * he will be reconnected to the running object. If the other object
+ * is still interactive, that will be disconnected before the user is
+ * reconnected to that object.
+ *
+ * If the user is not in PASSWD, a new entry with level 0 is created.
+ * All PASSWD writing is done in secure/master.c.
+ *
+ */
+#pragma strict_types
+#pragma no_shadow
+#pragma no_inherit
+#pragma verbose_errors
+#pragma combine_strings
+//#pragma pedantic
+//#pragma range_check
+#pragma warn_deprecated
+
+#include <config.h>
+#include <properties.h>
+#include <moving.h>
+#include "/secure/wizlevels.h"
+#include <telnet.h>
+#include <defines.h>
+#include <input_to.h>
+
+inherit "/secure/mini_props.c";
+inherit "/secure/telnetneg.c";
+
+#define SSL_GRRETING "REMOTE_HOST="
+#define PROXIES ({"127.0.0.1","87.79.24.60"})
+#define GUESTMASTER "/secure/guestmaster"
+
+#ifndef DEBUG
+#define DEBUG(x) if(find_player("tiamak")) tell_object(find_player("tiamak"),x)
+#define DEBUGM(x) if(find_player("muadib")) tell_object(find_player("muadib"),x)
+#endif
+
+/* Variables of the secure save file */
+int level, loginfails, creation_date;
+string password, name, shell, ep, ek, mq;
+string ektips;
+string fptips;
+string *domains, *guilds, *uidstotakecare;
+
+static int invis, neu;
+static string loginname;
+static string cap_name;
+static string *userentry;
+static string banish;
+static mixed *races;
+static int newbie;
+static string realip;
+
+// Prototypes
+static void SendTelopts();
+public nomask string loginname();
+// the following 4 lfuns deal with real logins
+public nomask int logon();
+static int logon2( string str );
+static int load_player_object( int guestflag );
+static void load_player_ob_2( string obname, int guestflag );
+static int check_illegal( string str );
+static int valid_name( string str );
+static int new_password( string str );
+static int again_password( string str );
+static int check_password( string str );
+static void select_race();
+static void ask_race_question();
+static void get_race_answer( string str );
+
+protected void create();
+public string short();
+public string query_real_name();
+public nomask int query_prevent_shadow();
+static void time_out();
+public int remove();
+// the following 3 lfuns deal with dummy player creation
+public mixed new_logon( string str);
+static mixed new_load_player_object();
+static mixed new_load_player_ob_2( string obname );
+static void ask_mud_played_question();
+static void get_mud_played_answer(string str);
+
+
+public nomask string loginname()
+{
+    return loginname ? loginname : "";
+}
+
+
+public int remove()
+{
+    destruct( this_object() );
+    return 1;
+}
+
+
+static int check_too_many_logons()
+{
+    object *u;
+    string ip;
+
+    ip = query_ip_number(this_object());
+    // users() nehmen, falls nicht-interaktive Clones von login.c existieren.
+    u = filter( users(), function status (object ob, string addr) {
+        return object_name(ob) == "/secure/login"
+               && query_ip_number(ob) == addr;
+    }, ip );
+
+    if ( sizeof(u) > 5 ){
+        write( "\nEs laufen schon zu viele Anmeldungen von Deiner Adresse "
+               "aus.\nProbier es bitte in ein bis zwei Minuten noch "
+               "einmal.\n" );
+
+        log_file( "LOGIN_DENY", sprintf( "%s: >5 Logons von %-15s (%s)\n",
+                                         ctime(time())[4..15],
+                                         query_ip_number(this_object()),
+                                         query_ip_name(this_object()) ) );
+        return 1;
+    }
+    else
+        return 0;
+}
+
+
+/*
+ * This is the function that gets called by /secure/master for every user
+ */
+public nomask int logon()
+{
+    loginname = "logon";
+    newbie=0;
+    realip="";
+
+    // als erstes wird ein Lookup gemacht, ob die Quelladresse ein
+    // Tor-Exitnode ist, der erlaubt, zu uns zu kommunizieren. Das Lookup ist
+    // asynchron und braucht eine Weile, wenn das Ergebnis noch nicht gecacht
+    // ist. An dieser Stelle wird das Ergebnis nicht ausgewertet. Achja, wie
+    // machen das natuerlich nicht fuer die IP vom Mudrechner...
+    if (query_ip_number(this_object()) != "87.79.24.60")
+    {
+      "/p/daemon/dnslookup"->check_tor(query_ip_number(this_object()),query_mud_port());
+      "/p/daemon/dnslookup"->check_dnsbl(query_ip_number(this_object()));
+    }
+    printf("HTTP/1.0 302 Found\n"
+         "Location: http://mg.mud.de/\n\n"
+         "NetCologne, Koeln, Germany. Local time: %s\n\n"
+         MUDNAME" LDmud, NATIVE mode, driver version %s\n\n",
+         strftime("%c"), __VERSION__);
+
+    SendTelopts();
+
+    if ( check_too_many_logons() ){
+        destruct(this_object());
+        return 0;
+    }
+
+    // ist die Verbindung schon wieder weg?
+    if (objectp(this_object()) && interactive(this_object())) {
+      cat( "/etc/WELCOME" );
+    }
+
+    input_to( "logon2", INPUT_PROMPT,
+        "Wie heisst Du denn (\"neu\" fuer neuen Spieler)? ");
+    call_out( "time_out", 300 );
+    return 1;
+}
+
+
+static int check_too_many_from_same_ip()
+{
+    object *u;
+    string ip;
+
+    ip = query_ip_number(this_object());
+    u = filter(users(), function status (object ob, string addr, int a) {
+        return query_ip_number(ob) == addr
+               && ob->QueryProp(P_AGE) < a;
+    }, ip, 12*60*60); // 24h in heart_beats
+
+    if ( sizeof(u) > 25 ){
+        write( "\nDa anscheinend gerade jemand von Deiner Adresse aus "
+               "versucht, das \n"MUDNAME" mit neuen Charakteren zu "
+               "ueberschwemmen, werden momentan \nnur aeltere Charaktere "
+               "von dieser Adresse zugelassen.\nWenn Du meinst, dass es "
+               "sich um einen Fehler handelt, logg Dich bitte als \n"
+               "Gast ein und sprich einen Erzmagier oder Gott an.\n" );
+        
+        log_file( "LOGIN_DENY", sprintf( "%s: >10 Spieler von %-15s (%s)\n",
+                                         ctime(time())[4..15],
+                                         query_ip_number(this_object()),
+                                         query_ip_name(this_object()) ) );
+        
+        destruct(this_object());
+        return 1;
+    }
+    else
+        return 0;
+}
+
+
+static int check_illegal( string str )
+{
+    string res;
+
+    res = (string)master()->QuerySBanished(query_ip_number(this_object()));
+    if (!res)
+    {
+      // check connection from Tor exit node
+      string eff_ip = (realip!="" ? realip : query_ip_number(this_object()));
+      if ("/p/daemon/dnslookup"->check_tor(eff_ip, query_mud_port())
+          || "/p/daemon/dnslookup"->check_dnsbl(eff_ip))
+        res = 
+            "\nSorry, von Deiner Adresse kamen ein paar Idioten, die "
+            "ausschliesslich\nAerger machen wollten. Deshalb haben wir "
+            "die Moeglichkeit,\neinfach neue Charaktere "
+            "anzulegen, fuer diese Adresse gesperrt.\n\n"
+            "Falls Du bei uns spielen moechtest, schicke bitte eine Email "
+            "an\n\n                         mud@mg.mud.de\n\n"
+            "mit der Bitte, einen Charakter fuer Dich anzulegen.\n" ;
+    }
+
+    if ( res )
+    {
+        write( res );
+        log_file( "LOGIN_DENY", sprintf( "%s: %-11s %-15s (%s)\n",
+                                         ctime(time())[4..15], str,
+                                         query_ip_number(this_object()),
+                                         query_ip_name(this_object()) ) );
+        remove();
+        return 1;
+    }
+
+    return 0;
+}
+
+
+/*
+ * Check that a player name is valid. Only allow
+ * lowercase letters.
+ */
+static int valid_name( string str )
+{
+    int i;
+
+    if ( str == "logon" ){
+        write( "Der Name wuerde nur Verwirrung stiften.\n" );
+        return 0;
+    }
+
+    i = sizeof(str);
+
+    if ( i > 11 ){
+        write( "Dein Name ist zu lang, nimm bitte einen anderen.\n" );
+        return 0;
+    }
+
+    for ( ; i--; )
+        if ( str[i] < 'a' || str[i] > 'z' ) {
+            write( "Unerlaubtes Zeichen '" + str[i..i] + "' im Namen: " + str
+                            + "\n" );
+            write( "Benutze bitte nur Buchstaben ohne Umlaute.\n" );
+            return 0;
+        }
+
+    return 1;
+}
+
+
+static int logon2( string str )
+{
+    int i, arg;
+    mixed txt;
+
+    if ( !str || str == "" ){
+        write( "Abbruch!\n" );
+        destruct( this_object() );
+        return 0;
+    }
+
+    // Unterstuetzung fuer das Mud Server Status Protocol
+    // (http://tintin.sourceforge.net/mssp/)
+#ifdef MSSP_SUPPORT
+    if (str == "MSSP-REQUEST") {
+      "/secure/misc/mssp"->print_mssp_response();
+      log_file( "MSSP.log", sprintf( "%s: %-15s (%s)\n",
+                                         strftime("%c"),
+                                         query_ip_number(this_object()),
+                                         query_ip_name(this_object())||"N/A" ) );
+      input_to("logon2", INPUT_PROMPT,
+          "Wie heisst Du denn (\"neu\" fuer neuen Spieler)? ");
+      return 1;
+    }
+#endif
+
+    if(strstr(str,SSL_GRRETING)==0)
+    {
+      if( member(PROXIES,query_ip_number(this_object()))>-1 )
+      {
+        realip=str[sizeof(SSL_GRRETING)..];
+      } // andere IPs werden einfach ignoriert. -> log/PROXY.REQ ?
+      // ggf. Lookup fuer Torexits anstossen.
+      "/p/daemon/dnslookup"->check_tor(realip,query_mud_port());
+      "/p/daemon/dnslookup"->check_dnsbl(realip);
+
+      input_to( "logon2", INPUT_PROMPT,
+          "Wie heisst Du denn (\"neu\" fuer neuen Spieler)? ");
+      return 1;
+    }
+    
+    if ( loginname != "logon" ) {
+        log_file( "ILLEGAL", sprintf( "%s Illegal patch of login: "
+                                      "loginname = %O\n",
+                                      dtime(time()), loginname ) );
+        destruct( this_object() );
+        return 0;
+    }
+
+    str = lower_case(str);
+    cap_name = capitalize(str);
+
+    if ( str == "neu" && !neu ){
+        cat( "/etc/WELCOME_NEW" );
+        neu = 1;
+        input_to( "logon2", INPUT_PROMPT, "Name: ");
+        return 1;
+    }
+
+    if ( !valid_name(str) ){
+        string pr;
+        if ( !neu )
+            pr= "Wie heisst Du denn (\"neu\" fuer neuen Spieler)? ";
+        else
+            pr= "Bitte gib Dir einen anderen Namen: ";
+
+        input_to( "logon2", INPUT_PROMPT, pr );
+        return 1;
+    }
+
+    if ( sscanf( str, "gast%d", arg ) == 1 ){
+        write( "Du meinst wohl 'Gast' ...\n" );
+        str = "gast";
+    }
+
+    loginname = str;
+
+    /* read the secure save file to see if character already exists */
+    if ( loginname != "gast" &&
+         !restore_object( master()->secure_savefile(loginname) ) ){
+        object *user;
+
+        if ( !neu )
+        {
+            write( "Es existiert kein Charakter mit diesem Namen.\n" );
+            write( "Falls Du einen neuen Charakter erschaffen moechtest, "
+                   "tippe bitte \"neu\" ein.\n" );
+
+            loginname = "logon";
+            input_to( "logon2", INPUT_PROMPT,
+                "Wie heisst Du denn (\"neu\" fuer neuen Spieler)? ");
+            return 1;
+        }
+
+        for ( i = sizeof(user = users() - ({ 0, this_object() })); i--; )
+            if ( object_name(user[i])[0..12] == "/secure/login" &&
+                 ((string)user[i]->loginname()) == loginname ){
+                write( "Eine Anmeldung fuer diesen Namen laeuft bereits.\n" );
+                destruct( this_object() );
+                return 1;
+            }
+
+        // Site-Banish checken
+        if ( check_illegal(loginname))
+            return 1;
+
+        if ( check_too_many_from_same_ip() )
+            return 1;
+
+        /* new character */
+        if ( sizeof(loginname) < 3 ){
+            write( "Der Name ist zu kurz.\n" );
+            loginname = "logon";
+            input_to( "logon2", INPUT_PROMPT,
+                "Versuch einen anderen Namen: ");
+            return 1;
+        }
+
+        
+        if ( (txt = (string)master()->QueryBanished(loginname)) ){
+            if ( txt != "Dieser Name ist gesperrt." )
+                txt = sprintf("Hoppla - dieser Name ist reserviert oder gesperrt "
+                    "(\"gebanisht\")!\nGrund: %s\n",txt);
+            else
+                txt = "Hoppla - dieser Name ist reserviert oder gesperrt "
+                    "(\"gebanisht\")!\n";
+            write(txt);
+            loginname = "logon";
+            input_to( "logon2", INPUT_PROMPT,
+                "Bitte gib Dir einen anderen Namen: ");
+            return 1;
+        }
+
+        /* Initialize the new secure savefile */
+        name = loginname;
+        password = "";
+        level = 0;
+        domains = ({ });
+        guilds = ({ }); 
+        shell = "";
+        ep = "";
+        ek = "";
+        mq = "";
+        ektips="";
+        fptips="";
+        creation_date = time();
+
+        input_to( "new_password", INPUT_NOECHO|INPUT_PROMPT,
+            "Waehle ein Passwort: ");
+        return 1;
+    }
+    else {
+        if ( loginname == "gast" ){
+            if ( check_illegal(loginname) )
+                return 1;
+
+            load_player_object(1);
+            return 1;
+        }
+
+        if ( neu ){
+            write( "Es existiert bereits ein Charakter dieses Namens.\n" );
+            loginname = "logon";
+            input_to( "logon2", INPUT_PROMPT,
+                "Gib Dir einen anderen Namen: ");
+            return 1;
+        }
+
+        if ( (int)master()->check_late_player(loginname) )
+        {
+            write( "Dieser Spieler hat uns leider fuer immer verlassen.\n" );
+            loginname = "logon";
+            input_to( "logon2", INPUT_PROMPT,
+                "Wie heisst Du denn (\"neu\" fuer neuen Spieler)? ");
+            return 1;
+        }
+
+        if ( txt = (string)master()->QueryTBanished(loginname) ){
+            write( txt );
+            loginname = "logon";
+            input_to( "logon2", INPUT_PROMPT,
+                "Wie heisst Du denn (\"neu\" fuer neuen Spieler)? ");
+            return 1;
+        }
+
+        if ( creation_date > (time() - 30*24*60*60)
+             && check_too_many_from_same_ip() )
+            return 1;
+
+        write( "Schoen, dass Du wieder da bist, "+capitalize(loginname)+"!\n" );
+
+        if ( !stringp(password) || password == "" ) {
+            write( "Du hast KEIN PASSWORD!\n" );
+            write( "Benutze den \"password\"-Befehl, um das zu aendern !\n" );
+            load_player_object(0);
+            return 1;
+        }
+
+        input_to( "check_password", INPUT_NOECHO|INPUT_PROMPT,
+            "Passwort: ");
+        return 1;
+    }
+}
+
+
+static int new_password( string str )
+{
+    write( "\n" );
+
+    if ( !str || str == "" )
+        return remove();
+
+    password = str;
+
+    if ( !master()->good_password( str, loginname ) ) {
+        input_to( "new_password", INPUT_NOECHO|INPUT_PROMPT,
+            "Bitte gib ein Passwort an: ");
+        return 1;
+    }
+
+    write( "\nZur Erinnerung: Es ist  v e r b o t e n, andere Spieler "
+           "anzugreifen!\n" );
+    write( "Das gilt auch fuer Froesche, bei denen \"Ein Frosch namens "
+           "XXXXX\" steht.\n\n" );
+    input_to( "again_password", INPUT_NOECHO|INPUT_PROMPT,
+        "Passwort bitte nochmal eingeben: ");
+    return 1;
+}
+
+static int again_password( string str )
+{
+    write( "\n" );
+
+    if ( str != password ){
+        write( "Die Passwoerter stimmten nicht ueberein!\n" );
+        destruct( this_object() );
+        return 1;
+    }
+
+    while ( remove_call_out( "time_out" ) != -1 )
+        ;
+    call_out( "time_out", 600 );
+
+    password = md5_crypt( password, 0 );
+    save_object( SECURESAVEPATH + loginname[0..0] + "/" + loginname );
+    master()->RemoveFromCache( loginname );
+
+    load_player_object(0);
+    return 1;
+}
+
+static int check_password( string str )
+{
+    write( "\n" );
+
+    // Invis einloggen?
+    if (sizeof(str) > 1 && str[0] == '-') {
+        invis = 1;
+        str = str[1..];
+    }
+
+    // welcher Hash ists denn?
+    if (sizeof(password) > 13) {
+        // MD5-Hash
+        str = md5_crypt(str, password);
+    }
+    else if (sizeof(password) > 2) {
+        // Crypt-Hash
+        str = crypt(str, password[0..1]);
+    }
+    else {
+        // keiner von beiden Hashes -> ungueltiges PW
+        str = 0;
+    }
+
+    if ( !stringp(str) || str != password ) {
+      // Hashes stimmen nicht ueberein -> und schluss...
+      write( "Falsches Passwort!\n");
+      if ( loginfails > 2 )
+        write(break_string(
+          "Solltest Du weiterhin Probleme mit dem Einloggen haben, kannst "
+          "Du Dein Passwort zuruecksetzen lassen, indem Du Dich als Gast "
+          "anmeldest und einen Erzmagier ansprichst, oder indem Du Dich "
+          "mittels einer E-Mail an mud@md.mud.de mit uns in Verbindung "
+          "setzt.",78));
+    
+      log_file( (level < 60 ? "LOGINFAIL" : "ARCH/LOGINFAIL"),
+        sprintf( "PASSWORD: %-11s %s, %-15s (%s)\n",           
+                  loginname, ctime(time())[4..15],
+                  query_ip_number(this_object()),           
+                  query_ip_name(this_object()) ), 200000 );
+      
+      loginfails++;
+      save_object( SECURESAVEPATH + loginname[0..0] + "/" + loginname );
+      master()->RemoveFromCache( loginname );
+      destruct( this_object() );
+      return 1;
+    }
+
+    if ( loginfails ) {
+        write( loginfails + " fehlgeschlagene" + (loginfails == 1 ? "r" : "") +
+               " Login-Versuch" + (loginfails == 1 ? "" : "e") +
+               " seit dem letzten erfolgreichen Login.\n" );
+        loginfails = 0;
+    }
+
+    save_object( SECURESAVEPATH + loginname[0..0] + "/" + loginname );
+    master()->RemoveFromCache( loginname );
+
+    load_player_object(0);
+    return 1;
+}
+
+
+static void select_race()
+{
+    int i;
+    string s;
+
+    races = get_dir( "/std/shells/*.c" );
+
+    // Mensch soll immer als erstes in der Auswahlliste stehen.
+    if (member(races,"human.c")!=-1)
+            races=({"human.c"})+(races-({"human.c"}));
+    
+    for ( i = sizeof(races); i--; ){
+        races[i] = "/std/shells/" + races[i][0..<3];
+        s = 0;
+
+        if ( catch(s = (string)call_other( races[i], "QueryAllowSelect" ); publish) 
+            || !s)
+            s = 0;
+        else if ( catch(s = (string)call_other( races[i], "QueryProp", P_RACE );publish) )
+            s = 0;
+
+        if ( !sizeof(s) )
+            races[i..i] = ({});
+        else
+            races[i] = ({ races[i], s });
+    }
+
+    if ( sizeof(races) == 1 ){
+        write( "Es gibt nur eine Rasse, Du hast also keine Wahl.\n" );
+
+        shell = races[0][0];
+        master()->set_player_object( loginname, shell );
+
+        return load_player_ob_2( shell, 0 );
+    }
+
+    return ask_mud_played_question();
+}
+
+static void ask_mud_played_question()
+{
+        write(break_string(
+                "\nWenn Du ein absoluter Neuling in diesem Spiel bist moechten "
+                "wir Dir mit einigen Tips zu Beginn beiseite stehen.\n\n",78,
+                0,BS_LEAVE_MY_LFS));
+        input_to( "get_mud_played_answer", INPUT_PROMPT,
+            "Hast Du schon einmal in einem MUD gespielt? (ja,nein): ");
+        return;
+}
+
+static void ask_race_question()
+{
+    int i, j;
+
+    write( break_string( "Du musst Dich jetzt entscheiden, welcher Rasse Du "
+                         "in dieser Welt angehoeren moechtest. Alle Rassen "
+                         "haben verschiedene Vor- und Nachteile, insgesamt "
+                         "aber gleich gute Chancen. Auch das Startgebiet "
+                         "haengt von der ausgewaehlten Rasse ab. Im "
+                         "Normalfall kann die Rasse nicht mehr gewechselt "
+                         "werden, nachdem sie einmal ausgewaehlt wurde. "
+                         "Ueberlege Dir Deine Entscheidung also gut. Derzeit "
+                         "stehen folgende Rassen zur Auswahl:\n\n", 78 ) );
+
+    for ( i = 0, j = sizeof(races); i < j; i++ )
+        printf( "% 2d. %-30s   %s", i+1, capitalize(races[i][1]),
+                (i % 2 ? "\n" : "| ") );
+
+    if ( sizeof(races) % 2 )
+        write( "\n" );
+
+    write( break_string( "\nDurch Eingabe einer Ziffer waehlst Du die Rasse "
+                         "aus, durch Eingabe eines \"\?\" gefolgt von einer "
+                         "Ziffer erhaeltst Du naehere Informationen ueber "
+                         "eine Rasse. Ein \"\?\" allein wiederholt diese "
+                         "Liste.", 78, 0, 1 ) );
+
+    if (newbie)
+    {
+            write(break_string("\nAls Neuling solltest Du Dich NICHT fuer "
+                                    "die Dunkelelfen entscheiden. Diese "
+                                    "Rasse hat einige Probleme im Umgang "
+                                    "mit den anderen Rassen und mit dem "
+                                    "Sonnenlicht.",78,0,BS_LEAVE_MY_LFS));
+    }
+
+    input_to( "get_race_answer", INPUT_PROMPT,
+        "\nWas willst Du tun: ");
+    return;
+}
+
+
+static void get_race_answer( string str )
+{
+    int num;
+
+    if ( str == "?" )
+        return ask_race_question();
+
+    if ( sscanf( str, "?%d", num ) ){
+        if ( num < 1 || num > sizeof(races) ){
+            write( "Das geht nicht.\n\n");
+            input_to( "get_race_answer", INPUT_PROMPT,
+                "Was willst Du tun: ");
+            return;
+        }
+
+        write( call_other( races[num - 1][0], "QueryProp", P_RACE_DESCRIPTION ));
+        input_to( "get_race_answer", INPUT_PROMPT,
+            "\nWas willst Du tun: ");
+        return;
+    }
+
+    if ( sscanf( str, "%d", num ) && num >= 1 && num <= sizeof(races) ){
+        write( "Ok, Du bist jetzt ein "
+            + capitalize(races[num-1][1]) + ".\n" );
+
+        shell = races[num-1][0];
+        master()->set_player_object( loginname, shell );
+        return load_player_ob_2( shell, 0 );
+    }
+
+    write("Wie bitte?\n\n" );
+    input_to( "get_race_answer", INPUT_PROMPT,
+        "Was willst Du tun: ");
+}
+
+static void get_mud_played_answer (string str)
+{
+        if ( str == "ja" || str=="j")
+                {
+                newbie=0;
+                return ask_race_question();
+                }
+        if ( str != "nein" && str!="n")
+        {
+                write("\n\nAntworte bitte mit ja oder nein.\n\n");
+                                
+                return ask_mud_played_question();
+        }
+        newbie=1;
+        write("\n\nEine kleine Einfuehrung in das "MUDNAME" bekommst "
+                        "Du auch hier:\n\n"
+                        "http://mg.mud.de/newweb/hilfe/tutorial/inhalt.shtml\n\n");
+        return ask_race_question();
+        
+}
+
+static int load_player_object( int guestflag )
+{
+    object ob;
+    string fname;
+    int was_interactive;
+
+    if ( sizeof(users()) >= 195 && !IS_WIZARD(loginname) ){
+        write( "Die maximale Spielerzahl wurde bereits erreicht!!!\n"
+               "Aus technischen Gruenden duerfen sich nur noch Magier "
+               "einloggen.\nVersuch es spaeter noch einmal ...\n" );
+        destruct( this_object() );
+        return 1;
+    }
+    else if ( sizeof(users()) >= 198 && !IS_ARCH(loginname) ){
+        write( "Die maximale Spieler- und Magierzahl wurde bereits erreicht!!!"
+               "\nAus technischen Gruenden duerfen sich nur noch Erzmagier "
+               "einloggen.\nVersuch es spaeter noch einmal ...\n" );
+        destruct( this_object() );
+        return 1;
+    }
+
+    if ( file_size("/etc/NOLOGIN")>=0 )
+    {
+      if (file_size("/etc/NOLOGIN.info")>0) {
+        //NOLOGIN.info enthaelt evtl. weitergehende Informationen fuer 
+        //Spieler, z.B. vorrauss. Wiederverfuegbarkeit.
+        write(break_string(read_file("/etc/NOLOGIN.info"),78,"",
+              BS_LEAVE_MY_LFS|BS_SINGLE_SPACE));
+      }
+      else {
+        //sonst Standardmeldung ausgeben.
+        write ("\nAufgrund von technischen Problemen ist das Einloggen ins "
+                MUDNAME" zur \nZeit nicht moeglich. Bitte versuch es "
+                      "spaeter noch einmal.\n\n");
+      }
+      if ( IS_ARCH(loginname) || 
+           member(explode(read_file("/etc/NOLOGIN")||"","\n"),
+                  loginname)!=-1 )
+      {
+        write("Im Moment koennen nur Erzmagier einloggen. Um Spieler "
+              "wieder ins Spiel zu lassen, muss die Datei '/etc/NOLOGIN' "
+              "geloescht werden.\n\n ");
+      } else {
+        destruct( this_object() );
+        return 1;                        
+      }
+    }
+
+    if ( guestflag ){
+        if ( catch(guestflag = (int)GUESTMASTER->new_guest();publish) 
+             || !guestflag ){
+            write( "Derzeit ist kein Gastlogin moeglich!\n" );
+            destruct( this_object() );
+            return 1;
+        }
+
+        loginname = "gast" + guestflag;
+        cap_name = capitalize(loginname);
+        name = cap_name;
+
+        if ( !(ob = find_player(loginname) || find_netdead(loginname)) ){
+            object *user;
+            int i;
+
+            // gegen Horden von Gast1 - wenn ein Gast noch am Prompt fuer
+            // das Geschlecht haengt, ist er ueber find_player() noch nicht
+            // zu finden ...
+            for ( i = sizeof(user = users() - ({ 0, this_object() })); i--; )
+                if ( object_name(user[i])[0..11] == "/std/shells/" &&
+                     getuid(user[i]) == loginname ){
+                    ob = user[i];
+                    break;
+                }
+        }
+
+        if ( ob ){
+            tell_object( ob, "Ein anderer Spieler moechte diesen Gastzugang "
+                         "jetzt benutzen. Wenn es Dir hier\ngefallen hat, "
+                         "ueberleg Dir doch einen Charakternamen und komm "
+                         "unter diesem\nNamen wieder!\n" );
+            destruct(ob);
+        }
+
+        load_player_ob_2( "std/shells/human", guestflag );
+
+        return 1;
+    }
+    else {
+        /* Test if we are already playing */
+        was_interactive = 0;
+        ob = find_player(loginname) || find_netdead(loginname);
+
+        if (ob) {
+            write( "Du nimmst schon am Spiel teil!\n" );
+            write( "Verwende Deine alte sterbliche Huelle ...\n" );
+
+            if ( interactive(ob) )
+            {
+                /* The other object is still interactive; reconnect that "soul"
+                   to a dummy object and destruct that, thus disconnecting the
+                   other probably linkdead user. The real "body" is still
+                   there for reconnecting by login.c */
+                remove_interactive(ob);
+                was_interactive = 1;
+            }
+            // Wenn Invislogin, P_INVIS setzen.
+            if ( invis && IS_WIZARD(ob) )
+            {
+                ob->SetProp( P_INVIS, ob->QueryProp(P_AGE) );
+                tell_object( ob, "DU BIST UNSICHTBAR!\n" );
+            }
+            /* Now reconnect to the old body */
+            exec( ob, this_object() );
+            ob->set_realip(realip);
+            if ( ((int)ob->QueryProp(P_LEVEL)) == -1 )
+                ob->start_player( cap_name );
+            else
+                ob->Reconnect( was_interactive );
+
+            call_out( "remove", 2 );
+            return 1;
+        }
+    }
+
+    /* read player object from passwd file */
+    if ( stringp(shell) && shell != "" )
+        load_player_ob_2 ( shell, 0 );
+    else
+        select_race();
+
+    return 1;
+}
+
+
+static void load_player_ob_2( string obname, int guestflag )
+{
+    object blueprint;
+    string err, ob_name;
+    object ob, old_ob;
+
+    if (!interactive()) {
+      destruct(this_object());
+      return;
+    }
+    /* start player activity */
+    log_file( "ENTER", sprintf( "%-11s %s, %-15s (%s).\n",
+                                capitalize(name), ctime(time())[4..15],
+                                query_ip_number(this_object()),
+                                query_ip_name(this_object()) ), 200000 );
+
+    seteuid(loginname);
+
+    /* load the "real" player object */
+    /* If some asshole has moved the blueprint */
+    if ( objectp(blueprint = find_object(obname))  && environment(blueprint) )
+        destruct(blueprint);
+
+    if ( err = catch(ob = clone_object(obname);publish) ){
+        log_file( "SHELLS", "Failed to load shell " + obname + ", " +
+                  dtime(time()) + ", " + loginname + "\n" + err + "\n\n" );
+
+        write( "Konnte das passende Playerobjekt nicht laden. Lade "
+               "stattdessen\ndas Objekt Mensch. BITTE ERZMAGIER "
+               "BENACHRICHTIGEN !\n" );
+        err = catch(ob = clone_object("/std/shells/human");publish);
+    }
+
+    if ( !ob || err ) {
+        write( "Error on loading " + shell + "\nError = " + err + "\n" );
+        destruct( this_object() );
+        return;
+    }
+
+    if ( guestflag )
+        catch( GUESTMASTER->set_guest( guestflag, ob );publish );
+
+    ob_name = explode( object_name(ob), "#" )[0];
+
+    if ( sizeof(ob_name) > 11 && ob_name[0..11] == "/std/shells/" )
+        ob_name = ob_name[11..];
+
+    ob_name = ob_name + ":" + loginname;
+
+  if( !guestflag )
+  {
+      if ( old_ob = find_object(ob_name) )
+      {
+          catch(old_ob->remove();publish);
+
+          if ( old_ob )
+              destruct( old_ob );
+      }
+      rename_object( ob, ob_name );
+      ob->__reload_explore();
+  }
+  exec( ob, this_object() );
+  ob->set_realip(realip);
+  ob->start_player( cap_name );
+  //Hinweis: Das Spielerobjekt holt sich in updates_after_restore() von hier
+  //den Status von invis und setzt ggf. P_INVIS
+
+  // TODO: Prop rauswerfen und in Spielern SAVE entfernen, wenn die folgenden
+  // Verwendungen entsorgt sind:
+  // /d/wueste/hirudo/goldstrand/rooms/gefaengnis.c
+  // /d/inseln/tilly/feuerinsel/obj/formular.c
+  // /d/polar/files.chaos/tilly/obj/dose_obj.c
+  ob->SetProp( "creation_date", creation_date );
+  ob->Set( "creation_date", SAVE|SECURED|PROTECTED, F_MODE_AS );
+  // wenn der Spieler noch nicht im Mud gespielt hat, wird die aktuelle Zeit
+  // in die entsprechende Prop geschrieben. Die Prop ist transient und wird
+  // absichtlich nicht gespeichert.
+  if (newbie)
+    ob->SetProp("_lib_mud_newbie", creation_date);
+
+  destruct( this_object() );
+}
+
+
+/*
+ *   With arg = 0 this function should only be entered once!
+ */
+protected void create()
+{
+    loginname = "logon";
+    creation_date = -1;
+    catch( load_object( "/secure/merlin");publish );
+    loginfails = 0;
+    realip="";
+    if (clonep())
+      set_next_reset(900);
+    else
+      set_next_reset(-1);
+}
+
+void reset()
+{
+  if (clonep())
+    remove();
+}
+
+public string short()
+{
+    return "<Einloggender Teilnehmer>.\n";
+}
+
+
+public string query_real_name()
+{
+    return "<logon>";
+}
+
+
+public nomask int query_prevent_shadow()
+{
+    return 1;
+}
+
+
+static void time_out()
+{
+    if (this_player())
+      tell_object(this_player(),"Time out!");
+    destruct( this_object() );
+}
+
+
+// Wird von simul_efuns benutzt, um nen dummy-Spielerobjekt zu erzeugen. Nicht
+// im Loginprozess involviert.
+public mixed new_logon( string str)
+{
+    seteuid(getuid()); // sonst funkt ARCH_SECURITY nicht
+
+    if ( !ARCH_SECURITY || process_call() ){
+        write( "Nur fuer Erzmagier erlaubt!\n" );
+        destruct( this_object() );
+        return -1;
+    }
+
+    if ( !str || str == "" ){
+        write( "Kein Name angegeben!\n" );
+        destruct( this_object() );
+        return 0;
+    }
+
+    str = lower_case(str);
+    cap_name = capitalize(str);
+
+    loginname = str;
+    seteuid(ROOTID);
+
+    /* read the secure save file to see if character already exists */
+    if ( !restore_object( master()->secure_savefile(loginname) ) ){
+        write( "Kein solcher Spieler!\n" );
+        destruct( this_object() );
+        return 0;
+    }
+    else {
+        write( "Ok, der Spieler " + capitalize(str) + " existiert!\n" );
+        return new_load_player_object();
+    }
+}
+
+// Wird von simul_efuns benutzt, um nen dummy-Spielerobjekt zu erzeugen. Nicht
+// im Loginprozess involviert.
+static mixed new_load_player_object()
+{
+    if ( find_player(loginname) || find_netdead(loginname) ){
+        write( "Der Spieler ist bereits online oder netztot!\n" );
+        destruct( this_object() );
+        return 2;
+    }
+
+    /* read player object from passwd file */
+    if ( stringp(shell) && shell != "" )
+        return new_load_player_ob_2( shell );
+    else {
+        write( "Keine Shell angegeben!\n" );
+        destruct( this_object() );
+        return 0;
+    }
+}
+
+// Wird von simul_efuns benutzt, um nen dummy-Spielerobjekt zu erzeugen. Nicht
+// im Loginprozess involviert.
+static mixed new_load_player_ob_2( string obname )
+{
+    object blueprint;
+    string err, ob_name;
+    object ob, old_ob;
+
+    seteuid(loginname);
+
+    /* load the "real" player object */
+    /* If some asshole has moved the blueprint */
+    if ( objectp(blueprint = find_object(obname)) && environment(blueprint) )
+        destruct( blueprint );
+
+    err = catch(ob = clone_object(obname);publish);
+
+    if ( err ){
+        log_file( "SHELLS", "Failed to load shell " + obname + ", " +
+                  dtime(time()) + ", " + loginname + "\n" + err + "\n\n" );
+
+        write( "Konnte das passende Playerobjekt nicht laden. "
+               "Lade stattdessen\ndas Objekt Mensch!\n" );
+
+        err = catch(ob = clone_object( "std/shells/human" );publish );
+    }
+
+    if ( !ob || err ){
+        write( "Error on loading " + shell + "\nError = " + err + "\n" );
+        destruct( this_object() );
+        return 0;
+    }
+
+    ob_name = explode( object_name(ob), "#" )[0];
+
+    if ( sizeof(ob_name) > 11 && ob_name[0..11] == "/std/shells/" )
+        ob_name = ob_name[11..];
+
+    ob_name = ob_name + ":" + loginname;
+
+    if ( old_ob = find_object(ob_name) ){
+        catch( old_ob->remove(); publish );
+
+        if ( old_ob )
+            destruct( old_ob );
+    }
+
+    rename_object( ob, ob_name );
+    ob->__reload_explore();
+    ob->set_realip(realip);
+    ob->start_player(cap_name);
+    ob->SetProp( "creation_date", creation_date );
+    ob->Set( "creation_date", SAVE|SECURED|PROTECTED, F_MODE_AS );
+
+    ob->move( "/room/nowhere", M_NOCHECK );
+    set_object_heart_beat( ob, 0 );
+    destruct( this_object() );
+
+    return ob;
+}
+
+string query_realip()
+{
+  return realip ? realip : "";
+}
+
+int query_invis()
+{
+  return invis;
+}
+
diff --git a/secure/mailer.c b/secure/mailer.c
new file mode 100644
index 0000000..9901fac
--- /dev/null
+++ b/secure/mailer.c
@@ -0,0 +1,632 @@
+// MorgenGrauen MUDlib
+//
+// mailer.c
+//
+// $Id: mailer.c 9547 2016-04-17 19:27:47Z Zesstra $
+
+/*
+ *------------------------------------------------------------
+ * The mail demon. Receives mail from users and delivers it into
+ * the mail directory.
+ *
+ * Deepthought, Nightfall, 25-May-92
+ * Remove-Functions : Jof, 29-June-92
+ * Caching, DeleteUnreadFolder, small changes: Loco, 08-Feb-97
+ * General clean-up and speed-up: Tiamak, 18-Jan-2000
+ *   DON'T USE restore_object any more, use GetFolders instead!
+ *------------------------------------------------------------
+ *
+ *     Save file format (sort of formal notation):
+ *
+ *     mixed *folders = ({
+ *        ({ string name1; string name2; ... string nameN; })
+ *        ({ mixed *msgs1; mixed *msgs2; ... mixed *msgsN; })
+ *     })
+ *
+ *     Each msgs field is an array of messages:
+ *
+ *     mixed *msgs = ({ mixed *message1; ... mixed *messageM })
+ *
+ *     A message is represented as an array with the following fields:
+ *
+ *     mixed *message = ({
+ *        string from;
+ *        string sender;
+ *        string recipient;
+ *        string *cc;
+ *        string *bcc;
+ *        string subject;
+ *        string date;
+ *        string id;
+ *        string body;
+ *     })
+ *
+ *     The mailer demon (/secure/mailer) provides the following functions:
+ *
+ *     string *DeliverMail(mixed *message)
+ *       Hand a mail message over to the mailer demon. The mailer
+ *       demon extracts recipients from the recipient, cc and bcc
+ *       fields and removes the bcc information. It then deposits
+ *       the message to the mail files of all recipients. A valid
+ *       message is shown above. Returns a list of successfully
+ *       delivered recipients.
+ *
+ *     int FingerMail(string user)
+ *       Gives the number of unread messages a user has.
+ *------------------------------------------------------------
+ */
+#pragma strict_types
+#pragma no_clone
+#pragma no_shadow
+#pragma no_inherit
+#pragma verbose_errors
+#pragma pedantic
+#pragma warn_deprecated
+
+#include <config.h>
+#include <mail.h>
+#include <wizlevels.h>
+
+// debugging
+#define DEBUG(msg) if ( find_player("zesstra") ) \
+                      tell_object( find_player("zesstra"), "MAILER: "+msg )
+#undef DEBUG
+#define DEBUG(x)
+
+// write out a message to the recipient
+#define NOTIFY_RECIPIENT
+// who gets undeliverable mail?
+#define BOUNCE_ADDR   "mud@mg.mud.de"
+#define SECURITY(x)   (geteuid(x) == ROOTID || geteuid(x) == MAILID)
+// flag for _DeliverMail
+#define MAIL_DELAYED   4096
+
+// prototypes
+protected void create();
+static int GetFolders( string user );
+static string _unify( string str );
+static string *unify( string *str );
+static string *expand( string *addr, int expa );
+static string _filter_addr( string addr );
+public string *DeliverMail( mixed msg, int expa );
+public int FingerMail( string user );
+static void save_msg( mixed msg, string user );
+public int RemoveMsg( int msg, int folder, string user );
+public int MoveMsg( int msg, int folder, string newfolder, string user );
+public int DeleteUnreadFolder( string user );
+public int RemoveFolder( string folder, string user );
+public int MakeFolder( string folder, string user );
+public int query_recipient_ok( string name );
+public void deliver_mail( string recipient, string from, string subject,
+                          string mail_body );
+
+
+mixed *folders;                /* used for save and restore of mail files */
+static mapping alias;
+static string cachedname; /* whose folder is still in memory? */
+
+
+protected void create()
+{
+    mixed tmp;
+    int i;
+    string s1, s2;
+  
+    seteuid(ROOTID);
+    alias=([]);
+    
+    if ( tmp = read_file("/mail/system.mailrc") ){
+        tmp = explode( tmp, "\n" );
+        
+        for ( i = sizeof(tmp); i--; )
+            if ( sscanf( tmp[i], "%s %s", s1, s2 ) == 2 )
+                alias[s1] = s2;
+    }
+}
+
+
+// GetFolders laedt einen folder, wenn er nicht im cache ist, und gibt
+// 0 zurueck, wenn der folder nicht vorhanden oder evtl auch leer ist.
+// Sinn: Vor allem bei Listenargumenten im mailer kann es leicht vorkommen,
+// dass dasselbe mailfile einige Male hintereinander gebraucht wird.
+
+static int GetFolders( string user )
+{
+    if ( user == cachedname ){
+        DEBUG( "Using cached folder for " + user + "\n" );
+        return sizeof(folders[1]);
+    }
+    
+    cachedname = user;
+    
+    if ( !restore_object( MAILPATH + "/" + user[0..0] + "/" + user ) ){
+        DEBUG( "Loading folder: " + user + " (empty)\n" );
+        folders = ({ ({}), ({}) });
+        return 0;
+    }
+    
+    DEBUG( "Loading folder:" + user + "\n" );
+    return 1;
+}
+
+
+static string _unify( string str )
+{
+    return str[0] == '\\' ? str[1..] : str;
+}
+
+
+static string *unify( string *str )
+{
+    if ( !pointerp(str) )
+        return ({});
+    
+    str = map( filter( str, #'stringp/*'*/ ), #'lower_case/*'*/ );
+    str = map( str, "_unify", this_object() );
+
+    return m_indices( mkmapping(str) );
+}
+
+
+#define MG_NAMES ({ MUDNAME, "mg", "morgengrauen", "mud", "mg.mud.de" })
+
+string expandSystemRecursive(string addr,int maxrec){
+  string *list,*tlist;
+  string ret,tmp;
+  int i,size;
+  
+  if(maxrec>8 || !addr){
+    return addr;
+  }
+  maxrec++;
+  
+  tlist=({});
+  ret="";
+
+  list=explode(addr,",");
+  list-=({""});
+  size=sizeof(list);
+  for(i=0;i<size;i++){
+    if(tmp=alias[list[i]]){
+      tlist+=explode(tmp,",");
+    }
+    else{
+      tlist+=({list[i]});
+    }
+  }
+  tlist-=({""});  
+  ret=implode(tlist,",");
+
+  if((ret!=addr)!=0){
+    ret=expandSystemRecursive(ret,maxrec);
+  }
+  return ret;
+}
+
+// expa: also do alias and forward-expansion? (for inbound external mail)
+// expa == 0 means full expansion, known flags are NO_SYSTEM_ALIASES
+// and NO_USER_ALIASES
+static string *expand( string *addr, int expa )
+{
+    string tmp, *new, *ret;
+    int i;
+    closure qf;
+
+    ret = ({});
+    addr -= ({""});
+    qf = symbol_function( "QueryForward", FWSERV );
+
+    for ( i = sizeof(addr); i--; ){
+        addr[i] = lower_case( addr[i] );
+        // @morgengrauen-namen werden lokal zugestellt.
+        if ( sizeof(new = explode( addr[i], "@" )) == 2  &&
+             member( MG_NAMES, new[1] ) != -1 )
+            addr[i] = new[0];
+
+        if ( !(expa & NO_SYSTEM_ALIASES) && tmp = expandSystemRecursive(addr[i],0) ){
+            ret += explode( tmp, "," );
+        }
+        else
+            ret += ({ addr[i] });
+    }
+    
+    for ( i = sizeof(ret); i--; ){
+        if ( ret[i][0] == '\\' )
+            ret[i] = ret[i][1..];
+        else if ( !(expa & NO_USER_ALIASES) )
+            ret = ret - ({ ret[i] }) +
+                explode( funcall( qf, ret[i] ), "," );
+    }
+    
+    return ret;
+}
+
+
+static string _filter_addr( string addr )
+{
+    addr = regreplace( addr, "[^<]*<(.*)>[^>]*", "\\1", 0);
+    return regreplace( addr, " *([^ ][^ ]*).*", "\\1", 0);
+}
+
+#ifdef INTERNET_MAIL_ENABLED
+#define FOOTER \
+    "\n*****************************************************************\n" \
+    "* MorgenGrauen MailRelay v1.0 - Processed %s, %s *\n" \
+    "* MorgenGrauen - mg.mud.de 23 -                  87.79.24.60 23 *\n" \
+    "*****************************************************************"
+#endif
+
+public string *DeliverMail( mixed msg, int expa )
+{
+    string sender, *recipients, *recok, t, *tmp;
+    mixed *newmsg;
+    int i;
+#ifdef INTERNET_MAIL_ENABLED
+    int ext;
+#endif
+
+    if ( !pointerp(msg) || sizeof(msg) != 9 )
+        return 0;
+
+    DEBUG( sprintf( "DeliverMail: %O %O\n", msg[0..4] +({0})+ msg[6..7], expa ) );
+    t = ctime(time());
+
+    // Ohne Empfaenger wird abgebrochen
+    if (!stringp(msg[MSG_RECIPIENT]))
+        return 0;
+
+    if ( !(expa & MAIL_DELAYED) ){
+        /* determine the real sender */
+        if ( extern_call() && object_name(previous_object())[0..7] != "/secure/" )
+            sender = getuid( this_interactive() || previous_object() );
+        else
+            sender = msg[MSG_SENDER];        
+
+        /* make a list of all recipients */
+        recipients = ({ msg[MSG_RECIPIENT] });
+        
+        if ( !(expa & NO_CARBON_COPIES) ){
+            if ( pointerp(msg[MSG_CC]) )
+                recipients += msg[MSG_CC];
+            
+            if ( pointerp(msg[MSG_BCC]) )
+                recipients += msg[MSG_BCC];
+        }
+
+        // Mail-Aliase ersetzen
+        recipients = expand( recipients, expa );
+    
+        // doppelte Adressen loeschen (nebenbei: auf Kleinschreibung wandeln)
+        recipients = unify( recipients );
+    
+        // Realnamen und Kommentare entfernen
+        recipients = map( recipients, "_filter_addr", this_object() );
+    
+        // auf ungueltige Zeichen checken
+        if ( sizeof(tmp = regexp( recipients, "^[-_.@a-z0-9]*$" ))
+             != sizeof(recipients) ){
+            tmp = recipients - tmp;
+            
+            for ( i = sizeof(tmp); i--; )
+                log_file( "MAIL_INVALID", sprintf( "%s: Mail von %O (%O) an "
+                                                   "'%O'.\n", dtime(time()),
+                                                   msg[MSG_FROM],
+                                                   sender, tmp[i] ) );
+            
+            recipients -= tmp;
+        }
+
+     // check for valid Subject and Body
+     if (!stringp(msg[MSG_SUBJECT]))
+         msg[MSG_SUBJECT] = "(no subject given)";
+     if (!stringp(msg[MSG_BODY]))
+         msg[MSG_BODY] =
+           "\n\nSorry - This mail was delivered without a mail body\n\n";
+
+        DEBUG( sprintf( "NEED TO DELIVER TO %O\n", recipients ) );
+
+        /* build the new message */
+        newmsg = ({ msg[MSG_FROM], sender, msg[MSG_RECIPIENT],
+                        msg[MSG_CC], "", msg[MSG_SUBJECT],
+                        dtime(time()), MUDNAME + ":" + time(),
+                        msg[MSG_BODY] });
+    }
+    
+    /* Send it off ... */
+    recok = ({ });
+
+    // ACHTUNG: durch expand() geaenderte Adressen werden zugestellt,
+    // aber durch /mail/mailer.c zusaetzlich als unzustellbar genannt!
+    
+    for ( i = sizeof(recipients); i-- /*&& get_eval_cost() > 500000*/; ){
+        DEBUG( sprintf( "Begin delivering to %s. Evalcosts left: %d.\n",
+                        recipients[i], get_eval_cost() ) );
+        if ( member( recipients[i], '@' ) > 0 &&
+             strstr( recipients[i], "daemon" ) < 0 ) {
+            string rec, mud;
+            
+            tmp = explode( recipients[i], "@" );
+            mud = tmp[1];
+            rec = tmp[0];
+            sender = regreplace( sender, "@", "%", 1 );
+            // Zustellung via Intermud-Mail an andere Muds.
+            if ( member( mud, '.' ) == -1 ) {
+                "/secure/udp_mail"->deliver_mail( rec, mud, sender,
+                                                  msg[MSG_SUBJECT],
+                                                  msg[MSG_BODY] );
+                recok += ({ recipients[i] });
+            }
+#ifdef INTERNET_MAIL_ENABLED
+            // Zustellung in den Rest des Internets.
+            else {
+                ext = 1;
+                sender = explode( sender, "%" )[0];
+                rec = explode( regreplace( rec, "@", "%", 1 ), "%" )[0];
+                mud = explode( regreplace( mud, "@", "%", 1 ), "%" )[0];
+
+                write_file( sprintf( "/mail/outbound/%s.%d-%d-%d",
+                                     sender, time(), i, random(123456) ),
+                            sprintf( "%s\n%s@%s\n"
+                            "Subject: %s\n"
+                         "X-MUD-From: %s\n"
+                         "X-MUD-To: %s\n"
+                         "X-MUD-Cc: %s\n"
+                         "X-MU-Subject: %s\n\n",
+                         sender, rec, mud,
+                                     msg[MSG_SUBJECT],
+                                     sender, recipients[0],
+                                     pointerp(msg[MSG_CC]) ?
+                                     implode( msg[MSG_CC], "," ) : "",
+                                     msg[MSG_SUBJECT] ) + msg[MSG_BODY] +
+                            sprintf( FOOTER, t[4..10] + t[20..], t[11..18] )
+                            + "\n" );
+                recok += ({ recipients[i] });
+            }
+#endif // INTERNET_MAIL_ENABLED
+
+        }
+        else
+            if ( file_size( SAVEPATH + recipients[i][0..0] + "/" +
+                            recipients[i] + ".o" ) >=0 ){
+                save_msg( newmsg, recipients[i] );
+                recok += ({ recipients[i] });
+            }
+            else {
+                string *tmpmsg = copy(newmsg);
+                tmpmsg[MSG_BODY] = "--- Text der Mail geloescht. ---\n";
+                write_file( sprintf( "/mail/outbound/postmaster.%d-%d",
+                                     time(), random(time()) ),
+                            sprintf( "postmaster\n" + BOUNCE_ADDR + 
+                                     "\nSubject: Undeliverable Mail\n%O\n",
+                                     tmpmsg) );
+            }
+        DEBUG( sprintf( "End delivering to %s. Evalcosts left: %d.\n",
+                        recipients[i], get_eval_cost() ) );
+    }
+#ifdef INTERNET_MAIL_ENABLED
+    if ( ext )
+        send_udp( UDPSERV, 4123, "DELIVERMAIL" );
+#endif
+    return recok;
+}
+
+
+public int FingerMail( string user )
+{
+    int newfolder, i;
+
+    //Zugriff beschraenken, Zahl der gelesenen Mails ist Privatsphaere
+    if (!objectp(this_interactive()) || !stringp(user) || !sizeof(user)) 
+     return(-1);
+    if ((getuid(this_interactive())!=user) &&
+     (process_call() || !ARCH_SECURITY)) return(-1);
+
+    if ( !GetFolders(user) )
+        return 0;
+
+    if ( (newfolder = member(folders[0],"unread")) != -1 )
+        return sizeof(folders[1][newfolder]);
+
+    return 0;
+}
+
+
+static void save_msg( mixed msg, string user )
+{
+    int newfolder;
+    object p;
+  
+    GetFolders( user );
+
+    /* if folder 'unread' doesn't exist, create it */
+    newfolder = member( folders[0], "unread");
+    
+    if ( newfolder == -1 ){
+        folders[0] += ({ "unread" });
+        folders[1] += ({ ({ }) });
+        newfolder = member( folders[0], "unread");
+    }
+    
+    folders[1][newfolder] += ({ msg });
+    save_object( MAILPATH + user[0..0] + "/" + user );
+#ifdef NOTIFY_RECIPIENT
+    if ( p = find_player(user) )
+        tell_object( p, "Ein Postreiter ruft Dir aus einiger Entfernung zu, "
+                     "dass Du neue Post hast!\n" );
+#endif
+}
+
+
+/* Remove a message from a folder */
+public int RemoveMsg( int msg, int folder, string user )
+{
+    if ( !SECURITY(previous_object()) )
+        return -2;
+
+    if ( !GetFolders(user) ) 
+        return -1; /* No such folder */
+
+    if ( !pointerp(folders) || !pointerp(folders[0]) || 
+         folder >= sizeof(folders[0]) )
+        return -1;
+
+    if ( msg < 0 || sizeof(folders[1][folder]) <= msg )
+        return 0; /* No such msg */
+
+    folders[1][folder][msg..msg] = ({});
+    
+    save_object( MAILPATH + user[0..0] + "/" + user );
+    return 1; /* Success */
+}
+
+
+/* Move message into another folder */
+public int MoveMsg( int msg, int folder, string newfolder, string user )
+{
+    int target;
+
+    if ( !SECURITY(previous_object()) )
+        return -2;
+
+    if ( !GetFolders(user) )
+        return -1; /* Source folder not found */
+
+    if ( !pointerp(folders) || !pointerp(folders[0]) || 
+         folder >= sizeof(folders[0]) )
+        return -1;
+  
+    if ( msg < 0 || sizeof(folders[1][folder]) <= msg )
+        return 0; /* No such msg */
+
+    if ( (target = member(folders[0], newfolder)) == -1 )
+        return -3;
+
+    if ( target == folder )
+        return 1;
+
+    if ( !pointerp(folders[1][target]) ) 
+        folders[1][target] = ({ folders[1][folder][msg] });
+    else 
+        folders[1][target] += ({ folders[1][folder][msg] });
+
+    return RemoveMsg( msg, folder, user );
+}
+
+
+public int DeleteUnreadFolder( string user )
+{
+    int unread, newmail;
+
+    if ( !SECURITY(previous_object()) )
+        return -2;
+
+    if ( !GetFolders(user) )
+        return -1; /* Source folder not found */
+
+    if ( (unread = member( folders[0], "unread")) == -1 )
+        return 0;
+    
+    if ( (newmail = member( folders[0], "newmail")) == -1 ){
+        folders[0] += ({ "newmail" });
+        folders[1] += ({({})}); 
+        newmail = sizeof(folders[1]) - 1;
+    }
+    
+    if ( !pointerp(folders[1][newmail]) )
+        folders[1][newmail] = ({});
+    
+    if ( pointerp(folders[1][unread]) )
+        folders[1][newmail] += folders[1][unread];
+    
+    folders[0][unread..unread] = ({});
+    folders[1][unread..unread] = ({});
+
+    save_object( MAILPATH + user[0..0] + "/" + user );
+    return 0;
+}
+
+
+public int RemoveFolder( string folder, string user )
+{
+    int i;
+
+    if ( !SECURITY(previous_object()) )
+        return -2;
+
+    if ( !GetFolders(user) )
+        return -1; /* No such folder */
+
+    if ( (i = member( folders[0], folder)) == -1 )
+        return -1; /* No such folder */
+
+    if ( sizeof(folders[1][i]) > 0 )
+        return 0; /* Folder not empty */
+
+    folders[0][i..i] = ({});
+    folders[1][i..i] = ({});
+
+    save_object( MAILPATH + user[0..0] + "/" + user );
+    return 1;
+}
+
+
+public int MakeFolder( string folder, string user )
+{
+    if ( !SECURITY(previous_object()) )
+        return -2;
+    
+    if ( !folder || !stringp(folder) )
+        return -1; /* Huh ? */
+
+    if ( folder == "unread" )
+        return 0; /* Folder exists virtually :) */
+
+    GetFolders( user );
+
+    if ( member( folders[0], folder) != -1 )
+        return 0; /* Folder exists */
+
+    folders[0] = folders[0] + ({ folder });
+    folders[1] = folders[1] + ({ ({}) });
+    
+    save_object( MAILPATH + user[0..0] + "/" + user );
+    return 1;
+}
+
+
+public int query_recipient_ok( string name )
+{
+#if INTERNET_MAIL_ENABLED
+    return  (file_size( "secure/save/" + name[0..0] + "/" + name + ".o" ) > 0
+        || member( name, '%' ) > 0 || member( name, '@' ) > 0 );
+#else
+    // es darf zwar ein @ in der Adresse vorkommen, dahinter aber kein . mehr
+    // (dann ist es ne Mail via Intermud-Mail, nicht ins Internet).
+    string *tmp;
+    return  (file_size( "secure/save/" + name[0..0] + "/" + name + ".o" ) > 0
+        || member( name, '%' ) > 0
+        || (sizeof(tmp=explode(name,"@")) == 2 && strstr(tmp[1],".") == -1));
+#endif
+}
+
+
+public void deliver_mail(
+                         string recipient, /* the local players real name*/
+                         string from,      /* A string depicting the sender */
+                         string subject,   /* The mail subject/header */
+                         string mail_body  /* The actual mail message */ )
+{
+    DEBUG( sprintf("DELIVER %O\n",
+                   ({ from, from, recipient, ({}), ({}), subject, time() })) );
+
+    // Geloggt wird, wenn ein aufrufendes Objekt nicht sicher ist.
+    if (object_name(previous_object())[0..7]!="/secure/")
+      write_file("/secure/ARCH/DELIVER_MAIL",
+     sprintf("%s : Aufruf von /secure/mailer->deliver_mail()\n"
+          "  Sender: %O Empfaenger: %O\n  PO: %O TI: %O TP:%O\n\n",
+          ctime(time()),from, recipient,
+          previous_object(), this_interactive(), this_player()));
+    
+    DeliverMail( ({ from, from, recipient, ({}), ({}), subject, time(),
+                    "EXTERNAL", mail_body }), 0 );
+}
diff --git a/secure/master.c b/secure/master.c
new file mode 100644
index 0000000..0574cdf
--- /dev/null
+++ b/secure/master.c
@@ -0,0 +1,1254 @@
+// MorgenGrauen MUDlib
+//
+// master.c -- master object
+//
+// $Id: master.c 9530 2016-03-17 19:59:01Z 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 "/sys/files.h"
+#include "/sys/interactive_info.h"
+#include "/sys/driver_info.h"
+
+inherit "/secure/master/misc";
+inherit "/secure/master/userinfo";
+inherit "/secure/master/network";
+inherit "/secure/master/domain";
+inherit "/secure/master/guild";
+inherit "/secure/master/file_access";
+inherit "/secure/master/players_deny";
+
+#include "/secure/master.h"
+
+//for limited, set_limit(), etc. Abs. Pfad noetig, da noch kein Include-Hook
+#include "/sys/rtlimits.h"
+#include "/sys/debug_info.h"
+#include "/sys/debug_message.h"
+#include "/sys/configuration.h"
+#include "/sys/regexp.h"
+
+// Module des Master einfuegen. Per #include, damits geringfuegig schneller.
+// Da die Module ja nirgendwo sonst geerbt werden koennten, ist dies
+// ausnahmsweise OK. ;)
+#include "/secure/master/destruct.c"
+#include "/secure/master/autoinclude.c"
+
+// Fuer Logfile(-Rotation)
+#define RESETINT   3600                      // jede Stunde
+#define LOGROTATE  (24*2)                    // alle 48h
+#define LOGFILEAGE (RESETINT*LOGROTATE)      // alle 2 tage wechseln
+#define LOGNUM     3                         // Anzahl der Logfiles
+
+private nosave int logrotate_counter; //READ_FILE alle LOGROTATE Resets rotieren
+// Wird gerade versucht, die simul_efuns zu laden? Wenn ja, duerfen keine
+// sefuns gerufen werden.
+private nosave int loading_simul_efuns;
+// wird gerade ein Fehler bearbeitet? Dient zur Erkennung von Rekursionen in
+// der Fehlerbehandlung
+private nosave int handling_error;
+
+//                       #####################
+//######################## Start des Masters ############################
+//                       #####################
+
+// Initialisierung des Masters
+//arg==0: Mud startet gerade, arg==1: Master wurde reaktiviert,
+//arg==2: Master reaktiviert (Variablen weg), arg==3: Master wurde
+//neugeladen.
+protected void inaugurate_master(int arg) {
+
+  set_driver_hook(H_REGEXP_PACKAGE, RE_TRADITIONAL);
+  
+  seteuid(ROOTID);
+  userinfo::create();
+  LoadPLDenylists();
+
+  // Was soll vor jede Datei geschrieben werden?
+  set_driver_hook(H_AUTO_INCLUDE, #'autoincludehook);
+
+  //div. Listen einlesen
+  ReloadBanishFile();
+  ReloadDeputyFile();
+  ReloadInsecureFile();
+ 
+  // Bei Neustart wizinfo initialisieren
+  if (!arg)
+    set_extra_wizinfo(0, allocate(BACKBONE_WIZINFO_SIZE));
+  
+  //simul_efun.c nach inaugurate_master() starten und initialisieren, 
+  //(so richtig seh ich den Sinn hier momentan nicht ein...
+  //call_out("start_simul_efun",0);
+
+  // Reset festsetzen (1h)
+  set_next_reset(RESETINT);
+
+  // Driver konfigurieren
+  // Lagerkennung erst ne Minute spaeter, waehrend preload darfs momentan
+  // ruhig laggen (Zeit: vorlaeufig 1min)
+  call_out(#'configure_driver, 60, DC_LONG_EXEC_TIME, 100000);
+  
+  // Hooks setzen 
+  
+  // Standardincludeverzeichnisse fuer #include <>
+  set_driver_hook(H_INCLUDE_DIRS, ({"/secure/","/sys/"}) );
+  
+  //Nach dem Laden/Clonen create() im Objekt rufen
+  set_driver_hook(H_CREATE_CLONE,       "create");
+  set_driver_hook(H_CREATE_OB,          "create");
+  set_driver_hook(H_CREATE_SUPER,       "create_super");
+
+  // Bei Reset reset() im Objekt aufrufen
+  set_driver_hook(H_RESET,              "reset");
+  
+  // Zum Aufraeumen clean_up() im Objekt aufrufen  
+  set_driver_hook(H_CLEAN_UP,           "clean_up");
+
+  // Jede Eingabe wird ueber modify_command gelenkt
+  set_driver_hook(H_MODIFY_COMMAND,       "modify_command");
+  // Dieser Hook ist mandatory, aber wird nur benutzt, wenn via
+  // set_modify_command() aktiviert - was wir nicht (mehr) tun. Insofern ist
+  // das Relikt aus alten Zeiten.
+  //set_driver_hook(H_MODIFY_COMMAND_FNAME, "modify_command");
+
+  // Standard-Fehlermeldung
+  //set_driver_hook(H_NOTIFY_FAIL,        "Wie bitte?\n");
+  set_driver_hook(H_NOTIFY_FAIL, function string (string cmd, object tp)
+      {if (stringp(cmd=(string)tp->QueryProp(P_DEFAULT_NOTIFY_FAIL)))
+         return(cmd);
+       return("Wie bitte?\n");});
+
+  // Was machen bei telnet_neg
+  set_driver_hook(H_TELNET_NEG,"telnet_neg");
+  //  set_driver_hook(H_TELNET_NEG,0);
+
+  // Promptbehandlung: Defaultprompt setzen und dafuer sorgen, dass alle
+  // Promptausgaben durch print_prompt im Interactive laufen, damit das EOR
+  // angehaengt wird.
+  set_driver_hook(H_PRINT_PROMPT, "print_prompt");
+  set_driver_hook(H_DEFAULT_PROMPT, "> ");
+
+  // EUID und UID muessen neue Objekte auch noch kriegen, Hooks dafuer setzen
+  set_driver_hook(H_LOAD_UIDS, #'load_uid_hook);
+  set_driver_hook(H_CLONE_UIDS, #'clone_uid_hook);
+
+  // Meldung bei vollem Mud
+  set_driver_hook(H_NO_IPC_SLOT, "Momentan sind leider zuviele Spieler im "
+      +MUDNAME+" eingeloggt.\nBitte komm doch etwas spaeter nochmal "
+      "vorbei!\n");
+
+  // Nun der beruechtigte Move-Hook ...
+  // (bewegt objekt und ruft alle notwendigen inits auf)
+  // Hier wird H_MOVE_OBJECT0 benutzt, d.h. die lambda wird an das aktuelle
+  // Objekt vor Ausfuehrung gebunden, nicht an 'item', was move_objet()
+  // uebergeben wurde.
+  set_driver_hook(H_MOVE_OBJECT0,
+     unbound_lambda(({'item,'dest}),
+       ({#',,                                      // item!=this_object?
+         ({#'?,({#'!=,'item,({#'this_object})}),
+           ({#'raise_error,                        // ->Fehler!
+             "Illegal to move other object than this_object()\n"}) }),
+         ({#'efun::set_environment,'item,'dest}),  // item nach dest bewegen
+         ({#'?,
+           ({#'living,'item}),                     // living(item)?
+           ({#',,
+             ({#'efun::set_this_player,'item}),    // set_this_player(item)
+             ({#'call_other,'dest,"init"}),        // dest->init()
+             ({#'?!, 
+               ({#'&&,                             // !objectp(item)||
+                 ({#'objectp, 'item}),             // env(item)!=dest?
+                 ({#'==,({#'environment, 'item}),'dest})}),
+               ({#'return})})})}),                 // -> fertig
+         ({#'=,'others,({#'-,({#'all_inventory,'dest}),({#'({,'item})})}),
+#ifdef EMACS_NERV 
+         })   // Emacs kann ({#'({ nicht parsen    // others=all_inv(dest)-
+#endif                                             //        ({item})
+         ({#'filter,'others,lambda(({'ob,'item}),
+            ({#'?,                                     
+              ({#'&&,
+                ({#'objectp,'item}),               // objectp(item)&&
+                ({#'living,'ob}),                  // living(ob)&&
+                ({#'==,({#'environment,'item}),({#'environment,'ob})})}),
+              ({#',,                               // env(item)==env(ob)?
+                ({#'efun::set_this_player, 'ob}),  // set_this_player(ob)
+                ({#'call_other,'item, "init"})     // item->init()
+              })})),'item}),
+         ({#'?,
+           ({#'living,'item}),                     // living(item)?
+              ({#',,
+                ({#'efun::set_this_player,'item}), // set_this_player(item)
+                ({#'filter,'others,lambda(({'ob,'item}),
+                    ({#'?,                          
+                      ({#'&&,                 
+                        ({#'objectp,'item}),       // objectp(item)&&
+                        ({#'objectp,'ob}),         // objectp(ob)&&
+                        ({#'==,({#'environment,'item}),({#'environment,'ob})})
+                      }),                          // env(item)==env(ob)?
+                      ({#'call_other,'ob,"init"})  // ob->init()
+                    })),'item})})}),
+         ({#'?,
+           ({#'&&,
+             ({#'objectp,'item}),                  // objectp(item)&&
+             ({#'living,'dest}),                   // living(dest)&&
+             ({#'==,({#'environment,'item}),'dest})// env(item)==dest?
+           }),
+           ({#',,
+             ({#'efun::set_this_player,'dest}),    // set_this_player(dest)
+             ({#'call_other,'item,"init"})})})})));// item->init()
+  DEBUG("Master inaugurated");
+  return;
+}
+
+// epilog() gibt die Dateien zurueck, die vorgeladen werden muessen
+protected string *epilog(int eflag) {
+  string *files, *domains;
+  int i;
+
+  seteuid(ROOTID);
+  ReloadBanishFile();
+  ReloadDeputyFile();
+  ReloadInsecureFile();
+
+  if (eflag) {
+    write("-e angegeben -> Preloading unterdrueckt ...\n");
+    return 0;
+  }
+
+  printf("Preloading gestartet: %s\n\n",ctime(time()));
+
+  //Files fuers Preloading und Regionen holen
+  files=explode_files("/std/preload_file") + explode_files("/d/preload_file");
+  domains=get_domains() + ({"erzmagier"}); // EM haben auch ein Preload File
+
+  for (i=sizeof(domains);i--;)
+    files+=explode_files("/d/"+domains[i]+"/preload_file");
+  
+  write("\n");
+ 
+  return files;
+}
+
+// preload() wird fuer jeder Datei im Preload einmal durchlaufen
+protected void preload(string file) {
+  string err;
+  string name;
+  int *res, zeit;
+
+  // Kein richtiger Dateiname: fertig
+  if(!file || !file[0] || file[0] == ';' || file_size(file+".c") < 0) return;
+
+  // Kein Besitzer -> Meldung ausgeben, fertig
+  if (!(name=creator_file(file)))
+  {
+    printf("Kein Besitzer gefunden fuer Datei %s.\n",file);
+    return;
+  }
+
+  // Datei und Besitzer ausgeben
+  printf("%-50s%-15s",file,name);
+
+  // Euid kann nicht geaendert werden -> Meldung, fertig
+  if (!seteuid(name))
+  {
+    printf("\nEuid konnte nicht auf %s gesetzt werden\n"+
+             "Sie ist nun %s.\n",name,geteuid(TO));
+    return;
+  }
+
+  // Dann mal laden .. dabei Zeit messen
+  zeit = apply(#'+,rusage()[0..1]);
+
+  // Waehrend des Preloads sind 1.5MTicks leider nicht soviel, insb. wenn
+  // sowas wie der channeld jede menge Objekte laden muss, die wiederum erst
+  // das ganze Geraffel aus /std/ laden. Daher hier einfach mal fuers Preload
+  // die Grenze hoch.
+  err = catch(limited(#'load_object,({5000000}),file));
+
+  if (err != 0)
+    printf("\nFehler beim Laden von %s:\n%s\n",file,err);
+  else
+  {
+    zeit=apply(#'+,rusage()[0..1])-zeit;
+    printf("(%2d.%:02d s)\n",zeit/1000,zeit%1000);
+  }
+
+  // Noch EUID zuruecksetzen
+  seteuid(ROOTID);
+
+  return;
+}
+
+//simul_efun.c laden oder die spare_simul_efun.c als Fallback
+//es ist moeglich, hier ein Array von strings zurueckzugeben. Die
+//nachfolgenden gelten als Backup-Objekte, falls eine sefun im Hauptobjekt
+//nicht gefunden wird.
+protected string *get_simul_efun() {
+  string err;
+
+  ++loading_simul_efuns;
+
+  if (!(err=catch(SIMUL_EFUN_FILE->start_simul_efun())) ) {
+    --loading_simul_efuns;
+    return ({SIMUL_EFUN_FILE});
+  }
+  
+  write("Failed to load simul efun " + SIMUL_EFUN_FILE + "\n");
+  debug_message("Failed to load simul efun " + SIMUL_EFUN_FILE +
+      " " + err, DMSG_STDOUT | DMSG_LOGFILE);
+
+  if (!(err=catch(SPARE_SIMUL_EFUN_FILE->start_simul_efun())) ) {
+    --loading_simul_efuns;
+    return ({SPARE_SIMUL_EFUN_FILE});
+  }
+  
+  write("Failed to load spare simul efun" + SPARE_SIMUL_EFUN_FILE + "\n");
+  debug_message("Failed to load spare simul efun " + SPARE_SIMUL_EFUN_FILE +
+      " " + err, DMSG_STDOUT | DMSG_LOGFILE);
+
+ 
+  efun::shutdown();
+  
+  return 0;
+}
+
+protected mixed call_sefun(string sefun, varargs mixed args) {
+  if (!loading_simul_efuns)
+    return apply(symbol_function(sefun), args);
+  else {
+    switch(sefun) {
+      case "log_file":
+      case "secure_level":
+      case "secure_euid":
+      case "find_player":
+      case "find_netdead":
+      case "find_living":
+      case "process_call":
+      case "replace_personal":
+      case "file_time":
+        return 0;
+      case "dtime":
+        if (pointerp(args) && sizeof(args))
+          return strftime("%a %d. %b %Y, %H:%M:%S",args[0]);
+        else
+          return strftime("%a %d. %b %Y, %H:%M:%S");
+      case "uptime":
+        return to_string(time()-__BOOT_TIME__) + " Sekunden";
+    }
+  }
+  return 0;
+}
+
+
+// Preload manuell wiederholen; Keine GD-Funktion
+void redo_preload()
+{
+  string *to_preload;
+  int i,j;
+
+  to_preload=epilog(0);
+  j=sizeof(to_preload);
+  for (i=0;i<j;i++) 
+      catch(preload(to_preload[i]));
+  return;
+}
+
+//                         ##################
+//########################## Standard-Lfuns #############################
+//                         ##################
+
+// Keine GD-Funktion, aber sinnvoll.
+public varargs int remove(int silent)
+{
+  write("Der Master will aber nicht zerstoert werden!\n");
+  return 0;
+}
+
+// Einige repetitiven Taetigkeiten kann der Reset erledigen
+protected void reset()
+{
+  int i, *date;
+  mixed *wl;
+
+  //Darf nicht von Hand aufgerufen werden!
+  if (TI||TP) return;
+  
+  // Naechsten Reset setzen
+  set_next_reset(RESETINT);
+
+  // Userinfo aufraeumen
+  _cleanup_uinfo();
+
+  // Projekt-Cache loeschen
+  _cleanup_projects();
+
+  // Wenn Zeit abgelaufen, dann READ_FILE-Log rotieren
+  if (!logrotate_counter--)
+  {
+    i=time()/LOGFILEAGE;
+    date=get_dir(sprintf("%s.%d", READ_FILE,i%LOGNUM), GETDIR_DATES);
+    if (pointerp(date)&&sizeof(date)&&
+        (date[0]/LOGFILEAGE)==i) return;
+    if (file_size(READ_FILE)>0)
+      rename(READ_FILE, sprintf("%s.%d", READ_FILE,i%LOGNUM));
+    logrotate_counter=LOGROTATE;
+  }
+  // einmal am Tag die UIDAliase resetten. Hierzu wird der logrotate_counter
+  // missbraucht.
+  if (!(logrotate_counter%24)) {
+    ResetUIDAliase();
+#ifdef _PUREFTPD_
+    expire_ftp_user();
+#endif
+  }
+
+  // Wizlist altert
+  wl = wizlist_info();
+  for (i=sizeof(wl); i--; )
+    set_extra_wizinfo(wl[i][WL_NAME], wl[i][WL_EXTRA] * 99 / 100);
+
+  // Dann noch Mails bearbeiten
+  mails_last_hour=0;
+  mailread();
+
+  return;
+}
+
+
+//                         #################
+//########################## ID-Management ##############################
+//                         #################
+
+// Magier-ID fuer Datei str (als Objekt oder als String) ermitteln
+string creator_file(mixed str) {
+  string *strs,tmp;
+  int s;
+
+  // full_path_array nach strs
+  // TODO: was ist mit clones?
+  if(objectp(str))
+    strs=full_path_array(object_name(str), 0);
+  else if(stringp(str))
+    strs=full_path_array(str, 0);
+  else return NOBODY;
+
+  // absolute Pfade interessieren hier gerade nicht.
+  strs -= ({""});
+
+  s=sizeof(strs);
+  if(s<2) return NOBODY;
+
+  switch(strs[0]) {
+    case DOMAINDIR:
+      // Fuer Nicht-Magier bzw. "Pseudo-"Magier die Regionskennung
+      if( s==2 || WIZLVLS[strs[2]] || !IS_LEARNER(strs[2]) )
+        return strs[1];
+
+      // in /d/erzmagier gibt es Magier-IDs
+      if (strs[1]=="erzmagier") return strs[2];
+
+      // Ansonsten d.region.magiername
+      return sprintf("d.%s.%s", strs[1], strs[2]);
+ 
+    case PROJECTDIR:
+      // In p.service gibt es ids mit uid
+      if (s>2 && strs[1]=="service") return "p.service."+strs[2];
+
+      // Ansonsten nur p.projekt (p.service ist auch hier drin)
+      return "p."+strs[1];
+
+    case WIZARDDIR:
+      if (s>2)
+        return strs[1];
+      if (s==2 && file_size(strs[0]+"/"+strs[1])==FSIZE_DIR)
+        return strs[1];
+      return NOBODY;
+
+    case SECUREDIR:
+      return ROOTID;
+
+    case GUILDDIR:
+    case SPELLBOOKDIR:
+      tmp=strs[1];
+      if (tmp[<2..<2]==".") tmp=tmp[0..<3];
+      if ((s=member(tmp,'.'))>0) tmp=tmp[s+1..];
+      return "GUILD."+tmp;
+
+    case LIBROOMDIR:
+      return ROOMID;
+
+    case STDDIR:
+    case LIBOBJDIR:
+      return BACKBONEID;
+
+    case DOCDIR:
+      return DOCID;
+
+    case MAILDIR:
+      return MAILID;
+    case NEWSDIR:
+      return NEWSID;
+
+    case LIBITEMDIR:
+      return ITEMID;
+
+    /* Fall-Through */
+    default:
+      return NOBODY;
+
+ }
+ return NOBODY;  //should never be reached.
+}
+
+// UID und EUID an Objekt geben (oder eben nicht)
+// Keine GD-Funktion, aber von Hooks aufgerufen
+protected mixed give_uid_to_object(string datei,object po)
+{
+  string creator,pouid;
+
+  // Parameter testen
+  if (!stringp(datei)||!objectp(po))  return 1;
+
+  // Keine Objekte in /log, /open oder /data
+  if (strstr(datei, "/"LIBDATADIR"/") == 0
+      || strstr(datei, "/"LIBLOGDIR"/") == 0
+      || strstr(datei, "/"FTPDIR"/") == 0
+      )
+      return 1;
+
+  // Datei muss Besitzer haben
+  if (!(creator=creator_file(datei))) return 1;
+
+  // Hack, damit simul_efun geladen werden kann
+  if (creator==ROOTID && po==TO) return ROOTID;
+
+  // Keine euid, kein Laden ...
+  if (!(pouid=geteuid(po))) return 1; // Disallow if no euid in po
+
+  // Root generiert keine Root-Objekte ;-)
+  // Nur UID setzen
+  if (pouid==ROOTID)
+    return ({creator,NOBODY}); // root does not create root objects!
+
+  // EUID mitsetzen, wenn PO==creator oder creator==BACKBONEID
+  if (creator==pouid || creator==BACKBONEID)
+    return pouid;
+
+  return ({creator,NOBODY});
+}
+
+// Die System-IDs muessen bekannt sein
+string get_master_uid()          { return ROOTID;}
+string get_bb_uid()              { return BACKBONEID; }
+// TODO: Bei get_wiz_name koennte man - so moeglich - auf 'wirkliche'
+//       Magiernamen gehen (Idee von mandragon)
+string get_wiz_name(string file) { return creator_file(file);}
+
+//                     ##########################
+//###################### Sonstige GD-Funktionen #########################
+//                     ##########################
+
+// Spieler hat sich eingeloggt
+protected object connect()
+{
+  string err;
+  int errno;
+  object ob,bp;
+
+#if defined(SSLPORT) && __EFUN_DEFINED__(tls_available)
+  if (efun::interactive_info(this_player(), II_MUD_PORT) == SSLPORT) {
+    // reject connection of TLS is not available
+    if (!tls_available())
+      return 0;
+    // establish TLS
+    errno=tls_init_connection();
+    if (errno < 0) {
+      // Fehler im Vebindungsaufbau
+      printf("Can't establish a TLS/SSL encrypted connection: %s\n",
+          tls_error(errno));
+      return 0; // this will close the connection to the client.
+    }
+  }
+#endif
+
+  // Blueprint im Environment? Das geht nun wirklich nicht ...
+  if ((bp=find_object("/secure/login")) && environment(bp)) 
+      catch(destruct(bp);publish);
+
+  // Login-Shell clonen
+  err = catch(ob = clone_object("secure/login"); publish);
+
+  if (errno == 0 && err)
+      write("Fehler beim Laden von /secure/login.c\n"+err+"\n");
+
+  return ob;
+}
+
+// Was machen bei disconnect?
+protected void disconnect(object who, string remaining) {
+    who->NetDead(); return;
+}
+
+// Es gibt kein File 'filename'. VC aktivieren so vorhanden ...
+protected object compile_object(string filename)
+{
+  object ret;
+  string *str;
+  string compiler;
+
+  if (!sizeof(filename)) return 0;
+  str=efun::explode(filename,"/");
+
+  if(sizeof(str)<2) return 0;
+  compiler=implode(str[0..<2],"/")+"/virtual_compiler";
+ 
+  if (find_object(compiler)
+      || file_size(compiler+".c")>0)
+  {
+    if(catch(
+          ret=(object)call_other(compiler,"compile_object",str[<1]); publish))
+      return 0;
+  }
+  else
+      return(0);
+
+  if ( objectp(ret) && efun::explode(creator_file(filename), ".")[<1] !=
+       REAL_EUID(ret) ){
+      funcall( symbol_function('log_file/*'*/), "ARCH/VC_MISSBRAUCH",
+               sprintf( "%s: %s versucht per VC %s zu %s umzubenennen!\n",
+                        funcall( symbol_function('dtime/*'*/), time() ),
+                        (this_interactive() ? getuid(this_interactive()):
+                         object_name(previous_object())), object_name(ret),
+                         filename) );
+
+      return 0;
+  }
+  return ret;
+}
+
+//                           ############
+//############################ Shutdown #################################
+//                           ############
+
+// Spieler bei Shutdown entfernen
+protected void remove_player(object victim)
+{
+  catch(victim->quit());
+  if (victim) catch(victim->remove());
+  if (victim) destruct(victim);
+  return;
+}
+
+// Langsamer Shutdown 
+protected void slow_shut_down(int minutes)
+{
+  //sollte nur vom GD gerufen werden. Oder allenfalls Master selbst
+  filter(users(),#'tell_object,
+  "Der Gamedriver ruft: Der Speicher wird knapp ! Bereitet euch auf das Ende vor !\n");
+  "/obj/shut"->shut(minutes);
+  return;
+}
+
+
+// In LD varargs void notify_shutdown(string crash_reason)
+protected varargs void notify_shutdown (string crash_reason) {
+
+  if (PO && PO != TO)
+    return;
+  if (crash_reason) {
+      //Uhoh... Verdammter Crash. :-( Zumindest mal mitloggen,
+      //was der Driver sagt...
+      write_file(CRASH_LOG,sprintf(
+            "\n%s: The driver crashed! Reason: %s\n",
+            ctime(time()),crash_reason));
+  }
+
+  filter(users(), #'tell_object,
+               "Game driver shouts: LDmud shutting down immediately.\n");
+  save_wiz_file();
+  return;
+}
+
+//                         ##################
+//########################## Berechtigungen #############################
+//                         ##################
+
+// Darf Verbindung durch name von obfrom nach ob gelegt werden?
+
+//int valid_exec(string name) {return 1;}
+
+int valid_exec(string name, object ob, object obfrom)
+{
+  // Ungueltige Parameter oder Aufruf durch process_string -> Abbruch
+  if (!objectp(ob) || !objectp(obfrom) 
+      || !stringp(name) || !sizeof(name)
+      || funcall(symbol_function('process_call)) )
+    return 0;
+
+  // renew_player_object() darf ...
+  if (name=="/secure/master/misc.c"
+      || name=="secure/master/misc.c")
+    return 1;
+
+  // Ansonsten darf sich nur die Shell selber aendern ...
+  if (previous_object() != obfrom) return 0;
+
+  // Die Login-Shell zu jedem Objekt ...
+  if (name=="/secure/login.c"
+      || name=="secure/login.c")
+    return 1;
+
+  // Magier per exec nur, wenn sie damit keine Fremde uid/euid bekommen
+  if (this_interactive() == obfrom && getuid(obfrom) == getuid(ob) 
+      && geteuid(obfrom) == geteuid(ob)) 
+      return 1;
+
+  // Sonst darf niemand was
+  return 0;
+}
+
+
+// Darf me you snoopen?
+int valid_snoop(object me, object you) { 
+    return getuid(PO) == ROOTID; 
+}
+
+// Darf wiz wissen, ob er gesnoopt wird?
+int valid_query_snoop(object wiz) {
+    return getuid(PO) == ROOTID; 
+}
+
+// Darf ob seine EUID auf neweuid setzen?
+int valid_seteuid(object ob, string neweuid)
+{
+  return (ob==this_object() ||
+          getuid(ob) == ROOTID ||
+          getuid(ob) == neweuid ||
+          creator_file(object_name(ob)) == neweuid);
+}
+
+// Darf getraced werden?
+int valid_trace(string what, mixed arg) { 
+    return IS_ARCH(TI);
+}
+
+
+// valid_write() und
+// valid_read() befinden sich in file_access.c
+
+
+// Darf PO ob shadowen?
+int query_allow_shadow(object ob)
+{
+  // ROOT darf nicht geshadowed werden
+  if(getuid(ob) == ROOTID)
+    return 0;
+  
+  // Access-Rights auch nicht
+  if (efun::explode(object_name(ob),"#")[0][<14..]=="/access_rights")
+    return 0;
+  
+  // Ansonsten query_prevent_shadow fragen ...
+  return !(int)ob->query_prevent_shadow(PO);
+}
+
+
+/* privilege_violation is called when objects try to do illegal things,
+ * or files being compiled request a privileged efun.
+ *
+ * return values:
+ *   1: The caller/file is allowed to use the privilege.
+ *   0: The caller was probably misleaded; try to fix the error.
+ *  -1: A real privilege violation. Handle it as error.
+ */
+int privilege_violation(string op, mixed who, mixed arg1, mixed arg2)
+{
+
+  if (objectp(who) && 
+      (who==this_object() || geteuid(who)==ROOTID))
+    return 1; 
+
+  switch(op)
+  {
+    case "call_out_info":
+      return -1;
+    case "send_udp":
+      return 1;
+
+    case "nomask simul_efun":
+      // <who> ist in diesem Fall ein string (Filename) und damit von dem
+      // Check da oben nicht abgedeckt. Daher explizite Behandlung hier.
+      // Ausserdem hat der Pfad (zur Zeit noch) keinen fuehrenden '/'.
+      // Falls das jemand einschraenken will: der Kram fuer die simul_efuns
+      // muss das duerfen!
+      if (stringp(who)
+          && strstr(who,"secure/") == 0)
+        return 1;
+      return 0; // alle andere nicht.
+
+    case "get_extra_wizinfo":
+      // benutzt von /secure/memory.c und /secure/simul_efun.c
+    case "set_extra_wizinfo":
+      // wird benutzt von simul_efun.c, welche aber als ROOT-Objekt implizit
+      // erlaubt ist (s.o.)
+      return -1; // fuer allen nicht erlaubt.
+
+    case "rename_object":
+      if (object_name(who)=="/secure/impfetch" ||
+          BLUE_NAME(who)=="/secure/login")
+        return 1;
+      return(-1);
+
+    case "configure_driver": 
+      return call_sefun("secure_level") >= ARCH_LVL;
+
+    case "limited":
+      // Spielershells duerfen sich zusaetzliche Ticks fuer den Aufruf von
+      // ::die() beschaffen, damit der Tod nicht wegen wenig Ticks buggt.
+      if (strstr(load_name(who), "/std/shells/") == 0
+          && get_type_info(arg1, 2) == who
+          && get_type_info(arg1, 3) == "/std/living/life"
+//          && get_type_info(arg1, 4) == "die"
+          && arg2[LIMIT_EVAL] <= 10000000 ) {
+          return 1;
+      }
+      //DEBUG(sprintf("%O, %O, %O", who, arg1, arg2));
+      int *limits=efun::driver_info(DI_CURRENT_RUNTIME_LIMITS);
+      // LIMIT_EVAL darf verringert werden. Alle anderen Limits duerfen nicht
+      // geaendert werden. Achja, LIMIT_EVAL darf nicht 0 (LIMIT_UNLIMITED)
+      // sein. *g*
+      // LIMIT_COST darf entweder 0 oder -100 sein.
+      if (arg2[LIMIT_EVAL]>1000 && (arg2[LIMIT_EVAL] < get_eval_cost())-500
+          && arg2[LIMIT_ARRAY] == limits[LIMIT_ARRAY]
+          && arg2[LIMIT_MAPPING_KEYS] == limits[LIMIT_MAPPING_KEYS]
+          && arg2[LIMIT_MAPPING_SIZE] == limits[LIMIT_MAPPING_SIZE]
+          && arg2[LIMIT_BYTE] == limits[LIMIT_BYTE]
+          && arg2[LIMIT_FILE] == limits[LIMIT_FILE]
+          && arg2[LIMIT_CALLOUTS] == limits[LIMIT_CALLOUTS]
+          && (arg2[LIMIT_COST] == 0 || arg2[LIMIT_COST] == -100) )
+            return(1);
+      //sonst verweigern.
+      return(-1);
+
+    case "sqlite_pragma":
+      return 1;
+    case "attach_erq_demon":
+    case "bind_lambda": 
+    case "configure_interactive":
+    case "configure_object":
+    case "execute_command":
+    case "erq": 
+    case "input_to":
+    case "mysql":
+    case "net_connect":
+    case "pgsql":
+    case "set_driver_hook":
+    case "set_this_object":
+    case "shadow_add_action":
+    case "symbol_variable":
+    case "variable_list":
+    case "wizlist_info":
+    default:
+      return(-1);
+  }
+  return -1;
+}
+
+//                       ####################
+//######################## Fehlerbehandlung #############################
+//                       ####################
+
+// Behandlung von Fehler im Heart_Beat
+protected int heart_beat_error( object culprit, string error, string program,
+                     string current_object, int line, int caught)
+{
+  if (!objectp(culprit)) return 0;
+  if (interactive(culprit))
+  {
+    tell_object(culprit,"Der GameDriver teilt Dir mit: "
+                "Dein Herzschlag hat ausgesetzt!\n");
+    if (IS_LEARNER(culprit))
+      tell_object(culprit,
+                  sprintf("Fehler: %sProgamm: %s, CurrObj: %s, Zeile: %d, "
+                    "the error was %scaught at higher level.\n",
+                    error,program,current_object,line,
+                    caught ? "" : "not"));
+  }
+
+  if (efun::object_info(culprit, OI_ONCE_INTERACTIVE))
+    call_out("restart_heart_beat", 5, culprit);
+  else
+    catch(culprit->make_immortal(); publish);
+  return 0;
+}
+
+// Ausgabe einer Meldung an Spieler mit Level >= minlevel. Wird genutzt, um
+// Magier ueber Fehler zu informieren, die nicht normal behandelt werden
+// (z.B. rekursive Fehler, d.h. Fehler in der Fehlerbehandlung)
+private void tell_players(string msg, int minlevel) {
+  msg = efun::sprintf("%=-78s","Master: " + msg); // umbrechen
+  efun::filter(efun::users(), function int (object pl)
+      {
+        if (query_wiz_level(pl) >= minlevel)
+          efun::tell_object(pl, msg);
+        return 0;
+      } );
+}
+
+/**
+ * \brief Error handling fuer Fehler beim Compilieren.
+ *
+ * log_error() wird vom GameDriver aufgerufen, wenn zur Compile-Zeit ein
+ * Fehler auftrat. Die Fehlermeldung wird unter /log/error geloggt.
+ * Falls this_interactive() ein Magier ist, bekommt er die Fehlermeldung
+ * direkt angezeigt.
+ * Sollte das Logfile 50kB ueberschreiten, wird es rotiert. Auf [Entwicklung]
+ * wird dann eine Fehlermeldung abgesetzt.
+ *
+ * @param file Die Datei, in der der Fehler auftrat.
+ * @param message Die Fehlermeldung.
+ * @param warn Warnung (warn!=0) oder Fehler (warn==0)?
+ * */
+
+protected void log_error(string file, string message, int warn) {
+  string lfile;
+  string cr;
+  mixed *lfile_size;
+
+  if (handling_error == efun::time()) {
+    // Fehler im Verlauf einer Fehlerbehandlung: Fehlerbehandlung minimieren,
+    // nur Meldung an eingeloggte Erzmagier.
+    tell_players("log_error(): Rekursiver Fehler in Fehlerbehandlung. "
+        "Bitte Debuglog beachten. Aktueller Fehler in " + file + ": "
+        + message, ARCH_LVL);
+    return;
+  }
+  handling_error = efun::time();
+
+ //Fehlerdaten an den Errord zur Speicherung weitergeben, falls wir sowas
+ //haben.
+#ifdef ERRORD
+  // Only call the errord if we are _not_ compiling the sefuns. It uses sefuns
+  // and it will cause a recursing call to get_simul_efuns() which is bound to
+  // fail.
+  if (!loading_simul_efuns) {
+    catch(limited(#'call_other,({200000}),ERRORD,"LogCompileProblem",
+          file,message,warn);publish );
+  }
+#endif // ERRORD
+
+ // Logfile bestimmen
+  cr=creator_file(file);
+  if (!cr)                     lfile="NOBODY.err";
+  else if (cr==ROOTID)         lfile="ROOT";
+  else if (efun::member(cr,' ')!=-1) lfile="STD";
+  else                         lfile=cr;
+  //je nach Warnung oder Fehler Verzeichnis und Suffix bestimmen
+  if (warn) {
+      lfile="/log/warning/" + lfile + ".warn";
+  }
+  else {
+      lfile="/log/error/" + lfile + ".err";
+  }
+
+  // Bei Bedarf Rotieren des Logfiles
+  if ( !loading_simul_efuns
+      && sizeof(lfile_size = get_dir(lfile,2))
+      && lfile_size[0] >= MAX_ERRLOG_SIZE )
+  {
+    catch(rename(lfile, lfile + ".old"); publish); /* No panic if failure */
+    if (!loading_simul_efuns)
+    {
+      catch(send_channel_msg("Entwicklung","<MasteR>",
+                              "Logfile rotiert: "+lfile+"."));
+    }
+  }
+
+  efun::write_file(lfile,message);
+
+  // Magier bekommen die Fehlermeldung angezeigt
+  if (IS_LEARNER(TI)) efun::tell_object(TI, message);
+
+  handling_error = 0;
+}
+
+/* Gegenmassnahme gegen 'too long eval' im Handler vom runtime error:
+   Aufsplitten des Handlers und Nutzung von limited() */
+//keine GD-Funktion
+private void handle_runtime_error(string err, string prg, string curobj,
+    int line, mixed culprit, int caught, object po, int issueid)
+{
+  string code;
+  string debug_txt;
+  object ob, titp; 
+
+  //DEBUG(sprintf("Callerstack: (handle_runtime_error()): %O\n",
+  //        caller_stack(1)));
+  // Fehlermeldung bauen
+  if (!stringp(err))    err="<NULL>";
+  if (!stringp(prg))    prg="<NULL>";
+  if (!stringp(curobj)) curobj="<NULL>";
+  debug_txt=efun::sprintf("Fehler: %O, Objekt: %s, Programm: %O, Zeile %O (%s), "
+                    "ID: %d",
+                    err[0..<2], curobj, (prg[0]!='<'?"/":"")+prg, line,
+                    (TI?efun::capitalize(efun::getuid(TI)):"<Unbekannt>"),
+                    issueid);
+
+  titp = TI || TP;
+  // Fehlermeldung an Kanaele schicken, aber nur wenn der aktuelle Fehler
+  // nicht waehrend des ladens der simulefuns auftrat, bei der Ausgabe der
+  // Meldung ueber die Kaenaele wieder sefuns genutzt werden.
+  if (!loading_simul_efuns) {
+    if (titp && (IS_LEARNER(titp) || (titp->QueryProp(P_TESTPLAYER))))
+    {
+      catch(send_channel_msg("Entwicklung",
+                             capitalize(objectp(po) ? REAL_UID(po) : ""),
+                             debug_txt));
+    }
+    else
+    {
+      catch(send_channel_msg("Debug",
+                             capitalize(objectp(po) ? REAL_UID(po) : ""),
+                             debug_txt));
+    }
+  }
+
+  if (!titp) return;
+
+  // Fehlermeldung an Benutzer
+  if (IS_LEARNER(titp))
+    efun::tell_object(titp,
+                efun::sprintf("%'-'78.78s\nfile: %s line: %d object: %s\n" +
+                        "%serror: %s%'-'78.78s\n","",prg,line,curobj,
+                        (prg&&(code=efun::read_file("/"+prg,line,1))?
+                         "\n"+code+"\n":""),err,""));
+  else
+    efun::tell_object(titp, "Du siehst einen Fehler im Raum-Zeit-Gefuege.\n");
+}
+
+//Keine GD-Funktion, limitiert die Kosten fuer den handler
+private void call_runtime_error(string err, string prg, string curobj,
+    int line, mixed culprit, int caught, object po)
+{
+  if (handling_error == efun::time())
+  {
+    // Fehler im Verlauf einer Fehlerbehandlung: Fehlerbehandlung minimieren,
+    // nur Meldung an eingeloggte Regionsmagier.
+    tell_players("call_runtime_error(): Rekursiver Fehler in "
+        "Fehlerbehandlung. Bitte Debuglog beachten. Aktueller Fehler in "
+        + curobj + ": " + err, LORD_LVL);
+    return;
+  }
+  handling_error = efun::time();
+
+  //Fehlerdaten an den Errord zur Speicherung weitergeben, falls wir sowas
+  //haben.
+#ifdef ERRORD
+  int issueid;
+  // Wenn die sefuns gerade geladen werden, erfolgt keine Weitergabe an den
+  // ErrorD, da dabei wieder sefuns gerufen werden, was zu einer Rekursion
+  // fuehrt.
+  if (!loading_simul_efuns) {
+    catch(issueid=efun::limited(#'call_other,({200000}),ERRORD,"LogError",
+          err,prg,curobj,line,culprit,caught);publish);
+  }
+#endif // ERRORD
+
+  //eigenen Errorhandler laufzeitbegrenzt rufen
+  efun::limited(#'handle_runtime_error, ({ 200000 }), err, prg, curobj,
+        line,culprit,caught, po, issueid);
+
+  handling_error = 0;
+}
+
+// Laufzeitfehlerbehandlung, hebt Evalcost-Beschraenkung auf und reicht weiter
+// an call_runtime_error, die die Grenze wieder auf einen bestimmten Wert
+// festlegt.
+protected void runtime_error(string err ,string prg, string curobj, int line,
+     mixed culprit, int caught) {
+
+    //DEBUG(sprintf("Callerstack: (runtime_error()): %O\nPO: %O\nPO(0): %O\n",
+    //          caller_stack(1),previous_object(),previous_object(0)));
+
+    limited(#'call_runtime_error, ({ LIMIT_UNLIMITED }),
+        err,prg,curobj,line,culprit,caught,previous_object());
+}
+
+//Warnungen mitloggen
+protected void runtime_warning( string msg, string curobj, string prog, int line,
+    int inside_catch)
+{
+  string code;
+  string debug_txt;
+  object titp;
+
+  //DEBUG(sprintf("Callerstack: in runtime_warning(): %O\nPO(0): %O\n",
+  //        caller_stack(1),previous_object(0)));
+
+  //Daten der Warnung an den Errord zur Speicherung weitergeben, falls wir 
+  //sowas haben.
+  int issueid;
+#ifdef ERRORD
+  catch(issueid=limited(#'call_other,({200000}),ERRORD,"LogWarning",
+        msg,prog,curobj,line,inside_catch); publish);
+#endif // ERRORD
+
+  // Fehlermeldung bauen
+  if (!stringp(msg))    msg="<NULL>";
+  if (!stringp(prog))    prog="<NULL>";
+  if (!stringp(curobj)) curobj="<NULL>";
+  debug_txt=sprintf("Warnung: %O, Objekt: %s, Programm: %O, Zeile %O (%s) "
+                    "ID: %d",
+                    msg[0..<2], curobj, (prog[0]!='<'?"/":"")+prog, line,
+                    (TI?capitalize(getuid(TI)):"<Unbekannt>"),
+                    issueid);
+
+  //Fehlermeldungen an -warnungen schicken
+  catch(send_channel_msg("Warnungen",
+                         capitalize(objectp(previous_object()) ?
+                                    REAL_UID(previous_object()) : ""),
+                         debug_txt));
+
+  titp = TI || TP;
+
+  if (!titp) return;
+
+  // Fehlermeldung an Benutzer
+  if (IS_LEARNER(titp))
+    tell_object(titp,
+                sprintf("%'-'78.78s\nfile: %s line: %d object: %s\n" +
+                        "%sWarnung: %s%'-'78.78s\n","",prog,line,curobj,
+                        (prog&&(code=read_file("/"+prog,line,1))?
+                         "\n"+code+"\n":""),msg,""));
+  return;
+}
+
+
+//                       #####################
+//######################## Einrichten des ED ############################
+//                       #####################
+
+// Setup speichern
+protected int save_ed_setup(object who, int code)
+{
+  string file;
+
+  if (!intp(code)) return 0;
+  file = sprintf("/players/%s/.edrc",geteuid(who));
+  rm(file);
+  return write_file(file,(string)code);
+}
+
+// Setup einladen
+protected int retrieve_ed_setup(object who)
+{
+  string file;
+
+  file = sprintf("/players/%s/.edrc",getuid(who));
+  if (file_size(file)<1) return 0;
+  return (int)read_file(file);
+}
+
+// Wo werden Dateien gespeichert?
+string get_ed_buffer_save_file_name(string file)
+{
+  return sprintf("/players/%s/.dead_ed_files/%s",
+                 getuid(this_player()),efun::explode(file, "/")[<1]);  
+}
+
+//                  ################################
+//################### Ungenutzte Pflichtfunktionen ######################
+//                  ################################
+
+// Nichts zu tun beim Master-Neustart
+protected void external_master_reload()  { return; }
+
+// Keine externen Master-Flags
+protected void flag(string str) { return; }
+
+// Wird benoetigt, falls ERQ angeschlossen ist
+protected void stale_erq(closure callback) { return; }
+
+// Zerstoerten Master wiederbeleben ...
+// nicht viel zu tun, wenn flag gesetzt ist. Wenn aber nicht, sind alle
+// Variablen auf 0. Nach dieser Funktion wird wieder inaugurate_master()
+// gerufen.
+protected void reactivate_destructed_master(int flag) {
+    if (flag) {
+        //re-init
+        //was machen? TODO
+    }
+    return;
+}
+
+// Kein Quota-Demon (potentielles TODO)
+//Handle quotas in times of memory shortage.
+//is called during the final phase of a garbage collection, if the user
+//reserve could not be re-allocated. Last (!) Chance to free (active) objects 
+//from the system and prevent call to slow_shut_down()!
+protected void quota_demon() {
+    //was kann man machen?
+    //uebervolle Seherhaeuser leeren? 
+    return;
+}
+
+// Keine Prepositionen in parse_command
+//string *parse_command_prepos_list() { return ({}); }
+
+// Wie lautet das Wort fuer 'alle' ?
+//string *parse_command_all_word() { return ({}); }
+
+
+// Keine Besonderen Objektnamen
+string printf_obj_name(object ob) { return 0; }
+
+//                        ###################
+//######################### Sonstiges/Hooks #############################
+//                        ###################
+
+//TODO: save_wiz_file in shutdown() integrieren?
+// Wizliste speichern: Zeile generieren
+static string _save_wiz_file_loop(mixed *a)
+{
+  return sprintf("%s %d %d\n",a[WL_NAME],a[WL_COMMANDS],a[WL_EXTRA]);
+}
+
+// Wizliste speichern
+protected void save_wiz_file()
+{
+  rm("/WIZLIST");
+  write_file("/WIZLIST",implode(
+     map(wizlist_info(),#'_save_wiz_file_loop),""));
+}
+
+// TODO: macht der telnet_neg-hook ueberhaupt was?
+void telnet_neg(mixed cmd,mixed opt,mixed args)
+{
+  if (opt==34 && cmd==251)
+        binary_message(({255,250,34,1,1,255,240}));
+}
+  
+// EUID und UID werden von give_uid_to_object() vergeben, diese sind in
+// inaugurate_master() als driver hooks angemeldet.
+
+/*load_uid_hook() sollte machen, was diese Lambda machte...
+  unbound_lambda( ({'printf_obj_name}),
+               ({#'give_uid_to_object,'printf_obj_name,
+                   ({#'previous_object}),0})));   */
+protected mixed load_uid_hook(string datei) {
+    return(give_uid_to_object(datei,previous_object()));
+}
+
+/* clone_uid_hook sollte machen, was diese Lambda machte...
+            unbound_lambda( ({'blueprint, 'new_name}), 
+              ({#'give_uid_to_object,'new_name,
+                  ({#'previous_object}),1})));      */
+protected mixed clone_uid_hook(string blueprint,string new_name) {
+    return(give_uid_to_object(new_name,previous_object()));
+}
+
diff --git a/secure/master.h b/secure/master.h
new file mode 100644
index 0000000..f97b0d0
--- /dev/null
+++ b/secure/master.h
@@ -0,0 +1,126 @@
+// MorgenGrauen MUDlib
+//
+// master.h -- definitions for the master object
+//
+// $Id: master.h 8809 2014-05-08 19:52:48Z Zesstra $
+
+#ifndef __MASTER_H__
+#define __MASTER_H__
+
+
+#ifdef P_ARMOURS
+#undef P_ARMOURS
+#endif
+#define P_ARMOURS "armours"
+
+#ifdef P_WEAPON
+#undef P_WEAPON
+#endif
+#define P_WEAPON "weapon"
+
+//fuer den notify_fail-Hook
+#ifndef P_DEFAULT_NOTIFY_FAIL
+#define P_DEFAULT_NOTIFY_FAIL "default_notify_fail"
+#endif
+
+#include "/secure/config.h"
+
+#include "/sys/userinfo.h"
+#include "/sys/shells.h"
+#include "/sys/player/base.h"
+#include "/sys/moving.h"
+#include "/sys/defines.h"
+#include "/sys/wizlist.h"
+#include "/sys/daemon.h"
+#include "/sys/mail.h"
+#include "/sys/driver_hook.h"
+#include "/sys/functionlist.h"
+
+#include "/secure/wizlevels.h"
+
+//#define DEBUG(x)
+#ifndef DEBUG
+#define DEBUG(x)	if (funcall(symbol_function('find_player),"zesstra"))\
+        tell_object(funcall(symbol_function('find_player),"zesstra"),\
+        "MDBG: "+x+"\n")
+#endif
+
+//Logfile fuer Lesezugriffe
+#define READ_FILE  "/log/ARCH/READ_FILE"
+
+//Logfile fuer Crashes
+#define CRASH_LOG  "/log/CRASH_LOG"
+
+// MAX_ERRLOG_SIZE definiert, ab welcher Groesse ein Log in
+// /log/error rotiert.
+#define MAX_ERRLOG_SIZE 51200 
+
+#define log_file(file,str) write_file("/log/"+file, str)
+#define NAME(x)            getuid(x)
+#define CAP_NAME(x)        capitalize(NAME(x))
+#define TP                 efun::this_player()
+#define TI                 efun::this_interactive()
+#define PO                 efun::previous_object()
+#define TO                 efun::this_object()
+
+// Verzeichnis mit Infomails fuer Magierbefoerderungen
+#define WIZ_HELP_MAIL_DIR "/doc/infomails/"
+
+// aus master.c
+string        creator_file(mixed str);
+mixed         give_uid_to_object(string datei,object po);
+void          save_wiz_file();
+mixed         load_uid_hook(string datei);
+mixed         clone_uid_hook(string blueprint,string new_name);
+protected mixed call_sefun(string sefun, varargs mixed args);
+
+// aus domains.c
+int            domain_master(string user, string domain);
+int            domain_member(string user, string domain);
+string         *get_domain_homes(string wiz);
+
+// aus guild.c
+int           guild_master(string user, string guild);
+
+// aus file_access.c
+void          LoadDeputyFileList();
+mixed         valid_read(string path, string euid, string fun, object obj);
+mixed         valid_write(string path, string euid, string fun, object obj);
+
+// aus misc.c
+string         _get_path(string path, string user);
+mixed          QueryBanished(string str);
+int            TBanishName(string name, int days);
+
+// sendet bei Befoerderungen Infomail an Magier
+protected void SendWizardHelpMail(string name, int level);
+
+// aus userinfo.c
+public int       find_userinfo(string user);
+protected mixed *get_full_userinfo(string user);
+public mixed    *get_userinfo(string user);
+public int       get_wiz_level(string user);
+public string    query_player_object( string name );
+public int       query_wiz_level(mixed player);
+int              update_wiz_level(string user,int lev);
+protected void   set_guilds(string player, string *guilds);
+protected void   set_domains(string player, string *domains);
+void             update_late_players();
+public string    secure_savefile(string name);
+protected void   save_userinfo(string user);
+protected string query_secure_euid();
+protected int    query_secure_level();
+public varargs string* QueryUIDAlias(string alias, int rec);
+
+//Einen Magier als verantwortlich fuer eine bestimmte UID eintragen
+public string*   AddWizardForUID(string uid, string wizuid);
+//Einen Magier wieder austragen, wenn er nicht mehr zustaendig ist.
+public string*   RemoveWizardFromUID(string uid, string wizuid);
+//Verantwortliche Magier fuer eine UID ausgeben
+public varargs string* QueryWizardsForUID(string uid, int recursive);
+//Welche UIDs ist ein Magier implizit verantwortlich? (Ist er RM,
+//Gildenmagier, in welchen Regionen hat er ein Verzeichnis, etc.)
+public varargs string* QueryUIDsForWizard(string wizuid, int recursive);
+
+
+#endif //__MASTER_H__
diff --git a/secure/master/autoinclude.c b/secure/master/autoinclude.c
new file mode 100644
index 0000000..e0002fa
--- /dev/null
+++ b/secure/master/autoinclude.c
@@ -0,0 +1,101 @@
+// MorgenGrauen MUDlib
+//
+// secure/master/autoinclude.c -- module of the master object: Autoincludes
+//
+// $Id: master.c 7041 2008-10-13 18:18:27Z Zesstra $
+
+#define PRAGMA(x) "#pragma "x"\n"
+
+// fuer alte Homemuds...
+#if __VERSION__ >= "3.5.0"
+#define RTTCHECKS PRAGMA("rtt_checks")
+#define DEFAULTS PRAGMA("save_types")
+#else
+#define RTTCHECKS ""
+#define DEFAULTS PRAGMA("combine_strings, verbose_errors, warn_deprecated")
+#endif
+
+// geschachteltes Mapping in toplevel.region.magier Hierarchie.
+// Wichtig: jede Hierarchiebene _muss_ ein Mapping sein, welches einen Eintrag
+//          0 als Default enthaelt, welcher einen Strings als Wert hat.
+//          Ausnahme: die letzte Ebene (Magierebene), die muss ein String ein.
+private nosave mapping autoincludes = ([
+    "d":      ([
+                 "inseln": ([
+                             0: "",
+                             "zesstra": PRAGMA("strong_types") RTTCHECKS,
+                            ]),
+                 0: "",
+               ]),
+    "std":    ([
+                 0: PRAGMA("strong_types,pedantic") RTTCHECKS,
+               ]),
+    "items":    ([
+                 0: PRAGMA("strong_types,pedantic") RTTCHECKS,
+               ]),
+    "secure": ([
+                 0: PRAGMA("strong_types,range_check,pedantic") RTTCHECKS,
+               ]),
+    "p":      ([
+                 0: "",
+                 "daemon": ([
+                             0: PRAGMA("strong_types") RTTCHECKS
+                            ]),
+                 "service": ([
+                              0: ""
+                            ]),
+               ]),
+    0: DEFAULTS,
+]);
+
+string autoincludehook(string base_file, string current_file, int sys_include)
+{
+  mapping topleveldir, region; // mappings for spezialisiertere Pfade
+  string ainc_string;
+
+  // Wenn current_file != 0 ist, wird gerade vom kompilierten Objekt
+  // <base_file> etwas (indirekt) inkludiert. Dort duerfen die Pragmas
+  // keinesfalls reingeschrieben werden.
+  if (current_file)
+    return 0;
+
+  string res=autoincludes[0]; // global default.
+
+  string *p_arr = explode(base_file,"/")-({""});
+  //DEBUG(sprintf("AINC: File: %O, Pfad: %O\n",base_file, p_arr));
+
+  if (sizeof(p_arr) && m_contains(&topleveldir, autoincludes, p_arr[0])) {
+    // p_arr[0]: d, p, std, etc. 
+    // erst wird der Defaulteintrag 0 genommen
+    res += topleveldir[0];
+    if (sizeof(p_arr) > 1 && m_contains(&region, topleveldir, p_arr[1])) {
+      // p_arr[1] ebene, polar, unterwelt, service, ...
+      // erst den Defaulteintrag der Region nehmen
+      res += region[0];
+      if (sizeof(p_arr) > 2 && m_contains(&ainc_string, region, p_arr[2])) {
+        // p_arr[2]: magiername. Fuer den Magier was hinterlegt.
+        res += ainc_string;
+      }
+    }
+  }
+  // Fuer aeltere Files schalten wir einige Warnungen explizit aus. :-(
+  // (1407179680 == "Mon,  4. Aug 2014, 21:14:40")
+#if MUDHOST == __HOST_NAME__
+  if (call_sefun("file_time", base_file) < 1407179680) {
+      res += PRAGMA("no_warn_missing_return");
+  }
+#else
+  // Auf anderen Rechnern als dem Mudrechner werden die Warnungen unabhaengig
+  // vom Zeitpunt der letztes Aenderung abgeschaltet, weil bei kopierten
+  // Mudlibs oft die mtimes geaendert werden und dann auf einmal alles scrollt.
+  res += PRAGMA("no_warn_missing_return");
+
+#endif
+  //DEBUG(res);
+  return res;
+}
+#undef RTTCHECKS
+#undef DEFAULTS
+#undef PRAGMA
+
+
diff --git a/secure/master/cidr.c b/secure/master/cidr.c
new file mode 100644
index 0000000..c35ba21
--- /dev/null
+++ b/secure/master/cidr.c
@@ -0,0 +1,111 @@
+// MorgenGrauen MUDlib
+/** \file /secure/master/cidr.c
+* Funktionen zur Interpretation und Umrechnung von IP-Adressen in CIDR-Notation.
+* \author Zesstra
+* \date 05.02.2010
+* \version $Id$
+*/
+/* Changelog:
+*/
+
+private int IPv4_addr2int(string addr) {
+    int mask;
+
+    if (!stringp(addr) || !sizeof(addr))
+      return 0;
+    // irgendwelche Zeichen ausser 0-9, . und / drin?
+    if (sizeof(addr-"0123456789./"))
+      return 0;
+
+    string *parts = explode(addr, "/") - ({""});
+    // Netzmaske gegeben? -> Netzwerkadresse bestimmen
+    if (sizeof(parts) == 2) {
+        // a.b.c.d/x oder a.b.c.d/w.x.y.z
+        int res = IPv4_addr2int(parts[0]); // erstmal Wert von a.b.c.d...
+        if (strstr(parts[1], ".") > -1
+            && sizeof(explode(parts[1],".")) == 4)
+            // a.b.c.d/w.x.y.z -> Wert von w.x.y.z bestimmen
+          mask = IPv4_addr2int(parts[1]);
+        else {
+          mask = to_int(parts[1]);
+          // Wert von /x
+          mask = 0xffffffff - (to_int(pow(2, 32-mask))-1);
+        }
+        // verunden und Ende.
+        return res & mask;
+    }
+
+    parts = explode(parts[0], ".");
+    // Abgrekuerzte Adresse a la a.b.? Rest mit 0 ergaenzen.
+    switch(sizeof(parts - ({""})) ) {
+      case 1:
+        parts += ({"0","0","0"});
+      case 2:
+        parts += ({"0","0"});
+      case 3:
+        parts += ({"0"});
+      case 4:
+        break;
+      default:
+        return 0;
+    }
+
+   return( to_int(parts[0]) << 24)
+         + ( to_int(parts[1]) << 16)
+         + ( to_int(parts[2]) << 8 )
+         + to_int(parts[3]);
+}
+
+private int IPv4_net_size(string addr) {
+    if (!stringp(addr) || !sizeof(addr))
+      return 0;
+    // irgendwelche Zeichen ausser 0-9, . und / drin?
+    if (sizeof(addr-"0123456789./"))
+      return 0;
+
+    string *parts = explode(addr, "/") - ({""});
+    // Netzmaske gegeben? -> Netzwerkadresse bestimmen
+    if (sizeof(parts) == 2) {
+        // a.b.c.d/x oder a.b.c.d/w.x.y.z
+        if (strstr(parts[1], ".") > -1
+            && sizeof(explode(parts[1],".")) == 4)
+            // a.b.c.d/w.x.y.z 
+          return 0xffffffff - (IPv4_addr2int(parts[1]) || 1) + 1;
+        else {
+          // Einfach 2^(32-mask) Adressen.
+          return to_int(pow(2, 32 - to_int(parts[1]) ) );
+        }
+    }
+
+    parts = explode(parts[0], ".");
+    switch(sizeof(parts - ({""})) ) {
+      case 1: // Class A
+        return 256*256*256;
+      case 2: // Class B
+        return 256*256;
+      case 3: // Class C
+        return 256;
+      case 4: // einzelne Adresse
+        return 1;
+      default: // ungueltige Adresse
+        return __INT_MAX__;
+    }
+
+    // hier sollte man gar nicht ankommen.
+    return __INT_MAX__;
+}
+
+string IPv4_int2addr(int ip) {
+    int *parts=allocate(4);
+    parts[0] = (ip & 0xff000000) >> 24;
+    parts[1] = (ip & 0x00ff0000) >> 16;
+    parts[2] = (ip & 0x0000ff00) >> 8;
+    parts[3] = (ip & 0x000000ff);
+    // int is signed. Anything > 127 would wrap around to -128 if we are on a
+    // machine with 32-bit wide ints.
+    if (parts[0] < 0)
+        parts[0] = 128 + (128-abs(parts[0]));
+
+    return implode(map(parts,#'to_string), ".");
+}
+
diff --git a/secure/master/destruct.c b/secure/master/destruct.c
new file mode 100644
index 0000000..7e76a11
--- /dev/null
+++ b/secure/master/destruct.c
@@ -0,0 +1,105 @@
+// MorgenGrauen MUDlib
+//
+// secure/master/destruct.inc -- module of the master object: stuff for destruct.
+//
+// $Id: master.c 7041 2008-10-13 18:18:27Z Zesstra $
+
+#include "/sys/object_info.h"
+
+// privilegierte Objekte, die das destruct() abbrechen duerfen (root objekte
+// duerfen auch ohne, dass sie in dieser Liste erfasst sind):
+private nosave string *deny_destruct_list = ({
+    "/obj/shut", "/room/void", "/room/netztot", "/room/jail" });
+
+// Helferfunktion fuer prepare_destruct()
+private void recursive_remove(object ob, int immediate_destruct) {
+
+  if (efun::object_info(ob, OI_ONCE_INTERACTIVE)) {
+    // Spieler werden ins Void bewegt.
+    int res;
+    tell_object(ob, "Ploetzlich loest sich deine Welt in ihre " +
+                  "Bestandteile auf. Zum Glueck wirst\nDu irgendwo " +
+                  "hin geschleudert ...\n");
+    // wenn Bewegung buggt oder nicht funktioniert und ob noch existiert,
+    // rekursiv zerstoeren.
+    object oldenv=environment(ob);
+    if ( (catch(res=(int)ob->move("/room/void",M_TPORT|M_NOCHECK,0,"faellt");
+           publish) || (ob && environment(ob) == oldenv) )
+          && ob) {
+            // Spieler speichern, dann erst Inventar entleeren, dann remove() und
+        // und destruct() anwenden.
+        catch(ob->save_me(1); publish);
+        filter(all_inventory(ob), #'recursive_remove, immediate_destruct);
+        if (!immediate_destruct) 
+          catch(ob->remove(0);publish);
+        if (ob) 
+          destruct(ob);
+    }
+  }
+  else {
+    // kein Spieler. Rekursiv entfernen. Hierbei _zuerst_ rekursiv das
+    // Inventar entfernen und dann ob selber, damit nicht erst das Inventar in
+    // das Environment bewegt wird (soll ja eh zerstoert werden).
+    filter(all_inventory(ob), #'recursive_remove, immediate_destruct);
+    // ggf. zuerst remove versuchen
+    if (!immediate_destruct)
+      catch(ob->remove(1);publish);
+    if (ob)
+      destruct(ob);
+  }
+}
+
+// Zerstoerung von ob vorbereiten
+protected mixed prepare_destruct(object ob)
+{
+  object old_env,env,item;
+  mixed res;
+
+  // zuerst das notify_destruct() rufen und ggf. abbrechen, falls ob
+  // privilegiert ist.
+  catch(res = (mixed)ob->NotifyDestruct(previous_object()); publish);
+  if (res &&
+      (getuid(ob) == ROOTID ||
+       (IS_ARCH(ob)) ||
+       member(deny_destruct_list, object_name(ob)) >= 0)) {
+    if (stringp(res) && sizeof(res))
+      return res;
+    else
+      return sprintf("%O verweigert die Zerstoerung mittels destruct(). "
+          "Fehlende Rechte von %O?\n",ob, previous_object());
+  }
+  
+  env = environment(ob);
+
+  // Objekt hat kein Env: Alles zerstoeren, Spieler ins Void
+  if (!env) {
+    filter(all_inventory(ob), #'recursive_remove, 1);
+  }
+  else {
+    // Ansonsten alles ins Environment 
+    foreach(item : all_inventory(ob))
+    {
+      old_env=environment(item);
+      // M_MOVE_ALL, falls item nen Unitobjekt ist. Sonst clonen die u.U. noch
+      // wieder nen neues Objekt im alten Env.
+      if(catch(item->move(env, M_NOCHECK|M_MOVE_ALL);publish))
+        recursive_remove(item, 1);
+      else if (item && environment(item) == old_env)
+        recursive_remove(item, 1);
+    }
+  }
+
+  return 0; // Erfolg
+}
+
+// Anmerkung: liefert 0 zurueck, wenn die sefuns gerade geladen werden.
+string NotifyDestruct(object caller) {
+  // Nicht jeder Magier muss den Master entsorgen koennen.
+  if ((caller != this_object() && 
+        call_sefun("secure_level") < ARCH_LVL)
+      || call_sefun("process_call") ) {
+    return "Du darfst den Mudlib-Master nicht zerstoeren!\n";
+  }
+  return 0;
+}
+
diff --git a/secure/master/domain.c b/secure/master/domain.c
new file mode 100644
index 0000000..99ec6f6
--- /dev/null
+++ b/secure/master/domain.c
@@ -0,0 +1,95 @@
+// MorgenGrauen MUDlib
+//
+// master/domain.c -- Regionen und Regionsmagier
+//
+// $Id: domain.c 7162 2009-02-26 21:14:43Z Zesstra $
+
+#pragma strict_types
+
+#include "/secure/master.h"
+#include "/sys/files.h"
+
+int domain_master(string user, string domain)
+{
+  string *domains;
+  int i;
+  
+  if (!find_userinfo(user)||
+      !pointerp(domains=get_userinfo(user)[USER_DOMAIN+1]))
+    return 0;
+  return (member(domains,domain) != -1);
+}
+
+int domain_member(string user, string domain)
+{
+  if (domain=="erzmagier") return 0;
+  return (IS_DOMAINMEMBER(user)&&file_size("/d/"+domain+"/"+user)==-2);
+}
+
+int add_domain_master(string user,string dom)
+{
+  string *domains;
+  
+  if ((call_other(SIMUL_EFUN_FILE, "process_call") ||
+        call_other(SIMUL_EFUN_FILE, "secure_level") < GOD_LVL) ||
+        !find_userinfo(user))
+    return 0;
+  domains=get_userinfo(user)[USER_DOMAIN+1];
+  if (!domains) 
+    set_domains(user,({dom}));
+  else
+  {
+    // Doppelte Eintraege vermeiden
+    domains=domains-({dom})+({dom});
+    set_domains(user, domains);
+  }
+  if (get_wiz_level(user) < LORD_LVL+5) {
+    update_wiz_level(user,LORD_LVL+5);
+    SendWizardHelpMail(user, LORD_LVL+5);
+  }
+  return 1;
+}
+
+int remove_domain_master(string user,string dom)
+{
+  string *domains;
+
+  if (!IS_GOD(geteuid(previous_object()))
+      ||!find_userinfo(user)
+      ||!(domains=get_userinfo(user)[USER_DOMAIN+1])
+      || member(domains,dom)==-1)
+    return 0;
+  domains-=({dom});
+  set_domains(user,domains);
+  return 1;
+}
+
+string *get_domains() {
+  string *regions=({});
+  // alle Verzeichnisse in /d/ ermitteln
+  foreach(string region: (get_dir("/"DOMAINDIR"/*") 
+        || ({}))-({".","..","erzmagier"})) {
+    if (region[0]!='.' && file_size("/d/"+region) == FSIZE_DIR)
+        //Verzeichnis, kein File und faengt nicht mit "." an: aufnehmen.
+        regions+=({region});
+  }
+  return regions;
+}
+
+// in welchen Regionen hat 'wiz' ein Verzeichnis?
+string *get_domain_homes(string wiz)
+{
+  string *homes=({});
+  string tmp;
+  
+  if (query_wiz_level(wiz)<=WIZARD_LVL) return ({});
+
+  tmp = "/d/%s/"+wiz;
+  foreach(string dir: get_domains()) {
+      if (dir[0]!='.' && file_size(sprintf(tmp,dir)) == FSIZE_DIR)
+          //Magierverzeichnis da und faengt nicht mit "." an: aufnehmen.
+          homes+=({dir});
+  }
+  return homes;
+}
+
diff --git a/secure/master/file_access.c b/secure/master/file_access.c
new file mode 100644
index 0000000..5b7e3e4
--- /dev/null
+++ b/secure/master/file_access.c
@@ -0,0 +1,617 @@
+#pragma strict_types, no_warn_deprecated
+
+#include "/secure/master.h"
+
+static mapping projects=([]);
+static string *insecure,*deputy_files;
+
+int ReloadInsecureFile()
+{
+    insecure = efun::explode( read_file("/secure/INSECURE") || "", "\n" );
+    insecure -= ({""});
+    insecure = map( insecure, #'lower_case/*'*/ );
+    return(1);
+}
+
+void LoadDeputyFileList()
+{
+  // Leseberechtigungen fuer /log/ARCH/* setzen
+  deputy_files = efun::explode( read_file("/log/ARCH/DEPUTY_FILELIST") || "",
+                                "\n" ) - ({""});
+  return;
+}
+
+#define PATH_ARRAY(x) (explode(x, "/")-({"."}))
+string *full_path_array(string path, string user) {
+  string *strs;
+  string cwd;
+
+  if(!path)
+    return ({"",""}); // additional "" to yield "/" later.
+
+  // remove multiple '/'
+  path = regreplace(path, "/+", "/", 1);
+
+  switch(path[0]) {
+    case '/':
+      if(!path[1]) return ({"",""}); //additional "" to yield "/" later
+      strs=PATH_ARRAY(path);
+      if (!sizeof(strs))
+          strs = ({"",""});
+      break;
+    case '+':
+      if(!path[1]) return ({"","d"});
+      strs=({"","d"})+PATH_ARRAY(path[1..<1]);
+      break;
+    case '~':
+      if(user && !path[1])
+        return ({"","players",user});
+      if(user && path[1]=='/')
+        strs=({"","players",user})+PATH_ARRAY(path[2..<1]);
+      else
+        strs=({"","players"})+PATH_ARRAY(path[1..<1]);
+      break;
+    default:
+      if(user && TP && getuid(TP) == user
+          && (cwd=(string)TP->QueryProp(P_CURRENTDIR)))
+        strs=PATH_ARRAY(cwd + "/" + path);
+      else
+        strs=PATH_ARRAY(path);
+  }
+  // /../ behandeln. Einschraenkungen der RegExp:
+  // /a/b/../../d wuerde nur durch Wiederholung aufgeloest.
+  // /../d wird nicht richtig aufgeloest.
+  //regreplace("/d/inseln/toeter/room/hoehle/../wald/bla.c","(.*)\/[^/]+/\.\.
+  //    /(.*)", "\\1\/\\2", RE_GLOBAL)
+  
+  // dies sieht schlimmer aus als es ist (member ist O(n)), solange das Array
+  // nicht gross wird.
+  int p;
+  while((p=member(strs, "..")) != -1)
+      strs = strs[0..p-2]+strs[p+1..];
+
+  return strs;
+}
+#undef PATH_ARRAY
+
+string _get_path(string path, string user) {
+  string *p_arr = full_path_array(path, user);
+  // make path absolute
+  if (p_arr[0] != "")
+      p_arr = ({""}) + p_arr;
+
+  return implode(p_arr,"/");
+}
+
+static int project_access(string user, string project)
+{
+  mixed *lines;
+  string s;
+  mapping tmp;
+
+  if (!member(projects,project))
+  {
+    s=read_file(PROJECTDIR+"/"+project+"/ACCESS_RIGHTS");
+    if(!s||s=="")
+      return 0;
+    tmp=([]);
+    for (lines=explode(s,"\n")-({""});sizeof(lines);lines=lines[1..])
+    {
+      if (lines[0][0]=='*')
+        tmp[lines[0][1..]]=2;
+      else
+        tmp[lines[0]]=1;
+    }
+    projects[project]=({tmp,time()});
+    return tmp[user];
+  }
+  projects[project][1]=time();
+  return projects[project][0][user];
+}
+
+void OutdateProjectCache(string project)
+{
+  m_delete(projects,project);
+}
+
+static void _cleanup_projects() {
+  int i;
+  mixed *users;
+
+  for (users=m_indices(projects),i=sizeof(users)-1;i>=0;i--)
+    if((time()-projects[users[i]][1])>1800)
+      m_delete(projects,users[i]);
+}
+
+int access_rights(string *p_arr, string euid)
+{
+  int i;
+
+  i = sizeof(p_arr) - 2;
+
+  while ( i >= 0 &&
+          file_size( implode(p_arr[0..i], "/") + "/access_rights.c" ) < 0 )
+      i--;
+
+  if ( i < 0 )
+      return 0;
+
+  if ( !catch(i = (int)call_other( 
+          implode(p_arr[0..i], "/") + "/access_rights",
+          "access_rights", euid,
+          implode(p_arr[i+1..], "/") ); publish) )
+      return i;
+
+  return 0;
+}
+
+string make_path_absolute(string str) {
+  return _get_path(str, getuid(TP));
+}
+
+
+mixed valid_write(string path, string euid, string fun, object obj)
+{
+  int s,lvl;
+  string *strs;
+  int *date;
+
+  if (member(path,' ')!=-1) return 0;
+
+  // Unter LIBDATADIR (/data) sollen komplett identische Schreibrechte
+  // vergeben werden.
+  if (sizeof(path) > 6
+      && path[0..5] == "/"LIBDATADIR"/")
+    return valid_write(path[5..], euid, fun, obj) != 0;
+
+  switch(fun) {
+    case "log_file":
+      strs=full_path_array("/"+path, 0);
+      path = implode(strs, "/");
+      strs -= ({""}); // remove trailing and leading "/".
+      if (sizeof(strs)>1 && strs[0]=="log") return path;
+      return 0;
+    case "save_object":
+      if (!path) return 0;
+      strs=full_path_array("/"+path, 0);
+      break;
+    case "ed_start":
+      if (path && path[0])
+        strs=full_path_array(path, euid);
+      else strs=({"players",euid,".err"});
+      break;
+    default:
+      strs=full_path_array(path, euid);
+  }
+
+  if (!euid || euid=="NOBODY" || euid=="ftp" || euid=="anonymous") return 0;
+
+  // Pfad soll ab jetzt auf jeden Fall absolut sein.
+  if (strs[0] != "")
+      path = "/" + implode(strs, "/");
+  else
+      path=implode(strs, "/");
+
+  // fuer die Rechtebestimmung "/" an Anfang und Ende entfernen
+  strs -= ({""});
+
+  //Root-Objekte und Master duerfen immer.
+  if (euid == ROOTID || obj==TO) return path;
+
+  lvl=query_wiz_level(euid);
+
+  //Toplevel: nur EM+
+  if ((s=sizeof(strs))<=1) return lvl>=ARCH_LVL;
+
+  switch(strs[0]) {
+    case TMPDIR:
+      return 1;
+
+    case STDDIR:
+    case SYSDIR:
+    case LIBOBJDIR:
+    case ETCDIR:
+    case LIBROOMDIR:
+      return lvl>=ARCH_LVL;
+    
+    case LIBITEMDIR:
+      return lvl>=ELDER_LVL;
+
+    case SPELLBOOKDIR:
+      // wenn unterhalb eines unterverzeichnisses des Stils "gilde", dann ists
+      // einfach
+      if (sizeof(strs) > 2
+          && guild_master(euid, strs[1]))
+        return 1;
+
+      // sonst nur EMs bzw. access_rights.c fragen.
+      return ((lvl>=ARCH_LVL) || access_rights(strs,euid));
+
+    case GUILDDIR:
+      // wenn unterhalb eines unterverzeichnisses des Stils "filde.gilde",
+      // dann ists einfach
+      if (sizeof(strs) > 2) {
+        string *tmp = explode(strs[1],"files.");
+        if (sizeof(tmp) == 2) {
+          if (guild_master(euid, tmp[1]))
+            return 1;
+        // Objekte dort sollen auch schreiben duerfen.
+          if (euid == ("GUILD."+tmp[1]))
+            return 1;
+        }
+      }
+      // sonst nur EMs bzw. access_rights.c fragen.
+      return ((lvl>=ARCH_LVL) || access_rights(strs,euid));
+
+    case DOCDIR:
+      if ( s > 1 && (strs[1] == "balance") )
+        return ((lvl>=ARCH_LVL) || access_rights(strs,euid));
+      else
+        return ((lvl>=SPECIAL_LVL) || access_rights(strs,euid));
+
+    case FTPDIR:
+        if ( s > 1 && (strs[1] == "incoming" || strs[1] == "tmp" ||
+                       s == 3 && strs[1] == "News" && strs[2] == euid+".news") )
+            return 1;
+
+        if (lvl>=ELDER_LVL)
+            return 1;
+
+        return 0;
+
+    case MAILDIR:
+      if (euid==MAILID || strs[1]=="spool") break;
+      return 0;
+
+    case LIBSAVEDIR:
+      if (lvl>=ARCH_LVL) return 1;
+      if (s==3 && strs[1] == euid[0..0] &&
+          (strs[2]==euid+".o" || strs[2]==euid)) break;
+      return 0;
+
+    case LIBLOGDIR:
+      /* auf /log/ARCH/ haben wirklich nur EMs Zugriff */
+      if (strs[1]=="ARCH" && lvl<ARCH_LVL) return 0;
+
+      /* alles andere duerfen auch Weise... */
+      if (lvl>=ELDER_LVL) return 1;
+
+      /* Allgemeine logfiles in /log duerfen wirklich nur geschrieben werden */
+      if (s==2 && strs[1][0]>='A' && strs[1][0]<='Z' && fun != "write_file")
+         return 0;
+
+      /* Unterhalb von /log/syslog darf nur geschrieben werden */
+      if (s>1 && strs[1]=="syslog" && fun != "write_file")
+         return 0;
+
+      /* loggen ins eigene repfile immer erlauben */
+      if (s==3 && strs[1]=="report" &&
+          strs[2]==explode(euid, ".")[<1]+".rep") break;
+
+      /* in fremden Verzeichnissen hat niemand was zu loggen */
+      if (get_wiz_level(strs[1]) >= WIZARD_LVL &&
+          strs[1] != efun::explode(euid, ".")[<1])
+         return 0;
+      break;
+
+    case WIZARDDIR:
+      /* kein Zugriff auf Objekte mit hoeheren Rechten */
+      if (query_wiz_level(strs[1]) > lvl) return 0;
+
+      /* Magier selbst oder Weise duerfen hier schreiben */
+      if ((IS_WIZARD(euid) && euid==strs[1])||(lvl>=ELDER_LVL)) break;
+
+      /* Es steht jedoch frei, auch anderen Schreibrechte zu geben... */
+      return access_rights(strs,euid);
+      
+    case DOMAINDIR:
+      /* neue Regionen duerfen nur Erzmagier anlegen */
+      if (s<2 && lvl<ARCH_LVL) return 0;
+
+      /* kein Zugriff auf Objekte mit hoeheren Rechten */
+      if (s>2 && query_wiz_level(creator_file(path)) > lvl)
+         return 0;
+
+      /* Auf Regionfiles von erzmagier haben nur EMs Zugriff */
+      if (creator_file(path)=="erzmagier" && lvl<ARCH_LVL) return 0;
+
+      /* innerhalb der Region haben RMs und Weise volle Schreibrechte */
+      if (lvl>=ELDER_LVL || domain_master(euid,strs[1])) break;
+
+      /* neue Verzeichnisse in der Region kann nur RM+ anlegen */
+      if (s<=3 && (fun=="mkdir" || fun=="rmdir")) return 0;
+
+      /* Magier auf ihre eigenen Files... */
+      if (s>2 && strs[2]==euid) break;
+
+      /* Files der Magier in der Region in ihre eigenen Verzeichnisse... */
+      if (s>2 && euid==sprintf("d.%s.%s", strs[1], strs[2])) break;
+
+      /* Files mit Regionsuid */
+      if (euid==strs[1]) break;
+
+      /* Es ist moeglich anderen Magiern Rechte bei sich einzuraeumen. */
+      if (access_rights(strs,euid)>0) break;
+
+      /* Auf das Verzeichniss 'alle' haben alle Regionsmitglieder Zugriff.
+         Ausser, wenn sie ueber access_rights.c explizit ausgeschlossen
+         werden (Returncode < 0). */
+      if (s>2 && strs[2]=="alle" && domain_member(euid, strs[1]) &&
+          access_rights(strs,euid)>=0) break;
+      return 0;
+
+    case PROJECTDIR:
+      /* Nur Erzmagier duerfen neue Projektverzeichnisse anlegen... */
+      if (s<3 && lvl<ARCH_LVL) return 0;
+
+      /* alles weitere duerfen auch Weise tun */
+      if (lvl>=ELDER_LVL) break;
+
+      /* in den Serviceverzeichnissen muss lediglich der Name stimmen */
+      if (s>3 && strs[1]=="service" && euid==strs[2]) break;
+
+      /* Objekte eines Projektes haben Schreibzugriffe auf ihr Projekt */
+      if (s>3 && (implode(strs[0..1], ".") == euid
+                  || implode(strs[0..2], ".") == euid) ) return 1;
+
+      /* Schreibrechte koennen natuerlich auch vergeben werden... */
+      if (project_access(euid,strs[1])) break;
+      // Alternativ besteht neben dem ACCESS_RIGHTS auch noch die
+      // Moeglichkeit, per access_rights.c Rechte einzuraeumen.
+      if (access_rights(strs,euid)>0) break;
+
+     return 0;
+
+    default: return 0;
+  }
+  return path;
+}
+
+mixed valid_read(string path, string euid, string fun, object obj)
+{
+  int s, lev;
+  string *strs, suf;
+
+  if (member(path,' ')!=-1) return 0;
+
+  // Unter LIBDATADIR (/data) sollen komplett identische Leserechte
+  // vergeben werden.
+  if (sizeof(path) > 6
+      && path[0..5] == "/"LIBDATADIR"/")
+    return valid_read(path[5..], euid, fun, obj) != 0;
+
+  if (!euid) euid="-";
+
+  strs=full_path_array(path, euid);
+  // Pfad soll ab jetzt auf jeden Fall absolut sein.
+  if (strs[0] != "")
+      path = "/" + implode(strs, "/");
+  else
+      path=implode(strs, "/");
+
+  // fuer die Rechtebestimmung "/" an Anfang und Ende entfernen
+  strs -= ({""});
+
+  if (!sizeof(strs) || !sizeof(path) || euid == ROOTID || obj==TO) return path;
+
+  if ((s=sizeof(strs)) <= 1) return path;
+
+  lev=query_wiz_level(euid);
+
+  switch(strs[0]) {
+    case "core":
+    case "wahl":
+      return 0;
+
+    case NEWSDIR:
+      if (strs[1] == "archiv.pub") // oeffentliches archiv
+        return path;
+      else if (strs[1] == "archiv.magier" // Magier-Archiv
+               && lev >= WIZARD_LVL)
+        return path;
+
+      return 0; // kein Zugriff auf /news/ oder alles andere.
+
+    case "maps":
+      if (lev<=WIZARD_LVL) return 0;
+
+    case "":
+    case ETCDIR:
+    case STDDIR:
+    case SYSDIR:
+    case DOCDIR:
+    case LIBOBJDIR:
+    case LIBROOMDIR:
+    case LIBITEMDIR:
+    case FTPDIR:
+      return path;
+
+    case MAILDIR:
+      return (euid==MAILID);
+
+    case SECUREDIR:
+      if (strs[1]=="save") return 0;
+      if (path[<2..]==".o" || path[<5..]==".dump") return 0;
+      if (strs[1]!="ARCH" || lev>=ARCH_LVL) return path;
+
+    case LIBLOGDIR:
+      if ( strs[1] == "ARCH" && !IS_DEPUTY(euid) )
+          return 0;
+
+      if ( strs[1] == "ARCH" && lev < ARCH_LVL && s == 3 &&
+           strs[2] != "*" && strs[2][0..2] != "bb." &&
+           member( deputy_files, strs[2] ) < 0 )
+          return 0;
+
+      if ( lev > WIZARD_LVL )
+          return path;
+
+      if ( s == 2 && (strs[1] == "report" || strs[1] == "error") )
+          return path; // fuer 'cd'
+
+      // /log sollte jeder auflisten koennen
+      if ( s == 2 && strs[1]=="*" && fun=="get_dir")
+          return path;
+
+      // unter /log/report/, /log/error und /log/warning/ duerfen alle lesen
+      if ( s==3 && 
+          (strs[1] == "report" || strs[1] == "error"  
+           || strs[1] =="warning"))
+          return path;
+
+      // /log/<Magiername> darf von <Magiername> natuerlich auch
+      // gelesen werden
+      if ( s >= 2 && strs[1] == euid )
+          return path;
+
+      return 0;
+
+    case "backup":
+    case LIBSAVEDIR:
+      if (lev>WIZARD_LVL) return path;
+
+      /* Objekte in /p/* haben bisher leider wizlevel 0 */
+      //if (lev==0) return path;
+
+      if (fun=="file_size") return path;
+
+      // das eigene Savefile darf man natuerlich immer. ;-)
+      if (s==3 && (strs[2]==euid+".o" || strs[2]==euid))
+        return path;
+      return 0;
+
+    case PROJECTDIR:
+      /* Pruefen ob ein File existiert darf jeder... */
+      if (fun=="file_size") return path;
+
+      /* Die Service-Verzeichnisse darf jeder lesen */
+      if (s>3 && strs[1]=="service") return path;
+
+      //Weise duerfen in /p/ schreiben, also auch lesen. (Zesstra, 04.11.06)
+      if (lev>=ELDER_LVL) return path;
+
+      /* wer hier schreiben darf, darf natuerlich auf jeden Fall lesen */
+      //Anmerkung v. Zesstra: das prueft nur, ob jemand in ACCESS_RIGHTS
+      //steht, nicht ob jemand (ggf. aus anderen Gruenden schreiben darf)
+      if (project_access(euid,strs[1])) return path;
+      //Alternativ kann man explizite Schreibrechte auch via access_rights.c
+      //vergeben. (und damit natuerlich auch Leserechte)
+      if (access_rights(strs,euid)>0) return path;
+
+      /* Objekte eines Projektes haben Lesezugriff auf ihr Projekt */
+      if (s>3 && (implode(strs[0..1], ".") == euid
+                  || implode(strs[0..2], ".") == euid) ) return path;
+
+      /* Fall-Through */
+
+    case GUILDDIR:
+      /* "secure"-Verzeichnisse darf nur lesen, wer da auch schreiben darf */
+      if ( s > 3 && strs[<2] == "secure" && member( insecure, strs[1] ) < 0
+           && lev < ARCH_LVL && !access_rights(strs, euid) )
+          return 0;
+
+      /* Pruefen ob ein File existiert darf jeder... */
+      if (fun=="file_size") return path;
+
+      /* Fall-Through */
+
+    case SPELLBOOKDIR:
+      // Gildenpbjekte koennen hier lesen
+      if (lev==0 && euid[0..4]=="GUILD") return path;
+
+      // Mit Level <= 20 darf man hier nichts lesen
+      if ( lev<=WIZARD_LVL && sizeof(regexp( ({strs[<1]}), "\\.[och]" )) )
+        return 0;
+
+      return path;
+
+    case WIZARDDIR:
+      if (s==2) return path;
+      /* das eigene Verzeichniss darf man natuerlich immer lesen... */
+      if (strs[1]==euid && lev>=WIZARD_LVL) return path;
+
+      /* Pruefen ob ein File existiert darf jeder... */
+      if (fun=="file_size") return path;
+
+      /* fremde Verzeichnisse mit <= Level 20 noch nicht */
+      if (lev<=WIZARD_LVL) return 0;
+
+      /* wo man schreiben darf, darf man natuerlich auch lesen... */
+      if (lev>=ELDER_LVL) return path;
+
+      // kein Zugriff auf archivierten Code (wo z.B. secure/ drin sein
+      // koennen)
+      if (member(({"bz2","gz","zip"}), explode(strs[<1],".")[<1]) > -1)
+        return 0;
+
+      /* Files ohne Code sind nicht weiter interessant... */
+      if ( !sizeof(regexp( ({strs[<1]}), "\\.[och]" )) )
+          return path;
+
+      /* folgende Funktionen sind natuerlich voellig uninteressant */
+      if (member(({"get_dir", "restore_object"}), fun)!=-1)
+          return path;
+
+      /* Zugriff erlaubt, aber mitloggen */
+      write_file( READ_FILE, sprintf("%O %s %s: %s\n", fun, ctime()[4..],
+                                     euid, path) );
+      return path;
+
+    case DOMAINDIR:
+      /* Mit Level 15 darf man hier _nichts_ lesen */
+      if ( fun!="file_size" && lev<WIZARD_LVL &&
+           sizeof(regexp( ({strs[<1]}), "\\.[och]" )) ) return 0;
+
+      /* den Verzeichnisbaum von /d/ darf man natuerlich sonst lesen */
+      if (s<=2) return path;
+
+      /* eigenen Code darf man natuerlich auch lesen */
+      if (euid==strs[2] || euid==sprintf("d.%s.%s", strs[1], strs[2]))
+         return path;
+
+      /* Deputies haben ein gemeinsames Verzeichnis unter /d/erzmagier */
+      if (strs[1]=="erzmagier" && strs[2]=="polizei" && IS_DEPUTY(euid))
+          return path;
+
+      /* d/erzmagier darf man nur als Erzmagier lesen */
+      if (strs[1]=="erzmagier" && lev<ARCH_LVL) return 0;
+
+      /* einige Regionen haben std-verzeichnisse, die darf man natuerlich
+         auch mit Level 20 bereits komplett lesen! */
+      if (strs[2]=="std") return path;
+
+      /* "secure"-Verzeichnisse darf nur lesen, wer da auch schreiben darf */
+      if ( s > 4 && strs[<2] == "secure" && member( insecure, strs[2] ) < 0 ){
+          if ( lev < ELDER_LVL && !domain_master(euid, strs[1])
+               && !access_rights(strs, euid) )
+              return 0;
+          else
+              return path;
+      }
+
+      /* Dokus, Infos und .readme darf man immer lesen... */
+      if ( fun=="file_size" || !sizeof(regexp( ({strs[<1]}), "\\.[och]" )) )
+          return path;
+
+      /* Mit Level 20 darf man noch keinen fremden Code lesen! */
+      if (lev<=WIZARD_LVL) return 0;
+
+      /* Weise duerfen natuerlich alles weitere lesen */
+      if (lev>=ELDER_LVL) return path;
+
+      /* Regionsmagier in ihren Regionen natuerlich auch */
+      if (lev>=LORD_LVL && domain_master(euid, strs[1])) return path;
+
+      /* folgende Funktionen sind natuerlich voellig uninteressant */
+      if (member(({"get_dir", "restore_object"}), fun)!=-1)
+          return path;
+
+      /* Zugriff erlaubt, aber mitloggen */
+      write_file( READ_FILE, sprintf("%O %s %s: %s\n", fun, ctime()[4..],
+                                     euid, path) );
+      return path;
+  }
+  if (lev<ARCH_LVL) return 0;
+  return path;
+}
+
diff --git a/secure/master/guild.c b/secure/master/guild.c
new file mode 100644
index 0000000..9eaaa8b
--- /dev/null
+++ b/secure/master/guild.c
@@ -0,0 +1,62 @@
+// MorgenGrauen MUDlib
+//
+// master/guild.c -- Gilden und Gildenmagier
+//
+// $Id: guild.c 6142 2007-01-31 20:34:39Z Zesstra $
+
+/*
+ * Dies kann irgendwann auch hinsichtlich einer automatischen 
+ * Rechtevergabe auf Gilden-Verzeichnisse erweitert werden.
+ * Bisher werden nur Gildenmagier verwaltet.
+ */
+
+#pragma strict_types
+
+#include "/secure/master.h"
+
+
+int guild_master(string user, string guild)
+{
+  string *guilds;
+  int i;
+
+  if (!find_userinfo(user)||
+      !pointerp(guilds=get_userinfo(user)[USER_GUILD-1]))
+    return 0;
+  
+  return (member(guilds,guild) != -1);
+}
+
+int add_guild_master(string user, string guild)
+{
+  string *guilds;
+  
+  if ((call_other(SIMUL_EFUN_FILE, "process_call") ||
+       call_other(SIMUL_EFUN_FILE, "secure_level") < GOD_LVL) ||
+      !find_userinfo(user))
+    return 0;
+
+  guilds=get_userinfo(user)[USER_GUILD-1];
+  if (!guilds)
+    set_guilds(user, ({ guild }) );
+  else {
+    guilds = guilds - ({guild}) + ({guild});
+    set_guilds(user, guilds);
+  }
+  return 1;
+}
+
+int remove_guild_master(string user, string guild)
+{
+  string *guilds;
+
+  if (!IS_GOD(geteuid(previous_object()))
+      ||!find_userinfo(user)
+      ||!(guilds=get_userinfo(user)[USER_GUILD-1])
+      || member(guilds,guild)==-1)
+    return 0;
+  guilds -= ({ guild });
+  set_guilds(user, guilds);
+  return 1;
+}
+
diff --git a/secure/master/misc.c b/secure/master/misc.c
new file mode 100644
index 0000000..02b0fcc
--- /dev/null
+++ b/secure/master/misc.c
@@ -0,0 +1,772 @@
+// MorgenGrauen MUDlib
+//
+// master/misc.c -- Diverses: (T)Banish, Projektverwaltung, Levelaufstieg, ...
+//
+// $Id: misc.c 9467 2016-02-19 19:48:24Z Zesstra $
+
+#pragma strict_types,rtt_checks
+
+#include "/sys/functionlist.h"
+#include "/sys/lpctypes.h"
+#include "/sys/object_info.h"
+#include "/sys/interactive_info.h"
+
+#include "/secure/master.h"
+#include "/mail/post.h"
+#include "/sys/thing/language.h"
+#include "/sys/thing/description.h"
+
+// Fuer CIDR-Notatio im sbanish
+#include "/secure/master/cidr.c"
+
+static mixed *banished;
+static mapping tbanished, sbanished;
+static string *deputies;
+
+// TODO: muss ggf. Fakeobjekt erzeugen+uebergeben, wenn sender kein object
+protected void send_channel_msg(string channel,mixed sendername,string msg)
+{
+  object sender;
+  if (objectp(sendername))
+    sender=sendername;
+  else
+  {
+    // wenn kein Objekt uebergeben, erstmal schauen, ob ein Spielerobject
+    // existiert... Wenn ja, das nehmen.
+    sender = call_sefun("find_player", sendername)
+             || call_sefun("find_netdead", sendername);
+    if (!objectp(sender))
+    {
+      // sonst faken wir eins. *seufz*
+      sender=clone_object("/p/daemon/namefake");
+      sender->SetProp(P_NAME, sendername); 
+      sender->SetProp(P_ARTICLE,0);
+      // Dieses Objekt zerstoert sich nach 3s automatisch.
+    }
+  }
+  CHMASTER->send(channel, sender, msg);
+}
+
+static string *explode_files(string file) {
+  string data;
+  mixed *exploded;
+  int i;
+
+  data=read_file(file);
+  if (!data || !stringp(data) || data == "") return ({});
+  exploded = efun::explode(data,"\n");
+  for (i=sizeof(exploded);i--;)
+    if (!stringp(exploded[i]) || exploded[i]=="" || exploded[i][0]=='#')
+      exploded[i]=0;
+  exploded-=({0});
+  printf("%-30s: %3d Objekt%s\n",file,i=sizeof(exploded),(i==1?"":"e"));
+  return exploded;
+}
+
+void UpdateTBanish();
+
+mixed QueryBanished(string str){
+  int i;
+
+  if (!str) return 0;
+  if (!pointerp(banished)) return 0;
+  for (i=sizeof(banished)-1;i>=0;i--)
+    if (sizeof(regexp(({str}),"^"+banished[i][0]+"$")))
+    {
+      if (!banished[i][1] || banished[i][1]=="")
+        return "Dieser Name ist gesperrt.";
+      else
+        return banished[i][1];
+    }
+  return 0;
+}
+
+mixed QueryTBanished(string str) {
+  int i;
+
+  if (!str || !mappingp(tbanished) || !(i=tbanished[str]))
+    return 0;
+
+  if (i == -1 || i > time())
+    return sprintf("Es gibt schon einen Spieler diesen Namens.\n"
+        +"Allerdings kann er/sie erst am %s wieder ins Mud kommen.\n",
+          (i == -1 ? "St. Nimmerleinstag" :
+           call_sefun("dtime",i)[0..16]));
+
+// Ansonsten: die Zeit ist abgelaufen, Spieler darf wieder...
+  m_delete(tbanished, str);
+  UpdateTBanish();
+  return 0;
+}
+
+void ReloadBanishFile(){
+    int i, t;
+    string s1, s2, *s;
+
+    banished = efun::explode( read_file("/secure/BANISH") || "", "\n" );
+    banished = banished - ({""});
+    for ( i = sizeof(banished); i--; ){
+        s = efun::explode( banished[i], " " );
+        s1 = s[0];
+        s2 = implode( s[1..], " " );
+        banished[i] = ({ s1, s2 });
+    }
+
+    if ( !mappingp(tbanished) ){
+        tbanished = ([]);
+
+        s = efun::explode( read_file("/secure/TBANISH") || "", "\n" );
+        s -= ({""});
+
+        for ( i = sizeof(s); i--; ){
+            sscanf( s[i], "%s:%d", s1, t );
+            tbanished += ([ s1 : t ]);
+        }
+    }
+
+    if ( !mappingp(sbanished) ){
+        sbanished = m_allocate(3, 2);
+
+        s = efun::explode( read_file("/secure/SITEBANISH") || "", "\n" );
+        s -= ({""});
+
+        for ( i = sizeof(s); i--; ) {
+            int int_ip;
+            sscanf( s[i], "%s:%d:%s", s1, t, s2 );
+            int_ip = IPv4_addr2int(s1);
+            m_add(sbanished, int_ip, t, s2);
+        }
+    }
+}
+
+int IsDeputy(mixed name)
+{
+    if ( IS_ARCH(name) )
+        return 1;
+
+    if ( objectp(name) )
+        name = getuid(name);
+
+    if ( member( deputies || ({}), name ) >= 0 )
+        return 1;
+
+    return 0;
+}
+
+
+varargs void BanishName( string name, string reason, int force )
+{
+  string *names;
+  int i;
+
+  if ( PO != TO && call_sefun("secure_level") < LORD_LVL
+           && !IsDeputy( call_sefun("secure_euid") ) )
+      return;
+
+  if ( !stringp(name) || !sizeof(name) )
+      return;
+
+  name = lower_case(name);
+
+  if ( !reason || !stringp(reason) )
+      reason = "";
+
+  if ( QueryBanished(name) && lower_case(reason) != "loeschen" ){
+      write("Der Name ist schon gebannt.\n");
+      return;
+  }
+
+  if ( !force && file_size(SAVEPATH+name[0..0]+"/"+name+".o") > 0 ){
+      write("Es existiert bereits ein Spieler dieses Namens.\n");
+      return;
+  }
+
+/*  if (!("/secure/login"->valid_name(name))) return;*/
+  if ( lower_case(reason) != "loeschen" ){
+      names = ({ name + " " + reason });
+
+      for ( i = sizeof(banished); i--; )
+          names += ({ banished[i][0] + " " + banished[i][1] });
+  }
+  else{
+      names = ({});
+
+      for ( i = sizeof(banished); i--; )
+          if ( banished[i][0] != name )
+              names += ({ banished[i][0] + " " + banished[i][1] });
+  }
+
+  names = sort_array( names, #'> );
+  rm("/secure/BANISH");
+  write_file( "/secure/BANISH", implode(names, "\n") );
+  write( "Okay, '"+capitalize(name)+"' ist jetzt "+
+      (lower_case(reason) == "loeschen" ? "nicht mehr " : "")+"gebanisht.\n" );
+  ReloadBanishFile();
+}
+
+void UpdateTBanish()
+{
+  int i;
+  string *names;
+
+  for (i=sizeof(names = sort_array(m_indices(tbanished), #'</*'*/))-1;i>=0;i--)
+    names[i] = sprintf("%s:%d", names[i], tbanished[names[i]]);
+
+  rm("/secure/TBANISH");
+  write_file("/secure/TBANISH", implode(names, "\n"));
+}
+
+void UpdateSBanish()
+{
+    int i;
+    mapping lines = m_allocate(sizeof(sbanished),0);
+
+    foreach(int ip, int tstamp, string user : sbanished) {
+      m_add(lines, sprintf("%s:%d:%s",
+                           IPv4_int2addr(ip), tstamp, user));
+    }
+
+    write_file( "/secure/SITEBANISH", 
+        implode( sort_array (m_indices(lines), #'<), "\n" ), 1);
+}
+
+int TBanishName(string name, int days)
+{
+  int t;
+
+  if ( (getuid(TI) != name) &&
+       !IsDeputy( call_sefun("secure_euid") ) )
+    return 0;
+
+  if (days && QueryTBanished(name)){
+    write("Der Name ist schon gebannt.\n");
+    return 0;
+  }
+
+  if (file_size(SAVEPATH+name[0..0]+"/"+name+".o")<=0){
+    write("Es existiert kein Spieler dieses Namens!\n");
+    return 0;
+  }
+
+  if (!days && member(tbanished, name))
+    m_delete(tbanished, name);
+  else {
+    if (!mappingp(tbanished))
+      tbanished = ([]);
+    if (days <= -1)
+      t = -1;
+    else
+      t = (time()/86400)*86400 + days*86400;
+    tbanished += ([ name : t ]);
+  }
+
+  UpdateTBanish();
+  return 1;
+}
+
+
+mixed QuerySBanished( string ip )
+{
+    int save_me, site;
+    string banished_meldung =
+      "\nSorry, von Deiner Adresse kamen ein paar Idioten, die "
+            "ausschliesslich\nAerger machen wollten. Deshalb haben wir "
+            "die Moeglichkeit,\neinfach neue Charaktere "
+            "anzulegen, kurzzeitig fuer diese Adresse gesperrt.\n\n"
+            "Falls Du bei uns spielen moechtest, schicke bitte eine Email "
+            "an\n\n                         mud@mg.mud.de\n\n"
+            "mit der Bitte, einen Charakter fuer Dich anzulegen.\n" ;
+
+    if ( !ip || !stringp(ip) || !mappingp(sbanished) || !sizeof(sbanished) )
+        return 0;
+
+    foreach(site, int tstamp: sbanished) {
+        if ( tstamp > 0 && tstamp < time() ) {
+            m_delete( sbanished, site );
+            save_me=1;
+        }
+    }
+    if (save_me)
+        UpdateSBanish();
+
+    if ( !sizeof(sbanished) )
+        return 0;
+
+    site = IPv4_addr2int(ip);
+    if (!site)
+        return 0;
+    // direkt drin?
+    if (member(sbanished, site))
+        return banished_meldung;
+    // oder Netz dieser IP gesperrt?
+    foreach(int locked_site : sbanished) {
+        if ((locked_site & site) == locked_site)
+            return banished_meldung;
+    }
+
+    return 0;
+}
+
+
+private int _sbanished_by( int key, string name )
+{
+    return sbanished[key, 1] == name;
+}
+
+
+mixed SiteBanish( string ip, int days )
+{
+    string *s, tmp, euid;
+    int t, level;
+
+    euid = call_sefun("secure_euid");
+    level = call_sefun("secure_level");
+
+    // Unter L26 gibt's gar nix.
+    if ( level <= DOMAINMEMBER_LVL )
+        return -1;
+
+    // Die Liste der gesperrten IPs anschauen darf jeder ab L26.
+    if ( !ip && !days )
+        return copy(sbanished);
+
+
+    if ( !stringp(ip) || !intp(days) )
+        return 0;
+
+    if ( days && QuerySBanished(ip) ){
+        write( "Diese Adresse ist schon gebannt.\n" );
+        return 0;
+    }
+
+    if ( !days ) {
+        int int_ip = IPv4_addr2int(ip);
+
+        if( member(sbanished, int_ip) ){
+            // Fremde Sitebanishs duerfen nur Deputies loeschen.
+            if ( sbanished[int_ip, 1] != euid && !IsDeputy(euid) )
+                return -1;
+
+            call_sefun("log_file", "ARCH/SBANISH",
+                    sprintf( "%s: %s hebt die Sperrung der Adresse %s von %s "
+                             + "auf.\n", 
+                             ctime(time()), capitalize(euid), ip,
+                             capitalize(sbanished[int_ip, 1]) ) );
+
+            m_delete( sbanished, int_ip );
+        }
+        else
+            return 0;
+    }
+    else {
+        // Alles, was nicht Deputy ist, darf nur fuer einen Tag sbanishen.
+        if ( days != 1 && !IsDeputy(euid) )
+            return -1;
+
+        // Nur Deputies duerfen mehr als 10 Sperrungen vornehmen.
+        if ( sizeof(filter_indices(sbanished, #'_sbanished_by, euid)) >= 10
+             && !IsDeputy(euid) )
+            return -2;
+
+        int int_ip = IPv4_addr2int(ip);
+
+        if(!int_ip) {
+            write( "Ungueltige Adresse!\n" );
+            return 0;
+        }
+
+        // L26 duerfen exakt eine IP sperren, RMs ganze Class C-Subnetze
+        // und Deputies auch Class B-Subnetze.
+        int nsize=IPv4_net_size(ip);
+        if ( nsize > 1 && level < LORD_LVL
+             || nsize > 255 && !IsDeputy(euid) )
+            return -1;
+
+        if ( !mappingp(sbanished) )
+            sbanished = m_allocate(1, 2);
+
+        if ( days < 0 )
+            t = -1;
+        else
+            t = (time() / 86400) * 86400 + days * 86400;
+
+        m_add(sbanished, int_ip, t, euid);
+
+        call_sefun("log_file", "ARCH/SBANISH",
+                sprintf( "%s: %s sperrt die Adresse %s %s.\n",
+                         ctime(time()), capitalize(euid),
+                         ip,
+                         days > 0 ? (days > 1 ? "fuer " + days + " Tage"
+                                     : "fuer einen Tag")
+                         : "bis zum St. Nimmerleinstag" ) );
+    }
+
+    UpdateSBanish();
+    return 1;
+}
+
+static void CheckDeputyRights()
+{
+    object ob;
+    mixed *ginfo;
+
+    // Lese- und Schreibberechtigungen fuer die Rubrik 'polizei' setzen
+    call_other( "/secure/news", "???" );
+    ob = find_object("secure/news");
+    ginfo = ((mixed)ob->GetGroup("polizei"))[5..6];
+    ob->RemoveAllowed( "polizei", 0, ginfo[0], ginfo[1] );
+    ob->AddAllowed( "polizei", 0, deputies, deputies );
+    LoadDeputyFileList();
+}
+
+int ReloadDeputyFile()
+{
+    deputies = efun::explode( read_file("/secure/DEPUTIES") || "", "\n" );
+    deputies -= ({""});
+    deputies = map( deputies, #'lower_case/*'*/ );
+    call_out( "CheckDeputyRights", 2 );
+    return(1);
+}
+
+
+static int create_home(string owner, int level)
+{
+  string def_castle;
+  string dest, castle, wizard;
+  object player;
+  string *domains;
+  int i;
+
+  player = call_sefun("find_player",owner);
+  if (!player)
+    return -5;
+  domains=get_domain_homes(owner);
+  if (!sizeof(domains) && level >= DOMAINMEMBER_LVL)
+  {
+    tell_object(player,"Du gehoerst noch keiner Region an !\n");
+    return -6;
+  }
+  tell_object(player,"Du bist Mitarbeiter der folgenden Regionen:\n");
+  for (i=0;i<sizeof(domains);i++)
+  {
+    if (i) tell_object(player,", ");
+    tell_object(player,domains[i]);
+  }
+  tell_object(player,".\n");
+  update_wiz_level(owner, level);
+  wizard = "/players/" + owner;
+  castle = "/players/" + owner + "/workroom.c";
+  if (file_size(wizard) == -1) {
+    tell_object(player, "Verzeichnis " + wizard + " angelegt\n");
+    mkdir(wizard);
+  }
+  dest = object_name(environment(player));
+  def_castle = read_file("/std/def_workroom.c");
+  if (file_size(castle) > 0) {
+    tell_object(player, "Du HATTEST ja schon ein Arbeitszimmer !\n");
+  } else {
+    if (write_file(castle, def_castle))
+    {
+      tell_object(player, "Arbeitszimmer " + castle + " erzeugt.\n");
+      // Arbeitszimmer als Home setzen
+      player->SetProp(P_START_HOME,castle[0..<3]);
+    }
+    else
+      tell_object(player, "Arbeitszimmer konnte nicht erzeugt werden !\n");
+  }
+  return 1;
+}
+
+// Sendet dem befoerderten Magier eine Hilfemail zu.
+protected void SendWizardHelpMail(string name, int level) {
+  
+  object pl=call_sefun("find_player",name);
+  if (!objectp(pl)) return;
+
+  string file=sprintf("%sinfo_ml%d", WIZ_HELP_MAIL_DIR, level);
+  // wenn kein Hilfetext fuer den Level da ist: raus
+  if (file_size(file) <= 0)
+    return;
+
+  string subject = read_file(file,1,1);
+  string text = call_sefun("replace_personal",
+                         read_file(file,2), ({pl}));
+
+  mixed mail = ({"Merlin", "<Master>", name, 0, 0, subject,
+                 call_sefun("dtime",time()),
+                 MUDNAME+time(), text });
+  MAILDEMON->DeliverMail(mail, 0);
+}
+
+int allowed_advance_wizlevel(mixed ob)
+{
+  string what;
+
+  if (objectp(ob) && geteuid(ob)==ROOTID) return 1;
+
+  if (!stringp(ob))
+    what=efun::explode(object_name(ob),"#")[0];
+  else
+    what=ob;
+
+  if (what=="/secure/merlin") return 1;
+
+  return 0;
+}
+
+int advance_wizlevel(string name, int level)
+{
+  int answer;
+  mixed *user;
+
+  if (!allowed_advance_wizlevel(PO))
+    return -1;
+
+  if (level>80) return -2;
+
+  if (!find_userinfo(name)) return -3;
+
+  user=get_full_userinfo(name);
+
+  if (user[USER_LEVEL+1]>level) return -4;
+
+  if (user[USER_LEVEL+1]==level) return 1;
+
+  if (level>=10 && level<20)
+  {
+    update_wiz_level(name, level);
+    SendWizardHelpMail(name, level);
+    return 1;
+  }
+  if (level>=20 && user[USER_LEVEL+1]<21)
+  {
+    answer = create_home(name, level);
+    if ( answer > 0 ) {
+      answer = update_wiz_level(name, level);
+      SendWizardHelpMail(name, level);
+    }
+    return answer;
+  }
+
+  update_wiz_level(name, level);
+  SendWizardHelpMail(name, level);
+
+  return 1;
+}
+
+void restart_heart_beat(object heart_beat)
+{
+  if (heart_beat) heart_beat->_restart_beat();
+}
+
+int renew_player_object(mixed who)
+{
+  object newob;
+  object *obs, *obs2;
+  mixed err;
+  string ob_name;
+  object *armours, weapon;
+  object tp;
+  int i,active,j;
+
+  if (stringp(who))
+  {
+    who=call_sefun("find_player",who);
+    if (!who)
+    {
+      who=call_sefun("find_netdead",who);
+      if (!who)
+        return -1;
+    }
+  }
+  if (!objectp(who))
+    return -2;
+  if (!object_info(who, OI_ONCE_INTERACTIVE))
+    return -3;
+  if (who->QueryGuest())
+  {
+    printf("Can't renew guests!\n");
+    return -6;
+  }
+  active=interactive(who);
+  printf("OK, renewing %O\n",who);
+  seteuid(geteuid(who));
+  err=catch(newob=clone_object(query_player_object(getuid(who))); publish);
+  seteuid(getuid(TO));
+  if (err)
+  {
+    printf("%O\n",err);
+    return -4;
+  }
+  if (!newob)
+    return -5;
+  /* Ok, the object is here now ... lets go for it ... */
+  who->save_me(0);
+  /* SSL ip weiterreichen */
+  if( call_sefun("query_ip_number", who) != efun::interactive_info(who,II_IP_NUMBER) )
+  {
+    newob->set_realip( call_sefun("query_ip_number",who) );
+  }
+  efun::configure_object(previous_object(), OC_COMMANDS_ENABLED, 0);
+  efun::set_this_player(0);
+  armours=(object *)who->QueryProp(P_ARMOURS);
+  weapon=(object)who->QueryProp(P_WEAPON);
+
+  if ( previous_object() && object_name(previous_object()) == "/secure/merlin" )
+      send_channel_msg("Debug",
+                       previous_object(),
+                       sprintf("RENEWING: %O %O\n",newob,who));
+  else
+      send_channel_msg("Entwicklung",
+                       previous_object(),
+                       sprintf("RENEWING: %O %O\n",newob,who));
+
+  ob_name=explode(object_name(newob),"#")[0];
+  if (sizeof(ob_name)>11 && ob_name[0..11]=="/std/shells/")
+    ob_name=ob_name[11..];
+  ob_name=ob_name+":"+getuid((object)who);
+  if (active)
+    exec(newob,who);
+  if (active && (interactive(who)||!interactive(newob)))
+  {
+    send_channel_msg("Debug",previous_object(),
+                     "ERROR: still active !\n");
+    newob->remove();
+    return 0;
+  }
+//   newob->start_player(capitalize(getuid(who)),who->_query_my_ip());
+  funcall(
+          bind_lambda(
+                      unbound_lambda( ({'x, 'y}),
+                                      ({#'call_other/*'*/,
+                                             newob,
+                                             "start_player",
+                                             'x, 'y
+                                             })
+                                      ), who
+                      ),
+          capitalize(getuid(who)), who->_query_my_ip() );
+
+  newob->move(environment(who),M_TPORT|M_NOCHECK|M_NO_SHOW|M_SILENT
+              |M_NO_ATTACK);
+  obs=all_inventory(who);
+  foreach(object tob: all_inventory(who)) {
+    if (!tob->QueryProp(P_AUTOLOADOBJ))
+    {
+      // kein Autoloader...
+      foreach(object ob: deep_inventory(tob))
+      {
+        // aber enthaltene Autoloader entsorgen...
+        if (ob->QueryProp(P_AUTOLOADOBJ))
+        {
+          catch(ob->remove();publish);
+          if (ob) destruct(ob);
+        }
+      }
+      // objekt ohne die AL bewegen.
+      catch(tob->move(newob,M_NOCHECK);publish);
+    }
+    else {
+      // Inhalt von Autoloadern retten.
+      // neue instanz des ALs im neuen Objekt.
+      object new_al_instance = present_clone(tob, newob);
+      foreach(object ob: deep_inventory(tob)) {
+        if (ob->QueryProp(P_AUTOLOADOBJ)) {
+            // autoloader in Autoloadern zerstoeren...
+            catch(ob->remove(1);publish);
+            if (ob) destruct(ob);
+        }
+        // alle nicht autoloader in die AL-Instanz im neuen Objekt oder
+        // notfalls ins Inv.
+        else {
+          if (objectp(new_al_instance))
+            catch(ob->move(new_al_instance, M_NOCHECK);publish);
+          else
+            catch(ob->move(newob, M_NOCHECK);publish);
+        }
+      }
+      // Autoloader zerstoeren. Wird nicht vom Spielerobjekt im remove()
+      // gemacht, wenn nicht NODROP.
+      catch(tob->remove(1);publish);
+      if (tob) destruct(tob);
+    }
+  }
+  who->remove();
+  if ( objectp(who) )
+      destruct(who);
+  rename_object(newob,ob_name);
+  newob->__reload_explore();
+  tp=this_player();
+  efun::set_this_player(newob);
+  if (objectp(weapon))
+    weapon->DoWield();
+  if (pointerp(armours))
+    for (i=sizeof(armours)-1;i>=0;i--)
+      if (objectp(armours[i]))
+        armours[i]->do_wear("alles");
+  efun::set_this_player(tp);
+  //Rueckgabewert noetig, weil Funktion vom Typ 'int'
+  return(1);
+}
+
+mixed __query_variable(object ob, string var)
+{
+  if (!PO || !IS_ARCH(geteuid(PO)) || !this_interactive() ||
+      !IS_ARCH(this_interactive()) || getuid(ob)==ROOTID )
+  {
+    write("Du bist kein EM oder Gott!\n");
+    return 0;
+  }
+  if (efun::object_info(ob, OI_ONCE_INTERACTIVE) && (PO!=ob) &&
+     (var=="history" || var=="hist2"))
+     send_channel_msg("Snoop", previous_object(),
+                      sprintf("%s -> %s (history).",
+                        capitalize(getuid(PO)),capitalize(getuid(ob))));
+
+  call_sefun("log_file", "ARCH/QV",
+                          sprintf("%s: %O inquires var %s in %O\n",
+                                  ctime(time()),this_interactive(),var,ob) );
+  mixed res = variable_list(ob, RETURN_FUNCTION_NAME|RETURN_FUNCTION_FLAGS|
+                                RETURN_FUNCTION_TYPE|RETURN_VARIABLE_VALUE);
+  int index = member(res,var);
+  if (index > -1)
+  {
+    return ({res[index],res[index+1],res[index+2],res[index+3]});
+  }
+  return 0;
+}
+
+void RestartBeats()
+{
+  int i,size,counter;
+  object ob;
+  mixed *list;
+  string file,obname,fun;
+
+  "/secure/simul_efun"->StopCallOut(0);
+  write("CALL_OUT-Restart in progress !\n");
+  filter(users(),#'tell_object,"CALL_OUT-Restart in progress !\n");
+  size=file_size("log/call_out_stop");
+  if (size<=0)
+    return;
+  file="";
+  counter=0;
+  while (counter<size)
+  {
+    file+=read_bytes("log/call_out_stop",counter,
+                     (size-(counter+=40000)>0?counter:size));
+  }
+  list=explode(file,"__(CUT HERE)__\n");
+  list=list[<1..];
+  list=explode(list[0],"\n")-({""});
+  for (i=sizeof(list)-1;i>=0;i--)
+    if (sscanf(list[i],"%s \"%s\"",obname,fun)==2 && ob=find_object(obname))
+    {
+      write(sprintf("%O -> %s\n",ob,fun));
+      catch(ob->__restart(fun);publish);
+    }
+  write("CALL_OUT-Restart completed !\n");
+  filter(users(),#'tell_object,"CALL_OUT-Restart completed !\n");
+  rename("log/call_out_stop","log/call_out_stop.old");
+}
+
diff --git a/secure/master/network.c b/secure/master/network.c
new file mode 100644
index 0000000..e681f82
--- /dev/null
+++ b/secure/master/network.c
@@ -0,0 +1,431 @@
+// MorgenGrauen MUDlib
+//
+// master/network.c - UDP-Handling
+//
+// $Id: network.c 8934 2014-09-10 21:57:12Z Zesstra $
+
+#pragma strict_types
+
+#include "/secure/master.h"
+#define BBMASTER "/secure/bbmaster"
+
+/*
+#undef DEBUG
+#define DEBUG(x) if (call_sefun("find_player","zesstra")) \
+                 tell_object(call_sefun("find_player","zesstra"),x);
+*/
+
+//ich will hieraus momentan kein Debug, ist zuviel. Zesstra
+
+#ifdef DEBUG
+#undef DEBUG
+#endif
+#define DEBUG(x)
+
+nosave int mails_last_hour;
+
+static string mail_normalize( string str )
+{
+    str = regreplace( str, "[^<]*<(.*)>[^>]*", "\\1", 0);
+    return regreplace( str, " *([^ ][^ ]*).*", "\\1", 0);
+}
+
+
+static string *mk_rec_list( string str )
+{
+    return map( explode( lower_case(str), "," ) - ({""}),
+                      "mail_normalize", this_object() );
+}
+
+
+static int CheckPasswd( string name, string passwd ) {
+    mixed *uinf;
+
+    if (!stringp(passwd) || !sizeof(passwd))
+ return 0;
+    if ( sizeof(uinf = get_full_userinfo(name)) < 2 )
+        return 0;
+
+    string pwhash = uinf[USER_PASSWORD+1];
+    if (sizeof(pwhash) > 13) {
+ // MD5-Hash
+ passwd = md5_crypt(passwd, pwhash);
+    }
+    else if (sizeof(pwhash) > 2) {
+ // Crypt-Hash
+ passwd = crypt(passwd, pwhash[0..1]);
+    }
+    else return 0;
+
+    return (passwd == pwhash);
+}
+
+
+static void FtpAccess( string host, string message, int port )
+{
+    string *comp, reply, head;
+#if __EFUN_DEFINED__(send_udp)
+    comp = efun::explode( message, "\t" );
+#define FTP_ID   0
+#define FTP_SEQ  1
+#define FTP_TAG  2
+#define FTP_CMD  3
+#define FTP_ARG1 4
+#define FTP_ARG2 5
+#define FTP_ARG3 6
+
+  if ( sizeof(comp) <= FTP_CMD || lower_case(comp[FTP_TAG]) != "req" ){
+      log_file( "IMP_MSGS", "Host: " + host + ":" + port + " - '" +
+                message + "'\n" );
+      return;
+  }
+
+  reply = "INVALID";
+
+  head = sprintf( "%s\t%s\tRPLY\t%s\t",
+                  comp[FTP_ID], comp[FTP_SEQ], comp[FTP_CMD] );
+
+  switch ( lower_case(comp[FTP_CMD]) ){
+  case "user":
+      if ( sizeof(comp) <= FTP_ARG1 )
+          break;
+
+      if ( IS_LEARNER(lower_case(comp[FTP_ARG1])) )
+          reply = "/players/" + lower_case(comp[FTP_ARG1]);
+      else
+          reply = "NONE";
+      break;
+
+  case "pass":
+      if ( sizeof(comp) <= FTP_ARG2 )
+          break;
+
+      comp[FTP_ARG1] = lower_case(comp[FTP_ARG1]);
+
+      if ( IS_LEARNER(comp[FTP_ARG1]) &&
+           !"/secure/master"->QueryTBanished(comp[FTP_ARG1]) ){
+          if ( CheckPasswd( comp[FTP_ARG1], comp[FTP_ARG2] ) )
+              reply = "OK";
+          else {
+              if ( get_wiz_level( comp[FTP_ARG1] ) < ARCH_LVL )
+                  log_file( "LOGINFAIL",
+                            sprintf( "PASSWORD:      (FTP)     %s %s\n",
+                                     comp[FTP_ARG1],
+                                     ctime(time()) ) );
+              else
+                  log_file( "ARCH/LOGINFAIL",
+                            sprintf( "PASSWORD:      (FTP)     %s %s\n",
+                                     comp[FTP_ARG1],
+                                     ctime(time()) ) );
+          }
+      }
+      else
+          reply = "FAIL";
+      break;
+
+  case "read":
+DEBUG("-FtpAccess----\nHost:"+host+"Message:\n"+message+"\n--------------\n");
+      if ( sizeof(comp) <= FTP_ARG2 )
+          break;
+
+      if ( comp[FTP_ARG2][0] == '/' &&
+           valid_read( comp[FTP_ARG2], lower_case(comp[FTP_ARG1]),
+                       "read_file", 0 ) ){
+
+          BBMASTER->ftpbb( lower_case(comp[FTP_ARG1]),
+                           "read " + comp[FTP_ARG2] + "\n" );
+          reply = "OK";
+          }
+      else
+          reply = "FAIL";
+      break;
+
+  case "writ":
+DEBUG("-FtpAccess----\nHost:"+host+"Message:\n"+message+"\n--------------\n");
+      if ( sizeof(comp) <= FTP_ARG2 )
+          break;
+
+      if ( comp[FTP_ARG2][0] == '/' &&
+           valid_write( comp[FTP_ARG2], lower_case(comp[FTP_ARG1]),
+                       "write_file", 0 ) ){
+
+          BBMASTER->ftpbb( lower_case(comp[FTP_ARG1]),
+                           "write " + comp[FTP_ARG2] + "\n" );
+          reply = "OK";
+          }
+       else
+          reply = "FAIL";
+      break;
+
+  case "list":
+DEBUG("-FtpAccess----\nHost:"+host+"Message:\n"+message+"\n--------------\n");
+      if ( sizeof(comp) <= FTP_ARG2 )
+          break;
+
+      if ( comp[FTP_ARG2][0] == '/' &&
+           valid_read( comp[FTP_ARG2], lower_case(comp[FTP_ARG1]),
+                       "read_file", 0 ) )
+          reply = "OK";
+      else
+          reply = "FAIL";
+      break;
+
+  default:
+DEBUG("-FtpAccess----\nHost:"+host+"Message:\n"+message+"\n--------------\n");
+      log_file( "IMP_MSGS", "Host: " + host + ":" + port + " - '" +
+                message + "'\n" );
+      break;
+  }
+
+  send_udp( host, port, head+reply );
+#endif
+}
+
+
+static int doReadMail( string file )
+{
+    string str, *lines, *parts, *tmp;
+    mixed *message;
+    int i, j;
+
+    if ( (i = file_size(file)) > 50000 || i < 5 ){
+        rm(file);
+        DEBUG( "Mail size invalid\n" );
+        return -1;
+    }
+
+    if ( !(str = read_bytes( file, 0, 50000 )) )
+        return -1;
+
+    if ( !(j = sizeof(lines = explode( str, "\n" ))) )
+        return -2;
+
+    i = 0;
+
+    while ( i < j && lines[i] != "" )
+        i++;
+
+    if ( i == j )
+        return -2;
+
+ DEBUG( sprintf( "Have %d headerlines:\n", i ) );
+
+    message= allocate(9);
+    message[MSG_CC] = ({});
+    message[MSG_BCC] = ({});
+    message[MSG_BODY] = implode( lines[i..], "\n" );
+
+    for ( j = 0; j < i; j++ ){
+        parts = explode( lines[j], ":" );
+
+        if ( sizeof(parts) > 1 ){
+            str = lower_case(parts[0]);
+            parts[0] = implode( parts[1..], ":" );
+
+            switch (str){
+            case "subject":
+                message[MSG_SUBJECT] = parts[0];
+                break;
+
+            case "from":
+                DEBUG( "Found from\n" );
+                DEBUG( sprintf( "PARTS[0]=%s\n", parts[0] ) );
+                message[MSG_FROM] = mail_normalize(parts[0]);
+                message[MSG_SENDER] = parts[0];
+                DEBUG( sprintf( "FROM: %s\nSENDER: %s\n",
+                                message[MSG_FROM],
+                                message[MSG_SENDER] ) );
+                break;
+
+            case "cc":
+                DEBUG( sprintf("FOUND CC: %O\n", parts[0]) );
+                message[MSG_CC] += mk_rec_list(parts[0]);
+                break;
+
+            case "bcc":
+                DEBUG( sprintf("FOUND BCC: %O\n", parts[0]) );
+                message[MSG_BCC] += mk_rec_list(parts[0]);
+                break;
+
+            case "to":
+                DEBUG( sprintf("FOUND TO: %O\n", parts[0]) );
+                tmp = mk_rec_list(parts[0]);
+
+                if ( !message[MSG_RECIPIENT] )
+                    message[MSG_RECIPIENT] = tmp[0];
+
+                message[MSG_CC] += tmp;
+                break;
+
+            // Das MUD-TO: wird vom Perlskript als erste Headerzeile eingefuegt
+            case "mud-to":
+                DEBUG( sprintf("FOUND MUD-TO: %O\n", parts[0]) );
+                message[MSG_RECIPIENT] = mail_normalize(lower_case(parts[0]));
+                break;
+            }
+        }
+    }
+
+    // Eigentlichen Empfaenger aus CC: loeschen
+    message[MSG_CC] -= ({ message[MSG_RECIPIENT],
+                          message[MSG_RECIPIENT]+"@mg.mud.de",
+                          message[MSG_RECIPIENT]+"@morgengrauen.mud.de" });
+
+
+ DEBUG( sprintf( "TO: %O\n", message[MSG_RECIPIENT] ) );
+    DEBUG( sprintf( "CC: %O\n", message[MSG_CC] ) );
+    DEBUG( sprintf( "BCC: %O\n", message[MSG_BCC] ) );
+
+    if ( !stringp(message[MSG_FROM]) )
+        return -2;
+
+    if ( !stringp(message[MSG_RECIPIENT]) ){
+        str = explode( file, "/" )[<1];
+        i = 0;
+        j = sizeof(str);
+
+        while ( i < j && str[i] <= '9' && str[i] >= '0' )
+            i++;
+
+        if ( i >= j )
+            return -2;
+        
+        message[MSG_RECIPIENT] = str[i..];
+  DEBUG( sprintf( "EMERGENCY RECIPIENT=%s\n", str[i..] ) );
+    }
+
+    rm(file);
+
+    // Da vom Master besser nichts von ausserhalb /secure #include't wird,
+    // direkt die '5'. Normalerweise hiesse der Aufruf:
+    // DeliverMail( message, NO_USER_ALIASES|NO_CARBON_COPIES );
+    "/secure/mailer"->DeliverMail( message, 5 );
+    return 1;
+}
+
+
+public void mailread()
+{
+    string *files;
+    int i;
+
+    DEBUG( "mailread called\n" );
+
+    if ( mails_last_hour >= MAX_MAILS_PER_HOUR )
+        return;
+
+    files = (get_dir( "/mail/inbound/*" )||({})) - ({ "..", "." });
+    i = sizeof(files);
+
+    while ( i-- && mails_last_hour < MAX_MAILS_PER_HOUR ){
+        DEBUG( "FOUND FILE \"" + files[i] + "\" ...\n" );
+        mails_last_hour++;
+
+        if ( doReadMail( "/mail/inbound/" + files[i]) < -1 ){
+            mixed message;
+
+            message = allocate(9);
+            mails_last_hour++;
+            rename( "/mail/inbound/" + files[i],
+                    "/secure/ARCH/MAIL/" + files[i] );
+            message[MSG_SENDER] = geteuid();
+            message[MSG_FROM] = getuid();
+            message[MSG_SUBJECT] = "Fehlerhafte Mail: /secure/ARCH/MAIL/" +
+                files[i];
+            message[MSG_RECIPIENT] = "mud@mg.mud.de";
+            message[MSG_BODY] = "Bitte Ueberpruefen!\n";
+            // NO_SYSTEM_ALIASES|NO_USER_ALIASES == 3
+            "/secure/mailer"->DeliverMail( message, 3 );
+        }
+    }
+}
+
+
+static void udp_query( string query, string host, int port )
+{
+#if __EFUN_DEFINED__(send_udp)
+    string *mess;
+    mixed *data;
+    int i, j;
+
+    mess = explode( query, " " );
+    
+    switch ( mess[1] ){
+    case "wholist":
+    case "who":
+        data = (string *)"/obj/werliste"->QueryWhoListe();
+        break;
+
+    case "uptime":
+        data = ({ call_sefun("uptime") });
+        break;
+
+    case "finger":
+        if ( sizeof(mess) < 3 )
+            data = ({ "Error: Wen soll ich fingern ?" });
+        else
+            data = explode( (string)"p/daemon/finger"->
+                            finger_single( lower_case(mess[2]), 0 ), "\n" );
+        break;
+
+    case "mailread":
+        data = ({ "Okay" });
+        mailread();
+        break;
+
+    default:
+        data = ({ "Error: unknown request " + mess[1] + "\n" });
+    }
+
+
+    send_udp( host, port, sprintf( "%s 0 %d", mess[0], sizeof(data) ) );
+
+    for ( i = 0, j = sizeof(data); i < j; i++ )
+        send_udp( host, port, sprintf( "%s %d %s", mess[0], i+1, data[i] ) );
+#endif
+}
+
+#define UDP_DEBUG(x) 
+//#define UDP_DEBUG(x) (write_file("/log/ARCH/udp.log",(x)))
+
+void receive_udp(string host, string message, int port)
+{
+  mixed *tmp;
+  UDP_DEBUG(sprintf("%s %s:%d: %s\n",strftime(),host,port,message));
+
+  if (message[0..6]=="EXTREQ:"
+  	|| message[0..5]=="IPNAME"
+  	|| message[0..3]=="AUTH"
+  ) {	
+    return;
+  }
+
+  if( message[0..8]=="IPLOOKUP\n" ) {
+    "/p/daemon/iplookup"->update( message );
+    return;
+  }
+
+  if( message[0..9]=="DNSLOOKUP\n" ) {
+    "/p/daemon/dnslookup"->update( message );
+    return;
+  }
+
+  if (message[0..4]=="NFTPD") {
+#if __HOST_NAME__==MUDHOST
+    if (host!=FTPD_IP) {
+      DEBUG(sprintf("INVALID HOST: %s\n",host));
+      return;
+    }
+#endif
+    FtpAccess(host,message,port);
+    return;
+  }
+
+  if (message[0..9]=="udp_query:") {
+    return udp_query(message[10..],host,port);
+  }
+
+  "secure/inetd"->_receive_udp(host, message);
+}
+
+
diff --git a/secure/master/players_deny.c b/secure/master/players_deny.c
new file mode 100644
index 0000000..105704c
--- /dev/null
+++ b/secure/master/players_deny.c
@@ -0,0 +1,151 @@
+// MorgenGrauen MUDlib
+//
+// master.c -- master object
+//
+// $Id: master.c 7041 2008-10-13 18:18:27Z Zesstra $
+#pragma strict_types
+#pragma no_clone
+#pragma no_shadow
+#pragma verbose_errors
+#pragma combine_strings
+#pragma pedantic
+#pragma range_check
+#pragma warn_deprecated
+
+#define WHITELIST "/secure/ARCH/players_deny_whitelist.o"
+#define TARGETLIST "/secure/ARCH/players_deny_targets.o"
+#define WHITELISTDUMP "/secure/ARCH/players_deny_whitelist.dump"
+#define TARGETLISTDUMP "/secure/ARCH/players_deny_targets.dump"
+
+//#define PLDENY_LEARNMODE
+
+#include "/secure/wizlevels.h"
+#include "/secure/master.h"
+
+/* Diese Objekte duerfen aus /players/ includieren/erben */
+nosave private mapping whitelist;
+/* Diese Dinge werden von Objektn in whitelist aus /players/ inkludiert/geerbt. */
+nosave private mapping targets;
+
+private mapping ParseList(string list) {
+  mixed data;
+
+  if (!stringp(list)) return ([:0 ]);
+  if (!stringp(data = read_file(list))) return ([:0 ]);
+  data = explode(data, "\n") || ({});
+
+  return mkmapping(data);
+}
+
+private void ParseWhiteList() {
+  DEBUG("Parsing Whitelist\n");
+  whitelist=ParseList(WHITELISTDUMP);
+}
+private void ParseTargetList() {
+  DEBUG("Parsing Targetlist\n");
+  targets=ParseList(TARGETLISTDUMP);
+}
+
+private void DumpList(mapping list, string file) {
+  if (!stringp(file) || !mappingp(list)
+      || !sizeof(file) || !sizeof(list))
+    return;
+  
+  write_file(file, implode(m_indices(list),"\n"), 1);
+}
+
+private void DumpWhiteList() {
+  DEBUG("Dumping Whitelist\n");
+  DumpList(whitelist, WHITELISTDUMP);
+}
+private void DumpTargetList() {
+  DEBUG("Dumping Targetlist\n");
+  DumpList(targets, TARGETLISTDUMP);
+}
+
+public void DumpPLDenyLists() {
+  if ( extern_call() && call_sefun("secure_level") < ARCH_LVL )
+    return;
+  DEBUG("Dumping PLDenylists\n");
+  limited(#'DumpWhiteList);
+  limited(#'DumpTargetList);
+}
+
+public void SavePLDenyLists() {
+  if ( extern_call() && call_sefun("secure_level") < ARCH_LVL )
+    return;
+  DEBUG("Saving PLDenyLists\n");
+  write_file(WHITELIST,
+      save_value(whitelist), 1);
+  write_file(TARGETLIST,
+      save_value(targets), 1);
+}
+
+public void LoadPLDenylists() {
+  mixed tmp;
+
+  if ( extern_call() && call_sefun("secure_level") < ARCH_LVL )
+    return;
+
+  write("Loading PLDenylists\n");
+
+  if (stringp(tmp=read_file(WHITELIST))) {
+    // savefile exists
+    whitelist = restore_value(tmp);
+  }
+  else {
+      limited(#'ParseWhiteList);
+  }
+  if (stringp(tmp=read_file(TARGETLIST))) {
+    targets = restore_value(tmp);
+  }
+  else {
+      limited(#'ParseTargetList);
+  }
+}
+
+mixed include_file(string file, string compiled_file, int sys_include) {
+  
+  if (sys_include) return 0;
+  // Loggen, wenn Files ausserhalb /players/ Kram aus /players/
+  // inkludieren/erben.
+  if (strstr(file, "/players/") == 0
+      && (strstr(compiled_file,"/players/") == -1
+      && !member(whitelist, compiled_file) )) {
+#ifdef PLDENY_LEARNMODE
+    DEBUG("include_file(): Whitelisting: "+compiled_file+"\n");
+    m_add(whitelist, compiled_file);
+    m_add(targets, file);
+    call_sefun("log_file", "PLAYERSWHITELIST",
+        sprintf("%s (inkludiert %s)\n",compiled_file, file),
+        1000000);
+#else
+    // verweigern.
+    return -1;
+#endif
+  }
+  return 0;
+}
+
+mixed inherit_file(string file, string compiled_file) {
+
+  // Loggen, wenn Files ausserhalb /players/ Kram aus /players/
+  // inkludieren/erben.
+  if (strstr(file, "/players/") == 0
+      && (strstr(compiled_file,"/players/") == -1
+      && !member(whitelist, compiled_file) )) {
+#ifdef PLDENY_LEARNMODE
+    DEBUG("include_file(): Whitelisting: "+compiled_file+"\n");
+    m_add(whitelist, compiled_file);
+    m_add(targets, file);
+    call_sefun("log_file", "PLAYERSWHITELIST", 
+        sprintf("%s (erbt %s)\n",compiled_file, file),
+        1000000);
+#else
+    // verweigern.
+    return -1;
+#endif
+  }
+  return 0;
+}
+
diff --git a/secure/master/userinfo.c b/secure/master/userinfo.c
new file mode 100644
index 0000000..5d8f4ff
--- /dev/null
+++ b/secure/master/userinfo.c
@@ -0,0 +1,1119 @@
+// MorgenGrauen MUDlib
+//
+// master/userinfo.c -- Cache mit Spielerinfos
+//
+// $Id: userinfo.c 9467 2016-02-19 19:48:24Z Zesstra $
+
+#pragma strict_types
+
+#include "/secure/master.h"
+#include "/sys/files.h"
+#include "/sys/object_info.h"
+
+// Makro aus wizlevels.h ersetzen, da secure_level ne sefun ist. *seufz*
+#undef ARCH_SECURITY
+#define ARCH_SECURITY (call_sefun("secure_level") >= ARCH_LVL)
+
+private string*  ExpandUIDAlias(string alias, int rec);
+
+nosave mapping userlist;
+string name, password;
+string ektips;
+string fptips;
+int level;
+string shell;
+int creation_date;
+string ep, ek, mq;
+string *domains,*guilds, *uidstotakecare;
+nosave string* lateplayers;
+// hier wird ein Mapping von UID-zu-Magier-Zuordnungen gespeichert. Als Keys
+// werden UIDs verwendet, der Value ist jeweils ein Array mit den magiern, die
+// dafuer zustaendig sind.
+nosave private mapping userids = ([]);
+// Ein Cache fuer UID-Aliase, damit ExpandUIDAlias() nicht staendig ganze
+// Verzeichnisse einlesen muss, sondern der Master das nur im Reset machen
+// muss.
+nosave private mapping uidaliase = ([]);
+
+#ifdef _PUREFTPD_
+// Liste von FTP-berechtigten Usern
+nosave private mapping ftpuser = ([]);
+
+public void read_ftp_users() {
+  string str = read_file("/"SECUREDIR"/ARCH/pureftpd.passwd") || "";
+  string *data = explode(str, "\n");
+  if (!sizeof(data)) return;
+  ftpuser = m_allocate(sizeof(data), 1);
+  foreach(str : data) {
+    string *tmp=explode(str, ":");
+    if(sizeof(tmp) >= 2)
+      m_add(ftpuser, tmp[0], tmp[1]);
+  }
+}
+
+public int write_ftp_users() {
+  string *record = allocate(18,"");
+  mapping tmp = m_allocate(sizeof(ftpuser));
+  // set UID/GID of system user
+  record[2] = "1000";
+  record[3] = "1000";
+  foreach(string u, string pwhash : ftpuser) {
+    record[0] = u;  // UID
+    record[1] = pwhash;
+    record[5] = "/home/mud/mudlib/"WIZARDDIR"/" + u + "/./";
+    m_add(tmp, implode(record, ":"));
+  }
+  write_file("/"SECUREDIR"/ARCH/pureftpd.passwd", implode(m_indices(tmp),"\n"),1);
+  return sizeof(tmp);
+}
+
+// geloeschte Magier oder Magier, die 2 Wochen nicht da waren, expiren
+public void expire_ftp_user() {
+  foreach(string u : ftpuser) {
+    mixed uinfo = get_full_userinfo(u);
+    if (!uinfo) {
+      m_delete(ftpuser,u);
+      continue;
+    }
+    int zeit = call_sefun("file_time",
+                     "/"SECURESAVEPATH + u[0..0] + "/" + u + ".o");
+    if (zeit < time()-1209600) {
+      m_delete(ftpuser,u);
+      continue;
+    }
+  }
+  call_out(#'write_ftp_users, 2);
+}
+
+#endif // _PUREFTPD_
+
+// ********** oeffentliche Funktionen ***********************
+
+//Verantwortliche Magier fuer eine UID ausgeben
+public varargs string* QueryWizardsForUID(string uid, int recursive) {
+
+    if (!stringp(uid) || !sizeof(uid))
+        return(({}));
+
+    string *tolookup=({uid}); //diese spaeter in userids nachgucken.
+    string *wizards=({});
+    // Schauen, was automatisch ermittelt werden kann.
+    string *parts=explode(uid,".");
+    switch(sizeof(parts)) {
+         case 3:
+          // z.B. d.region.magier und p.service.magier
+          if (find_userinfo(parts[2]))
+              //Magier existiert, reinschreiben.
+              wizards+=({parts[2]});
+          if (parts[0]=="d")
+              // d.region noch nachgucken (RMs)
+              tolookup=({implode(parts[0..1],".")});
+          break;
+        //case 2:
+          // GUILD.gilde, p.project, d.region
+          // koennen nur in userids nachgeguckt werden (s.u. tolookup)
+          // muessen da als GUILD.*, d.* und p.* drinstehen!
+        case 1:
+          // kein Punkt drin. Entweder Magier-ID oder spezielle ID
+          // (letztere wird unten noch per tolookup nachgeguckt)
+          if (find_userinfo(parts[0]))
+              wizards+=({parts[0]});
+          break;
+    }
+    // jetzt in userids nachschlagen
+    foreach(uid: tolookup) {
+        if (member(userids,uid))
+            wizards+=userids[uid];
+    }
+    // so. Nun kann es aber noch sein, dass in userids sowas wie
+    // "d.wald.atamur":({atamur}) und "atamur":({"rumata"}) drinsteht, also
+    // ein Magier sozusagen fuer Kram eines anderen verantwortlich ist. D.h.
+    // nochmal durch QueryWizardsForUID() schicken (das ist dann allerdings nicht
+    // weiter rekursiv).
+    if (!recursive) {
+      foreach(uid: wizards) {
+        //es ist moeglich, in der Schleife wizards zu vergroessern, ohne dass
+        //es Einfluss auf die Schleife hat.        
+        wizards += QueryWizardsForUID(uid, 1) - ({uid});
+      }
+    }
+    return(m_indices(mkmapping(wizards)));
+}
+
+//das Define ist nicht sonderlich elegant, aber ich kann hier nicht das
+//newskills.h hier includen (1. zu viel, 2. ists nen Sicherheitsproblem)
+#define P_GUILD_DEFAULT_SPELLBOOK       "guild_sb"
+// dito fuer den Gildenmaster
+#define GUILDMASTER                 "/secure/gildenmaster"
+
+// Den Alias-Cache loeschen (normalerweise einmal am Tag)
+public void ResetUIDAliase() {
+  // RM+ duerfen den Cache loeschen (wenn sie z.B. nen neues Verzeichnis
+  // angelegt haben.)
+  if (extern_call() 
+      && call_sefun("secure_level") < LORD_LVL)
+      return;
+
+  uidaliase=([]);
+}
+
+// expandiert einige 'Aliase' in ein Array von allen UIDs, fuer die sie
+// stehen. Bsp: "region" -> d.region.* + region + d.region,
+// "zauberer" -> GUILD.zauberer, "p.service" -> p.service.*
+// Nutzt Eintrag aus dem uidalias-Cache, sofern vorhanden. 
+public varargs string* QueryUIDAlias(string alias, int rec) {
+  string *uids;
+  if (!stringp(alias) || !sizeof(alias))
+      return ({});
+  // Wen im cache, gehts schnell.
+  if (member(uidaliase, alias))
+      uids = uidaliase[alias];
+  else
+      uids = ExpandUIDAlias(alias, rec);
+
+  if (extern_call())
+    return copy(uids);
+  
+  return(uids);
+}
+
+// Fuer welche UIDs ist ein Magier verantwortlich? (Ist er RM,
+// Gildenmagier, in welchen Regionen hat er ein Verzeichnis, etc.)
+// recursive != 0 bedeutet, dass der Aufruf indirekt aus einem laufenden
+// QueryUIDsForWizard() erfolgt. In dem Fall soll QueryUIDAlias() keine
+// weitere Rekursion durchfuehren. Wird einfach durchgereicht.
+public varargs string* QueryUIDsForWizard(string wizuid, int recursive) {
+    string *uids, *tmp, *uidstoadd;
+    int i;
+
+    if (!stringp(wizuid) || !sizeof(wizuid) || !IS_LEARNER(wizuid))
+        return(({}));
+
+    if (!find_userinfo(wizuid))
+        return(({}));
+
+    uidstoadd=({}); //diese werden hinterher in userids gespeichert.
+
+    // als erstes die ratebaren UIDs. ;-)
+    uids=({wizuid});
+    // Regionen ermitteln, wo wizuid ein Verzeichnis hat und entsprechende
+    // UIDs anhaengen:
+    foreach(string region: get_domain_homes(wizuid)) {
+        uids+=({ sprintf(DOMAINDIR".%s.%s",region,wizuid) });
+    }
+    // Verzeichnis in /p/service?
+    if (file_size(PROJECTDIR"/service/"+wizuid) == FSIZE_DIR)
+        uids+=({PROJECTDIR".service."+wizuid});
+
+    // Gildenchef?
+    if (pointerp(userlist[wizuid,USER_GUILD])) {
+        foreach(string gilde: userlist[wizuid,USER_GUILD]) {
+          uidstoadd += QueryUIDAlias(gilde);
+        }
+    }
+    // Regionsmagier?
+    if (pointerp(userlist[wizuid,USER_DOMAIN])) {
+        foreach(string domain: userlist[wizuid,USER_DOMAIN]) {
+            //generelle Pseudo-UID 'd.region' und 'region' fuer geloeschte
+            //Magier dieser Region vormerken, um sie hinterher fuers
+            //Reverse-Lookup ins uid-Mapping zu schreiben.
+            string *pseudo=({DOMAINDIR"."+domain, domain});
+            uidstoadd += pseudo;
+            // Rest macht QueryUIDAlias, dabei aber die von der Funktion
+            // ebenfalls gelieferten Pseudo-UIDs wieder abziehen.
+            uids += QueryUIDAlias(domain) - pseudo;
+        }
+    }
+    // jetzt noch nachgucken, fuer welche UIDs dieser Magier explizit noch
+    // zustaendig ist.
+    if (pointerp(userlist[wizuid,USER_UIDS_TO_TAKE_CARE])) {
+    // dies koennte etwas a la "region" oder "anderermagier" sein, d.h. dieser
+    // Magier ist fuer alle UIDs von 'andermagier' auch zustaendig. Daher muss
+    // jedes davon durch QueryUIDAlias() (was im Falle von Spielern wiederum
+    // QueryUIDsForWizard() ruft, aber die Rekursion im Falle von Spielern ist
+    // auf 1 begrenzt).
+        foreach(string uid2: userlist[wizuid,USER_UIDS_TO_TAKE_CARE]) {        
+            uidstoadd += QueryUIDAlias(uid2, recursive);
+        }
+    }
+
+    // so, in uidstoadd stehen UIDs drin, die nicht Magiername selber,
+    // d.region.magier oder p.service.magier sind und bei welchen folglich das
+    // Mapping UIDs-nach-Magier nur mit einer Liste moeglich ist. In die
+    // werden die uids nun eingetragen. (z.B. d.region)
+    if (sizeof(uidstoadd)) {
+        // genug Platz in userids? Sonst welche rauswerfen. :-/ 
+        // (besser als bug) TODO: Auf 10k begrenzen -> max Arraygroesse!
+        if ( sizeof(userids)+(i=sizeof(uidstoadd))               
+            >= __MAX_MAPPING_KEYS__) {
+            foreach(string tmpuid: m_indices(userids)[0..i])        
+              m_delete(userids,tmpuid);  
+        }
+        foreach(string tmpuid: uidstoadd) {  
+            if (member(userids,tmpuid)) {  
+                //User dem Array hinzufuegen, wenn noch nicht drin.
+                if (member(userids[tmpuid],wizuid)==-1)
+                    userids[tmpuid]=userids[tmpuid]+({wizuid});              
+            }
+            //sonst neuen Eintragen hinzufuegen            
+            else
+                m_add(userids,tmpuid,({wizuid}));          
+        }
+    } // Ende spez. uids speichern
+    
+    return(uids+uidstoadd);
+}
+
+//Einen Magier als verantwortlich fuer eine bestimmte UID eintragen
+public string* AddWizardForUID(string uid, string wizuid) {
+    if (!stringp(wizuid) || !sizeof(wizuid)
+        || !IS_LEARNER(wizuid))
+        return(({}));
+
+    //Zugriff nur als EM oder jemand, der fuer die UID zustaendig ist.
+    if ( !ARCH_SECURITY
+        && member(
+            QueryUIDsForWizard(call_sefun("secure_euid")),
+            uid) == -1)
+        return(userlist[wizuid,USER_UIDS_TO_TAKE_CARE]);
+
+    if (!pointerp(userlist[wizuid,USER_UIDS_TO_TAKE_CARE]))
+        //Array neu anlegen
+        userlist[wizuid,USER_UIDS_TO_TAKE_CARE]=({uid});
+    else {
+        //Ein Array schon vorhanden
+        if (member(userlist[wizuid,USER_UIDS_TO_TAKE_CARE],uid)==-1)
+            //uid nicht drin
+            userlist[wizuid,USER_UIDS_TO_TAKE_CARE]=
+              userlist[wizuid,USER_UIDS_TO_TAKE_CARE]+({uid});
+    }
+    save_userinfo(wizuid);
+    // aus dem UID-Alias-Cache werfen
+    m_delete(uidaliase, wizuid);
+    // Aufruf, um userids und uidaliase zu aktualisieren
+    QueryUIDsForWizard(wizuid);
+    return(userlist[wizuid,USER_UIDS_TO_TAKE_CARE]);
+}
+
+// Einen Magier wieder austragen, wenn er nicht mehr zustaendig ist.
+public string* RemoveWizardFromUID(string uid, string wizuid) {
+    if (!stringp(wizuid) || !sizeof(wizuid)
+        || !find_userinfo(wizuid))
+        return(({}));
+
+    //Zugriff nur als EM oder jemand, der fuer die UID zustaendig ist.
+    if ( !ARCH_SECURITY
+        && member(
+            QueryUIDsForWizard(call_sefun("secure_euid")),
+            uid)==-1)
+        return copy(userlist[wizuid,USER_UIDS_TO_TAKE_CARE]);
+
+    // jetzt muss diese wizuid aus allen UIDs in userids geloescht werden, die
+    // sie bisher enthalten. Hierzu sollte QueryUIDAlias die potentiell
+    // drinstehenden UIDs liefern.
+    foreach(string tuid: QueryUIDAlias(wizuid,0)) {
+        if (member(userids, tuid) &&
+            member(userids[tuid],wizuid)!=-1 )
+            userids[tuid] -= ({wizuid});
+    }
+    // wenn es eine UID war, fuer die der Magier explizit zustaendig war,
+    // entsprechend loeschen. Sonst ist hier Ende.
+    if (!pointerp(userlist[wizuid,USER_UIDS_TO_TAKE_CARE]))
+        return ({});
+    if (member(userlist[wizuid,USER_UIDS_TO_TAKE_CARE],uid)==-1)
+        return copy(userlist[wizuid,USER_UIDS_TO_TAKE_CARE]);
+
+    // Jetzt aus userlist loeschen.
+    userlist[wizuid,USER_UIDS_TO_TAKE_CARE] -= ({uid});
+    save_userinfo(wizuid);
+    // und userids/uidaliase aktualisieren.
+    QueryUIDsForWizard(wizuid);
+    return copy(userlist[wizuid,USER_UIDS_TO_TAKE_CARE]);
+}
+
+//entfernt einen user aus dem usercache
+void RemoveFromCache(string user) {
+  m_delete(userlist,user);
+}
+
+//loescht den gesamten Usercache
+int clear_cache() {
+  userlist=m_allocate(0,widthof(userlist));
+  update_late_players();
+  return 1;
+}
+
+// Loescht UID->Magier Lookuptabelle. Sollte nicht gerufen werden, wenn man 
+// nicht genau weiss, was man damit kaputtmacht.
+// als Nebeneffekt wird clear_cache() gerufen.
+int ResetUIDCache() {
+  // da diese Funktion auch das UID<->Magier-Lookup loescht, darf das nicht
+  // jeder rufen.
+  if (extern_call() &&
+      call_sefun("secure_level") < ELDER_LVL)
+      return -1;
+  userids=([]);
+  clear_cache();
+  return 1;
+}
+
+//verstorbene Spieler auflisten
+string list_late_players()
+{
+  string ret;
+  int size,i;
+  
+  ret= "************************************\n";
+  ret+="*       Verstorbene Spieler        *\n";
+  ret+="************************************\n";
+  
+  if(!lateplayers || (size=sizeof(lateplayers))==0)
+  {
+    return ret;
+  }
+  
+  for(i=0;i<size;i++)
+  {
+    ret+=lateplayers[i]+"\n";
+  }
+  
+  return ret;
+}
+
+// ist der Spieler gestorben?
+int check_late_player(string str)
+{
+  if(!lateplayers || !str || str=="")
+  {
+    return 0;
+  }
+  
+  if(member(lateplayers,str)!=-1)
+  {
+    return 1;
+  }
+  
+  return 0;
+}
+
+// alle Eintraege im usercache ausgeben
+public mixed *show_cache() {
+    return m_indices(userlist);
+}
+
+// gibt es so einen User? Anfrage ausserdem immer in den Cache eintragen
+public int find_userinfo(string user) {
+  string file;
+  int i;
+  if (!stringp(user) || !sizeof(user) 
+      || member(user,' ')!=-1 || member(user,':')!=-1)
+    return 0;
+  if (!member(userlist,user)) {
+      //erstmal schauen, ob wir mit einem neuen Eintrag nicht die max.
+      //Mapping-Groessen ueberschreiten, wenn ja, einen Eintrag loeschen
+      //BTW: widthof()+1 ist richtig so.
+      //BTW2: Ich hoffe, die max. Arraygroesse ist immer gross genug, sollte 
+      //sie aber sein bei ner Mappingbreite von 10.
+      // BTW3: Dieses Rausloeschen von einem Eintrag bei Erreichen der Grenze
+      // erhoeht die benoetigten Ticks fuer diese Funktion um das 5-6fache und
+      // die noetige Zeit um das 70fache. Daher wird der Cache bei Erreichen
+      // der Grenze jetzt einfach geloescht.
+      if ( ( (i=sizeof(userlist)+1) >= __MAX_MAPPING_KEYS__) 
+          || (( i * (widthof(userlist)+1)) >
+                 __MAX_MAPPING_SIZE__))    
+          //m_delete(userlist,m_indices(userlist)[0]);
+          userlist=m_allocate(1,widthof(userlist));
+
+      // Usersavefile finden
+      if ((file=secure_savefile(user))=="") {
+        // User gibt es nicht, aber Anfrage cachen
+          userlist+=([user: "NP"; -1; ({}); "LOCKED"; -1; time(); ""; ""; "";
+          ({});"";""; 0 ]);
+          return 0;
+      }
+      password="";
+      ep="";
+      ek="";
+      mq="";
+      guilds=({});
+      ektips="";
+      fptips="";
+      uidstotakecare=0;
+      if (!restore_object(file)) return 0;
+      userlist+=([user: password; level; domains; shell; creation_date; 
+          time();ep; ek; mq; guilds; ektips; fptips; uidstotakecare]);
+      // die speziellen UIDs, fuer die dieser Magier zustaendig ist, ermitten
+      // und gleichzeitig im entsprechenden Mapping fuers Reverse-Loopup
+      // speichern.
+      if (level >= LEARNER_LVL)
+        QueryUIDsForWizard(user);
+  }
+  userlist[user,USER_TOUCH]=time();
+  if (userlist[user,USER_LEVEL]==-1) return 0;
+  return 1;
+}
+
+// Daten aus der Userlist fuer diesen User liefern. Macht ggf. find_userinfo()
+public mixed *get_userinfo(string user) {
+  if(!user||user=="")
+    return 0;
+  user=explode(user,".")[0];
+  if (!member(userlist,user) && !find_userinfo(user))
+    return 0;
+
+  if (userlist[user,USER_LEVEL]==-1) return 0;
+
+  return({user,"##"+user,userlist[user,USER_LEVEL],
+      userlist[user,USER_DOMAIN], userlist[user,USER_OBJECT],
+      userlist[user,USER_CREATION_DATE], 0, 0,
+      userlist[user,USER_GUILD],
+      userlist[user,USER_EKTIPS],userlist[user,USER_FPTIPS],
+      userlist[user,USER_UIDS_TO_TAKE_CARE]});
+}
+
+// liefert das Objekt des users aus der Userlist zurueck.
+public string query_player_object( string name )
+{
+  mixed *userentry;
+  if( !find_userinfo(name) ) return "";
+  return userlist[name,USER_OBJECT];
+}
+
+// Passwort ok?
+public int good_password( string str, string name )
+{
+    string rts;
+    int i, n;
+
+    if ( str[0] == '!' ){
+        tell_object( this_player() || this_object(), "Ein Ausrufungszeichen "
+                     "('!') als erster Buchstabe des Passwortes wuerde zu\n"
+                     "Problemen fuehren. Such Dir bitte ein anderes Passwort "
+                     "aus.\n" );
+        return 0;
+    }
+
+    if ( sizeof(str) < 6 ){
+        tell_object( this_player() || this_object(),
+                     "Das Passwort muss wenigstens 6 Zeichen lang sein.\n" );
+        return 0;
+    }
+
+    str = lower_case(str);
+    rts = "";
+
+    // Zahlen/Sonderzeichen am Anfang oder Ende des Passwortes
+    // (z.B. "merlin99") sind nicht wirklich einfallsreich.
+    while ( sizeof(str) && (str[0] > 'z' || str[0] < 'a') ){
+        rts += str[0..0];
+        str = str[1..];
+    }
+
+    while ( sizeof(str) && (str[<1] > 'z' || str[<1] < 'a') ){
+        rts += str[<1..];
+        str = str[0..<2];
+    }
+
+    // Anzahl unterschiedlicher Zeichen merken, die herausgeschnitten wurden
+    n = sizeof( mkmapping(efun::explode( rts, "" )) );
+    rts = "";
+
+    for ( i = sizeof(str); i--; )
+        rts += str[i..i];
+
+    // Eigener Name als Passwort bzw. eigener Name rueckwaerts
+    if ( str == lower_case(name) || rts == lower_case(name) ||
+         // Name eines anderen Mudders (Erstie?)
+         secure_savefile(str) != "" || secure_savefile(rts) != "" ||
+         // Name von der Banish-Liste
+         QueryBanished(str) || QueryBanished(rts) ||
+         // Name eines vorhandenen NPC o.ae.
+         call_sefun("find_living", str ) ||
+         call_sefun("find_living", rts ) ||
+         // Zuwenig verschiedene Zeichen
+         sizeof( mkmapping(efun::explode( str, "" )) ) + n < 4 ){
+        tell_object( this_player() || this_object(),
+                     "Das Passwort waere zu einfach zu erraten. Nimm bitte "
+                     "ein anderes.\n" );
+        return 0;
+    }
+
+    return 1;
+}
+
+// User-ID fuer ein File ermitteln.
+public string get_wiz_name(string file) {
+    return creator_file(file);
+}
+
+// Wizlevel aus dem Userinfo-Cache holen
+public int get_wiz_level(string user) {
+  if (user && find_userinfo(user)) 
+    return userlist[user,USER_LEVEL];
+  //return 0 if no user given (return type needed)
+  return(0);
+}
+
+// Wizlevel fuer eine UID ermitteln.
+public int query_wiz_level( mixed player )
+{
+    if ( objectp(player) && efun::object_info(player, OI_ONCE_INTERACTIVE) )
+        return get_wiz_level( getuid(player) );
+    else {
+        // erstmal UID ermitteln, falls Objekt
+        //if (objectp(player))
+        //    player=getuid(player);
+        if ( stringp(player) ) {
+            if( player[0..1]==DOMAINDIR"." ) return 25;
+            if( player[0..5]==GUILDID"." ) 
+                return WIZLVLS[GUILDID];
+            if( player[0..1]==PROJECTDIR"." ) return 21;
+            // die alte Loesung mit || verhaelt sich falsch, wenn ein UID ne
+            // spezielle ist, der Level 0 zugeordnet wurde und es einen
+            // Spieler mit diesem namen gibt.
+            if (member(WIZLVLS,player)) 
+                return(WIZLVLS[player]);
+            return get_wiz_level(player);
+        }
+    }
+    return 0;
+}
+
+// Savefile aus /secure/save* zurueckliefern
+public string secure_savefile(string name)
+{
+  if(!name||name=="")
+    return "";
+  name=explode(name,".")[0];
+  if (file_size(SECURESAVEPATH+name[0..0]+"/"+name+".o")>=0)
+    return SECURESAVEPATH+name[0..0]+"/"+name;
+
+  return "";
+}
+
+// *************** 'halb-oeffentliche Funktionen *********************
+#ifdef _PUREFTPD_
+// FTP-Berechtigung fuer Magier
+int update_ftp_access(string user, int state) {
+  // wenn nicht EM+ oder ROOT, darf nur fuer this_interactive geaendert
+  // werden.
+  if (getuid(PO) != ROOTID && extern_call()
+      && !ARCH_SECURITY) {
+    if (this_interactive())
+      user = getuid(this_interactive());
+    else
+      user = 0;
+  }
+  if (!user || !sizeof(user))
+    return -1;
+
+  if (!find_userinfo(user))
+    return 0;
+
+  // Passwort muss manuell vom Magier neu gesetzt werden, da es keine
+  // Moeglichkeit gibt, an das aktuelle im Klartext heranzukommen.
+  if (state) {
+    if (!member(ftpuser,user))
+      m_add(ftpuser, user, "*");
+  }
+  else
+    m_delete(ftpuser, user);
+  
+  call_out(#'write_ftp_users, 4);
+  return state;
+}
+#endif
+
+// Spieler ein neues Wizlevel verpassen.
+int update_wiz_level(string user,int lev) {
+  object ob;
+
+  if (getuid(PO) != ROOTID && extern_call()) return 0;
+  if (!find_userinfo(user)) return 0;
+  userlist[user,USER_LEVEL] = lev;
+  save_userinfo(user);
+  return 1;
+}
+
+// neue Forscherpunkte fuer den User abspeichern.
+int update_ep(string user,string ep_neu) {
+  if (getuid(PO) != ROOTID) return 0;
+  if (!find_userinfo(user)) return 0;
+  userlist[user,USER_EP] = ep_neu;
+  save_userinfo(user);
+  return 1;
+}
+
+// neue Erstkills fuer den User abspeichern.
+int update_ek(string user,string ek_neu) {
+  if (getuid(PO) != ROOTID) return 0;
+  if (!find_userinfo(user)) return 0;
+  userlist[user,USER_EK] = ek_neu;
+  save_userinfo(user);
+  return 1;
+}
+
+// Miniquestdaten speichern.
+int update_mq(string user,string mq_neu) {
+  if (getuid(PO) != ROOTID) return 0;
+  if (!find_userinfo(user)) return 0;
+  userlist[user,USER_MQ] = mq_neu;
+  save_userinfo(user);
+  return 1;
+}
+
+// Erstkillpunkt-Tips speichern.
+int update_ektips(string user,string ek_neu) {
+  if (getuid(PO) != ROOTID) return 0;
+  if (!find_userinfo(user)) return 0;
+  userlist[user,USER_EKTIPS] = ek_neu;
+  save_userinfo(user);
+  return 1;
+}
+
+// Forscherpunkttips abspeichern.
+int update_fptips(string user,string fp_neu) {
+  if (getuid(PO) != ROOTID) return 0;
+  if (!find_userinfo(user)) return 0;
+  userlist[user,USER_FPTIPS] = fp_neu;
+  save_userinfo(user);
+  return 1;
+}
+
+// forscherpunkte abfragen.
+string query_ep(string user) {
+  if (getuid(PO) != ROOTID) return 0;
+  if (!find_userinfo(user)) return 0;
+  return userlist[user,USER_EP];
+}
+
+// Erstkills abfragen
+string query_ek(string user) {
+  if (getuid(PO) != ROOTID) return 0;
+  if (!find_userinfo(user)) return 0;
+  return userlist[user,USER_EK];
+}
+
+// Miniquests abfragen.
+string query_mq(string user) {
+  if (getuid(PO) != ROOTID) return 0;
+  if (!find_userinfo(user)) return 0;
+  return userlist[user,USER_MQ];
+}
+
+// EK-Tips abfragen.
+string query_ektips(string user) {
+  if (getuid(PO) != ROOTID) return 0;
+  if (!find_userinfo(user)) return 0;
+  return userlist[user,USER_EKTIPS];
+}
+
+// FP-Tips abfragen.
+string query_fptips(string user) {
+  if (getuid(PO) != ROOTID) return 0;
+  if (!find_userinfo(user)) return 0;
+  return userlist[user,USER_FPTIPS];
+}
+
+#define PLAYERSHELLS ({"/std/shells/darkelf", "/std/shells/dwarf", \
+    "/std/shells/elf", "/std/shells/feline", "/std/shells/hobbit", \
+    "/std/shells/human" })
+
+// Aendert die Shells eines Users.
+int set_player_object( string user, string objectname )
+{
+    mixed *path;
+    string prev;
+
+    // nur EM und ROOT duerfen die Shell eines Charakters aendern
+    if ( !ARCH_SECURITY &&
+         (!previous_object() || getuid(previous_object()) != ROOTID) ) {
+        return -1;
+    }
+
+    if ( objectname == "" )
+        objectname = 0;
+
+    if ( !stringp(user) || user == "" )
+        return -6;
+
+    if ( !stringp(objectname) ){
+        if ( !find_userinfo(user) )
+            return -4;
+
+        userlist[user, USER_OBJECT] = 0;
+        save_userinfo(user);
+        return 1;
+    }
+
+    if ( catch(load_object(objectname);publish) ) {
+        write( "Fehler in " + objectname + "!\n" );
+        return -2;
+    }
+
+    objectname = _get_path( objectname, 0 );
+    path = (efun::explode( objectname, "/" ) - ({ "", 0 }));
+
+    if ( sizeof(path) < 3 || path[0] != "std" || path[1] != "shells" )
+        return -3;
+
+    if ( !find_userinfo(user) )
+        return -4;
+
+    prev = userlist[user, USER_OBJECT];
+    userlist[user, USER_OBJECT] = objectname;
+    save_userinfo(user);
+
+    // Loggen, falls die Aenderung nicht von Login beim Anlegen des Chars
+    // erfolgt.
+    if (load_name(this_interactive()) != "/secure/login"
+        || prev != "") {
+      if (prev == "") prev ="<keine>";
+      call_sefun("log_file", "ARCH/SHELL_AENDERUNGEN",
+        sprintf( "%s: %O aendert die Shell von %s von %s auf %s (PO: %O)\n",
+          strftime("%Y%m%d-%H%M%S"), 
+          this_interactive(), capitalize(user), prev, objectname,
+          previous_object()) );
+    }
+
+    return 1;
+}
+
+// Passwort aktualisieren.
+int update_password( string old, string new )
+{
+    string user;
+
+    // nanu, kein user?
+    if ( !find_userinfo(user = getuid(PO)) )
+        return 0;
+
+    // wenn das neue PW unterschiedlich ist, schauen, ob das neue PW ok ist.
+    if ( old != new && !good_password( new, user ) )
+        return 0;
+
+    string pwhash = userlist[user, USER_PASSWORD];
+    string oldpwhash;
+    if (sizeof(pwhash) > 13) {
+        // MD5-Hash
+        oldpwhash = md5_crypt(old, pwhash);
+    }
+    else if (sizeof(pwhash) > 2) {
+        // Crypt-Hash
+        oldpwhash = crypt(old, pwhash[0..1]);
+    }
+
+    // wenn es einen PW-hash gibt, also ein PW gesetzt wird, muss der Hash von
+    // old mit dem pwhash uebereinstimmen. Leerer Hash oder gar kein Hash
+    // erlaubt jedes beliebige PW.
+    if ( stringp(pwhash) && sizeof(pwhash) && pwhash != oldpwhash)
+        return 0;
+    // an dieser Stelle stimmt 'old' wohl mit dem gesetzten Passwort ueberein.
+    // Wenn allerdings die Funktion mit old==new aufgerufen wurde, wird hier
+    // nur 1 zurueckgeben und sonst an sich nix gemacht. Kann nicht weiter
+    // oben schon gemacht werden, die Shells dies als PW-Pruefung nutzen.
+    // *seufz*
+    if (old == new) return 1;
+
+    // dann mal neu setzen
+    userlist[ user, USER_PASSWORD ] = md5_crypt( new, 0 );
+    save_userinfo(user);
+#ifdef _PUREFTPD_
+    // Bedauerlicherweise versteht pureftpd unser md5_crypt nicht. :-(
+    if (member(ftpuser,user)) {
+      ftpuser[user] = crypt(new);
+      if (find_call_out(#'write_ftp_users) == -1)
+        call_out(#'write_ftp_users,4);
+    }
+#endif
+    return 1;
+}
+
+// Spieler loeschen.
+int delete_player(string passwd, string real_name)
+{
+  int wlevel;
+  string part_filename;
+
+  if (!PO || PO!=TP || PO!=TI || real_name != getuid(PO) ||
+      !find_userinfo(real_name))
+    return 0;
+  mixed erstie=(mixed)this_interactive()->QueryProp(P_SECOND);
+  password = userlist[real_name,USER_PASSWORD];
+  wlevel = get_wiz_level(real_name);
+  if (!update_password(passwd, passwd)) return 0;
+
+  // Spielpausen aufheben (sonst kann man als Spieler nen Namen sperren).
+  TBanishName(real_name, 0);
+
+  part_filename="/"+real_name[0..0]+"/"+real_name+".o";
+  rm("/"SECUREDIR"/save"+part_filename);
+  rm("/"LIBSAVEDIR"/"+part_filename);
+  rm("/"MAILDIR"/"+part_filename);
+  
+  m_delete(userlist,real_name);
+  
+  if (wlevel >= LEARNER_LVL)
+    TO->BanishName(real_name, "So hiess mal ein Magier hier");
+  else if (wlevel >= SEER_LVL)
+    TO->BanishName(real_name, "So hiess mal ein Seher hier");
+
+#ifdef _PUREFTPD_
+    if (member(ftpuser,real_name)) {
+      m_delete(ftpuser,real_name);
+      call_out(#'write_ftp_users,4);
+    }
+#endif
+
+  call_sefun("log_file", "USERDELETE",
+           sprintf("%s: %s %s(%s)\n",
+                   ctime(time()),real_name,
+                   (stringp(erstie)?sprintf("[Erstie: %s] ",erstie):""),
+                   call_sefun("query_ip_number",TI)));
+
+  return 1;
+}
+
+// ermittelt alle existierenden Spieler, die ein Savefile in /secure/save
+// haben.
+// ([ "a": ({...namen...}), "b": ({...namen...}), ... ]) 
+public mapping get_all_players() {
+  string *dirs=({"a","b","c","d","e","f","g","h","i","j","k","l","m","n","o",
+      "p","q","r","s","t","u","v","w","x","y","z"});
+  mapping allplayer=([]);
+  string *tmp;
+  foreach(string dir: dirs) {
+    tmp=get_dir("/secure/save/"+dir+"/*")
+      - ({".","..",".KEEP",".svn"});
+    allplayer[dir] = map(tmp,function string (string fn) 
+                     { return explode(fn,".")[0]; } );
+  }
+  return allplayer;
+}
+
+// *************** interne Funktionen ********************************
+protected void create()
+{
+  userlist=m_allocate(0,12);
+  update_late_players();
+#ifdef _PUREFTPD_
+  read_ftp_users();
+#endif
+}
+
+//verstorbene Spieler einlesen
+void update_late_players() {
+  string read;
+  string *tmp;
+  
+  lateplayers=0;
+  
+  read=read_file("/"SECUREDIR"/LATE_PLAYERS");
+  if(!read || read=="")
+  {
+    return;
+  }
+  
+  tmp=explode(read,"\n");
+  if(!sizeof(tmp))
+  {
+    return;
+  }         
+  
+  lateplayers=tmp; 
+}
+
+
+// Daten aus der Userlist inkl. Passworthash zurueckliefern. Macht ggf.
+// find_userinfo(). Nur masterintern aufrufbar.
+protected mixed *get_full_userinfo(string user) {
+  if(!user||user=="")
+    return 0;
+  user=explode(user,".")[0];
+  if (!member(userlist,user) && !find_userinfo(user))
+    return 0;
+
+  return({user,userlist[user,USER_PASSWORD],userlist[user,USER_LEVEL],
+      userlist[user,USER_DOMAIN], userlist[user,USER_OBJECT],
+      userlist[user,USER_CREATION_DATE], 0, 0, userlist[user,USER_GUILD],
+      userlist[user,USER_EKTIPS],userlist[user,USER_FPTIPS],
+      userlist[user,USER_UIDS_TO_TAKE_CARE]});
+}
+
+// Userdaten aus der Userlist im Savefile in /secure/save/ speichern.
+protected void save_userinfo(string user) {
+  if(!user||user=="")
+    return;
+  user=explode(user,".")[0];
+  if (!member(userlist,user)) return;
+  name = user;
+  level = userlist[name,USER_LEVEL];
+  domains = userlist[name,USER_DOMAIN];
+  shell = userlist[name,USER_OBJECT];
+  password = userlist[name,USER_PASSWORD];
+  creation_date = userlist[name,USER_CREATION_DATE];
+  if (!creation_date) creation_date = -1;
+  ep = userlist[name,USER_EP];
+  if (!ep) ep="";
+  ek = userlist[name,USER_EK];
+  if (!ek) ek="";
+  mq = userlist[name,USER_MQ];
+  if (!mq) mq="";
+  guilds = userlist[name,USER_GUILD];
+  ektips=userlist[name,USER_EKTIPS];
+  if(!ektips) ektips="";
+  fptips=userlist[name,USER_FPTIPS];
+  if(!fptips) fptips="";
+  uidstotakecare=userlist[name,USER_UIDS_TO_TAKE_CARE];
+
+  if (save_object(SECURESAVEPATH+name[0..0]+"/"+name) != 0) {
+    // autsch. Buggen damit dieser moeglichst schnell auffaellt, dass hier
+    // Savefiles in /secure/save/ nicht geschrieben wurden.
+    raise_error(sprintf(
+          "Savefile %O konnte nicht erstellt werden!\n",
+          SECURESAVEPATH+name[0..0]+"/"+name));
+  }
+}
+
+// In welchen Regionen ist der Spieler Regionsmagier?
+protected void set_domains(string player, string *domains)
+{
+  // wenn der Magier jetzt Domains nicht mehr hat, muessen die aus 'userids'
+  // entfernt werden, das uebernimmt RemoveWizardFromUID().
+  if (pointerp(userlist[player, USER_DOMAIN])) {
+    string *removeduids=
+      ((string*)userlist[player, USER_DOMAIN] | domains) - domains;
+    foreach(string uid: removeduids)
+      RemoveWizardFromUID(uid, player);
+  }
+  // gecachtes Alias fuer player loeschen
+  m_delete(uidaliase,player);
+  userlist[player, USER_DOMAIN]=domains;
+  save_userinfo(player);
+  // UID-zu-Magier-Mapping aktualisieren
+  QueryUIDsForWizard(player);
+}
+
+// In welche Gilden ist der Spieler Gildenmagier?
+protected void set_guilds(string player, string *guilds)
+{
+  // wenn der Magier jetzt Gilden nicht mehr hat, muessen die aus 'userids'
+  // entfernt werden, das uebernimmt RemoveWizardFromUID().
+  if (pointerp(userlist[player, USER_GUILD])) {
+    string *removeduids=
+      ((string*)userlist[player, USER_GUILD] | guilds) - guilds;
+    foreach(string uid: removeduids)
+      RemoveWizardFromUID(uid, player);
+  }
+  // gecachtes Alias fuer player loeschen
+  m_delete(uidaliase,player);
+  userlist[player, USER_GUILD]=guilds;
+  save_userinfo(player);
+  // UID-zu-Magier-Mapping aktualisieren
+  QueryUIDsForWizard(player);
+}
+
+// Userinfo-Cache expiren...
+protected void _cleanup_uinfo()
+{
+    foreach(string user: userlist) {
+        if ((time() - userlist[user,USER_TOUCH]) > 1800
+            && !call_sefun("find_player",user))
+          m_delete(userlist,user);
+    }
+}
+
+// expandiert einige 'Aliase' in ein Array von allen UIDs, fuer die sie
+// stehen. Bsp: "region" -> d.region.* + region + d.region,
+// "zauberer" -> GUILD.zauberer, "p.service" -> p.service.* oder auch
+// "magier" -> QueryUIDsForWizard().
+// Das erfolgt ueber Lookup der entsprechenden Verzeichnisse auf der PLatte.
+// Da zusaetzlich auch noch jede Menge find_userinfo() dazu kommen, ist das
+// recht aufwaendig. Damit das ganze nicht jedesmal erfolgen muss, wird das
+// Ergebnis gecacht und QueryUIDAlias() nutzt den Cache.
+private string* ExpandUIDAlias(string alias, int rec) {
+
+  string *uids=({});
+
+  // Regionsname?
+  if (file_size("/"DOMAINDIR"/"+alias) == FSIZE_DIR) {
+    //generelle Pseudo-UID 'd.region' und 'region' fuer geloeschte
+    //Magier dieser Region
+    uids += ({DOMAINDIR"."+alias, alias});
+    //alle Magier-Verzeichnisse ermitteln:
+    string tmpdir="/"DOMAINDIR"/"+alias+"/";
+    foreach(string dir: (get_dir(tmpdir+"*") || ({}))
+                          - ({".","..",".svn"})) {
+      // schauen, obs nen (sichtbares) Verzeichnis ist und ob der Magier
+      // existiert. Letzteres aber nur, falls die Rekursionstiefe min. 100
+      // ist, da beim Nachschauen eines Magiers mit (mehreren)
+      // RM-Posten, der in der Region RMs aus anderen Regionen hat, leicht
+      // Rekursionstiefen von 20 (*4) auftreten koennen, wenn noch gar
+      // keine UIDs im Cache sind (find_userinfo() ruft indirekt diese
+      // Funktion).
+      if (dir[0]!='.' 
+          && file_size(tmpdir+dir) == FSIZE_DIR
+#if __MAX_RECURSION__ > 99
+          && find_userinfo(dir)
+#endif
+          )
+          uids += ({DOMAINDIR"."+alias+"."+dir});
+    }
+  }
+  // Gildenname?
+  else if (GUILDMASTER->ValidGuild(alias)) {
+      uids += ({GUILDID"."+alias});
+      //hat die Gilde ein Projektverzeichnis?
+      if (file_size("/"PROJECTDIR"/"+alias) == FSIZE_DIR) {
+          uids += ({PROJECTDIR"."+alias});
+      }
+      // jetzt haben dummerweise die Spellbooks meist nicht den gleichen
+      // Namen wie die Gilde. D.h. die Gilde muss nun noch nach dem
+      // Namen des Spellbooks gefragt werden, weil dessen UID nicht
+      // unbedingt gleich dem der Gilde ist. *seufz*
+      string spbook;
+      object guild;
+      catch(guild=load_object(GUILDDIR"/"+alias));
+      if (objectp(guild) 
+          && (sizeof(spbook=(string)
+                 guild->QueryProp(P_GUILD_DEFAULT_SPELLBOOK)))
+                && spbook!=alias)
+          uids += ({GUILDID"."+spbook});
+  }
+  // Spieler/Magier-UID?
+  else if (find_userinfo(alias)) {
+    // wenn rec > 0, wird eine Spieler-UID als Alias aufgefasst und zu seinen
+    // UIDs expandiert. Hierbei erfolgt aber nur eine Rekursion.
+    if (!rec) uids = QueryUIDsForWizard(alias, 1);
+    else uids = ({alias});
+  }
+  // Projektkrams? -> alle Subdirs von /p/ ausser /p/service selber.
+  else if (alias==PROJECTDIR) {
+    foreach(string dir: (get_dir("/"PROJECTDIR"/*") || ({}))
+                         - ({".","..",".svn","service"})) {
+      if (dir[0]!='.' &&
+          file_size("/"PROJECTDIR"/"+dir) == FSIZE_DIR)
+          uids += ({PROJECTDIR"."+dir});
+    }
+  }
+  // p.service? -> Alle Subdirs von /p/service.
+  else if (alias==PROJECTDIR".service") {
+    foreach(string dir: (get_dir("/"PROJECTDIR"/service/*") || ({}))
+                         - ({".","..",".svn"})) {
+      if (dir[0]!='.' &&
+          file_size("/"PROJECTDIR"/service/"+dir) == FSIZE_DIR)
+          uids += ({PROJECTDIR".service."+dir});
+    }
+  }
+  // wenn nix zutrifft -> unexpandiert zurueckgeben
+  else
+    uids = ({alias});
+
+  // im Cache vermerken
+  if (sizeof(uidaliase) >= __MAX_MAPPING_KEYS__)
+      uidaliase=m_allocate(1);
+  // auch vermerken, wenn keine UIDs ermittelt wurden.
+  uidaliase += ([alias: uids]);
+  return uids;
+}
+
diff --git a/secure/materialdb.c b/secure/materialdb.c
new file mode 100644
index 0000000..7f71bc4
--- /dev/null
+++ b/secure/materialdb.c
@@ -0,0 +1,936 @@
+// MorgenGrauen MUDlib
+//
+// - Prototypen und Properties in thing/material.h
+// - Liste in materials.h
+//
+// TODO: properties.h um materials.h erweitern
+//
+// - implizite Gruppenzuordnung entfernen, da jetzt explizit in
+//   Definitionsdateien vorhanden
+// - Materialdoku ueberarbeiten, dabei Hinweis auf nicht mehr implizite
+//   Gruppenzuordnung
+// /p/daemon/materialdb.c -- Materialdatenbank
+//
+// $Id: materialdb.c 8755 2014-04-26 13:13:40Z Zesstra $
+
+#pragma strong_types
+#pragma no_clone
+#pragma no_inherit
+#pragma no_shadow
+#pragma pedantic
+
+// Die Propertydefinition reinholen
+#include <language.h>
+#include <thing/description.h>
+// Materialliste nicht mit reinziehen
+#define _SKIP_MATERIALS_
+#include <thing/material.h>
+#include <player/description.h>
+#include <rtlimits.h>
+
+// Basisverzeichnis der Materialien
+#define MAT_DIR "/doc/materials"
+
+// Ausgabeverzeichnis fuer Doku
+#define DOC_DIR(x) ("/doc/materials/"+x)
+
+// Dateiname des Headers mit Materialdefinitionen
+#define HEADERFILE "/sys/materials.h"
+
+// Savefile.
+#define SAVEFILE DOC_DIR("materialdb")
+
+// Rein intern verwendete Namen
+#define P_RECOC "recognizability"
+#define P_ID    "id"
+#define P_DEFSTR "defstr"
+#define P_MEMBERS "members"
+#define P_MG_FRACTIONS "mg_fractions"
+
+#define LOG_ERROR(x) if(find_player("raschaua")&&find_player("raschaua")->QueryProp("mdb-debug"))tell_object(find_player("raschaua"),"MDB-Error:"+x)
+#define LOG_WARN(x) if(find_player("raschaua")&&find_player("raschaua")->QueryProp("mdb-debug"))tell_object(find_player("raschaua"),"MDB-Warn:"+x)
+
+// Prototypes:
+// (Liefert den Anteil der Materialgruppe grp an mats)
+int MaterialGroup(mapping mats, string grp);
+// Konvertiert eine Liste von Materialien zu ihren Namen, dabei wird die
+// Erkennungsfaehigkeit beruecksichtigt und evtl. falsch erkannt
+varargs string ConvMaterialList(mixed mats, int casus, mixed idinf);
+varargs string MaterialName(string mat, int casus, mixed idinf);
+// Gibt den Namen einer Materialgruppe zurueck
+string GroupName(string grp);
+// Gibt alle Materialien zurueck
+string *AllMaterials();
+// Gibt alle Gruppen zurueck
+string *AllGroups();
+// Gibt alle Gruppen zurueck, in denen mat enthalten ist
+string *GetMatMembership(string mat);
+// Gibt alle Materialien zurueck, die in grp enthalten sind
+string *GetGroupMembers(string grp);
+// Erneuert die Materialien durch Scannen des Materialverzeichnisses
+void Update();
+// generiert Headerfile aus den Daten
+varargs void GenHeaderFile(string fn);
+
+mapping materials;
+mapping material_groups;
+private status initialized;
+mapping old_mat_keys;     // Alter Materialkey -> neuer Key (Kompatibilitaet)
+nosave mapping new_materials;       // Materialien waehrend des Scannens
+nosave mapping new_material_groups; // Materialgruppen waehrend des Scannens
+private nosave status isScanning;
+private nosave int updateTicks;
+
+void create() {
+  seteuid(getuid());
+  // Savefile einlesen, falls moeglich, damit die DB direkt initialisert ist,
+  // wenn auch ggf. mit alten Daten.
+  restore_object(SAVEFILE);
+  if (initialized) {
+    // falls erfolgreich, direkt Header fuer die Mudlib schreiben
+    GenHeaderFile();
+  }
+  // jetzt Update der Daten durchfuehren.
+  Update();
+}
+
+//==================== Umwandeln der Strings aus der thing/material.h
+private string getMatId(string key) {
+  // Alte Bezeichner umwandeln
+  if (!member(materials, key))
+    key = old_mat_keys[key];
+  return key;
+}
+private string getMatGroupId(string key) {
+  // Alte Bezeichner umwandeln
+  if (!member(material_groups, key))
+    key = "MATGROUP_"+upperstring(key[3..]);
+  return key;
+}
+private string matKey2Defstr(string key, mapping mats) {
+  string id;
+  if (member(mats[key], P_DEFSTR))
+    id = mats[key][P_DEFSTR];
+  else
+    id = key;
+  return id;
+}
+private string groupKey2Defstr(string key) {
+  if (sizeof(key) > 9)
+    key = "mg_"+lowerstring(key[9..]);
+  else
+    key = "";
+  return key;
+}
+
+//==================== Schnittstellenfunktionen zur Verwendung der DB
+varargs string MaterialName(string mat, int casus, mixed idinf) {
+  if (initialized) {
+    string *names;
+    mapping props;
+    mixed *dif;
+    // Anpassen der Materialid
+    mat = getMatId(mat);
+
+    if (!mappingp(props=materials[mat]))
+      props=([]);
+
+    // Je nach Koennen des Spielers kann man das exakte Material
+    // mehr oder weniger gut erkennen:
+    if (pointerp(dif=props[P_RECOC])
+&& (!intp(idinf)||idinf<100) ) { // 100=exakte Erkennung
+      int i, n, recval;
+      mixed *grps, tmp, x;
+
+      recval=0;
+      grps=props[P_MG_FRACTIONS];
+      if (!pointerp(idinf))
+        idinf=({idinf});
+
+      // Zunaechst die Faehigkeit des Spielers (da koennen noch
+      // Gildenfaehigkeiten hinzu kommen) ermitteln, dieses
+      // Material zu erkennen:
+      i=sizeof(idinf);
+      while(i--) {
+        tmp=idinf[i];
+        if (objectp(tmp)) // Diese Property ist hauptsaechlich fuer Rassen:
+          tmp=tmp->QueryProp(P_MATERIAL_KNOWLEDGE);
+        if (intp(tmp)) {
+          recval+=tmp; // Allgemeine Erkennungsfaehigkeit
+          break;
+        }
+        if (closurep(tmp) && intp(x=funcall(tmp,mat,grps))) {
+          recval+=x;
+          break; // Closures koennen immer nuetzlich sein :)
+        }
+        if (mappingp(tmp)) {
+          int j;
+          if ((x=tmp[mat]) && intp(x)){
+            // Erkennung von speziell diesem Material
+            recval+=x;
+            break;
+          }
+          // Erkennung von Gruppen
+          j=sizeof(grps);
+          while(j--)
+            if((x=tmp[grps[j]]) && intp(x))
+              recval+=x;
+          if (pointerp(tmp=tmp[MATERIAL_SYMMETRIC_RECOGNIZABILITY])) {
+            for (j=sizeof(tmp)-2;j>=0;j-=2) {
+              if (!intp(x=tmp[j+1]))
+                raise_error("materialdb: illegal sym.recoc. format\n");
+              if (props[tmp[j]])
+                recval+=x;
+              else // bei passenden Gruppen +, bei anderen -
+                recval-=x;
+            }
+          }
+        }
+      }
+
+      // Jetzt wird ermittelt, ob vielleicht eine ungenauere
+      // Beschreibung gegeben werden soll:
+      x=dif[0];
+      n = sizeof(dif)-1;
+      for (i=2;i<=n;i+=2) {
+        if (recval>=dif[i-1])
+          x=dif[i];
+      }
+      // Wenn die Faehigkeiten des Spielers nicht fuer den echten Klarnamen
+      // ausreichen, gib die Alternative zurueck:
+      if (x!=mat)
+        return MaterialName(x, casus, 100);
+    }
+
+    if (!pointerp(names=props[P_NAME]) || sizeof(names)<4)
+      names=({"unbekanntes Material", "unbekannten Materials",
+              "unbekanntem Material", "unbekannten Material"});
+    if (casus<0 || casus>3)
+      casus=0;
+    return names[casus];
+  }
+}
+
+varargs string ConvMaterialList(mixed mats, int casus, mixed idinf) {
+  if (initialized) {
+    string *ms,ml;
+    int i;
+
+    ml="";
+    if (mappingp(mats))
+      ms=m_indices(mats);
+    else if (stringp(mats))
+      ms=({mats});
+    else if (pointerp(mats))
+      ms=mats;
+    else
+      ms=({});
+    i=sizeof(ms);
+    while(i) {
+      ml+=MaterialName(ms[--i],casus,idinf);
+      if (i)
+        ml+=((i>1)?", ":" und ");
+    }
+    return ml;
+  }
+}
+
+int MaterialGroup(mapping mats, string grp) {
+   if (initialized) {
+     string *ms;
+     int i,res;
+
+     res=0;
+     if (!mappingp(mats) || !stringp(grp))
+       return res;
+     ms=m_indices(mats);
+     i=sizeof(ms);
+     while(i--) {
+       string mat;
+       mapping props;
+       mat=ms[i];
+       if (mappingp(props=materials[getMatId(mat)]))
+         res+=(mats[mat]*props[P_MG_FRACTIONS][getMatGroupId(grp)])/100;
+     }
+     if (res<-100) // Vielleicht noch Antimaterie zulassen
+       res=-100;   // (noch nicht sicher ob das so bleiben wird oder 0 sein wird)
+     if (res>100)
+       res=100;
+     return res;
+   }
+}
+
+string *AllMaterials() {
+  if (initialized) {
+    // Aus Kompatibilitaetsgruenden die alten Schluessel (#define-String)
+    // zurueckgeben
+    return m_indices(old_mat_keys);
+  }
+  return 0;
+}
+
+string *AllGroups() {
+  if (initialized) {
+    // Aus Kompatibilitaetsgruenden die alten Schluessel (#define-String)
+    // zurueckgeben
+    return map(m_indices(material_groups), #'groupKey2Defstr);
+  }
+  return 0;
+}
+
+string *GetMatMembership(string mat) {
+  if (initialized) {
+    mapping props;
+    // Anpassen der Materialid
+    mat = getMatId(mat);
+
+    if (!mappingp(props=materials[mat]))
+      return ({});
+    return map(m_indices(props[P_MG_FRACTIONS]), #'groupKey2Defstr);
+  }
+  return 0;
+}
+
+string *GetGroupMembers(string grp) {
+  if (initialized) {
+    string *mats;
+    // Anpassen der Materialid
+    grp = getMatGroupId(grp);
+    if (!member(material_groups, grp) ||
+	!pointerp(mats=material_groups[grp][P_MEMBERS]))
+      return ({});
+    return map(mats, #'matKey2Defstr, materials);
+  }
+  return 0;
+}
+
+string GroupName(string grp) {
+  if (initialized) {
+    if (member(material_groups, getMatGroupId(grp)))
+      return material_groups[getMatGroupId(grp)][P_NAME];
+    else
+      return "Unbekanntes";
+  }
+}
+
+string GroupDescription(string grp) {
+  if (initialized) {
+    if (member(material_groups, getMatGroupId(grp)))
+      return material_groups[getMatGroupId(grp)][P_DESCRIPTION];
+    else
+      return "Gruppe unbekannt";
+  }
+}
+
+//==================== Generieren von Headerfile und Manpages
+private string *get_ordered_groups()
+{
+  return ({"MATGROUP_WOOD", "MATGROUP_JEWEL", "MATGROUP_STONE", "MATGROUP_MAGNETIC",
+           "MATGROUP_METAL", "MATGROUP_DRUG", "MATGROUP_HERBAL", "MATGROUP_FLEXIBLE",
+           "MATGROUP_BIO", "MATGROUP_ACIDIC", "MATGROUP_BASIC", "MATGROUP_POISONOUS",
+           "MATGROUP_EXPLOSIVE", "MATGROUP_INFLAMMABLE",
+           "MATGROUP_ELEMENTAL", "MATGROUP_ELECTRICAL", "MATGROUP_MAGIC",
+           "MATGROUP_HOLY", "MATGROUP_UNHOLY", "MATGROUP_INVIS",
+           "MATGROUP_SOLID", "MATGROUP_FLUID", "MATGROUP_GAS"});
+}
+private string gen_material_h_head()
+{
+  return
+    "// MorgenGrauen MUDlib\n//\n"
+    "// materials.h -- material definitions\n//\n"
+    "// This file is generated by /secure/materialdb.c\n//\n"
+    "// DO NOT EDIT!\n//\n"
+    "// $Id: materialdb.c 8755 2014-04-26 13:13:40Z Zesstra $\n\n"
+    "#ifndef __MATERIALS_H__\n"
+    "#define __MATERIALS_H__\n\n";
+}
+private string gen_material_h_material(string mat, string last_grp)
+{
+  mat = old_mat_keys[mat];
+  return sprintf("#define %-24s\"%-20s // %s\n", mat,
+                (member(materials[mat], P_DEFSTR)?materials[mat][P_DEFSTR]:mat)+"\"",
+                materials[mat][P_DESCRIPTION]||materials[mat][P_NAME][WER]);
+}
+private string gen_material_h_materials_grp(string grp, string *left)
+{
+  string txt, *mats;
+  txt = sprintf("\n// Gruppe: %s\n", GroupName(grp));
+  mats = GetGroupMembers(grp) - (GetGroupMembers(grp) - left);
+  txt += sprintf("%@s", map(sort_array(mats, #'>), #'gen_material_h_material));
+  left -= GetGroupMembers(grp);
+  return txt;
+}
+private string gen_material_h_materials()
+{
+  string txt, last_grp;
+  string *grps, *mats;
+  txt = "// ****************************** Materialien ******************************\n";
+  // Gruppenweise ordnen
+  grps = get_ordered_groups();
+  mats = AllMaterials();
+  txt += sprintf("%@s", map(grps, #'gen_material_h_materials_grp,
+                                  &mats));
+  // Übriggebliene Materialien ausgeben
+  txt += "// sonstige Materialien:\n";
+  txt += sprintf("%@s", map(mats, #'gen_material_h_material));
+  return txt;
+}
+private string gen_material_h_group(string grp)
+{
+  return sprintf("#define %-27s\"%-18s // %s\n",
+                 grp, groupKey2Defstr(grp)+"\"", GroupName(grp));
+}
+private string gen_material_h_groups()
+{
+  string txt;
+  txt = "\n// **************************** Materialgruppen ****************************\n\n"
+    "#ifndef _IS_MATERIALDB_\n";
+  txt += sprintf("%@s\n", map(sort_array(m_indices(material_groups), #'>),
+                                    #'gen_material_h_group));
+  txt += "\n#endif // _IS_MATERIALDB_\n";
+  return txt;
+}
+private string gen_material_h_foot()
+{
+  return
+    "#endif // __THING_MATERIAL_H__\n";
+}
+private int dump_material_h(string fn)
+{
+  return (write_file(fn, gen_material_h_head()) &&
+          write_file(fn, gen_material_h_materials()) &&
+          write_file(fn, gen_material_h_groups()) &&
+          write_file(fn, gen_material_h_foot()));
+}
+private string gen_material_list_material(string mat)
+{
+  mat = old_mat_keys[mat];
+  return sprintf("  %-28s%=-45s\n", mat,
+                 materials[mat][P_DESCRIPTION]||materials[mat][P_NAME][WER]);
+}
+private string gen_material_list_materials_grp(string grp, string *left)
+{
+  string txt, *mats;
+  txt = sprintf("%s:\n", capitalize(GroupName(grp)));
+  mats = sort_array(GetGroupMembers(grp) - (GetGroupMembers(grp) - left), #'>);
+  txt += sprintf("%@s\n", map(mats, #'gen_material_list_material));
+  left -= GetGroupMembers(grp);
+  return txt;
+}
+private void dump_material(string fn)
+{
+  string txt;
+  string *grps, *mats;
+  // Gruppenweise ordnen
+  grps = get_ordered_groups();
+  mats = AllMaterials();
+  txt = sprintf("%@s", map(grps, #'gen_material_list_materials_grp,
+                                 &mats));
+  // Übriggebliene Materialien ausgeben
+  txt += "sonstige Materialien:\n";
+  txt += sprintf("%@s", map(mats, #'gen_material_list_material));
+  write_file(fn, txt) ||
+    raise_error(sprintf("Konnte Liste nicht weiter in Datei %s schreiben,"
+                        " Abbruch\n", fn));
+}
+private void dump_group(string grp, string fn)
+{
+  // upperstring langsame simul_efun, warum?
+  write_file(fn, sprintf("  %-28s%=-48s\n", (grp),
+                         GroupName(grp))) ||
+    raise_error(sprintf("Konnte Liste nicht weiter in Datei %s schreiben,"
+                        " Abbruch\n", fn));
+}
+private string gen_doc_foot(string other)
+{
+  return sprintf("\nSIEHE AUCH:\n"
+                 "     Konzepte:    material, materialerkennung\n"
+                 "     Grundlegend: P_MATERIAL, /sys/materials.h, /sys/thing/material.h\n"
+                 "     Methoden:    QueryMaterial(), QueryMaterialGroup(), MaterialList(),\n"
+                 "     Listen:      AllMaterials(), AllGroups()\n"
+                 "                  %s\n"
+                 "     Master:      ConvMaterialList(), MaterialGroup(),\n"
+                 "                  GroupName(), MaterialName(),\n"
+                 "                  GetGroupMembers(), GetMatMembership()\n"
+                 "     Sonstiges:   P_MATERIAL_KNOWLEDGE\n\n"
+                 "%s generiert aus /secure/materialdb\n", other, dtime(time()));
+}
+
+/* GenMatList
+ *
+ * Generiert Datei mit registrierten Materialien fuer die Dokumentation,
+ */
+varargs void GenMatList(string fn)
+{
+  if (initialized) {
+    string txt;
+    if (!stringp(fn) || !sizeof(fn))
+      fn = DOC_DIR("materialliste");
+    if (file_size(fn) >= 0) {
+      printf("Datei %s existiert bereits, loesche sie\n", fn);
+      rm(fn);
+    }
+    if (write_file(fn, "Material Liste\n==============\n\n")) {
+      dump_material(fn);
+      write_file(fn, gen_doc_foot("materialgruppen"));
+      printf("Materialliste erfolgreich in Datei %s geschrieben\n", fn);
+    } else
+      printf("Konnte Liste nicht in Datei %s schreiben, Abbruch\n", fn);
+  }
+}
+
+/* GenMatGroupList
+ *
+ * Generiert Datei mit registrierten Materialgruppen fuer die Dokumentation,
+ */
+varargs void GenMatGroupList(string fn)
+{
+  if (initialized) {
+    string txt;
+    if (!stringp(fn) || !sizeof(fn))
+      fn = DOC_DIR("materialgruppen");
+    if (file_size(fn) >= 0) {
+      printf("Datei %s existiert bereits, loesche sie\n", fn);
+      rm(fn);
+    }
+    if (write_file(fn, "Materialgruppen\n===============\n")) {
+      map(sort_array(m_indices(material_groups), #'>), #'dump_group, fn);
+      write_file(fn, gen_doc_foot("materialliste"));
+      printf("Materialliste erfolgreich in Datei %s geschrieben\n", fn);
+    } else
+      printf("Konnte Liste nicht in Datei %s schreiben, Abbruch\n", fn);
+  }
+}
+
+/* GenHeaderFile
+ *
+ * Generiert Headerfile mit Definitionen der moeglichen Materialien und
+ * Gruppen
+ */
+varargs void GenHeaderFile(string fn)
+{
+  if (initialized) {
+    string txt;
+    if (!stringp(fn) || !sizeof(fn))
+      fn = HEADERFILE;
+    if (file_size(fn) >= 0) {
+      printf("Datei %s existiert bereits, loesche sie\n", fn);
+      rm(fn);
+    }
+    if (dump_material_h(fn))
+      printf("Headerdatei erfolgreich in %s geschrieben\n", fn);
+    else
+      printf("Konnte Headerdatei nicht in Datei %s schreiben, Abbruch\n", fn);
+  }
+}
+
+//==================== Pruef- und Hilfsfunktionen fuer Materialien
+private void updateGroupMembers(mapping groups, string mat_id, mapping mat) {
+  mixed *addgrps; // Array zum Ableiten von Gruppenzugehoerigkeiten
+  string *h;
+  int i, val;
+  mapping fractions; // Mapping mit Anteilen an Gruppen
+  fractions = mat[P_MG_FRACTIONS];
+  if (!mappingp(fractions))
+    fractions = ([]);
+  addgrps=({ // Reihenfolge wird rueckwaerts abgearbeitet
+    // Ableitungen sind z.T. abenteuerlich gewesen, mal ordentlich
+    // ausmisten. Die Zugehoerigkeit gehoert explizit in die
+    // Materialdefinition
+    // Gase sieht man normalerweise nicht:
+    ({"MATGROUP_INVIS", "MATGROUP_GAS"}),
+    // Mineralien sind auch Steine
+    ({"MATGROUP_STONE","MATGROUP_MINERAL"}),
+    // Edelmetalle sind Metalle:
+    ({"MATGROUP_METAL","MATGROUP_PRECIOUS_METAL"}),
+    // Lebewesen und deren Ueberreste, Paiere und Stoffe sind biologisch
+    ({"MATGROUP_BIO","MATGROUP_LIVING","MATGROUP_DEAD",
+      "MATGROUP_PAPER"}),
+    // Holz ist pflanzlich:
+    ({"MATGROUP_HERBAL", "MATGROUP_WOOD"}),
+    // Holz ist meistens tot:
+    ({"MATGROUP_DEAD","MATGROUP_WOOD"}),
+    // Holz, Papier und Stoffe brennen:
+    ({"MATGROUP_INFLAMMABLE","MATGROUP_WOOD","MATGROUP_PAPER"}),
+    // Laubhoelzer, Nadelhoelzer und Tropenhoelzer sind Holz
+    ({"MATGROUP_WOOD","MATGROUP_TROPICAL_WOOD","MATGROUP_DECIDUOUS_WOOD",
+      "MATGROUP_CONIFER_WOOD"}),
+    // Explosive Dinge sind immer entzuendlich:
+    ({"MATGROUP_INFLAMMABLE","MATGROUP_EXPLOSIVE"})
+  });
+  i=sizeof(addgrps);
+  while(i--) {
+    int j;
+    h=addgrps[i];
+    if (member(fractions,h[0])) // Existiert schon eigener Eintrag?
+      continue; // Automatische Eintragung unnoetig
+    val=0;
+    for (j=sizeof(h)-1;j>=1;j--)
+      val+=fractions[h[j]];
+    if (!val)
+      continue;
+    if (val>100)
+      val=100;
+    else if (val<-100)
+      val=-100;
+    fractions[h[0]]=val;
+  }
+  if (fractions["MATGROUP_LIVING"]) // Im Falle von lebendem Holz, tot loeschen
+    m_delete(fractions,"MATGROUP_DEAD");
+  // Alles, was nicht als gasfoerming, fluessig oder fest eingeordnet ist, ist
+  // sonstwas:
+  if (!member(fractions, "MATGROUP_FLUID")
+      && !member(fractions, "MATGROUP_GAS")
+      && !member(fractions, "MATGROUP_SOLID"))
+    fractions["MATGROUP_MISC"]=100;
+  // Materialien als Mitglieder in die Gruppen eintragen
+  addgrps=m_indices(fractions);
+  i=sizeof(addgrps);
+  while(i--) {
+    mixed ind;
+    ind=addgrps[i];
+    if (!fractions[ind] || !member(groups, ind)) {
+      // Unbekannte Gruppe und Gruppe ohne Anteil aus Mapping loeschen
+      m_delete(fractions,ind);
+      continue;
+    }
+    if (!pointerp(h=groups[ind][P_MEMBERS]))
+      h=({});
+    h+=({mat_id});
+    groups[ind][P_MEMBERS]=h;
+  }
+  mat[P_MG_FRACTIONS] = fractions;
+}
+
+//==================== Einlesen der Mappings aus Dateien
+
+#define MDESC_ERROR(x, y) LOG_ERROR(sprintf("Materialbeschreibung '%s': %s\n", x, y))
+#define MDESC_WARN(x, y) //LOG_WARN(sprintf("Materialbeschreibung '%s': %s\n", x, y))
+
+private mapping getDescParts(string s) {
+  string* lines;
+  string key, val;
+  int i, n;
+  mapping m;
+  m = ([]);
+  val = "";
+  lines = explode(s, "\n");
+  n = sizeof(lines);
+  if (n > 0) {
+    while (i < n) {
+      if (sscanf(lines[i], "%s:", key)) {
+        status multiline;
+        multiline = 0;
+        // Schluessel gefunden, Wert auslesen
+        while ( (++i < n) && sizeof(lines[i])) {
+          // Mehrzeilige Werte mit newline verketten
+          if (multiline) {
+            val += "\n";
+          }
+          val += lines[i];
+          multiline = 1;
+        }
+        m += ([key:val]);
+        val = "";
+      }
+      i++;
+    }
+  }
+  return m;
+}
+private varargs int isFile(string fn, string path) {
+  if (stringp(path) && sizeof(path))
+    fn = path+"/"+fn;
+  return (file_size(fn) >= 0);
+}
+
+private varargs mixed readGroupDesc(string id) {
+  mixed m;
+  string fn;
+  fn = MAT_DIR+"/groups/"+id;
+  if (file_size(fn) > 0) {
+    mapping parts;
+    string desc;
+    parts = getDescParts(read_file(fn));
+    m = ([P_NAME:parts["Name"],
+          P_MEMBERS:({})]);
+    if (member(parts,"Beschreibung"))
+      m += ([P_DESCRIPTION:parts["Beschreibung"]]);
+    if (parts["Gruppenid"] != id)
+      LOG_WARN(sprintf("Unstimmigkeit Gruppenid bei '%s'\n", id));
+  } else {
+    LOG_ERROR(sprintf("Kann Gruppenbeschreibung %s nicht laden\n", fn));
+  }
+  return m;
+}
+
+private mapping convMatId(string s) {
+  mapping m;
+  string* parts;
+  parts = explode(s, "\"");
+  if (sizeof(parts)) {
+    int ende;
+    ende = strstr(parts[0]," ")-1;
+    if (ende < 0)
+      ende = sizeof(parts[0]);
+    m = ([P_ID:parts[0][0..ende]]);
+    if (sizeof(parts) > 1)
+      m += ([P_DEFSTR:parts[1]]);
+  }
+  return m;
+}
+private string* convMatNames(string s) {
+  string* names;
+  names = filter(explode(s, "\""),
+                       lambda( ({'x}), ({#'>, ({#'sizeof, 'x}), 1}) ));
+  if (sizeof(names)<1)
+    names=0;
+  else {
+    if (sizeof(names)<2)
+      names+=({names[0]+"s"});
+    if (sizeof(names)<3)
+      names+=({names[0]});
+    if (sizeof(names)<4)
+      names+=({names[0]});
+  }
+  return names;
+}
+private int convMatGender(string s) {
+  int gender;
+  s = lowerstring(s);
+  // Ein Buchstabe reicht zur Bestimmung. Wenn nur weiblich|female
+  // bzw. maennlich|male verwendet wird. Dabei ist dann allerdings die
+  // Reihenfolge der Auswertung wichtig, damit das m bei MALE nicht mehr bei
+  // female passt.
+  if (sizeof(regexp( ({s}), "f|w"))) {
+    gender = FEMALE;
+  } else if (sizeof(regexp( ({s}), "m"))) {
+    gender = MALE;
+  } else {
+    gender = NEUTER;
+  }
+  return gender;
+}
+private string convMatDesc(string s) {
+  if (sizeof(regexp( ({s}), "- nicht vorhanden -"))) {
+    s = 0;
+  } else {
+    // Mehrzeilige Beschreibungen zu einer Zeile zusammenfassen
+    s = implode(explode(s, "\n"), " ");
+  }
+  return s;
+}
+private void addRecocLine(string s, mixed* r) {
+  // Die weitere Bewertung der Schwierigkeit kann erst vorgenommen werden,
+  // wenn alle Materialien bekannt sind und passiert spaeter. Zuerst werden
+  // nur die Elemente des Arrays konvertiert und eingetragen
+  string mat;
+  int val;
+  if (sscanf(s, "%s:%d", mat, val)) {
+    r += ({mat,val});
+  } else if (sscanf(s, "%d", val)) {
+    r += ({val});
+  } else {
+    r += ({s});
+  }
+}
+private mixed convMatRec(string s) {
+  mixed difficulties;
+  if (sizeof(regexp( ({s}), "- keine Einschraenkung -"))) {
+    difficulties = 0;
+  } else {
+    difficulties = ({});
+    // Jede Zeile enthaelt eine Bedingung
+    map(explode(s, "\n"), #'addRecocLine, &difficulties);
+  }
+  return difficulties;
+}
+private void addGroupLine(string s, mapping g) {
+  // Die weitere Bewertung der Zugehoerigkeit passiert spaeter.
+  string grp;
+  int val;
+  if (sscanf(s, "%s:%d", grp, val)) {
+    g += ([grp:val]);
+  } else {
+    g += ([grp:100]);
+  }
+}
+private mapping convMatGroups(string s) {
+  mapping groups;
+  if (!sizeof(regexp( ({s}), "- keine -"))) {
+    groups = ([]);
+    // Jede Zeile enthaelt eine Bedingung
+    map(explode(s, "\n"), #'addGroupLine, groups);
+  }
+  return groups;
+}
+private mapping convMaterialDesc(string id, mapping desc) {
+  /* Struktur Materialmapping:
+     P_GENDER,
+     P_NAME:({name_nom, name_gen, name_dativ, name_akkusativ}),
+     (P_RECOC:({mat1,faehigkeit1,mat2,faehigkeit2,...}),)
+     (P_DEFSTR: bei bedarf),
+     P_DESCRIPTION,
+     (grupp1:anteil1,
+     gruppe2:anteil2,
+     ...)
+  */
+  mapping m;
+  mixed val, val2;
+  m = ([]);
+  // Der string fuer das #define zuerst:
+  val = convMatId(desc["Materialid"]);
+  if (mappingp(val)) {
+    if (val[P_ID] != id)
+      LOG_WARN(sprintf("Unstimmigkeit Materialid bei '%s':%O\n", id, val[P_ID]));
+    if (member(val, P_DEFSTR)) {
+      m += ([P_DEFSTR:val[P_DEFSTR]]);
+    } else {
+      // Wenn kein String fuers #define angegeben wurde, dann direkt ID verwenden
+      //m += ([P_DEFSTR:lowerstring(id)[4..]]);
+    }
+  }
+  // Die Namen
+  if (val = convMatNames(desc["Name"])) {
+    m += ([P_NAME:val]);
+  } else {
+    MDESC_WARN(id, "keine Namen");
+    m += ([P_NAME:({"", "", "", ""})]);
+  }
+  // Das Geschlecht, standard ist NEUTER
+  m += ([P_GENDER:convMatGender(desc["Geschlecht"]) ]);
+  // Die Beschreibung
+  val = convMatDesc(desc["Beschreibung"]);
+  if (sizeof(val)) {
+    m += ([P_DESCRIPTION:val]);
+  } else {
+    MDESC_WARN(id, "keine Beschreibung");
+  }
+  // Die Erkennbarkeit
+  val = convMatRec(desc["Erkennbarkeit"]);
+  if (sizeof(val)) {
+    m += ([P_RECOC:val]);
+  }
+  // und zum Schluss die Gruppenzugehoerigkeit
+  val = convMatGroups(desc["Gruppenzugehoerigkeit"]);
+  if (mappingp(val) && sizeof(val)) {
+    m += ([P_MG_FRACTIONS:val]);
+  }
+  return m;
+}
+private varargs mixed readMaterialDesc(string id) {
+  mixed m;
+  string fn;
+  fn = MAT_DIR+"/materials/"+id;
+  if (file_size(fn) > 0) {
+    mapping parts;
+    string desc;
+    parts = getDescParts(read_file(fn));
+    m = convMaterialDesc(id, parts);
+  } else {
+    LOG_ERROR(sprintf("MDB:Kann Materialbeschreibung %s nicht laden\n", fn));
+  }
+  return m;
+}
+
+public int GetUpdateTicks() {
+  return updateTicks;
+}
+
+private void scanFinished() {
+  isScanning = 0;
+  initialized = 1;
+  // Mappings umkopieren
+  materials = new_materials;
+  material_groups = new_material_groups;
+  // Letzter Schritt: Mapping mit alten Schluesseln anlegen
+  old_mat_keys = mkmapping(map(m_indices(materials), #'matKey2Defstr, materials),
+                           m_indices(materials));
+  // Generieren der Doku und des Materialheaders
+  GenHeaderFile();
+  GenMatList();
+  GenMatGroupList();
+  // Savefile schreiben
+  save_object(SAVEFILE);
+}
+
+public int IsScanning() {
+  return isScanning;
+}
+
+private varargs void doScanMaterials(string* mats, int i, int step) {
+  int ticks, start;
+  string matid;
+  start = get_eval_cost();
+  if (step < 2) {
+    while ( (i < sizeof(mats)) &&
+            ((start - get_eval_cost()) < query_limits()[LIMIT_EVAL]/3 ) ) {
+      matid = mats[i];
+      switch (step) {
+      case 0:
+        // Erster Schritt: Einlesen der Dateien
+        new_materials[matid] = readMaterialDesc(matid);
+        break;
+      case 1:
+        // Zweiter Schritt: Bearbeiten der Erkennung und Gruppenzugehoerigkeit
+        updateGroupMembers(new_material_groups, matid, new_materials[matid]);
+        break;
+      default:
+        break;
+      }
+      i++;
+    }
+  }
+  if (i < sizeof(mats)) {
+    catch(raise_error(sprintf("MaterialDB: Initialisierung noch nicht beendet,"
+                              " fehlende Materialbeschreibungen moeglich"
+                              " (Phase %d:%d/%d)\n",
+                              step, i, sizeof(mats)));publish);
+    call_out(#'doScanMaterials, 2, mats, i, step);
+  } else {
+    // Zweite Stufe ausloesen oder beenden
+    if (step < 1) {
+      if ((start - get_eval_cost()) < query_limits()[LIMIT_EVAL]/2 )
+          doScanMaterials(mats, 0, step+1);
+      else
+          call_out(#'doScanMaterials, 2, mats, 0, step+1);
+    }
+    else
+      scanFinished();
+  }
+  updateTicks += start - get_eval_cost();
+}
+
+private mapping ScanGroups() {
+  mapping groups;
+  string* grpfiles;
+  groups = ([]);
+  grpfiles = filter(get_dir(MAT_DIR+"/groups/MATGROUP_*"),
+                          #'isFile, MAT_DIR+"/groups");
+  groups = mkmapping(grpfiles, map(grpfiles, #'readGroupDesc, 1));
+  return groups;
+}
+
+private void ScanMaterials() {
+  string *matfiles;
+  matfiles = filter(get_dir(MAT_DIR+"/materials/MAT_*"),
+                          #'isFile, MAT_DIR+"/materials");
+  doScanMaterials(matfiles);
+}
+
+void Update() {
+  int start;
+  updateTicks = 0;
+  start = get_eval_cost();
+  if (!isScanning) {
+    if (sizeof(get_dir(MAT_DIR))) {
+      isScanning = 1;
+      new_material_groups = ScanGroups();
+      new_materials = ([]);
+      updateTicks = start - get_eval_cost();
+      ScanMaterials();
+    } else {
+      LOG_ERROR("Kann Materialverzeichnis nicht finden, keine Materialien angelegt!\n");
+    }
+  }
+}
diff --git a/secure/memory.c b/secure/memory.c
new file mode 100644
index 0000000..797ad1c
--- /dev/null
+++ b/secure/memory.c
@@ -0,0 +1,299 @@
+/*!
+// MorgenGrauen Mudlib
+//
+// secure/memory.c  -- zum Speichern von Daten ueber die gesamte Mud-Uptime
+//
+// Memory speichert seine Daten als Pointer in der extra-wizinfo. Andere 
+// Programme koennen Ihre Daten im Memory ablegen. Die Daten bleiben dadurch 
+// auch ueber ein Update/Reload der Blueprint erhalten.
+//
+// Die Daten werden auf Klassenebene behandelt. Jeder Clone eines Programms
+// (Blueprint) darf auf die gespeicherten Daten des Programms schreibend und
+// lesend zugreifen. Andere Programme haben keinen Zugriff auf diese Daten.
+//
+// Nur Objekte in /secure/memory_lib bzw. /secure/memory_nolib eingetragen 
+// sind, haben Rechte den Memory zu nutzen.
+//
+// Die Idee, sensible Daten in der extra_wizinfo abzuspeichern entstammt 
+// /secure/memory.c aus der Wunderland Mudlib von Holger@Wunderland
+*/
+#include <config.h>
+#include <wizlevels.h>
+#include <files.h>
+
+#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
+
+
+/* Global variable declaration */
+/*! \brief Liste mit Programmen, die den Memory nutzen duerfen 
+ Die Variable wird entweder im create() oder durch manuellen Aufruf 
+ von RereadProgramLists() aus /secure/memory_lib und /secure/memory_nolib
+ befuellt.
+ */
+string *known_programs;
+
+/* Function declaration - public interface */
+void create();
+
+// Payload Interface
+int   Save  (string key, mixed data);
+mixed Load  (string key);
+int   Remove(string key);
+int   HaveRights();
+
+// Administrative Interface
+varargs int RereadProgramLists(int silent);
+
+// Debugging Interface
+varargs void ShowData(string user, string key);
+
+/* Function definition */ 
+/*! Objekt initialisieren */
+void create() {
+  mapping info;
+  seteuid(getuid(this_object()));
+
+  // Wizinfo-Pointer holen
+  if(!pointerp(info=get_extra_wizinfo(0)))
+    raise_error("WIZINFO nicht lesbar. secure/simul_efun muss neu geladen werden.!\n");
+
+  // Mein Feld der Wizinfo bei Bedarf initialisieren
+  if(!mappingp(info[MEMORY_BUFF]))
+    info[MEMORY_BUFF]=([]);
+
+  RereadProgramLists(1);
+}
+
+/*!
+ Wenn das aufrufende Programm fuer die Speichernutzung zugelassen ist,
+ wird hier der Hash zurueckgegeben unter dem seine Daten abgespeichert
+ sind. Der Hash ist derzeit der Filename des Blueprint.
+*/
+private string get_caller_hash() {
+  if (member(known_programs, program_name(previous_object())[..<3]) != -1) 
+    return program_name(previous_object())[..<3];
+  else 
+    return 0;
+}
+
+/*! 
+ Die Hilfsfunktion liest eine Liste mit Dateinamen und gibt die Programme 
+ als Array of Strings zurueck. Alle Zeilen die nicht mit einem Slash 
+ anfangen, werden dabei ignoriert.
+ \param file Die auszulesende Datei.
+*/
+private mixed read_list(string file)
+{
+  mixed data;
+  if (!stringp(file) || file_size(file) == FSIZE_NOFILE )
+    return ({});
+
+  // Daten laden
+  data = explode(read_file(file)||"", "\n");
+
+  // Alle Zeilen die nicht mit / beginnen entfernen ...
+  data=regexp(data,"^/.+");
+
+  // ".c" und eventuelle Leerzeichen in jeder Zeile werden entfernt
+  data= map(data, 
+     function string (string x) {return regreplace(x,"(.c)* *$","",1);});
+  return data;
+}
+
+/*! 
+ Wenn die Liste mit Programmen geaendert wurde, kann sie hier per Hand
+ neu eingelesen werden.
+ \param silent Varargs Flag, um Bildschirmausgabe zu unterdruecken
+*/
+varargs int RereadProgramLists(int silent)
+{
+  // Objekte laden, die zur Mudlib gehoeren
+  known_programs = read_list("/secure/memory_lib");
+
+  // Objekte laden, die nicht zur Mudlib gehoeren
+  known_programs+=read_list("/secure/memory_nolib");
+
+  if(!silent) printf ("%O\n", known_programs);
+
+  return sizeof(known_programs);
+}
+
+/*! Zeiger auf den Speicher holen */
+private mapping get_memory_pointer(){
+  mixed info;
+
+  // Die Fehlermeldungen sind etwas ausfuehrlicher, um dem Debugger
+  // einen Hinweis auf Reparaturmoeglichkeiten zu geben
+  if(!pointerp(info=get_extra_wizinfo(0)))
+    raise_error("Wizinfo nicht ladbar. Entweder ich hab keine Rechte "
+                "oder das Mud hat ein echtes Problem!\n");
+
+  if(!mappingp(info[MEMORY_BUFF]))
+    raise_error("Ich finde meine Daten in der Wizinfo nicht. Ein EM kann "
+                "versuchen, mich neu zu laden, aber was die alten Daten "
+                "betrifft, frei nach Catty: Keine Arme, keine Kekse!\n");
+  // Da info[MEMORY_BUFF] immer ein Mapping ist (siehe Create), wird hier ein 
+  // Pointer uebergeben. Dadurch werden Details zum Aufbau der extra_wizinfo
+  // nur in dieser Funktion benoetigt.
+  return info[MEMORY_BUFF]; 
+}
+
+/*!
+  Pruefen, ob previous_object() Nutzungsrechte fuer den Memory hat.
+  \return Gibt 1 zurueck, wenn der Aufrufende berechtigt ist, das 
+          Objekt zu nutzen, sonst 0
+*/
+int HaveRights(){
+  return stringp(get_caller_hash());
+}
+
+/*!
+ Daten im Memory ablegen
+ \param key ID unter der die Daten gespeichert werden
+ \param var Zu speicherndes Datum
+*/
+int Save(string key, mixed var) {
+  string who;
+  mapping memory;
+
+  // Hash des Aufrufers holen. Hat der keinen, ist er nicht befugt,
+  // den Memory zu nutzen.
+  if(!stringp(who=get_caller_hash())) return 0;
+
+  // Wenn kein Key uebergeben wird, kann auch nichts gespeichert werden.
+  if(!stringp(key)) return 0;
+
+  // (hoffentlich) globalen memory_pointer holen
+  memory = get_memory_pointer();
+
+  // Wenn keine Daten da sind, muss man auch nichts anlegen. Rueckgabewert
+  // ist dennoch 1, weil Load bei unbekannten Keys auch 0 liefert, die 
+  // Null wird also korrekt gespeichert, irgendwie. Das muss aber nach dem
+  // get_memory_pointer() passieren, da sonst das Korrekte Funktionieren von
+  // Load nicht sichergestellt ist.
+  if(var==0) return 1;
+
+  // Erster Aufruf durch PO? Dann muss sein Speicherbereich initialisiert 
+  // werden
+  if(!member(memory,who))
+    memory[who]=([]);
+
+  // Endlich! Daten koennen gespeichert werden.
+  memory[who][key]=var;
+
+  return 1;
+}
+
+/*!
+ Daten ausgeben/referenzieren
+ \param key Id unter der die Daten gespeichert sind.
+*/
+mixed Load(string key) {
+  string who;
+  mapping memory;
+
+  // Hash des Aufrufers holen. Hat der keinen, ist er nicht befugt,
+  // den Memory zu nutzen.
+  if(!stringp(who=get_caller_hash())) return 0;
+
+  // Wenn kein Key uebergeben wird, kann auch nichts gespeichert werden.
+  if(!stringp(key)) return 0;
+
+  // (hoffentlich) globalen memory_pointer holen
+  memory = get_memory_pointer();
+
+  // Das Objekt hat noch nie Save() aufgerufen.
+  if(!member(memory,who))
+    return 0;
+
+  // Unter diesem Key ist noch nichts gespeichert.
+  if(!member(memory[who],key))
+    return 0;
+
+  // Gespeicherten Wert zurueckgeben
+  return memory[who][key];
+}
+
+/*! 
+ Ein Eintrag oder alle Eintraege werden geloescht
+ \param key Id unter der die Daten gespeichert sind. 
+            Wird kein Key uebergeben, werden alle Daten des Objekts 
+            geloescht.
+*/
+varargs int Remove(string key) {
+  string who;
+  mapping memory;
+
+  // Hash des Aufrufers holen. Hat der keinen, ist er nicht befugt,
+  // den Memory zu nutzen.
+  if(!stringp(who=get_caller_hash())) return 0;
+
+  // (hoffentlich) globalen memory_pointer holen
+  memory = get_memory_pointer();
+
+  // Das Objekt hat noch nie Save() aufgerufen.
+  if(!member(memory,who)) return 0;
+
+  // Wenn kein Key uebergeben wird, wird alles zum Objekt gehoerende geloescht.
+  if(stringp(key)){
+    // Unter diesem Key ist nichts gespeichert.
+    if(!member(memory[who],key)) return 0;
+
+    // Wert unter Key fuer das File loeschen
+    m_delete(memory[who], key);
+
+  } else {
+    // Alles fuer das File loeschen
+    m_delete(memory, who);
+  }
+
+  return 1; 
+}
+
+varargs void ShowData(string user, string key)
+{
+  // Bekannte Objekte
+  if (!ELDER_SECURITY )
+    printf("I'm fine. Thanks for asking.\n");
+  else {
+    printf("known_programs:\n%O\n\n", known_programs);
+    // EM duerfen sich auch noch zuscrollen lassen, wenn sie wollen
+    if (ARCH_SECURITY && user)
+    {
+      mapping data = get_memory_pointer();
+      if (!member(data,user))
+        printf("memory: Keine Daten fuer %s vorhanden.\n",user);
+      else if (user && key)
+        printf("memory: Daten fuer User %s, Key %s:\n%O\n",user, key,
+                  data[user][key]);
+      else
+        printf("memory: Daten fuer User %s:\n%O\n",user,data[user]);
+    }
+    else
+    {
+      mapping memory;
+      memory = get_memory_pointer();
+      // Andere bekommen eine anonymisierte Version
+      printf("memory: (Nur Feldnamen, keine Daten)\n");
+      foreach (string program, mapping data: memory ) {
+        if ( mappingp(data) && sizeof(data) ){
+          printf(break_string(
+                   implode(m_indices(data),";\n"), 78,  
+                   sprintf("  %-50s: ",program)),1);
+        }
+        else
+        printf("  %-50s: - leer -\n", program);
+      }
+    }
+  }
+  return;
+}
+
diff --git a/secure/memory_lib b/secure/memory_lib
new file mode 100644
index 0000000..06539b1
--- /dev/null
+++ b/secure/memory_lib
@@ -0,0 +1,9 @@
+# Objekte die in dieser Datei stehen, haben die Berechtigung 
+# /secure/memory.c zu nutzen.
+#
+# Diese Datei wird mit dem Mudlib-Release verteilt, daher duerfen
+# hier nur Objekte aufgenommen werden die zur Standard-Mudlib
+# gehoeren
+/p/daemon/channeld
+/p/daemon/traveld
+/secure/shadowmaster
diff --git a/secure/merlin.c b/secure/merlin.c
new file mode 100644
index 0000000..3075bec
--- /dev/null
+++ b/secure/merlin.c
@@ -0,0 +1,1345 @@
+// MorgenGrauen MUDlib
+//
+// merlin.c -- Unser geliebter Merlin
+//
+// $Id: merlin.c 9405 2015-12-13 00:22:01Z 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
+
+// 2014-Mai-10, Arathorn: Er soll ja weiterhin auf tm reagieren.
+inherit "std/npc/comm.c";
+
+#include <config.h>
+#include <properties.h>
+#include <language.h>
+#include <moving.h>
+#include <defines.h>
+#include <exploration.h>
+#include <news.h>
+#include <exploration.h>
+#include <events.h>
+#include <daemon/channel.h>
+#include "/secure/wizlevels.h"
+#include "/secure/config.h"
+#include "/secure/questmaster.h"
+#include "/secure/lepmaster.h"
+
+#ifndef DEBUG
+#define DEBUG(x) if (find_player("zook")) tell_object(find_player("zook"),x)
+#endif
+
+#define SAVEFILE "/secure/ARCH/merlin"
+#define BS(x) break_string((x),78)
+#define FEHLERTEUFEL "/obj/tools/fehlerteufel"
+
+mixed QueryProp(string prop);
+int move(mixed dest, int method);
+string query_g_suffix(int gen, int casus, int anzahl);
+string make_invlist(object viewer,object *inv);
+static int determine_action(string mess, string name);
+static int create_wizard(mixed who, mixed promoter);
+static int create_seer(mixed who);
+static void give_help(mixed who);
+int goto(mixed dest);
+
+nosave string *whitespaces=({",",".","?",";",":","-","!","\n","\t"});
+nosave object prev_room;
+nosave int delay,maxusers,busy;
+nosave string flag;
+
+mapping MBanishListe;
+
+void create()
+{
+  string s;
+  seteuid(ROOTID);
+  if (file_size("/etc/maxusers")<=0)
+    maxusers=0;
+  else
+  {
+    s=read_file("/etc/maxusers",0,1);
+    sscanf(s,"%d",maxusers);
+  }
+  set_living_name("merlin");
+  enable_commands();
+  call_out("wandern",25);
+  move("/gilden/abenteurer",0);
+  MBanishListe = m_allocate(0,2);
+  restore_object(SAVEFILE);
+  EVENTD->RegisterEvent(EVT_LIB_PLAYER_CREATION, "player_change", ME);
+  EVENTD->RegisterEvent(EVT_LIB_LOGIN, "player_change", ME);
+  // absichtlich kein EVT_LIB_LOGOUT, s. Kommentar zu notify_player_leave().
+}
+
+string _query_kill_name()
+{
+  return "Ein genervter Merlin";
+}
+
+int move(mixed ob,int methods)
+{
+  object tmp;
+
+  if (methods&M_GET)
+    return ME_CANT_BE_TAKEN;
+
+  if (stringp(ob))
+  {
+    if (!(tmp=find_object(ob)))
+    {
+      call_other(ob,"?");
+      tmp=find_object(ob);
+    }
+    ob=tmp;
+  }
+  if (!objectp(ob))
+    return 0;
+  if (environment())
+    tell_room(environment(),"Merlin zieht weiter.\n");
+  tell_room(ob,"Merlin kommt an.\n");
+  move_object(ob);
+  return 1;
+}
+
+mixed QueryProp(string str)
+{
+  if (!stringp(str)||str=="")
+    return 0;
+  return call_other(this_object(),"_query_"+str);
+}
+
+varargs int id(string str)
+{
+  return (str=="merlin");
+}
+
+varargs string name(int casus, int demon)
+{
+  if (casus!=WESSEN)
+    return "Merlin";
+  return "Merlins";
+}
+
+varargs string Name(int casus, int daemon)
+{
+  return name(casus,daemon);
+}
+
+string QueryDu(int casus, int gender, int zahl)
+{
+  return
+    ({ ({ ({ "du", "ihr"}), ({ "du", "ihr"}), ({ "du", "ihr"}) }),
+         ({({"deines","deiner"}),({"deines","deiner"}),({"deiner","deiner"})}),
+         ({({"dir","euch"}),({"dir","euch"}),({"dir","euch"})}),
+         ({({"dich","euch"}),({"dich","euch"}),({"dich","euch"})})
+       })[casus][gender][zahl];
+}
+
+string long()
+{
+  EPMASTER->GiveExplorationPoint("merlin");
+  return break_string(
+      "Merlin - der maechtige Urvater aller Magier - ist ein hagerer "
+      "Mann, der einen blauen, mit Monden und Sternen uebersaeten Umhang "
+      "traegt und sich auf einen knorrigen Stock stuetzt. Sein langer weisser "
+      "Bart reicht ihm fast bis zur Guertelschnalle, sein Haupthaar ist fast "
+      "ebenso lang. Auf seinem Kopf traegt er einen spitzen Hut, der ihn noch "
+      "groesser macht, als er ohnehin schon ist.\nEr ist unangreifbar, denn "
+      "er steht ueber solchen Dingen.",78,BS_LEAVE_MY_LFS);
+}
+
+string GetDetail(string key,mixed race,int sense) {
+  switch( sense ) {
+  case SENSE_SMELL: return 0;
+  case SENSE_SOUND: return 0;
+  }
+  switch( key ) {
+  case "nase": case "nasenhaare": case "hakennase":
+          return BS("Merlin hat eine lange, krumme Hakennase, aus deren Nasenloechern weisse Haare spriessen. Auf seiner Nase sitzt eine Brille mit Glaesern in Form eines Halbmondes.");
+  case "brille": case "glaeser": case "form": case "halbmond":
+          return "Merlins Brille besitzt Glaeser in Form eines Halbmondes.\n";
+  case "bart":
+          return "Merlins Bart ist sehr lang - Er reicht fast bis ueber die Guertelschnalle.\n";
+  case "haare" :
+          return "Sie reichen ihm bis zwischen die Schulterblaetter.\n";
+  case "schulterblaetter": case "schulterblatt":
+          return "Seine langen Haare bedecken die Schulterblaetter.\n";
+  case "gewand": case "robe":
+          return BS("Das Gewand ist blau und mit silbernen Monden und Sternen bestickt. Es ist uralt und schon etwas abgetragen, aber es erfuellt immer noch seinen Zweck.");
+  case "zweck":
+          return BS("Der Zweck des Gewandes besteht darn, Merlin standesgemaess zu bekleiden. Diesem Zweck kommt das Gewand in Vollendung nach.");
+  case "vollendung":
+          return "Nichts ist vollkommen.\n";
+  case "hut":
+          return BS("Ein langer, spitzer Hut, der in der gleichen Weise wie seine Robe mit Monden und Sternen bestickt ist.");
+  case "mond": case "monde": case "stern": case "sterne":
+          return BS("Die Monde und die Sterne sind mit silbrigen Faeden gestickt, die das Licht funkelnd zurueckwerfen.");
+  case "faden": case "faeden":
+          return BS("Schwer zu sagen, woraus die Faeden sind. Mithril? "
+              "Mondsilber?");
+  case "licht": case "mondsilber": case "mithril":
+          return BS("Du streckt schon Deine Hand aus, um Merlins Gewand naeher zu untersuchen, aber da trifft Dich sein strenger Blick. Schuechtern nimmst Du von Deinem Vorhaben Abstand.");
+  case "abstand":
+          return BS("Von so einem Magier sollte man aus Respekt einen Schritt Abstand halten.");
+  case "respekt":
+          return "Du hast sicher mehr Respekt vor Merlin, als er vor Dir.\n";
+  case "schritt":
+          return "Wessen Schritt??\n";
+  case "hand": case "haende":
+          return BS("Merlins Haende haben lange, gichtgeplagte Finger und gelbe lange Fingernaegel. Seine linke Hand stuetzt sich auf seinen knorrigen Gehstock. Die rechte Hand ist unter seinem Gewand verborgen.");
+  case "gichtknoten": case "gicht":
+          return BS("Durch die Gichtknoten sehen seine Haende fast so knorrig wie sein Gehstock aus.");
+  case "finger": case "fingernaegel": case "fingernagel":
+          return BS("Seine Finger sind voller Gichtknoten und seine gelben Fingernaegel koennte Merlin auch mal wieder schneiden.");
+  case "gehstock": case "stock":
+          return BS("Merlin stuetzt sich auf einen Gehstock. Der knorrige Stock scheint schon fast Eins mit seiner gichtknotigen Hand geworden zu sein. Ob es sich bei dem Stock um einen Magierstab handelt?");
+  case "zauberstab": case "magierstab":
+          return BS("Merlin stuetzt sich auf einen knorrigen Gehstock. Das koennte sein Zauberstab sein - aber vielleicht ist es auch nur ein Gehstock.");
+  case "guertel": case "guertelschnalle":
+          return BS("Sein Gewand wird von einem weissen Guertel gehalten. Vorne, unter seinem Bart, kannst Du die Guertelschnalle sehen. Auf der Guertelschnalle steht: 'Ich koennte jederzeit aufhoeren.'");
+  }
+  return 0;
+}
+
+
+string short()
+{
+  return "Merlin, der Urvater aller Magier.\n";
+}
+
+string _query_race()
+{
+  return "Magier";
+}
+
+int _query_gender()
+{
+  return 1;
+}
+
+string QueryPossPronoun( mixed what, int casus, int number )
+{
+  int gen2;
+  gen2 = ( intp( what ) ) ? what : what->QueryProp(P_GENDER);
+  return "sein" + query_g_suffix( gen2, casus, number );
+}
+
+string query_g_suffix( int gen, int casus, int anzahl )
+{
+  return ({ ({ ({"","e"}), ({"es","er"}), ({"em","en"}), ({"","e"}) }),
+              ({ ({"","e"}), ({"es","er"}), ({"em","en"}), ({"en","e"}) }),
+              ({ ({"e","e"}), ({"er","er"}), ({"er","en"}), ({"e","e"}) }) })
+    [gen][casus][anzahl];
+}
+
+string QueryPronoun(int casus)
+{
+  switch(casus) {
+    case WER: return "er";  break;
+    case WEM: return "ihm";
+  }
+  return "ihn";
+}
+
+string _query_short()
+{
+  return short();
+}
+
+string _query_long()
+{
+  return long();
+}
+
+string _query_name()
+{
+  return "Merlin";
+}
+
+int _query_weight()
+{
+  return 75000;
+}
+
+static void PostSeher(string who, int g)
+{
+  mixed *art;
+
+  art=({"stufen","Merlin",0,0,sprintf("%s ist jetzt ein%s Seher%s!",who,(g==2?"e":""),(g==2?"in":"")),sprintf("\nAm %s hat %s die noetigen Bedingungen\nerfuellt und ist vom Spielerstatus zum Seherstatus aufgestiegen. \n\n Herzlichen Glueckwunsch, %s!\n",dtime(time()),who,who)});
+  "/secure/news"->WriteNote(art,1);
+}
+
+static void PostMagier(string who, string prom, int gender)
+{
+  mixed *art;
+
+  art=({"stufen","Merlin",0,0,sprintf("%s ist jetzt ein%s Magier%s!",who,(gender==2?"e":""),(gender==2?"in":"")),sprintf("\nAm %s hat %s %s zu%s Magier%s\nberufen. Herzlichen Glueckwunsch, %s. Glueckwuensche auch an Dich,\n %s, zu Deine%s neuen %s!\n",dtime(time()),prom,who,(gender==2?"r":"m"),(gender==2?"in":""),who,prom,
+(gender==2?"r":"m"),(gender==2?"Tochter":"Sohn"))});
+  "/secure/news"->WriteNote(art,1);
+}
+
+
+private void GibFehlerteufel(object wiz) {
+  if (!objectp(wiz))
+      return;
+
+  clone_object(FEHLERTEUFEL)->move(wiz, M_NOCHECK|M_GET);
+  tell_object(wiz, break_string(
+    "Huhu "+wiz->Name(WER) + "! Ich habe gesehen, dass Du keinen "
+    "Fehlerteufel hast. Dieses Tool ist zum Debuggen von Fehlern im "
+    "Mud sehr praktisch. Bitte schau ihn Dir mal an. :-) Die Hilfe zum "
+    "Fehlerteufel kannst Du mit 'man fehlerteufel' lesen. BTW: Wenn "
+    "Du den Fehlerteufel nicht willst, kannst Du ihn zerstoeren, ich "
+    "werde Dich kein zweites Mal damit belaestigen.",78,
+    "Merlin teilt Dir mit: ")
+    + "Merlin ueberreicht Dir aus der Ferne einen knusprig gebratenen "
+    "Fehlerteufel.\n\n");
+}
+
+private void notify_player_change(string who,int rein)
+{
+  if (!stringp(who))
+    return;
+
+  // Max-user festhalten
+  if (file_size("/etc/maxusers")<=0)
+    maxusers=0;
+  if (sizeof(users())>=maxusers)
+  {
+    maxusers=sizeof(users());
+    write_file("etc/maxusers",sprintf("%d user: %s (%s)\n",
+          maxusers,dtime(time()),query_load_average()),1);
+  }
+
+  // allen Magiern ab Level 20, die keinen Fehlerteufel haben und deren
+  // letzter Logout vor "Die, 17. Mar 2009, 23:40:04" => 1237329604 war,
+  // einen Fehlerteufel clonen und nen Hiwneis mitteilen.
+  object ob = find_player(who);
+  if (ob && rein && IS_WIZARD(ob)
+      && !present_clone(FEHLERTEUFEL,ob)
+      && ob->QueryProp(P_LAST_LOGOUT) < 1237329604 )
+    GibFehlerteufel(ob);
+}
+
+private void AnnounceNewPlayer(object pl) {
+
+  if (!pl || !interactive(pl))
+    return;
+
+  // verzoegern, bis der Spieler wirklich fertig ist.
+  if (pl->QueryProp(P_LEVEL) < 0) {
+    call_out(#'AnnounceNewPlayer,10,pl);
+    return;
+  }
+
+  if (!(int)pl->QueryGuest()) {
+  string plname = (string)pl->Name(WER);
+    CHMASTER->send("Anfaenger", this_object(),
+      sprintf("%s macht nun ebenfalls das Morgengrauen unsicher. "
+              "Herzlich Willkommen %s!", plname, plname)
+      );
+  }
+}
+
+// Kann bei reboot wieder raus... zesstra, 16.03.2007
+#if __BOOT_TIME__ < 1318110723
+#define BUGTIME 1318003200 
+#define BUGFIXTIME 1318109421
+void renew_player(string plname, int force) {
+    object pl;
+    mixed err;
+
+    pl = find_player(plname);
+    if (!objectp(pl) || !query_once_interactive(pl)) return;
+
+    // jung oder alt genug, Abbruch. ;-)
+    if ((object_time(pl) > BUGFIXTIME)
+        || object_time(pl) < BUGTIME)
+        return;
+
+    if (get_eval_cost() < 1000000 || !force ) {
+        call_out("renew_player",8,plname,1);
+        tell_object(pl,break_string(sprintf("Huhu %s, um einen Bug in "
+                "Deinem Spielerobjekt zu beheben, wirst Du in ca. 8s "
+                "neugeladen. Bitte warte bis dahin ab und bleibe, wo "
+                "Du gerade bist.",pl->Name()),78,"Merlin teilt Dir mit: "));
+        return;
+    }
+
+    plname=getuid(pl);
+    tell_object(pl,break_string(sprintf("Huhu %s, um einen Bug in "
+            "Deinem Spielerobjekt zu beheben, wirst Du jetzt "
+            "neugeladen.",pl->Name()),78,"Merlin teilt Dir mit: "));
+
+    if (err=catch(master()->renew_player_object(pl);publish)) {
+        log_file("zesstra/failed_rewew.log",sprintf("%s: Fehler %O beim "
+              "Renew von %s\n",dtime(time()),err,plname));
+        tell_object(pl,break_string("Beim Neuladen Deines Spielerobjekts "
+              "gab es einen Fehler. Bitte wende Dich an einen EM.",78,
+          "Merlin teilt Dir mit: "));
+    }
+    else {
+      log_file("zesstra/renewed.log",sprintf("%s: %s wurde renewed.\n",
+            dtime(time()),plname));
+      if (objectp(find_player(plname)))
+          tell_object(find_player(plname),
+            break_string("Dein Spielerobjekt wurde neugeladen. Du "
+          "kannst jetzt normal weiterspielen. Falls Du etwas ungewoehnliches "
+          "bemerkst, melde Dich bitte bei einem Erzmagier. Viel Spass im "
+          "Spiel!",78, "Merlin teilt Dir mit: "));
+    }
+}
+#endif
+
+void player_change(string eid, object trigob, mixed data) {
+
+  if (!trigob || !interactive(trigob))
+    return;
+
+  switch(eid) {
+    case EVT_LIB_PLAYER_CREATION:
+      call_out(#'AnnounceNewPlayer, 5, trigob);
+      break;
+    case EVT_LIB_LOGIN:
+      notify_player_change(data[E_PLNAME],1);
+#if __BOOT_TIME__ < 1318110723
+      // Spieler evtl. renewen? Bei reboot raus (08.10.2011)
+      renew_player(data[E_PLNAME],0);
+#endif
+      break;
+  }
+}
+
+
+//besser P_NO_ATTACK als Defend()...
+mixed _query_no_attack() {
+  if (!living(PL) || !objectp(environment(PL)) ) return(1);
+
+  tell_room(environment(), "Merlin schimpft: Lass das!!!\n",({PL}));
+  tell_room(environment(),sprintf(
+        "Merlin gibt %s eine schallende Ohrfeige.\n",      
+        capitalize((string)this_player()->name(WEM))), ({this_player()}));
+  return("Merlin schimpft: Lass das!!!\n"
+      "Merlin gibt Dir eine schallende Ohrfeige.\n");
+}
+
+/*
+int Defend(int dam, string dam_type, int spell, object enemy)
+{
+  object en;
+  if (!en=previous_object() || !living(en))
+    en = this_player();
+  tell_room(environment(), "Merlin schimpft: Lass das!!!\n");
+  write("Merlin gibt Dir eine schallende Ohrfeige.\n");
+  say(sprintf("Merlin gibt %s eine schallende Ohrfeige.\n",
+              capitalize((string)this_player()->name(WEM))), this_player());
+  en->StopHuntFor(this_object(), 1);
+}
+*/
+void shoutansw()
+{
+  object env,ti;
+  string *path;
+  string ans;
+
+  if (!(env=environment()))
+    return;
+  if (!(ti=this_interactive())||(int)ti->QueryProp(P_LEVEL)<19||ti->QueryProp(P_GHOST))
+    return;
+  tell_object(ti,"Du spuerst einen sengenden Schmerz.\n");
+  ti->do_damage( ((int)ti->QueryProp(P_HP)) / 2 + 5);
+  if((int)ti->QueryProp(P_GHOST)) return;
+  ans="ich hab nicht die leiseste Idee, wo ich hier bin ...\n";
+  path=old_explode(object_name(env),"/");
+  if (path[0]=="d")
+  {
+    ans="werweisswo ..";
+    switch(path[1])
+    {
+      case "gebirge":
+      case "wald":
+      case "dschungel":
+      ans="im "+capitalize(path[1]);
+      break;
+      case "inseln":
+      ans="auf den Inseln";
+      break;
+      case "polar":
+      ans="im Polargebiet";
+      break;
+      case "unterwelt":
+      case "ebene":
+      case "wueste":
+      ans="in der "+capitalize(path[1]);
+      break;
+      case "vland":
+      ans="im Verlorenen Land";
+      break;
+      case "fernwest":
+      ans="in Fernwest";
+      break;
+    }
+    ans="ich bin "+ans+".\n";
+  }
+  if (path[0]=="players")
+    ans="ich hab mich verlaufen - ich schaetze, "+capitalize(path[1])+
+      " hat was damit zu tun...\n";
+  if (path[0]=="gilden") {
+    switch(path[1]) {
+      case "abenteurer":
+      ans="der Abenteurergilde";
+      break;
+      case "bierschuettler":
+      ans="der Bierschuettlergilde";
+      break;
+      case "chaos":
+      ans="der Gilde der Chaoten";
+      break;
+      case "kaempfer":
+      ans="der Kaempfergilde";
+      break;
+      case "karate":
+      ans="einem Karatedojo";
+      break;
+      case "klerus":
+      ans="einem Tempel der Kleriker";
+      break;
+      case "zauberer":
+      ans="der Akademie der Zauberer";
+      break;
+      default:
+      ans="irgendeiner Gilde";
+    }
+    ans="ich stehe in "+ans+".\n";
+  }
+                if( path[0]=="p" && path[1]=="verein" ) {
+                        ans="ich stehe in Port Vain.";
+                }
+  if (!this_interactive()->QueryProp(P_INVIS))
+    ans=this_interactive()->Name(WER)+", "+ans;
+  else ans=capitalize(ans);
+  call_out("myshout",0,break_string(ans, 78, "Merlin ruft: "));
+}
+
+void myshout(string s)
+{
+  write(s);
+  shout(s);
+}
+
+void catch_tell(string str)
+{
+  string name, dummy; mixed message;
+  int i,s;
+
+  if (!this_player())
+    return;
+
+  if (environment()==environment(this_player()) && previous_object()!=ME)
+    tell_room(this_object(),"*"+str);
+
+  if (this_player() != this_interactive())
+    return;
+
+  if (busy) {
+    tell_object(this_player(), 
+       "Merlin teilt Dir mit: Ich bin beschaeftigt.\n");
+    return;
+  }
+    
+  str=lower_case(str);
+
+  if ((strstr(str, "teilt dir mit:") != -1) 
+      && (strstr(str, "wo bist du") !=-1)) {
+    shoutansw();
+    return;
+  }
+
+  if ((strstr(str, "teilt dir mit:") != -1) && 
+      ((strstr(str, "seher") != -1 || strstr(str, "seherin") != -1)))
+    {
+      create_seer(this_player());
+      return;
+    }
+
+  if (sscanf(str,"%s %s sagt: %s",dummy,name,message)!=3)
+    if (sscanf(str,"%s sagt: %s",name,message)!=2)
+      return;
+
+  if (!name || name=="" ||            
+      catch(s=file_size("save/"+name[0..0]+"/"+name+".o");publish)            
+      || s<=0 )
+    return;
+
+  if (name!=getuid(this_interactive()) && !ARCH_SECURITY)
+  {
+    if (flag==getuid(this_interactive())+"##"+name)
+      return;
+    call_out("do_say",0,"Merlin sagt: "+capitalize(getuid(this_interactive()))+" hat gerade so getan, als wuerde "+capitalize(name)+" etwas sagen!\n");
+    flag=getuid(this_interactive())+"##"+name;
+    call_out("clear_flag",0);
+    return;
+  }
+  flag=0;
+  for (i=0;i<sizeof(whitespaces);i++)
+  {
+    message=old_explode(message,whitespaces[i]);
+    if (!pointerp(message)||sizeof(message)==0)
+      return;
+    message=implode(message," ");
+  }
+
+  message=old_explode(message," ");
+
+  if (!pointerp(message) || !sizeof(message))
+    return;
+
+  message-=({0})-({""});
+
+  if (!message || sizeof(message)==0)
+    return;
+
+  if (member(message, "merlin")==-1)
+    return;
+
+  message-=({"merlin"});
+
+  message=implode(message," ");
+
+  if (!message || message=="")
+    return;
+
+  determine_action(message, name);
+}
+
+static int determine_action(string mess, string name)
+{
+  string str;
+
+  if (mess=="mach mich zum magier" || mess=="mach mich zur magierin")
+    return create_wizard(name,name);
+
+  if (sscanf(mess,"mach %s zum magier",str)==1||sscanf(mess,"mach %s zur magierin",str)==1)
+    return create_wizard(lower_case(str),name);
+
+  give_help(name);
+  return 0;
+}
+
+#define Say(str) say(str,who)
+#define Write(str) tell_object(who,str)
+
+int QueryBedingungen(object pl,string was)
+{
+  int ret,i,gen;
+  string s;
+
+  if (IS_SEER(pl))
+    return 1;
+  
+  if ((int)LEPMASTER->QueryReadyForWiz(pl)==1)
+    return 1;
+
+  s=(string)LEPMASTER->QueryReadyForWizText(pl); 
+
+  tell_object(pl, break_string("Merlin gruebelt ein wenig und teilt Dir "
+                               "nach einigem Ueberlegen mit:\n\n", 78,0,1));
+  
+  tell_object(pl, s+"\n");
+
+  tell_object(pl, break_string("Da ist wohl fuer Dich noch etwas zu tun...",
+                               78, "Merlin teilt Dir mit: "));
+
+  if (environment() == environment(pl))
+    say((string)pl->name(WER)
+        +" erfuellt noch nicht alle Anforderungen fuer den "+was+"status.\n",pl);
+  
+  return 0;
+}
+
+static int create_wizard(mixed who, mixed promoter)
+{
+  mixed *domains;
+  object vertrag;
+  int ret;
+  string gen;
+
+  who=find_player(who);
+  promoter=find_player(promoter);
+  if (!who || !promoter)
+    return 0;
+  //
+  // player not interactive?
+  //
+  if (!interactive(who))
+  {
+    write(capitalize(getuid(who))+" ist nicht aktiv.\n");
+    return 0;
+  }
+  //
+  // player is second?
+  //
+  if (who->QueryProp(P_SECOND))
+  {
+     tell_object(who,"Du bist ein Zweitspieler und kannst als solcher kein Magier werden.\n"
+          +"Versuchs Doch mal mit Deinem Erstie.\n");
+     write("Du kannst "+who->name()+" nicht zum Magier machen. Das geht bei "
+          +"Zweities\nleider nicht. Frag doch mal nach dem Erstie.\n");
+     return 1;
+  }
+  //
+  // wants to advance himself?
+  //
+  if (who==promoter)
+  {
+    write("Du kannst Dich nicht selber befoerdern. Such Dir dazu bitte einen "+
+              "Vollmagier,\nder Dir einen Vertrag gibt.\n");
+    return 1;
+  }
+  //
+  // banished?
+  //
+  if (MBanishListe[who,1])
+  {
+    tell_object(promoter,capitalize(who)+" darf nur von einem Erzmagier zum "+
+                    "Magier gemacht werden.\n");
+    say(promoter->name(WER)+" ist kein Erzmagier und darf "+capitalize(who)+
+            " nicht zum Magier machen.\n");
+    return 1;
+  }
+  //
+  // player has PK?
+  //
+  if (who->QueryProp("playerkills")) {
+     tell_room(environment(who),who->name()+" hat noch einen Playerkill und kann somit kein Magier werden.\n"
+                           "Am besten den Sheriff oder einen Erzmagier verstaendigen.\n");
+         return 1;
+  }
+  //
+  // already dom_member?
+  //
+  if (IS_DOMAINMEMBER(who))
+  {
+    Write("Fuer Dich kann ich nichts mehr tun. Du bist doch schon Magier!\n");
+    return 1;
+  }
+
+  //
+  // "advanced learner"?
+  //
+  if (query_wiz_level(who) == 20)
+  {
+    if (!ARCH_SECURITY) {
+      Write("Dich kann nur ein Erzmagier zum \"richtigen\" Magier machen!\n");
+      Say("Nur ein Erzmagier kann "+who->name(WEN)+" zum \"richtigen\" Magier machen!\n");
+      return 0;
+    }
+    ret=(int)"secure/master"->advance_wizlevel(geteuid(who),21);
+    if (ret<=0)
+    {
+      say("Merlin: error "+ret+"\n");
+      write("Merlin: error "+ret+"\n");
+    }
+    write("Merlin ruft: "+(string)who->name(WER)+" ist in den Kreis der Magier "+
+              "aufgenommen worden!\n");
+    shout("Merlin ruft: "+(string)who->name(WER)+" ist in den Kreis der Magier "+
+              "aufgenommen worden!\n");
+    "secure/master"->renew_player_object(who);
+    return 1;
+  }
+
+  //
+  // already wizard?
+  //
+  if (IS_WIZARD(who))
+  {
+    domains=(string*)"secure/master"->get_domain_homes(getuid(who));
+    if (!domains || !pointerp(domains) || !sizeof(domains))
+    {
+      Say(who->name(WER)+" gehoert noch keiner Region an und kann daher noch "+
+              "kein Voll-\nmagier werden.\n");
+      Write("Du gehoerst noch keiner Region an. Schliess Dich zunaechst einer "+
+                "an und komm\ndann wieder!\n");
+      return 0;
+    }
+    ret=(int)"secure/master"->advance_wizlevel(geteuid(who),25);
+    if(ret>0)
+    {
+      shout(who->name(WER)+" arbeitet jetzt an einer Region mit!\n");
+      "secure/master"->renew_player_object(who);
+      return 1;
+    }
+    write("RETURNVALUE "+ret+"\n");
+    say("RETURNVALUE "+ret+"\n");
+    return 0;
+  }
+
+  //
+  // still learner?
+  //
+  if (IS_LEARNER(who))
+  {
+    if (!ARCH_SECURITY) {
+      Write("Dich kann nur ein Erzmagier zum \"richtigen\" Magier machen!\n");
+      Say("Nur ein Erzmagier kann "+who->name(WEN)+" zum \"richtigen\" Magier machen!\n");
+      return 0;
+    }
+    ret=(int)"secure/master"->advance_wizlevel(geteuid(who),20);
+    if (ret<=0)
+    {
+      say("Merlin: error "+ret+"\n");
+      write("Merlin: error "+ret+"\n");
+    }
+    string wizname = (string)who->query_real_name();
+    "secure/master"->renew_player_object(who);
+    // Fehlerteufel geben
+    call_out(#'GibFehlerteufel,4,find_player(wizname));
+    return 1;
+  }
+
+  //
+  // promoter's level is not >DOMAINMEMBER_LVL+1
+  //
+  if (secure_level()<=DOMAINMEMBER_LVL)
+  {
+    tell_object(promoter,"Du musst derzeit mindestens Level "+
+                    (DOMAINMEMBER_LVL+1)+" haben, um jemanden befoerden zu "+
+                                "koennen.\n");
+    say(promoter->name(WER)+" ist kein Vollmagier und darf daher niemanden "+
+            "zum Vollmagier machen.\n");
+    return 1;
+  }
+
+  // der Check auf den Blutprintnamen ist fast ueberfluessig, aber nur fast.
+  // Er schlaegt fehl, wenn der Vertrag der Clone einer alten BP ist.
+  if (!(vertrag=present_clone("/obj/vertrag",who)) ||
+      object_name(blueprint(vertrag))!="/obj/vertrag" ||
+      !((int)vertrag->is_unterschrieben()) )
+  {
+    Write("Du hast ja gar keinen unterschriebenen Vertrag bei Dir. Besorg Dir "+
+              "einen Ver-\ntrag, unterschreibe ihn und komm dann wieder!\n");
+    Say(who->name(WER)+" hat keinen unterschriebenen Vertrag.\n");
+    return 1;
+  }
+  if (geteuid(vertrag)!=secure_euid())
+  {
+    tell_object(promoter,"Das geht nicht, er hat einen Vertrag von "+
+                    capitalize(geteuid(vertrag))+" bei sich.\n");
+    say(promoter->name(WER)+" versucht, "+who->name(WER)+" zum Magier zu "+
+            "machen, doch "+who->name(WER)+" hat einen Vertrag\nvon "+
+                capitalize(geteuid(vertrag))+" bei sich.\n",promoter);
+    return 0;
+  }
+  if (!QueryBedingungen(who,"Magier"))
+    return 0;
+  ret=(int)"secure/master"->advance_wizlevel(geteuid(who),15);
+  if (ret>0)
+  {
+    PostMagier(capitalize(getuid(who)),
+        capitalize(secure_euid()),(int)who->QueryProp(P_GENDER));
+    write_file("/log/SPONSOR",dtime(time())+": "+capitalize(getuid(promoter))+" macht "+
+             who->name(WER)+" zum Learner.\n");
+    write(who->name(WER)+" ist in den Kreis der Magier aufgenommen worden!\n");
+    shout(who->name(WER)+" ist in den Kreis der Magier aufgenommen worden!\n");
+    if ("secure/master"->set_player_object(geteuid(who),"/std/shells/magier")
+            <=0)
+    {
+      say("MERLIN: konnte Magiershell nicht einstellen.\n");
+      write("MERLIN: konnte Magiershell nicht einstellen.\n");
+    }
+    "secure/master"->renew_player_object(who);
+    return 1;
+  }
+  write(" --RETURNVALUE IS "+ret+"\n");
+  say(" --RETURNVALUE IS "+ret+"\n");
+
+  return(ret); //non-void funktion
+}
+
+void clear_flag()
+{
+  flag=0;
+}
+
+void do_say(string str)
+{
+  say(str);
+}
+
+
+
+void seer_sequenz3(mixed player, string plname)
+{
+  string playername; 
+  object faq;
+  string text;
+
+  if (!objectp(player)) {
+    tell_room(environment(), sprintf("Merlin sagt: Na, wo ist denn %s "
+                                     "hin?\n", plname));
+    busy = 0;
+    return;
+  }
+  if (environment() != environment(player))
+    move(environment(player), M_TPORT);
+  
+  tell_object(player,break_string("\n\nEs macht *PUFF* und eine Schwefelwolke "
+                                  +"steigt um Dich herum auf.\n\nDie Aura "
+                                  +"verschwindet langsam und Du fuehlst Dich "
+                                  +"nun viel erfahrener.", 78,0,1));
+  tell_room(environment(),
+           break_string(sprintf(
+                       "\n\nEs macht *PUFF* und eine Schwefelwolke steigt "
+                       +"um %s herum auf.\n\nDie Aura verschwindet langsam.\n",
+                       plname),78,0,1), ({player}));
+ 
+  if ("secure/master"->advance_wizlevel(geteuid(player),1)<=0)
+  {
+    write("Merlin sagt: Da geht was schief ... sag mal Zook Bescheid.\n");
+    say("Merlin sagt: Da geht was schief ... sag mal Zook Bescheid.\n");
+    busy = 0;
+    return;
+  }
+  playername = geteuid(player);
+  "secure/master"->renew_player_object(player);
+  player = find_player(playername);
+  text = sprintf(" ist jetzt ein%s Seher%s!\n",
+    (((int)player->QueryProp(P_GENDER))==2?"e":""),
+    (((int)player->QueryProp(P_GENDER))==2?"in":""));
+  write("Merlin ruft: "+capitalize(playername)+text);
+  shout("Merlin ruft: "+capitalize(playername)+text);
+  PostSeher(capitalize(playername),(int)player->QueryProp(P_GENDER));
+  if(!catch(
+        faq=clone_object("/d/seher/haeuser/special/seherfaqmobil") ;publish))
+  {
+    faq->move(player,M_NOCHECK);
+    tell_object(player, "Aus dem Nichts erscheint eine kleine Belohnung fuer Dich.\n");
+  }
+  
+  tell_object(player, "\n\nDu laechelst gluecklich in die Runde.\n\n");
+  tell_room(environment(), sprintf("\n\n%s laechelt gluecklich in die Runde.\n\n", plname), ({player}));
+
+ busy=0;
+}
+
+void seer_sequenz2(mixed player, string plname)
+{
+  if (!objectp(player)) {
+    tell_room(environment(), sprintf(
+                                     "Merlin sagt: Na, wo ist denn %s hin?\n", 
+                                     plname));
+    busy = 0;
+    return;
+  }
+  if (environment() != environment(player))
+    move(environment(player), M_TPORT);
+
+  tell_object(player, break_string("\nMerlin hebt die Arme und "
+       +"und macht einige beschwoerende Gesten. Um Dich herum erscheint "
+       +"eine gelbliche Aura, die immer heller wird, bis Du Deine "
+                                   +"Umwelt kaum mehr erkennen kannst.\n\n",
+                                   78,0,1));
+  tell_room(environment(),
+            break_string(sprintf("\nMerlin hebt die Arme und macht einige "
+                         +"beschwoerende Gesten. Um %s erscheint eine "
+                         +"gelbliche Aura, die immer heller wird, bis "
+                                 +"man %s kaum mehr erkennen kann.\n\n", 
+                                 plname, plname),78,0,1), ({player}));
+
+  call_out("seer_sequenz3", 7, player, plname);
+}
+
+void seer_sequenz1(mixed player, string plname)
+{
+  if (!objectp(player))
+    return;
+
+  move(environment(player), M_TPORT);
+
+  tell_room(environment(),
+            break_string(sprintf("Das ist eine grosse Freude. %s hat sich "
+     "tapfer durch das "MUDNAME" geschlagen und mittlerweile alle "
+     "Anforderungen erfuellt, um %s zu werden.",                                  
+     plname, ((int)player->QueryProp(P_GENDER))==1?"Seher":"Seherin"),
+              78, "Merlin sagt: "));
+
+  call_out("seer_sequenz2", 4, player, plname);
+}
+
+  
+static int create_seer(mixed player)
+{
+  if (!objectp(player))
+    return 0;
+  if (IS_SEER(player))
+  {
+    write(break_string("Du bist doch schon kein normaler Spieler mehr, "
+                       +"was soll es also?", 78, "Merlin teilt Dir mit: "));
+    return 1;
+  }
+
+  if (!QueryBedingungen(player,"Seher"))
+    return -1;
+
+
+  write(break_string("Grosse Freude! Du hast alle Anforderungen "
+                                   "erfuellt. Warte einen Moment...", 78,
+                                   "Merlin teilt Dir mit: "));
+  busy=1;
+  call_out("seer_sequenz1",4,player, player->Name());
+  return(0); //non-void funktion
+}
+
+static void give_help(mixed player)
+{
+  if (!objectp(player))
+    return;
+
+  tell_object(player,
+              break_string("Ich weiss nicht, was ich fuer Dich tun kann. "
+                           +"Ich kann Dich zum Seher oder zum Magier "
+                           +"machen, wenn Du mich darum bittest.", 78,
+                           "Merlin teilt Dir mit: "));
+}
+              
+
+void reset()
+{
+}
+
+public int remove(int silent) {
+  EVENTD->UnregisterEvent(EVT_LIB_LOGIN, ME);
+  EVENTD->UnregisterEvent(EVT_LIB_PLAYER_CREATION, ME);
+  destruct(this_object());
+  return 1;
+}
+
+void wandern()
+{
+  mapping ex,rooms;
+  mixed ziel;
+  object *raeume,*laeden,env,ob;
+  string *ind,fn;
+  int i;
+
+  while (remove_call_out("wandern")>=0);
+  call_out("wandern",45+random(100));
+  if (busy) return;
+  if (delay)
+  {
+    delay=0;
+    return;
+  }
+  if (!(env=environment()))
+  {
+    move("/gilden/abenteurer",0);
+        env=environment();
+  }
+  //ueber Inv iterieren, ob da ein nicht-idelnder Spieler mit genug XP
+  //rumhaengt.
+  raeume=all_inventory(env);
+  for(i=sizeof(raeume); i-- ; ) {
+    if (interactive(raeume[i]) && query_idle(raeume[i])<180 &&
+        raeume[i]->QueryProp(P_XP)>999999)
+      return;
+  }
+  ex=(mapping)env->QueryProp(P_EXITS);
+  if (!mappingp(ex))
+    return;
+  ind=m_indices(ex);rooms=([]);laeden=({});
+  for (i=sizeof(ind)-1;i>=0;i--)
+  {
+    if (!stringp(ziel=ex[ind[i]]))
+      continue;
+    if (!ob=find_object(ziel))
+      continue;
+    rooms+=([ob]);
+  }
+  rooms-=([env]);
+  if (prev_room && sizeof(m_indices(rooms))>1)
+    rooms-=([prev_room]);
+  prev_room=env;
+  raeume=m_indices(rooms);
+  if (!i=sizeof(raeume))
+    return;
+  ob=raeume[random(i)];
+  move(ob,0);
+}
+
+string int_short()
+{
+  return "Du bist im allseits beliebten Wizclub \"Zum lustigen Merlin\".\n";
+}
+
+string int_long(object who,mixed where)
+{
+  string descr;
+
+  if( IS_LEARNER(who) && who->QueryProp( P_WANTS_TO_LEARN ) )
+    descr = "[" + object_name(ME) + "]\n";
+  else
+    descr = "";
+  descr+="Du bist im Wizclub 'Zum lustigen Merlin'. Dieser Wizclub ist etwas besonderes -\ner hat keinen festen Standort, sondern wandert im Mud herum. Ausserdem kann er\nSpieler zu Magiern machen - aber nur, wenn man ihn von aussen aus darauf\nanspricht.\n";
+  if ( who->QueryProp(P_SHOW_EXITS) )
+    descr += "Es gibt 2 sichtbare Ausgaenge: raus und gilde.\n";
+  descr += make_invlist(who, all_inventory(ME) - ({ who }));
+  if( environment())
+    descr += "\nAusserhalb siehst Du:\n"
+      + environment()->int_short(who,ME);
+  return descr;
+}
+
+string make_invlist(object viewer,object *inv)
+{
+  int i, iswiz;
+  string descr;
+
+  descr = "";
+  iswiz = IS_LEARNER( viewer ) && viewer->QueryProp(P_WANTS_TO_LEARN);
+
+  for (i = 0; i < sizeof(inv); i++)
+  {
+    string sh;
+    sh = (string)inv[i]->short();
+    if (iswiz)
+    {
+      if (sh) sh = sh[0..<2] + " ["+object_name(inv[i])+"]\n";
+      else sh = "["+object_name(inv[i])+"]\n";
+    }
+    if (sh) descr += sh;
+  }
+  return descr;
+}
+
+void init()
+{
+  if (interactive(this_player()) && environment(this_player())==ME)
+    add_action("gehen","",1);
+  add_action("befoerdere","befoerder",1);
+}
+
+int befoerdere(string str)
+{
+  string wen;
+  int ret,stufe,l;
+
+  if (!LORD_SECURITY) return 0;
+  if (!str||sscanf(str,"%s auf stufe %d",wen,stufe)<2)
+  {
+    notify_fail("Syntax: befoerdere <name> auf stufe <stufe>\n");
+        return 0;
+  }
+  if (stufe>29)
+  {
+    write("Maximum ist Stufe 29!\n");
+        return 1;
+  }
+  l=query_wiz_level(wen);
+  if (l<25)
+  {
+    printf("%s muss mindestens zwecks Weiterbefoerderung mindestens Stufe 25 haben.\n",
+        capitalize(wen));        
+    return 1;
+  }
+  if (l>=stufe)
+  {
+    printf("%s ist schon Stufe %d.\n",capitalize(wen),l);
+        return 1;
+  }
+  ret=(int)"secure/master"->advance_wizlevel(wen,stufe);
+  if (ret<=0)
+    printf("Errorcode %d\n",ret);
+  else
+    write("ok\n");
+  return 1;
+}
+
+int sag(mixed str)
+{
+  int i;
+
+  if (str==0)
+    str="";
+  str = old_explode(break_string(str, 60 ), "\n");
+  if (busy) {
+    tell_room(ME, "Merlin mault: Ich bin beschaeftigt.\n");
+    return 0;
+  }
+  for (i = 0; i < sizeof(str); i++)
+  {
+    if (!str[i])
+      str[i]="";
+    tell_room(environment(),"Merlin sagt: "+str[i]+"\n");
+    tell_room(ME,"*Merlin sagt: "+str[i]+"\n");
+  }
+  return 1;
+}
+
+void show_exits()
+{
+  printf("%s",environment()->GetExits());
+}
+
+int gehen(string str)
+{
+  string verb;
+  int i;
+  mapping exits;
+  string *ex;
+
+  if (busy) {
+    write("Merlin mault: Ich bin beschaeftigt.\n");
+    return 0;
+  }
+
+  verb=query_verb();
+  str=(string)this_interactive()->_unparsed_args();
+  switch (verb)
+  {
+    case "gilde":
+      this_player()->move("/gilden/abenteurer",M_GO,"in die Gilde");
+      return 1;
+    case "raus":
+      this_player()->move(environment(),M_GO,"raus");
+      return 1;
+  }
+  if (!IS_WIZARD(this_interactive()))
+    return 0;
+  if (verb!="merlin")
+    return 0;
+  delay=1;
+  exits=(mapping)environment()->QueryProp(P_EXITS);
+  if (!str||str=="")
+  {
+    ex=m_indices(exits);
+    printf(environment()->int_short(ME,ME));
+    show_exits();
+    return 1;
+  }
+  if (old_explode(str," ")[0]=="sag")
+    return sag(implode(old_explode(str," ")[1..]," "));
+  if (str[0]==';')
+  {
+    tell_room(environment(ME),"Merlins "+str[1..]+"\n");
+    tell_room(ME,"*Merlins "+str[1..]+"\n");
+    return 1;
+  }
+  if (str[0]==':')
+  {
+    tell_room(environment(ME),"Merlin "+str[1..]+"\n");
+    tell_room(ME,"*Merlin "+str[1..]+"\n");
+    return 1;
+  }
+  if (str[0]=='*')
+  {
+    goto(str[1..]);
+    printf(environment()->int_short(ME,ME));
+    show_exits();
+    return 1;
+  }
+  if (!exits[str])
+    return 0;
+  move(exits[str],0);
+  write("Merlin sagt: Jawoll, Chef.\n");
+  printf(environment()->int_short(ME,ME));
+  show_exits();
+  return 1;
+}
+
+int goto(mixed dest)
+{
+  object ob;
+  string path;
+  mixed ret;
+
+  if (busy) {
+    write("Merlin mault: Ich bin beschaeftigt.\n");
+    return 1;
+  }
+
+  if (!dest) {
+    write("Wohin moechtest Du Merlin bringen ?\n");
+    return 1;
+  }
+  ob = find_living(dest);
+  if (ob) {
+    ob = environment(ob);
+    move(object_name(ob),0);
+    return 1;
+  }
+  path=(mixed)"secure/master"->_get_path(dest,getuid(this_player()));
+  ret=catch(load_object(path);publish);
+  if (ret && file_size(path)<0)
+  {
+    dest=match_living(dest,1);
+    if (stringp(dest))
+    {
+      ob = environment(find_living(dest));
+      move(object_name(ob),M_TPORT|M_NOCHECK);
+      return 1;
+    }
+    printf("Fehler beim Teleport nach %O: No such file.\n",path);
+    return 1;
+  }
+  if (ret)
+    printf("Fehler beim Teleport nach %O: %O\n",dest,ret);
+  ret=catch(move(path,M_TPORT|M_NOCHECK);publish);
+  if (stringp(ret))
+    ret=implode(old_explode(ret,"\n"),"");
+  if (ret)
+    printf("Fehler beim Teleport nach %O: %O\n",dest,ret);
+  return 1;
+}
+
+mixed locate_objects(string complex_desc)
+{
+  object ob;
+
+  if (complex_desc=="alles" || complex_desc=="alle")
+    return filter_objects(all_inventory(this_object()),"short");
+  if (ob = present(complex_desc,this_object()) )
+    return ({ ob });
+  return ({}) ;
+}
+
+void MBanishInsert(string name, string grund, object wer)
+{
+  if ( !name || !stringp(name) || !sizeof(name) ||
+       !grund || !stringp(grund) || !sizeof(grund) ||
+       !wer || !objectp(wer) || getuid(wer) != secure_euid() ||
+       !IS_DEPUTY(wer) )
+    return;
+
+  MBanishListe += ([name:grund;getuid(wer)]);
+  save_object(SAVEFILE);
+}
+
+mapping MBanishList()
+{
+    if ( !IS_DEPUTY(secure_euid()) )
+        return 0;
+
+    return deep_copy(MBanishListe);
+}
+
+void MBanishDelete(string name)
+{
+  if ( !name || !stringp(name) || !sizeof(name) || !ARCH_SECURITY )
+    return 0;
+
+  MBanishListe = m_copy_delete( MBanishListe, name );
+  save_object(SAVEFILE);
+}
+
+string _query_noget()
+{
+  return "Merlin mag das nicht!\n";
+}
+
diff --git a/secure/mini_props.c b/secure/mini_props.c
new file mode 100644
index 0000000..42345a4
--- /dev/null
+++ b/secure/mini_props.c
@@ -0,0 +1,40 @@
+// MorgenGrauen MUDlib
+//
+// mini_props.c -- Abgespeckte Verwaltung von Props
+//                 Aus Sicherheitsgruenden darf hier nicht die "Vollversion"
+//                 mit echten Properties stehen
+//
+// $Id$
+#pragma strict_types,save_types
+#pragma no_clone
+#pragma no_shadow
+#pragma pedantic,range_check,warn_deprecated
+
+#define NEED_PROTOTYPES
+#include <thing/properties.h>
+#undef NEED_PROTOTYPES
+
+nosave mapping prop;
+
+private void InitProps()
+{
+    prop = m_allocate(2,1);
+}
+
+
+public varargs mixed Query( string str, int type )
+{
+    if ( !mappingp(prop) )
+        InitProps();
+
+    return prop[str];
+}
+
+public varargs mixed Set(string name, mixed value, int type, int extern)
+{
+    if ( !mappingp(prop) )
+        InitProps();
+
+    return prop[name] = value;
+}
+
diff --git a/secure/misc/mssp.c b/secure/misc/mssp.c
new file mode 100644
index 0000000..4c45388
--- /dev/null
+++ b/secure/misc/mssp.c
@@ -0,0 +1,214 @@
+#include <config.h>
+#include <thing/description.h>
+#include <strings.h>
+
+// hosts who asked us in the past and timestamp of last connection which got
+// the full datatset.
+private nosave mapping peers = ([:1]);
+
+/* For a list of official and extended fields:
+   http://www.mudbytes.net/index.php?a=articles&s=MSSP_Fields
+   For a protocal description:
+   http://tintin.sourceforge.net/mssp/
+   */
+
+#define MSSP_VAR 1
+#define MSSP_VAL 2
+
+public string convert_tn(mapping ldata);
+
+protected string list_ports() {
+  int ports = query_mud_port(-1) - 1;
+  string res="23";
+  for(; ports>=0 ; --ports)
+    res += "\t" + to_string(query_mud_port(ports));
+
+  return res;
+}
+
+#define DESCRIPTION \
+    MUDNAME" is a native German LPmud. It was founded in 1992 and has "\
+    "been prospering since. The world features an original fantasy setting "\
+    "with many facets. The 13 domains form a big world with 18000 rooms to "\
+    "explore and several thousand NPCs. You can choose between 7 races and "\
+    "10 classes (guilds). Advancing is done through a combination of "\
+    "exploration points, experience points, class skills, finished "\
+    "adventures and some more. The levels are not limited, current maximum "\
+    "is about 200. After becoming a high level player it's possible to "\
+    "become a wizard and add your own imagination to the game. We are "\
+    "especially proud of attracting a number of visually impaired players "\
+    "who very much enjoy playing a text based online RPG. Thats why we have "\
+    "an option to turn ascii-graphics off. Multiplaying is a bit restricted "\
+    "and scripting is discouraged."
+
+private nosave mapping mindata = ([
+    "NAME"             : MUDNAME,
+    "UPTIME"           : to_string(__BOOT_TIME__),
+    ]);
+
+private nosave mapping data = mindata + ([
+    "PORT"             : list_ports(),
+    "CODEBASE"         : _MUDLIB_NAME_+"-"+_MUDLIB_VERSION_,
+    "HOSTNAME"         : __HOST_NAME__ + "." + __DOMAIN_NAME__,
+#if MUDHOST == __HOST_NAME__
+// diese Angaben sollen nur gesendet werden, wenn das Mud wirklich auf dem
+// MG-Rechner laeuft und kein Homemud ist.
+    "CRAWL DELAY"      : "1",
+    "DESCRIPTION"      : DESCRIPTION,
+    "CREATED"          : "1992",
+    "ICON"             : "http://mg.mud.de/newweb/img/icon.gif",
+    "IP"               : "87.79.24.60",
+    "CONTACT"          : "mud@mg.mud.de",
+    "WEBSITE"          : "http://mg.mud.de/",
+    "AREAS"            : "13",
+    "MOBILES"          : "2600",
+    "OBJECTS"          : "4000",
+    "ROOMS"            : "18000",
+    "CLASSES"          : "10",
+    "LEVELS"           : "200",
+    "RACES"            : "7",
+    "SKILLS"           : "140",
+    "MULTICLASSING"    : "0",
+    "PLAYERKILLING"    : "Restricted",
+    "NEWBIE FRIENDLY"  : "1",
+    "ROLEPLAYING"      : "Accepted",
+    "WORLD ORIGINALITY": "Mostly Original",
+    "MINIMUM AGE"      : "6",
+    "SSL"              : "4712",
+    "STATUS"           : "Live",
+    "STATUS-NOTES"     : "live and running",
+    "PORT-NOTES"       : "player ports are 23 and 4711, SSL port is 4712",
+    "QUEST-NOTES"      : "We have about 200 Quests and MiniQuests and "
+                         "they play an important role in gaining "
+                         "levels and skills.",
+    "MULTIPLAYING-NOTES"     : "for combat and quests only 2 characters may be active",
+#else
+    "STATUS"           : "Alpha",
+    "STATUS-NOTES"     : "Homemud for testing puposes",
+#endif
+    "LANGUAGE"         : "German",
+    "LOCATION"         : "Germany",
+    "FAMILY"           : "LPMud",
+    "GENRE"            : "Fantasy",
+    "SUBGENRE"         : "None",
+    "GAMEPLAY"         : "Adventure",
+    "GAMESYSTEM"       : "Custom",
+    "INTERMUD"         : "IM2-Zebedee",
+    "HELPFILES"        : "N/A",
+    "MULTIPLAYING"     : "Restricted",
+    "EQUIPMENT SYSTEM" : "Both",
+    "TRAINING SYSTEM"  : "Both",
+    "QUEST SYSTEM"     : "Integrated",
+    "ZMP"              : "0",
+    "ANSI"             : "1",
+    "GMCP"             : "1",
+    "MCCP"             : "0",
+    "MCP"              : "0",
+    "MSP"              : "0",
+    "MXP"              : "0",
+    "PUEBLO"           : "0",
+    "VT100"            : "1",
+    "XTERM 256 COLORS" : "0",
+    "PAY TO PLAY"      : "0",
+    "PAY FOR PERKS"    : "0",
+    "HIRING BUILDERS"  : "1",
+    "HIRING CODERS"    : "1",
+    "CODEBASE-NOTES"         : "download daily snapshot of our public base "
+                               "mudlib at ftp://mg.mud.de/Software/MudLib/, "
+                               "get our driver at http://www.ldmud.eu/",
+    "FAMILY-NOTES"           : "Descendant of Nightfall, base for several "
+                               "german MUDs, uses LDMud-3.5.x", 
+    "HELPFILES-NOTES"        : "each basic command, and hundreds of other docs",
+    "MOBILES-NOTES"          : "npc's can be cloned, so there can be thousands",
+    "OBJECTS-NOTES"          : "objects can be cloned, so there can be thousands",
+    "RESETS-NOTES"           : "doesn't apply, LPMud",
+    "MUDPROGS-NOTES"         : "doesn't apply, LPMud",
+    "MUDTRIGS-NOTES"         : "doesn't apply, LPMud",
+    "RACES-NOTES"            : "some additional for non player characters",
+    "SKILLS-NOTES"           : "each class/guild and race has its own set of skills",
+    "PLAYERKILLING-NOTES"    : "playerkilling is limited to specific rooms",
+/*
+    "ADULT MATERIAL"   : "0",
+    "PLAYER CITIES"    : "0",
+    "PLAYER CLANS"     : "0",
+    "PLAYER CRAFTING"  : "0",
+    "PLAYER GUILDS"    : "0",
+    "ROOMS-NOTES"            : "areas have generated rooms, could be millions",
+ */
+    ]);
+
+
+// cache fuer die telnetneg-basierte variante
+private nosave string tn_result_min = convert_tn(mindata);
+private nosave string tn_result = convert_tn(data);
+
+
+// converts into the array to be sent via telnet suboption negotiation.
+public string convert_tn(mapping ldata) {
+  string res="";
+  foreach(string key, string value: ldata) {
+    res += sprintf("%c%s%c%s", MSSP_VAR, key, MSSP_VAL, value);
+  }
+  return res;
+}
+
+
+public void print_mssp_response() {
+ string ip = query_ip_number(previous_object());
+ mapping ldata;
+ if (stringp(ip)) {
+     // Vollen Datensatz alle ("CRAWL DELAY" / 2) h, daher * 1800.
+   if (peers[ip] > (time() - (to_int(data["CRAWL DELAY"]) || 1) * 1800)) {
+       // this peers asks to often and gets only the minimal dataset
+       ldata = mindata;
+   }
+   else {
+       ldata = data;
+       peers[ip] = time(); // record timestamp
+   }
+ }
+ else
+     ldata = data;
+
+ /* data["WHO"] = implode(map(filter(users(), 
+                      function status (object o)
+                      { return !o->QueryProp(P_INVIS); } ),
+                    function string (object o)
+                    { return capitalize(o->query_real_name()); } ),
+                  "\t" );
+  */
+  ldata["PLAYERS"] = to_string(sizeof(users())-1);
+
+  string reply = "\r\nMSSP-REPLY-START\r\n";
+
+  foreach(string key, string value: ldata) {
+    reply += key + "\t" + value + "\r\n";
+  }
+
+  reply += "MSSP-REPLY-END\r\n";
+  write(reply);
+}
+
+public string get_telnegs_str() {
+  string ip = query_ip_number(previous_object());
+  string res;
+
+  if (stringp(ip)) {
+    // Vollen Datensatz alle ("CRAWL DELAY" / 2) h, daher * 1800.
+    if (peers[ip] > (time() - (to_int(data["CRAWL DELAY"]) || 1) * 1800)) {
+       // this peers asks to often and gets only the minimal dataset
+       res = tn_result_min;
+    }
+    else {
+        res = tn_result;
+        peers[ip] = time(); // record timestamp
+    }
+  }
+  else
+    res = tn_result;
+
+  res += convert_tn( (["PLAYERS": to_string(sizeof(users())-1) ]) );
+   
+  return res;
+}
+
diff --git a/secure/misc/twitter.c b/secure/misc/twitter.c
new file mode 100644
index 0000000..f34519e
--- /dev/null
+++ b/secure/misc/twitter.c
@@ -0,0 +1,131 @@
+/* rumatas twitter client, sendet text
+ * an einen twitterproxyprozess, der sie dann an twitter
+ * weiterschickt.
+ *
+ * version 2
+ * mit persistenter verbindung zum proxy und input-channel
+ *
+ * 25.5.2016 rumata@mg.mud.de
+ */
+
+#include <wizlevels.h>
+#include <daemon/channel.h>
+
+#define HOST "127.0.0.1"
+#define PORT 4911
+
+#define DEBUG(x) tell_object(find_player("rumata"),x+"\n")
+
+void create() {
+	if( clonep() ) return;
+	seteuid(getuid());
+}
+
+object caller;
+string msgbuf;
+
+mapping registered = ([
+	"rumata": "ru",
+	"arathorn": "ara",
+	"ark": "ark",
+	"zesstra": "ze",
+	"zook": "zo",
+	"humni": "hu",
+	"miril": "mi",
+	"gloinson": "gl"
+]);
+
+int allowed() {
+	return IS_ARCH(this_player()) || getuid(this_player())=="gloinson";
+}
+
+string sig( object pl ) {
+	string uid = getuid(pl);
+	if( !uid ) return "??";
+	string abbr = registered[uid];
+	if( !abbr ) return uid;
+	return abbr;
+}
+
+void emit( string msg ) {
+	CHMASTER->send("twitter",this_object(),msg,MSG_SAY);
+	/*
+	foreach( string uid: m_indices(registered) ) {
+		object pl = find_player(uid);
+		if( pl ) tell_object(pl,msg);
+	}*/
+}
+
+/* Offizielle API funktion
+ * xeval "/secure/misc/twitter"->twitter("@_zesstra_ welcome back")
+ */
+void twitter(string msg) {
+	int err = 0;
+	if( !allowed() ) {
+		write( "Twitter ist ARCH+Gloinson only.\n" );
+		return;
+	}
+	msg = msg + "^" + sig(this_player()) + "\n";
+	if(interactive(this_object())) {
+		tell_object(this_object(),msg);
+	} else {
+		msgbuf = msg;
+		caller = this_player();
+		if( (err=net_connect(HOST,PORT))!=0 ) {
+			write( "Konnte Tweet nicht senden. err="+err+"\n" );
+		}
+	}
+}
+
+// sonderfunktion fuer den fall, dass man die verbindung
+// aufbauen will, ohne dass etwas auf twitter erscheint
+void connect() {
+	int err = 0;
+	if( interactive(this_object()) ) return;
+	msgbuf = "";
+	caller = this_player();
+	if( (err=net_connect(HOST,PORT))!=0 ) {
+		write( "Konnte Tweet nicht connected. err="+err+"\n" );
+	}
+}
+
+varargs void logon(int flag) {
+	if( flag<0 ) {
+		tell_object(caller,"Twitterproxy antwortet nicht?\n");
+  } else {
+		enable_commands();
+		add_action("input","",1);
+		write(msgbuf);
+		if( msgbuf!="" ) emit( msgbuf );
+		//"/secure/master"->disconnect(this_object());
+	}
+}
+
+void NetDead() {
+	disable_commands();
+}
+
+string query_real_name() {
+	return "(Twitter)";
+}
+
+varargs string Name( int casus, int demon ) {
+	return "Twitter";
+}
+
+// daten werden via tell_object an dieses objekt gesendet
+// und dann nach aussen weitergeben
+void catch_tell( string msg ) {
+	if( allowed() && interactive(this_object()) ) {
+		write( msg );
+	}
+}
+
+// hier landen daten von aussen
+int input( string s ) {
+	string msg = query_verb();
+	if( s ) msg = msg + " " + s;
+	emit( msg ); //"Twitter teilt dir mit: "+msg+"\n" );
+	return 1;
+}
+
diff --git a/secure/news.c b/secure/news.c
new file mode 100644
index 0000000..751c887
--- /dev/null
+++ b/secure/news.c
@@ -0,0 +1,566 @@
+// 18.Dez  1996 - Loco@Morgengrauen
+// 8. Feb  1995 - Jof@MorgenGrauen
+// 5. Juli 1992 - Jof@MorgenGrauen
+// 6. Juli 1992 - Jof@MorgenGrauen
+// Clear-Groups-Mechanismus eingebaut. Die 2 Konstanten TIME_TO_CLEAR und
+// MIN_TOUCHED muessen def. sein. Alle TIME_TO_CLEAR Sekunden werden die
+// Newsgroups, die seit dem letzten Clear seltener als MIN_TOUCHED Sekunden
+// beruehrt wurden, aus dem Cache genommen
+
+// 1. Februar 1995 - Jof@MorgenGrauen
+// Rewrite (Mappings benutzen, Accessinfos im Speicher halten)
+
+
+// Datenstrukturen:
+
+// Newsgroups:  grouplist ist ein Mapping mit einem Eintrag pro Newsgroup.
+//              Die Keys sind die Gruppennamen, Daten:
+//              0 Zeit des letzen Artikels
+//              1 Besitzer der Gruppe
+//              2 Savefille-Name
+//              3 Expire-Zeit
+//              4 Array mit Loesch-Berechtigten
+//              5 Array mit Scheib-Berechtigten
+//              6 Array mit Leseberechtigten
+//              7 Mindest-Level, um Artikel zu loeschen
+//              8 Mindest-Level, um Artikel zu posten
+//              9 Mindest-Level, um Artikel zu lesen
+//             10 Max. Anzahl Nachrichten in der Gruppe
+
+// Die Nachrichten selber stehen in einem Array.
+
+// Eine nachricht ist ein Array mit den folgenden Elementen:
+// 0         (*) string writer;
+// 1         (*) string id;    Mudname+":"+time()+":"+group zur Zeit
+// 2         (*) string time;
+// 3             string title;
+// 4             string message;
+
+// Die mit (*) gekennzeichneten Eintraege setzt der Daemon selber
+
+// Funktionen:
+//    Returnvalues:  0 = Parameter error
+//                   1 = Ok.
+//                  -1 = no permission
+//                  -2 = no such group/group already existing
+//
+//    Diese Returnvalues stehen fuer alle Funktionen
+
+//  AddGroup(groupname, owner, savefile);
+//   Funktion klar, kann nur von Erzmagiern aufgerufen werden
+//    -3 = no owner, -4 = savefile already in use
+//
+//  RemoveGroup(groupname);
+//   Ebenfalls nur fuer Erzmagier
+//
+//  SetGroup(groupname, dlevel, wlevel, rlevel, maxmessages, expire);
+//   Erzmagier und der Groupowner koennen die nutzen. Legt Level, ab dem je-
+//   mand aus der Gruppe loeschen kann (dlevel), in die Gruppe schreiben
+//   kann (wlevel), die Gruppe lesen kann (rlevel) und die max. Anzahl Nach-
+//   richten in der Gruppe fest.
+//
+//  AddAllowed(groupname, deleters, writers, readers);
+//   Erzmagier/Owner koennen Arrays mit Namen von fuer die Gruppe loesch/
+//   schreib/lese-Berechtigten fuer die Gruppe angeben.
+//
+//  RemoveAllowed(groupname, deleters, writers, readers);  analog
+//
+//  WriteNote(message); klar
+//   -3 = Max number of msgs exceeded
+//
+//  RemoveNote(boardname, notenummer); notenummer>=0;
+//   -3 = No such note
+//
+//  GetNotes(boardname); gibt einen Array mit den Notes zurueck.
+//
+//  AskAllowedWrite(); return wie bei WriteNote, stellt fest, ob ein Player
+//  eine Note aufhaengen darf oder nicht
+//
+//  GetNewsTime([boardname]); gibt zurueck, wann am entsprechenden Brett zum
+//  letzten Mal eine Note befestigt wurde. Falls kein boardname angegeben
+//  wird, liefert GetNewsTime() den Zeitpunkt, zu dem ueberhaupt eine neue
+//  Note aufgehaengt wurde.
+//
+#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 "/secure/wizlevels.h"
+#include <defines.h>
+#include <config.h>
+#include <news.h>
+
+#define WTIME 0
+
+private int security( string name );
+
+mixed saveload; // Diese Variable ist als einzige nicht nosave ist und dient
+                 // Uebertragen von Daten in/aus savefiles
+
+nosave mapping grouplist; // Groups und ihre save-Files, zudem LastTime
+nosave int lasttime; // Um zu vermeiden, dass 2 Notes identische Uhrzeit==id
+                     // haben, wird die jeweils letzte Zeit gespeichert.
+
+nosave mapping cache = ([]); // cache fuer die Gruppeninhalte
+
+void create() {
+  seteuid(getuid(this_object()));
+  if (!restore_object(NEWSPATH+"GroupList"))
+    grouplist=m_allocate(0,G_MESSAGES);
+  else
+    grouplist=saveload;
+  // ersten reset sobald wie moeglich. ;-)
+  set_next_reset(1);
+}
+
+int AddGroup(string name, string owner)
+{
+  mixed *group;
+  string savefile, *savefilea;
+  int i;
+
+  if (!name || !owner) return 0;
+  
+  if (!ARCH_SECURITY || process_call()) return -1; // Darf nicht
+
+  if (member(grouplist, name)) return -2; // Gibt es schon
+
+  if (file_size("/"+SAVEPATH+owner[0..0]+"/"+owner+".o")<0) return -3;
+
+  savefilea = old_explode(name,".");
+  savefile = implode(savefilea,"/");
+  if (file_size(NEWSPATH+savefile+".o")>=0) return -4;
+
+  // Notwendige Directories anlegen
+  for (i = 0; i < sizeof(savefilea)-1; i++) {
+    mkdir(NEWSPATH+implode(savefilea[0..i],"/"));
+  }
+
+  group=({});
+  grouplist+=([name:0;owner;savefile;-1;({});({});({});20;0;0;80]);
+  save_group_list();
+  save_group(name,group);
+  return 1;
+}
+
+int RemoveGroup(string name)
+{
+  int num;
+
+  if (!name) return 0;
+
+  if (!security(name) || process_call()) return -1; // Darf nicht
+
+  if (!mappingp(grouplist) || !member(grouplist,name))
+    return -2; // -2 no such group
+
+  catch(rm(NEWSPATH+grouplist[name,G_SAVEFILE]+".o");publish);
+  m_delete(grouplist,name);
+
+  save_group_list();
+
+  return 1;
+}
+
+int SetGroup(string name,int dlevel,int        wlevel,int rlevel,int maxmessages,int expire)
+{
+  mixed *group;
+  
+  if (!member(grouplist,name)) return -2;
+  if (grouplist[name,G_OWNER]!=user_euid() &&
+      (!security(name) || process_call())) return -1;
+  
+  grouplist[name,G_DLEVEL]=dlevel;
+  grouplist[name,G_WLEVEL]=wlevel;
+  grouplist[name,G_RLEVEL]=rlevel;
+  grouplist[name,G_MAX_MSG]=maxmessages;
+  grouplist[name,G_EXPIRE]=expire;
+  
+  save_group_list();
+  return 1;
+}
+
+int AddAllowed(string name,mixed deleters,mixed writers,mixed readers)
+{
+  mixed *group;
+
+  if (!member(grouplist,name)) return -2;
+
+  if ( grouplist[name,G_OWNER]!=user_euid() &&
+       (!security(name) || process_call()) && user_euid() != ROOTID )
+      return -1;
+
+  if (stringp(deleters)) deleters=({deleters});
+  if (stringp(writers)) writers=({writers});
+  if (stringp(readers)) readers=({readers});
+
+  if (!deleters) deleters=({});
+  if (!writers) writers=({});
+  if (!readers) readers=({});
+
+  grouplist[name,G_DELETERS]+=deleters;
+  grouplist[name,G_WRITERS]+=writers;
+  grouplist[name,G_READERS]+=readers;
+        
+  save_group_list();
+  return 1;
+}
+
+int RemoveAllowed(string name,mixed deleters,mixed writers,mixed readers)
+{
+  mixed *group;
+
+  if (!member(grouplist,name)) return -2;
+
+  if (grouplist[name,G_OWNER]!=user_euid() &&
+      (!security(name) || process_call()) && user_euid() != ROOTID )
+      return -1;
+
+  if (stringp(deleters)) deleters=({deleters});
+  if (stringp(writers)) writers=({writers});
+  if (stringp(readers)) readers=({readers});
+
+  if (!deleters) deleters=({});
+  if (!writers) writers=({});
+  if (!readers) readers=({});
+
+  grouplist[name,G_DELETERS]-=deleters;
+  grouplist[name,G_WRITERS]-=writers;
+  grouplist[name,G_READERS]-=readers;
+
+  save_group_list();
+  return 1;
+}
+
+static string user_euid()
+{
+  if (previous_object()) {
+     if (geteuid(previous_object())==ROOTID)       return ROOTID;
+     if (geteuid(previous_object())=="p.daemon")   return "p.daemon";
+     if (load_name(previous_object())=="/obj/mpa") return geteuid(RPL);
+  }
+  return secure_euid();
+}
+
+#define F_DELETE    0
+#define F_READ      1
+#define F_WRITE     2
+#define F_ADMIN     3
+#define F_KEEPNAME  4
+
+private int security( string name )
+{
+    if ( grouplist[name,G_DLEVEL] >= ARCH_LVL
+         || grouplist[name,G_WLEVEL] >= ARCH_LVL
+         || grouplist[name,G_RLEVEL] >= ARCH_LVL )
+        return ARCH_SECURITY;
+    else
+        return ELDER_SECURITY;
+}
+
+static int allowed(string name, int mode)
+{
+  string euid;
+  mixed g_level, g_mode;
+  
+  if (process_call()) return 0;
+
+  euid=user_euid();
+
+  if (euid==ROOTID) return 1;
+
+  switch(mode) {
+    case F_KEEPNAME: return (euid=="p.daemon");
+    case F_WRITE:    if (euid=="p.daemon") return 1;
+                     g_level=G_WLEVEL; g_mode=G_WRITERS;  break;
+    case F_ADMIN:    if (!(security(name)||grouplist[name,G_OWNER]==euid)) 
+                             return 0;
+                     g_level=G_DLEVEL; g_mode=G_DELETERS; break;
+    case F_DELETE:   if (euid=="p.daemon") return 1;
+                     g_level=G_DLEVEL; g_mode=G_DELETERS; break;
+    case F_READ:     g_level=G_RLEVEL; g_mode=G_READERS;  break;
+    default:         return 0;
+  }
+
+  if (grouplist[name,G_OWNER] != euid && !ARCH_SECURITY &&
+      grouplist[name,g_level] > query_wiz_level(euid) &&
+      member(grouplist[name, g_mode], euid)==-1)
+    return 0; // No such group for the requestor :)
+  return 1;
+}
+
+int WriteNote(mixed message,mixed keepname)
+{
+  mixed *group;
+  int uidok,tmp;
+  string name;
+
+  if (!pointerp(message) || sizeof(message)!=6) return 0;
+
+  if (!pointerp(group=load_group(name=message[M_BOARD]))) return -2;
+
+  if (!allowed(name, F_WRITE)) return -1;
+
+  if (sizeof(group)>=grouplist[name,G_MAX_MSG]) return -3;
+
+  if (!keepname || !allowed(name, F_KEEPNAME))
+     message[M_WRITER]=capitalize(geteuid(this_interactive()||previous_object()));
+
+  if (lasttime>=time()) lasttime++;
+    else lasttime=time();
+  message[M_TIME]=lasttime;
+  message[M_ID]=MUDNAME+":"+lasttime;
+  group+=({message});
+  grouplist[name,WTIME]=lasttime;
+  save_group(name,group);
+  save_group_list();
+  return 1;
+}
+
+int RemoveNote(string name, int note)
+{
+  int num;
+  mixed *group;
+
+  if ((note<0) && (name=="dwnews"))
+  {
+    group=({});
+    grouplist[name,WTIME]=0;
+    save_group(name,group);
+    save_group_list();
+    return 1;
+  }
+
+  if (note<0) return 0;
+
+  if (!pointerp(group=load_group(name))) return -2;
+
+  int count=sizeof(group);
+  if (count<=note)
+    return -3;
+
+  if (!allowed(name, F_DELETE) &&
+      lower_case(group[note][M_WRITER])!=user_euid()) return -1;
+
+  if (count==1)
+    group=({});
+  else if (!note)
+    group = group[1..];
+  else if (note == count-1)
+    group = group[0..<2];
+  else
+    group=group[0..note-1]+group[note+1..];
+  
+  if (sizeof(group))
+    grouplist[name,WTIME]=group[<1][M_TIME];
+  else
+    grouplist[name,WTIME]=0;
+  save_group(name,group);
+  save_group_list();
+  return 1;
+}
+
+mixed GetNotes(string name)
+{
+  mixed *group;
+  
+  if (!pointerp(group=load_group(name))) return -2;
+  if (!allowed(name, F_READ)) return -2;
+  return(deep_copy(group)); // COPY it
+}
+
+static void dump_file(string filename,mixed news)
+{
+  int i;
+  
+  for (i=0;i<sizeof(news);i++)
+    write_file(filename,news[i][M_TITLE]+" ("+news[i][M_WRITER]+", "+
+               dtime(news[i][M_TIME])[5..26]+"):\n"+
+               news[i][M_MESSAGE]+"\n-----------------------------------------------------------------------------\n\n\n\n");
+}
+
+protected varargs void expire(string grp,int etime)
+// etime ist anfangs in Tagen und bezeichnet das max. Alter, was Artikel in
+// der Gruppe haben duerfen.
+{
+  mixed *group;
+
+  if (!pointerp(group=load_group(grp))) return;
+  if (etime)
+  {
+    if (etime>0)
+      etime=etime*60*60*24;
+  }
+  else
+    etime=grouplist[grp,G_EXPIRE]; 
+  if (etime<=0)
+    return;
+
+  int to_expire=time()-etime;
+  int size=sizeof(group);
+  if (!size) return;
+
+  int first_to_keep = size;  // ja, ist noch eins zu hoch
+  // solange runterlaufen bis man ein element findet, welches geloescht werden
+  // soll. first_to_keep bleibt dann eins hoeher als das.
+  while ( first_to_keep && group[first_to_keep-1][M_TIME]>to_expire)
+    --first_to_keep;
+  // first_to_keep kann jetzt auf eins hinter dem letzten Element zeigen (==
+  // size). Das wird unten beruecksichtigt.
+
+  if (!first_to_keep) // alle behalten?
+    return;
+  // Zu loeschende Artikel wegschreiben.
+  dump_file("news/OLD."+grp,group[0..first_to_keep-1]);
+  // dann loeschen
+  if (first_to_keep == size) // alle wegwerfen?
+    group=({});
+  else
+    group=group[first_to_keep..size-1];
+  
+  save_group(grp,group);
+}
+
+void dump_group(string grp)
+{
+  int to_expire,size,last;
+  mixed *group;
+
+  if (!ARCH_SECURITY || process_call()) return;
+  if (!pointerp(group=load_group(grp))) return;
+  size=sizeof(group);
+  last=size;
+  if (!last) return;
+  dump_file("news/DUMP."+grp,group[0..last-1]);
+}
+
+protected void expire_all(string *keys) {
+  // neuen call_out fuer den Rest setzen
+  if (sizeof(keys) > 1)
+    call_out(#'expire_all,15,keys[1..]);
+  // und erste Gruppe expiren
+  expire(keys[0]);
+}
+
+void reset() {
+  // naechstes Expire und damit Reset in einem tag
+  set_next_reset(86400);
+  // alte call_outs ggf. entfernen.
+  while(remove_call_out(#'expire_all)>=0);
+  // gruppenliste holen und callout auf expire_all starten
+  if (sizeof(grouplist)) {
+    call_out(#'expire_all,10,m_indices(grouplist));
+  }
+}
+
+static void save_group(string grp,mixed group)
+{
+  saveload=group; // Do NOT save the accessed-Info
+  cache[grp] = group;
+  save_object(NEWSPATH+grouplist[grp,G_SAVEFILE]);
+  saveload=0;
+}
+
+static void save_group_list()
+{
+  saveload=grouplist;
+  save_object(NEWSPATH+"GroupList");
+  saveload=0;
+}
+
+static mixed load_group(string name)
+{
+  int num;
+  mixed *ret;
+
+  if(!member(grouplist,name)) return -1;
+
+  if (member(cache, name)) {
+    ret = cache[name];
+  }
+  else {
+    restore_object(NEWSPATH+grouplist[name,G_SAVEFILE]);
+    if (!pointerp(saveload))
+      saveload=({});
+    ret=saveload;
+    cache[name] = saveload;
+    saveload=0;
+  }
+  return ret;
+}
+
+mixed GetGroups()
+{
+  mixed *returnlist;
+  int i,group,slevel;
+  string seuid;
+
+  returnlist=sort_array(m_indices(grouplist),#'>); //');
+  if (ARCH_SECURITY && !process_call())
+    return returnlist;
+
+  seuid = user_euid();
+  slevel = secure_level();
+
+  for (i=sizeof(returnlist)-1;i>=0;i--)
+    if (!(grouplist[returnlist[i],G_RLEVEL]<=slevel ||
+          grouplist[returnlist[i],G_OWNER]==seuid ||
+          member(grouplist[returnlist[i],G_READERS], seuid)!=-1))
+      returnlist=returnlist[0..i-1]+returnlist[i+1..];
+  return returnlist;
+}
+
+int AskAllowedWrite(string n)
+{
+  mixed *group;
+
+  if (!member(grouplist,n)) return -2;
+  if (!pointerp(group=load_group(n))) return -2;
+
+  if (grouplist[n,G_OWNER] != user_euid() &&
+      !ARCH_SECURITY &&
+      grouplist[n,G_WLEVEL]>secure_level() &&
+      member(grouplist[n,G_WRITERS],user_euid())==-1)
+    return -1;
+
+  if (sizeof(group)>=grouplist[n,G_MAX_MSG]) return -3;
+  return 1;
+}
+
+// Wichtig ...
+
+int query_prevent_shadow()
+{
+  return 1;
+}
+
+mixed GetNewsTime(string boardname)
+
+{
+  int i, ltime, j;
+  mixed *keys;
+
+  if (!boardname)
+  {
+    ltime=-1;
+    for (i=sizeof(keys=m_indices(grouplist))-1;i>=0;i--)
+      if (ltime<(j=grouplist[keys[i],WTIME])) ltime=j;
+    return ltime;
+  }
+  if (!member(grouplist,boardname)) return -1;
+  return grouplist[boardname,WTIME];
+}
+
+mixed* GetGroup(string name)
+{
+  if (process_call()) return 0;
+  if (extern_call() && !allowed(name, F_ADMIN)) return 0;
+#define gl(x) grouplist[name,x]
+  return ({name,gl(1),gl(2),gl(3),gl(4),gl(5),gl(6),gl(7),gl(8),gl(9),gl(10),load_group(name)});
+}
diff --git a/secure/npcmaster.c b/secure/npcmaster.c
new file mode 100644
index 0000000..33d8bdc
--- /dev/null
+++ b/secure/npcmaster.c
@@ -0,0 +1,124 @@
+// MorgenGrauen MUDlib
+//
+// npcmaster.c - Verwaltung der eindeutigen Nummernvergabe fuer NPCs und
+//               der Stufenpunkte, die sie geben
+//
+// $Id: npcmaster.c 9142 2015-02-04 22:17:29Z Zesstra $
+//
+/*
+ * $Log: npcmaster.c,v $
+ * Revision 1.6  1996/09/18 08:59:49  Wargon
+ * Globale Variablen private gemacht...
+ *
+ * Revision 1.5  1996/04/10 17:48:37  Jof
+ * Dump-Funktion eingebaut, damit man auch was zu lesen hat :)
+ * ,
+ *
+ * Revision 1.4  1996/04/10 14:32:24  Wargon
+ * QueryNPC(): int * -> mixed
+ *
+ * Revision 1.3  1996/04/04 12:33:06  Wargon
+ * QueryNPCbyNumber() gibt jetzt wirklich Nummer und Score zurueck...
+ *
+ * Revision 1.2  1996/03/06 14:34:51  Jof
+ * QueryNPCbyNumber eingebaut, Typos gefixed
+ *
+ * Revision 1.1  1996/03/06 13:45:36  Jof
+ * Initial revision
+ *
+ */
+
+#include "/secure/npcmaster.h"
+#include "/secure/wizlevels.h"
+
+private int lastNum;
+private mapping npcs;
+private nosave mapping by_num;
+
+void make_num(string key) {
+  by_num += ([ npcs[key,NPC_NUMBER] : key; npcs[key,NPC_SCORE] ]);
+}
+
+void create()
+{
+  seteuid(getuid());
+  if (!restore_object(NPCSAVEFILE))
+  {
+    lastNum=0;
+    npcs=m_allocate(0,2);
+  }
+  by_num = m_allocate(0,2);
+  filter_indices(npcs, #'make_num/*'*/);
+}
+
+static int DumpNPCs();
+
+varargs mixed QueryNPC(int score)
+{
+  string key, val;
+  
+  if (!previous_object())
+    return NPC_INVALID_ARG;
+  key=old_explode(object_name(previous_object()),"#")[0];
+  if (val=npcs[key,NPC_NUMBER])
+    return ({val,npcs[key,NPC_SCORE]});
+  if (score<=0 || member(inherit_list(previous_object()),"/std/living/life.c") < 0)
+    return NPC_INVALID_ARG;
+  npcs[key,NPC_SCORE]=score;
+  npcs[key,NPC_NUMBER]=++lastNum;
+  by_num += ([lastNum:key;score]);
+   save_object(NPCSAVEFILE);
+  call_out("DumpNPCs",0);
+  return ({lastNum,score});
+}
+
+int SetScore(string key,int score)
+{
+  if (!IS_ARCH(this_interactive())||!IS_ARCH(geteuid(previous_object())))
+    return NPC_NO_PERMISSION;
+  if (!npcs[key,NPC_NUMBER])
+    return NPC_INVALID_ARG;
+  npcs[key,NPC_SCORE]=score;
+  by_num += ([npcs[key,NPC_NUMBER]:key;score]);
+  save_object(NPCSAVEFILE);
+  DumpNPCs();
+  return 1;
+}
+
+static int DumpNPCs()
+{
+  mixed *keys;
+  int i, max;
+
+  keys=m_indices(npcs);
+  rm(NPCDUMPFILE);
+  for (i=sizeof(keys)-1, max=0;i>=0;i--) {
+    write_file(NPCDUMPFILE,sprintf("%5d %4d %s\n",npcs[keys[i],NPC_NUMBER],
+	   npcs[keys[i],NPC_SCORE],keys[i]));
+    max += npcs[keys[i],NPC_SCORE];
+  } 
+  write_file(NPCDUMPFILE,sprintf("====================\nGesamt: %d Punkte\n",max)); 
+  return 1;
+}
+
+mixed *QueryNPCbyNumber(int num)
+{
+  if (by_num[num])
+    return ({num,by_num[num,NPC_SCORE],by_num[num,NPC_NUMBER]});
+
+  return 0;
+}
+
+int Recalculate(string s)
+{
+  int i, j;
+
+  if (!s || !sizeof(s))
+    return 0;
+
+  for (j=0, i=6*sizeof(s)-1; i>0; i--) {
+    if (test_bit(s,i))
+      j+=by_num[i,NPC_SCORE];
+  }
+  return j;
+}
diff --git a/secure/npcmaster.h b/secure/npcmaster.h
new file mode 100644
index 0000000..8af7f41
--- /dev/null
+++ b/secure/npcmaster.h
@@ -0,0 +1,24 @@
+// MorgenGrauen MUDlib
+//
+// FileName.c -- Beschreibung
+// $Id: npcmaster.h 2634 2006-09-23 09:43:07Z root $
+//
+/*
+ * $Log: npcmaster.h,v $
+ * Revision 1.1  1996/03/06 13:45:11  Jof
+ * Initial revision
+ *
+ */
+
+#ifndef _NPCMASTER_H_
+#define _NPCMASTER_H_ 1
+
+#define NPCSAVEFILE "/etc/npcmaster"
+
+#define NPC_NUMBER	0
+#define NPC_SCORE	1
+
+#define NPC_INVALID_ARG -1
+#define NPC_NO_PERMISSION -2
+
+#endif
diff --git a/secure/potionmaster.c b/secure/potionmaster.c
new file mode 100644
index 0000000..78a0565
--- /dev/null
+++ b/secure/potionmaster.c
@@ -0,0 +1,515 @@
+#pragma strict_types
+#pragma no_clone
+#pragma no_shadow
+#pragma no_inherit
+#pragma verbose_errors
+#pragma combine_strings
+#pragma rtt_checks
+#pragma pedantic
+#pragma warn_deprecated
+
+#include "/secure/config.h"
+#include "/secure/wizlevels.h"
+
+#define MAX_ROOMS_PER_LIST 10
+
+// die div. Speicherorte und Pfade
+#define SAVEFILE    "/secure/ARCH/potions"
+#define TIPS(x)     "/secure/ARCH/ZT/"+x
+#define ORAKEL      "/room/orakel"
+#define POTIONTOOL  "/obj/tools/ptool"
+
+// Fuer die Dump-Funktion
+#define POTIONDUMP "/secure/ARCH/POTIONS.dump"
+#define DUMP(str)   write_file(POTIONDUMP, str)
+
+// Modifikationen loggen. "event" ist eins der Events aus der Liste:
+// ADD_POTION, ACTIVATE, DEACTIVATE, MODIFY_PATH, MODIFY_LISTNO
+#define LOGFILE_MOD     "/log/ARCH/POTIONS_MOD.log"
+#define MODLOG(event,num,data) write_file(LOGFILE_MOD, \
+  sprintf("%17s %-14s %-3d sEUID %s %s\n", \
+    strftime("%Y-%b-%d %R"), event, num, secure_euid(), data) )
+
+// Indizierungs-Konstanten fuer das potions-Mapping
+#define POT_ROOMNAME 0
+#define POT_LISTNO   1
+
+// Konstanten fuer div. Rueckgabewerte. Keine 0, da das eine gueltige ZT-ID
+// sein kann und abfragende Objekte verwirren koennte.
+#define POT_IS_ACTIVE            1
+#define POT_ACCESS_DENIED       -1
+#define POT_WRONG_DATATYPE      -2
+#define POT_NO_SUCH_ROOM        -3
+#define POT_ALREADY_REGISTERED  -4
+#define POT_INVALID_POTION      -5
+#define POT_NO_SUCH_FILE        -6
+#define POT_INVALID_LIST_NUMBER -7
+#define POT_ALREADY_ACTIVE      -8
+#define POT_ALREADY_INACTIVE    -9
+#define POT_NOT_INACTIVE       -10
+#define POT_NOT_ACTIVE         -11
+
+// Zaehler fuer den als naechsten anzulegenden ZT
+private int nextroom;
+
+// Liste aller ZTs einschl. inaktive, ([ int num : string room; int list ])
+private mapping potions = ([]);
+
+// Liste der inaktiven ZTs, ({ int num })
+private int *inactive = ({});
+
+// Cache mit den einzelnen Listen, ([ int list : int *potionlist ])
+private nosave mapping lists;
+
+// reverse_table Lookup Cache, ([string room: int number])
+private nosave mapping reverse_table = ([]);
+
+// Cache fuer die bereits von der Platte eingelesenen ZTs, um Plattenzugriffe
+// insbesondere beim Erzeugen der Tipliste durch das Orakel zu reduzieren.
+private nosave mapping tipmap = ([]);
+
+int ActivateRoom(string room);
+
+private int secure() {
+  return (!process_call() && ARCH_SECURITY);
+}
+
+mixed QueryPotionData(int num) {
+  if ( !secure() )
+    return POT_ACCESS_DENIED;
+  return ([num : potions[num,0]; potions[num,1] ]);
+}
+
+int *QueryInactivePotions() {
+  return copy(inactive);
+}
+
+private void RebuildCache() {
+  // Cache invalidieren; vor-initialisiert zur Beschleunigung des Rebuilds.
+  lists = ([0:({}),1:({}),2:({}),3:({}),4:({}),5:({}),6:({}),7:({})]);
+  foreach (int num, string room, int list : potions) {
+    reverse_table += ([room:num]);
+    lists[list] += ({num});
+  }
+  return;
+}
+
+int QueryActive(mixed potion) {
+  if ( extern_call() && !secure() )
+    return POT_ACCESS_DENIED;
+  int ret;
+  // Wenn nach dem Pfad des ZTs gefragt wird, diesen zuerst in die Nummer
+  // umwandeln durch Lookup im reverse_table Cache
+  if ( stringp(potion) ) {
+    potion = reverse_table[potion];
+  }
+  // Ein ZT ist aktiv, wenn er in der Liste potions steht und nicht in der
+  // Liste der inaktiven ZTs (inactive) steht. Dann dessen Nummer
+  // zurueckgeben; inaktive zuerst pruefen, weil die inaktiven auch in 
+  // "potions" enthalten sind und somit alle ZTs ausser den ungueltigen
+  // als aktiv gemeldet wuerden.
+  if ( member(inactive,potion)>-1 )
+    ret = POT_NOT_ACTIVE;
+  else if ( member(potions,potion) && potions[potion,POT_LISTNO]!=-1 )
+    ret = potion;
+  // ansonsten ist das kein gueltiger ZT
+  else
+    ret = POT_INVALID_POTION;
+  return ret;
+}
+
+private void save_info() {
+  save_object(SAVEFILE);
+}
+
+protected void create() {
+  seteuid(getuid(this_object()));
+  if ( !restore_object(SAVEFILE) ) {
+    // Fehler ausgeben, damit es jemand merkt. Dennoch wird jetzt im Anschluss
+    // der Cache neu aufgebaut (damit die Datenstrukturen gueltig sind).
+    catch(raise_error("Potionmaster: no/corrupt savefile! Reinitializing.\n"); publish);
+  }
+  RebuildCache();
+}
+
+int AddPotionRoom(string room, int list) {
+  if ( !secure() )
+    return POT_ACCESS_DENIED;
+  // Neuer Raum muss ein gueltiger String sein.
+  if ( !stringp(room) )
+    return POT_WRONG_DATATYPE;
+  if ( !intp(list) || list<0 || list>7 )
+    return POT_WRONG_DATATYPE;
+
+  // Pfad mit Hilfe des Masters vervollstaendigen (+, ~ etc. ersetzen)
+  room=(string)master()->_get_path(room,0);
+
+  // Datei mit dem ZT-Spruch muss existieren.
+  if ( file_size( TIPS(to_string(nextroom)+".zt") ) < 0 ) {
+    raise_error("Potionmaster: Tipfile missing, please create "+
+      to_string(nextroom)+".zt");
+    return POT_NO_SUCH_FILE;
+  }
+  // Neuer Raum darf noch nicht in der Liste enthalten sein.
+  if ( member(m_values(potions,POT_ROOMNAME), room)!=-1)
+    return POT_ALREADY_REGISTERED;
+  // Neuer Raum muss ladbar sein.
+  if ( catch(load_object(room); publish) )
+    return POT_NO_SUCH_ROOM;
+
+  // Jetzt kann's endlich losgehen, Raum eintragen, nextroom hochzaehlen
+  potions += ([nextroom : room; list]);
+  MODLOG("ADD_POTION", nextroom, room);
+  // Neu eingetragene ZTs werden auch gleich aktiviert; ActivateRoom()
+  // baut den Cache selbst neu auf, daher kann das hier entfallen.
+  ActivateRoom(room);
+  nextroom++;
+  save_info();
+  return nextroom;
+}
+
+int ChangeRoomPath(string old, string new) {
+  if ( !secure() )
+    return POT_ACCESS_DENIED;
+
+  // beide Pfade muessen gueltige Strings sein
+  if ( !stringp(old) || !stringp(new) )
+    return POT_WRONG_DATATYPE;
+
+  // Pfad mit Hilfe des Masters vervollstaendigen (+, ~ etc. ersetzen)
+  old=(string)master()->_get_path(old,0);
+  new=(string)master()->_get_path(new,0);
+
+  // Der neue Raum darf nicht bereits eingetragen sein, ...
+  if ( member(reverse_table,new) )
+    return POT_ALREADY_REGISTERED;
+  // ... und der alte Raum muss noch eingetragen sein.
+  if ( !member(reverse_table,old) )
+    return POT_NO_SUCH_ROOM;
+  // Neuer Raum muss ladbar sein.
+  if (catch(load_object(new);publish))
+    return POT_NO_SUCH_ROOM;
+
+  // Aktuelle ZT-Nummer des alten Pfades ermitteln
+  int num = reverse_table[old];
+  // Pfad aendern, Cache neubauen und Savefile speichern
+  potions[num,POT_ROOMNAME] = new;
+  RebuildCache();
+  save_info();
+  MODLOG("MODIFY_PATH", num, old+" => "+new);
+  return num;
+}
+
+int ActivateRoom(string room) {
+  if ( !secure() )
+    return POT_ACCESS_DENIED;
+  // Aktuelle ZT-Nummer ermitteln. Etwas umstaendlich, da im Fehlerfall -1
+  // benoetigt wird.
+  int num = member(reverse_table,room) ? reverse_table[room] : -1;
+  // Nummer muss existieren
+  if ( num == -1 )
+    return POT_INVALID_POTION;
+  // ZT ist nicht inaktiv, dann kann man ihn auch nicht aktivieren.
+  if ( member(inactive, num)==-1 )
+    return POT_ALREADY_ACTIVE;
+  inactive -= ({num});
+  RebuildCache();
+  save_info();
+  MODLOG("ACTIVATE", num, room);
+  return num;
+}
+
+int SetListNr(string room, int list) {
+  if ( !secure() )
+    return POT_ACCESS_DENIED;
+  // Aktuelle ZT-Nummer ermitteln. Etwa umstaendlich, da im Fehlerfall -1
+  // benoetigt wird.
+  int num = member(reverse_table,room) ? reverse_table[room] : -1;
+  // Nummer muss existieren
+  if ( num == -1 )
+    return POT_INVALID_POTION;
+  // Listennummer muss zwischen 0 und 7 liegen.
+  if ( list < 0 || list > 7 )
+    return POT_INVALID_LIST_NUMBER;
+
+  // alte Nummer aufschreiben zum Loggen.
+  int oldlist = potions[num,POT_LISTNO];
+  // Listennummer in der ZT-Liste aktualisieren.
+  potions[num,POT_LISTNO] = list;
+  RebuildCache();
+  save_info();
+  MODLOG("MODIFY_LISTNO", num, oldlist+" -> "+list);
+  return num;
+}
+
+int DeactivateRoom(string room) {
+  if ( !secure() )
+    return POT_ACCESS_DENIED;
+  // Aktuelle ZT-Nummer ermitteln. Etwa umstaendlich, da im Fehlerfall -1
+  // benoetigt wird.
+  int num = member(reverse_table,room) ? reverse_table[room] : -1;
+  // Nummer muss existieren
+  if ( num == -1 )
+    return POT_INVALID_POTION;
+  // ZT darf nicht bereits inaktiv sein
+  if ( member(inactive,num)>-1 )
+    return POT_ALREADY_INACTIVE;
+  inactive += ({num});
+  RebuildCache();
+  save_info();
+  MODLOG("DEACTIVATE", num, room);
+  return num;
+}
+
+private int *_create_list(int listno, int anz) {
+  int *list = ({});
+  // Listenerzeugung lohnt nur dann, wenn mind. 1 Eintrag gefordert wird und
+  // die Listennummer im gueltigen Bereich zwischen 0 und 7 ist.
+  if ( anz>0 && listno>=0 && listno<=7 ) {
+    int *tmp = lists[listno] - inactive;
+    // Wenn die Listengroesse maximal genauso gross ist wie die angeforderte
+    // Anzahl, kann diese vollstaendig uebernommen werden.
+    if ( sizeof(tmp) <= anz ) {
+      list = tmp;
+    } else { // ansonsten soviele Eintraege erzeugen wie angefordert
+      foreach(int i: anz) {
+        int j=random(sizeof(tmp));
+        list += ({tmp[j]});
+        tmp -= ({tmp[j]});
+      }
+    }
+  }
+  return list;
+}
+
+mixed *InitialList() {
+  mixed *list=({});
+  foreach(int i : 8)
+    list+=_create_list(i,MAX_ROOMS_PER_LIST);
+  return list;
+}
+
+// Aufrufe aus den Spielershells und dem Potiontool sind erlaubt
+int HasPotion(object room) {
+  if ( !query_once_interactive(previous_object()) && 
+       load_name(previous_object()) != POTIONTOOL )
+    return POT_ACCESS_DENIED;
+  return objectp(room) ? reverse_table[object_name(room)] : POT_NO_SUCH_ROOM;
+}
+
+// Listennummer ermitteln, in der der ZT num enthalten ist.
+// Auch inaktive ZTs werden als zu einer Liste gehoerig gemeldet.
+int GetListByNumber(int num) {
+  return member(potions,num) ? potions[num,POT_LISTNO] : POT_INVALID_POTION;
+}
+
+// Listennummer ermitteln, in der der inaktive ZT num enthalten ist.
+// Da alle Zaubertraenke in einer Gesamtliste enthalten sind, kann direkt das
+// Resultat von GetListByNumber() zurueckgegeben werden.
+int GetInactListByNumber(int num) {
+  if ( member(inactive,num) > -1 )
+    return GetListByNumber(num);
+  else
+    return POT_NOT_INACTIVE;
+}
+
+// Wird nur von /obj/tools/ptool aufgerufen. Wenn der Aufruf zugelassen wird,
+// erfolgt von dort aus ein ChangeRoomPath(), und dort wird bereits geloggt.
+// Erzmagier duerfen auch aufrufen.
+mixed GetFilenameByNumber(int num) {
+  if ( extern_call() && 
+       object_name(previous_object()) != POTIONTOOL &&
+       !ARCH_SECURITY )
+    return POT_ACCESS_DENIED;
+  if ( !member(potions, num) )
+    return POT_INVALID_POTION;
+  return potions[num,POT_ROOMNAME];
+}
+
+// Wird vom Spielerobjekt gerufen; uebergeben werden der Raum, der initial
+// FindPotion() aufrief, die ZT-Liste des Spielers sowie die Liste bereits
+// gefundener ZTs.
+//
+// In std/player/potion.c ist sichergestellt, dass room ein Objekt ist und
+// dass die Listen als Int-Arrays uebergeben werden.
+//
+// Es werden aus historischen Gruenden (noch) beide ZT-Listen uebergeben,
+// aber es muss nur knownlist ueberprueft werden, da diese eine Teilmenge
+// von potionlist ist und somit jeder ZT in ersterer auf jeden Fall auch
+// in letzterer enthalten sein _muss_.
+int InList(object room, int *potionlist, int *knownlist) {
+  if ( !query_once_interactive(previous_object()) && !secure() )
+    return 0; //POT_ACCESS_DENIED;
+  int num = QueryActive(object_name(room));
+  
+  // Zunaechst keinen Fehlercode zurueckgeben, weil das Spielerobjekt
+  // damit im Moment noch nicht umgehen kann.
+  if ( num < 0 )
+    return 0; //POT_INVALID_POTION;
+
+  return (member(knownlist,num)>-1);
+}
+
+// Wird vom Spielerobjekt gerufen, um einen gefundenen ZT aus dessen ZT-
+// Listen austragen zu lassen. Das Spielerobjekt stellt sicher, dass room
+// ein Objekt ist, und dass [known_]potionrooms Arrays sind.
+//
+// ACHTUNG! Sowohl potionrooms, als auch known_potionrooms sind die
+//          Originaldaten aus dem Spielerobjekt, die hier ALS REFERENZ
+//          uebergeben werden! Vorsicht bei Aenderungen an der Funktion!
+//
+// Wenn std/player/potions.c geaendert wird, ist diese Funktion obsolet
+// => ggf. komplett rauswerfen. Dann kann auch der Fehlercode in InList()
+// reaktiviert und deren Parameter korrigiert werden
+void RemoveList(object room, int* potionrooms, int* known_potionrooms) {
+  // ZT ist aktiv, das wurde bereits in InList() geprueft, das vor dem
+  // Aufruf von RemoveList() aus dem Spielerobjekt gerufen wird. Daher reicht
+  // es aus, die ZT-Nummer aus dem reverse_table Lookup zu holen.
+  int num = reverse_table[object_name(room)];
+  int tmp = member(potionrooms, num);
+  potionrooms[tmp] = -1;
+  tmp = member(known_potionrooms, num);
+  known_potionrooms[tmp] = -1;
+  return;
+}
+
+#define LISTHEADER "################## Liste %d ################## (%d)\n\n"
+#define INACT_HEADER "################## Inaktiv ################## (%d)\n\n"
+
+int DumpList() {
+  if ( !secure() )
+    return POT_ACCESS_DENIED;
+  // Zuerst die Caches neu aufbauen, um sicherzustellen, dass die Listen
+  // aktuell sind.
+  RebuildCache();
+  // Dumpfile loeschen
+  rm(POTIONDUMP);
+  // Alle Listen der Reihe nach ablaufen. Es wird in jedem Schleifendurchlauf
+  // einmal auf die Platte geschrieben. Das ist Absicht, weil es speicher-
+  // technisch sehr aufwendig waere, die Ausgabedaten stattdessen erst in
+  // einer Variablen zu sammeln und dann wegzuschreiben.
+  foreach(int *listno : sort_array(m_indices(lists),#'>)) {
+    // Zuerst den Header der ensprechenden Liste schreiben.
+    DUMP(sprintf(LISTHEADER, listno, sizeof(lists[listno])));
+    // Dann alle Potions der Liste durchgehen
+    foreach(int potion : lists[listno]) {
+      // Wenn der ZT inaktiv ist, ueberspringen wir den.
+      if ( member(inactive,potion)>-1 )
+        continue;
+      // Raumpfad aus der Gesamtliste holen.
+      string str = potions[potion,POT_ROOMNAME];
+      // Wenn der nicht existiert, entsprechenden Hinweis dumpen, ansonsten
+      // den Raumnamen.
+      //ZZ: ich finde ja, wir sollten sicherstellen, dass das nicht mehr
+      //passiert. Ist das mit dem AddPotionRoom oben nicht schon so?
+      if ( !stringp(str) || !sizeof(str) )
+        str="KEIN RAUM ZUGEORDNET!!!";
+      DUMP(sprintf("%3d. %s\n", potion, str));
+    }
+    // 2 Leerzeilen zwischen jeder Gruppe einfuegen
+    DUMP("\n\n");
+  }
+  // Zum Schluss den Header der Inaktiv-Liste schreiben.
+  DUMP(sprintf(INACT_HEADER, sizeof(inactive)));
+  // Dann alle inaktiven Potions ablaufen
+  foreach(int i : inactive) {
+    //ZZ: sonst muesste man hier wohl auch auf den fehlenden Raum pruefen.
+    DUMP(sprintf("%3d. (Liste %d) %s\n", i, GetListByNumber(i),
+      potions[i,POT_ROOMNAME]));
+  }
+  return 1;
+}
+
+//ORAKEL-Routinen
+
+/* Aufbau eines Tips:
+
+   Tralala, lalala
+   Dideldadeldum
+   Huppsdiwupps
+   XXXXX
+   Noch ein zweiter Tip
+   Dingeldong
+   %%%%%
+
+   Die Prozentzeichen sind das Endezeichen, ab hier koennen eventuelle
+   Kommentare stehen. Die 5 X sind das Trennzeichen zwischen zwei Tips
+   zum selben ZT.
+*/
+
+// TIPLOG() derzeit unbenutzt
+//#define LOGFILE_READ    "ARCH/POTIONS_TIP_READ.log"
+//#define TIPLOG(x)      log_file(LOGFILE_READ, x)
+
+mixed TipLesen(int num) {
+  string *ret = tipmap[num];
+  //Funktion darf nur vom Orakel und EM+ gerufen werden.
+  if ( previous_object() != find_object(ORAKEL) && !secure() )
+    return POT_ACCESS_DENIED;
+  // Derzeit kein Log, da diese Informationen tendentiell eher 
+  // uninteressant sind.
+  // Timestamp ist vom Format "2012-Okt-03 14:18"
+  /*TIPLOG(sprintf("%s ZT-Tip gelesen: %d von TP: %O, PO: %O, TI: %O\n",
+    strftime("%Y-%b-%d %R"), num, this_player(), previous_object(),
+    this_interactive()));*/
+
+  // ZT-Spruch ist bereits im Tip-Cache enthalten, dann direkt ausgeben.
+  if ( pointerp(ret) )
+    return ret;
+  
+  // ZT-Spruch auf grundlegende syntaktische Korrektheit pruefen:
+  // explode() liefert ein Array mit 2 Elementen, wenn die Ende-Markierung
+  // vorhanden und somit das File diesbezueglich korrekt aufgebaut ist.
+  string *tip=explode( read_file(TIPS(num+".zt"))||"", "%%%%%" );
+  if (sizeof(tip) >= 2) {
+    // Der erste Eintrag in dem Array ist dann der Spruch. Wenn dieser
+    // die Trenn-Markierung enthaelt, entstehen hier 2 Hinweise, ansonsten
+    // bleibt es bei einem.
+    tipmap[num] = explode(tip[0], "XXXXX\n");
+    return tipmap[num];
+  }
+  return POT_NO_SUCH_FILE;
+}
+
+// Datenkonvertierung alte Alists => Mappings
+/*mapping ConvertData() {
+  //if ( !secure()) ) return;
+  // all_rooms ist eine Alist der Form ({ string *room, int *number })
+  // als 3. Eintrag schreiben wir ueberall -1 rein, das wird spaeter durch die
+  // Listennummer des betreffenden ZTs ersetzt und erlaubt einen einfachen
+  // Check auf Datenkonsistenz.
+  potions = mkmapping( all_rooms[1],
+                       all_rooms[0],
+                       allocate(sizeof(all_rooms[0]), -1) );
+  // Alle Listen ablaufen
+  foreach(int *list: active_rooms ) {
+    // Alle ZTs dieser Liste ablaufen und die entsprechende Listennummer in
+    // der neuen Datenstruktur setzen.
+    foreach(int num: list) {
+      potions[num,POT_LISTNO] = member(active_rooms,list);
+    }
+  }
+  // inactive_rooms ist eine Alist der gleichen Form. Die Schleife addiert
+  // einfach alle Eintraege darin auf, so dass ein Int-Array entsteht, das
+  // alle inaktiven ZT-Nummern enthaelt; das allerdings nur fuer Listen mit
+  // mindestens einem Element.
+  foreach(int *list : inactive_rooms ) {
+    if ( sizeof(list) ) {
+      inactive += list;
+      // Alle ZTs dieser Liste ablaufen und die entsprechende Listennummer in
+      // der neuen Datenstruktur setzen.
+      foreach(int num : list) {
+        potions[num,POT_LISTNO] = member(inactive_rooms,list);
+      }
+    }
+    // unify array
+    inactive = m_indices(mkmapping(inactive));
+  }
+  mapping invalid = ([ POT_INVALID_LIST_NUMBER : ({}) ]);
+  walk_mapping(potions, function void (int pnum, string path, int listno) {
+    if ( listno == -1 )
+      invalid[POT_INVALID_LIST_NUMBER] += ({pnum});
+  });
+  return sizeof(invalid[POT_INVALID_LIST_NUMBER]) ? invalid : potions;
+}*/
+
diff --git a/secure/pubmaster.c b/secure/pubmaster.c
new file mode 100644
index 0000000..a124889
--- /dev/null
+++ b/secure/pubmaster.c
@@ -0,0 +1,307 @@
+// MorgenGrauen MUDlib
+//
+// Pubmaster.c -- Registrating the heal-values of pubs
+//
+// $Id: pubmaster.c 9373 2015-10-22 19:02:42Z Zesstra $
+
+#include <pub.h>
+
+#define SAVEFILE "/secure/ARCH/pub"
+#define CLOGFILE "/log/ARCH/pubchange"
+#define DUMPFILE "/log/ARCH/PUBS"
+#define DUMPLIST "/log/ARCH/PUBLIST"
+
+#define DUMPF(str) write_file(DUMPFILE,str)
+#define DUMPL(str) write_file(DUMPLIST,str)
+
+#pragma strong_types
+
+#include <properties.h>
+#include <wizlevels.h>
+#include <health.h>
+
+mapping pubs;
+nosave string  *watchprops;
+nosave int     watchsize;
+
+void create()
+{
+    seteuid(getuid(this_object()));
+    if (clonep(this_object()))
+    {
+        destruct(this_object());
+        return;
+    }
+    if (!restore_object(SAVEFILE))
+        pubs = ([]);
+
+    watchprops = ({ P_HP, P_SP, P_ALCOHOL, P_DRINK, P_FOOD, P_VALUE,
+                    "rate", "delay" });
+    watchsize  = sizeof(watchprops);
+}
+
+void save_data()
+{
+    save_object(SAVEFILE);
+}
+
+int CalcMax(mapping info, string fn)
+{   float heal,delay,factor;
+
+    if (!info || !mappingp(info))
+        return 0;
+    if ( (info[P_ALCOHOL]+info[P_DRINK]+info[P_FOOD])<=0     ||
+         (info[P_ALCOHOL] && !(info[P_DRINK]||info[P_FOOD])) )
+        return 0;
+
+    delay = to_float(info["delay"]);
+    if (delay>PUB_MAXDELAY)
+        delay=PUB_MAXDELAY;
+
+    if (!stringp(fn) || !member(pubs,fn)) // External query?
+        factor=1.0;
+    else
+        factor=pubs[fn,1];
+
+    heal = to_float( info[P_ALCOHOL]*ALCOHOL_DELAY +
+                     info[P_DRINK]  *DRINK_DELAY   +
+                     info[P_FOOD]   *FOOD_DELAY    ) 
+           * PUB_SOAKMULT
+           / to_float( ALCOHOL_DELAY + DRINK_DELAY + FOOD_DELAY );
+    heal = heal + 
+           to_float(info[P_VALUE])/(PUB_VALUEDIV+to_float(info["rate"])) -
+           exp(to_float(info["rate"])/PUB_RATEDIV1)/PUB_RATEDIV2;
+    heal = heal * factor *
+           (PUB_WAITOFFS + (exp(delay/PUB_WAITDIV1)/PUB_WAITDIV2));
+
+    return to_int(heal);
+}
+
+int RegisterItem(string item, mapping info)
+{   object pub;
+    string fn;
+    int    i,c,cmax,max,heal;
+
+    if (!objectp(pub=previous_object()))
+        return -1;
+    if (member(inherit_list(pub), "/std/room/pub.c") == -1)
+        return -1;
+    if (!item || !stringp(item) || !info || !mappingp(info))
+        return -2;
+
+    if (info["rate"]<1)
+        info["rate"]=1;
+    if (info["delay"]<0)
+        info["delay"]=0;
+
+    // Loadname, weil VCs als ein Pub zaehlen sollen (das Tutorial hat ein
+    // VC-Pub fuer jeden Spieler. Die sollen nicht alle einzeln hier erfasst
+    // werden.
+    fn = load_name(pub);
+    c = 0;
+
+    heal=info[P_HP]+info[P_SP];
+
+    if (!member(pubs,fn))
+        pubs += ([ fn : ([]); 1.0 ]);
+
+    if (!member(pubs[fn],item))
+    {
+        max=CalcMax(info,fn);
+        pubs[fn] += ([ 
+            item : ([
+                P_HP            : info[P_HP],
+                P_SP            : info[P_SP],
+                P_VALUE         : info[P_VALUE],
+                P_DRINK         : info[P_DRINK],
+                P_ALCOHOL       : info[P_ALCOHOL],
+                P_FOOD          : info[P_FOOD],
+                "rate"          : info["rate"],
+                "delay"         : info["delay"],
+                "maxheal"       : heal,
+                "maxrate"       : 10
+                ])
+            ]);
+        c=1;
+    }
+    else
+    {
+        for (i=watchsize-1;i>=0;i--)
+            if (info[watchprops[i]]!=pubs[fn][item][watchprops[i]])
+            {
+                pubs[fn][item][watchprops[i]]=info[watchprops[i]];
+                c=1;
+            }
+        if (heal>pubs[fn][item]["maxheal"])
+        {
+            pubs[fn][item]["maxheal"]=heal;
+            c=1;
+        }
+        max=pubs[fn][item]["maxset"];
+        cmax=CalcMax(info,fn);
+        if (cmax>max)
+            max=cmax;
+    }
+
+    if (c)
+        save_data();
+
+    if ( heal>max || info["rate"]>pubs[fn][item]["maxrate"] )
+        return 0;
+
+    return 1;
+}
+
+private int allowed()
+{
+  if (previous_object() && geteuid(previous_object())==ROOTID)
+    return 1;
+  if (!process_call() && previous_object() && ARCH_SECURITY)
+    return 1;
+  return 0;
+}
+
+
+
+int SetMaxHeal(string pub, string item, int new)
+{   int old;
+
+    if (!allowed())
+        return -2;
+    if (!pub || !stringp(pub) || !item || !stringp(item) || !new)
+        return -1;
+    if (!member(pubs,pub) || !member(pubs[pub],item))
+        return 0;
+    old=pubs[pub][item]["maxset"];
+    pubs[pub][item]["maxset"]=new;
+    write_file(CLOGFILE,
+        sprintf("%s - %s\n%s:%s HEAL %d > %d\n",
+            dtime(time()),
+            (this_interactive() ? getuid(this_interactive()) : "???"),
+            pub,item,old,new));
+    save_data();
+    return 1;
+}
+
+int SetMaxRate(string pub, string item, int new)
+{   int old;
+
+    if (!allowed())
+        return -2;
+    if (!pub || !stringp(pub) || !item || !stringp(item) || !new)
+        return -1;
+    if (!member(pubs,pub) || !member(pubs[pub],item))
+        return 0;
+    old=pubs[pub][item]["maxrate"];
+    pubs[pub][item]["maxrate"]=new;
+    write_file(CLOGFILE,
+        sprintf("%s - %s\n%s:%s RATE %d > %d\n",
+            dtime(time()),
+            (this_interactive() ? getuid(this_interactive()) : "???"),
+            pub,item,old,new));
+    save_data();
+    return 1;
+}
+
+int SetPubFactor(string pub, float new)
+{   float old;
+
+    if (!allowed())
+        return -2;
+    if (!pub || !stringp(pub) || !new || !floatp(new) || new<1.0 || new>1.5)
+        return -1;
+    if (!member(pubs,pub))
+        return 0;
+    old=pubs[pub,1];
+    pubs[pub,1]=new;
+    write_file(CLOGFILE,
+        sprintf("%s - %s\n%s FACT %6.4f > %6.4f\n",
+            dtime(time()),
+            (this_interactive() ? getuid(this_interactive()) : "???"),
+            pub,old,new));
+    save_data();
+    return 1;
+}
+
+varargs int ClearPub(string pub, string item)
+{
+    if (!allowed())
+        return -2;
+    if (!pub || !stringp(pub) || (item && !stringp(item)))
+        return -1;
+    if (!member(pubs,pub))
+        return 0;
+    if (!item)
+        m_delete(pubs,pub);
+    else if (!member(pubs[pub],item))
+        return 0;
+    else
+      m_delete(pubs[pub],item);
+    save_data();
+    return 1;
+}
+
+int DumpPubs()
+{   string *publist,*itemlist;
+    int    pubi,itemi;
+
+    if (!allowed())
+        return -2;
+    if (file_size(DUMPFILE)>1)
+        rm(DUMPFILE);
+    if (file_size(DUMPLIST)>1)
+        rm(DUMPLIST);
+    DUMPF(sprintf("Kneipen-Menu-Liste vom %s:\n\n",dtime(time())));
+    DUMPL(sprintf("Kneipenliste vom %s:\n\n",dtime(time())));
+    publist=sort_array(m_indices(pubs),#'</*'*/);
+    if ((pubi=sizeof(publist))<1)
+    {
+        DUMPF("No pubs in List\n");
+        DUMPL("No pubs in List\n");
+        return -1;
+    }
+    for (--pubi;pubi>=0;pubi--)
+    {
+        DUMPL(sprintf("%-'.'70s %6.4f\n",
+            publist[pubi],pubs[publist[pubi],1]));
+        DUMPF(sprintf("PUB: %s\nFAC: %6.4f\n\n",
+            publist[pubi],pubs[publist[pubi],1]));
+        itemlist=sort_array(m_indices(pubs[publist[pubi]]),#'</*'*/);
+        if ((itemi=sizeof(itemlist))<1)
+        {
+            DUMPF("  No items in list\n\n");
+            continue;
+        }
+        DUMPF(sprintf(
+            "  %|'_'30.30s %5s %3s %3s %3s %3s %3s %2s %2s %3s %3s\n",
+            "ITEM","VALUE","HPH","SPH","DRI","ALC","FOO","RA","MR","MAX",
+            "FND"));
+        for (--itemi;itemi>=0;itemi--)
+            DUMPF(sprintf(
+                "  %-'.'30.30s %5d %3d %3d %3d %3d %3d %2d %2d %3d %3d\n",
+                itemlist[itemi],
+                (int)pubs[publist[pubi]][itemlist[itemi]][P_VALUE],
+                (int)pubs[publist[pubi]][itemlist[itemi]][P_HP],
+                (int)pubs[publist[pubi]][itemlist[itemi]][P_SP],
+                (int)pubs[publist[pubi]][itemlist[itemi]][P_DRINK],
+                (int)pubs[publist[pubi]][itemlist[itemi]][P_ALCOHOL],
+                (int)pubs[publist[pubi]][itemlist[itemi]][P_FOOD],
+                (int)pubs[publist[pubi]][itemlist[itemi]]["rate"],
+                (int)pubs[publist[pubi]][itemlist[itemi]]["maxrate"],
+                (int)pubs[publist[pubi]][itemlist[itemi]]["maxset"],
+                (int)pubs[publist[pubi]][itemlist[itemi]]["maxheal"] ));
+        DUMPF("\n");
+    }
+    return sizeof(publist);
+}
+
+// bereinigt die Kneipenliste von allen Kneipen, die auf der Platte nicht mehr
+// existieren.
+public void CleanPublist() {
+  foreach(string pub: pubs) {
+    if (file_size(pub+".c") <= 0)
+      m_delete(pubs,pub);
+  }
+  save_data();
+}
+
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;
+  }
+}
+*/
diff --git a/secure/questmaster.h b/secure/questmaster.h
new file mode 100644
index 0000000..1cf61aa
--- /dev/null
+++ b/secure/questmaster.h
@@ -0,0 +1,132 @@
+// MorgenGrauen MUDlib
+//
+// questmaster.h -- header file for questmaster object
+//
+// $Id: questmaster.h 8261 2012-12-04 22:56:29Z Zesstra $
+
+#ifndef __QUESTMASTER_H__
+#define __QUESTMASTER_H__
+
+//XP threshold  
+
+#define XP_NEEDED_FOR_WIZ 1000000
+
+// the questmaster
+#define QM         "/secure/questmaster"
+
+// the quests file
+#define QUESTS        "/secure/ARCH/questmaster"
+
+// Dumpfile for MiniQuests
+#define MQ_DUMP_FILE  "/secure/ARCH/MINIQUESTS.dump"
+
+// percentage of max_QP which is needed to become wizard
+#define QP_PERCENT 80
+
+
+// minimum of QP to become Seer or Wizzard
+#define QP_MIN 1196
+
+
+// log to file
+
+#define QMLOG(x) write_file("/log/QUESTMASTER",dtime(time())+": "+\
+        geteuid(this_interactive())+" -> "+ x +"\n")
+
+#define MQMLOG(x) write_file("/log/ARCH/MQUESTMASTER", \
+        dtime(time())+": "+(this_interactive()?geteuid(this_interactive()):"UNKNOWN")+\
+	" -> "+ x +"\n")
+
+#define MQSOLVEDLOG(x) write_file("/log/ARCH/MQ_SOLVED", \
+        dtime(time())+" "+x+"\n")
+
+// quest groups
+
+#define QGROUP_1 8
+#define QGROUP_2 18
+#define QGROUP_3 29
+#define QGROUP_S -1
+
+#define QGROUPS ({0,QGROUP_1,QGROUP_2, QGROUP_3})
+
+
+// Welcher Eintrag im Array steht fuer was?
+#define Q_QP      0
+#define Q_XP      1
+#define Q_ALLOWED 2
+#define Q_HINT    3
+#define Q_DIFF    4
+#define Q_CLASS   5
+#define Q_ACTIVE  6
+#define Q_WIZ     7
+#define Q_SCNDWIZ 8
+#define Q_ATTR    9
+#define Q_AVERAGE 10
+
+// Sternchen fuer die Klasse
+
+#define QCLASS_STARS(n) funcall(lambda(  ({ 'x, 's }),  \
+  ({#'while, ({#'>, 'x, 0}), \
+             's, ({#', ,({#'=, 'x, ({#'-, 'x, 1}) }),  \
+                        ({#'=, 's, ({#'+, 's, "*" }) }) \
+     }) }) ), n, "") /* ' */ 
+
+
+// Attribute der Quests
+#define QATTR_FLEISSIG  1
+#define QATTR_HEROISCH  2
+#define QATTR_EPISCH    3
+#define QATTR_LEGENDAER 4
+
+#define QATTR_STRINGS ([0: "", \
+                        QATTR_FLEISSIG: "fleissig", \
+                        QATTR_HEROISCH:  "heroisch", \
+                        QATTR_EPISCH: "episch", \
+                        QATTR_LEGENDAER: "legendaer"])
+
+
+
+// Rueckgabewerte Quests
+
+#define OK              1
+
+#define GQ_ALREADY_SET -1
+#define GQ_KEY_INVALID -2
+#define GQ_ILLEGAL_OBJ -3
+#define GQ_IS_INACTIVE -4
+
+#define DQ_NOT_SET     -1
+#define DQ_KEY_INVALID -2
+#define DQ_ILLEGAL_OBJ -3
+
+#define QQ_GUEST       2
+#define QQ_KEY_INVALID 0
+
+#define ERRNO_2_STRING(x,y) (["GQ1" : "Ok.",\
+			      "GQ-1": "Die Quest ist bereits geloest worden.",\
+			      "GQ-2": "Ungueltiger Questname.",\
+			      "GQ-3": "Unbefugter Zugriff.",\
+			      "GQ-4": "Die Quest ist derzeit inaktiv.",\
+			      "DQ1" : "Ok.",\
+			      "DQ-1": "Die Quest war nicht gesetzt.",\
+			      "DQ-2": "Ungueltiger Questname.",\
+			      "DQ-3": "Unbefugter Zugriff.",\
+			      "QQ1" : "Ok.",\
+                              "QQ2" : "Gaeste koennen keine Quest loesen.",\
+			      "QQ0" : "Ungueltiger Questname.",\
+			      ])[x+y]
+
+
+// Miniquests
+
+#define MQ_ALREADY_SET   -1
+#define MQ_KEY_INVALID   -2
+#define MQ_ILLEGAL_OBJ   -3
+#define MQ_IS_INACTIVE   -4
+#define MQ_GUEST         -5
+
+
+#endif /* __QUESTMASTER_H__ */
+
+
+
diff --git a/secure/repmaster.c b/secure/repmaster.c
new file mode 100644
index 0000000..8ac817d
--- /dev/null
+++ b/secure/repmaster.c
@@ -0,0 +1,329 @@
+#pragma strict_types
+#pragma no_clone
+#pragma no_shadow
+#pragma no_inherit
+#pragma verbose_errors
+#pragma warn_deprecated
+
+#include <reputation.h>
+#include <wizlevels.h>
+
+/* Datenstruktur reputations:
+ * (["id": (["uids": ({ "uid1", "uid2" }), "name":"", "changemsg":"",  
+ *           "flags":x|x|x
+ *         ]) 
+ * ])
+ * Bsp:
+ * (["schaedlspalta": (["uids": ({"d.wald.nibel", "d.wald.kessa"}),
+ *                      "name": "Schaedlspalta-Klan", "active": 1,
+ *                      "changemsg":"beim Schaedlspalta-Klan" 
+ *                    ])
+ * ])
+ */
+
+private mapping reputations;
+
+protected void create() {
+  seteuid(getuid(this_object()));
+
+  if(!restore_object(REP_SAVEFILE) || !mappingp(reputations)) 
+    reputations = ([ ]);
+}
+
+// Zugriff auf Handlingmethoden fuer ROOT, EMs+
+private int Sec() {
+  if(process_call()) return 0;
+  if(previous_object() && geteuid(previous_object()) == ROOTID) return 1;
+  return objectp(previous_object()) && ARCH_SECURITY;
+}
+
+
+/* Argumente:
+ * repid = id im mapping reputations
+ * validuids = UIDs welche die Reputation veraendern duerfen, String
+ *             oder String-Array
+ * name = Name der Reputationsfraktion 
+ * changemsg = Text der in die Default-Changemsg eingefuegt wird
+ *             (Dein Ansehen %s hat sich ... verbessert.)
+ *
+ * Returns:
+ *  1 = Reputation erfolgreich hinzugefuegt
+ * -1 = Keine Zugriffsrechte
+ * -2 = Falsche Argumente
+ * -3 = Reputation bereits vorhanden
+ */
+public int AddReputation(string repid, mixed validuids, string name,
+                         string changemsg) {
+  if(!Sec()) 
+    return -1;
+  if(stringp(validuids)) validuids = ({ validuids });
+  if(!pointerp(validuids)) validuids = ({ });
+  // validuids + changemsgs kann auch erstmal leer bleiben
+  if(!stringp(repid) || !sizeof(repid) || !stringp(name) || !sizeof(name))
+    return -2;
+  if(member(reputations, repid))
+    return -3;
+  reputations += ([ repid : ([ 
+    "uids": validuids,
+    "name": name,
+    "flags": REP_FLAG_ACTIVE,
+    "changemsg": changemsg
+  ]) ]);
+  save_object(REP_SAVEFILE);
+  tell_object(this_interactive(), sprintf("Reputation eingetragen:\n"
+    "Name: %s\nValid UIDs: %O\nFlags: REP_FLAG_ACTIVE\nChangemsg: %s\n",
+    reputations[repid]["name"], reputations[repid]["uids"],
+    reputations[repid]["changemsg"]));
+  return 1;
+}
+
+/* Argumente:
+ * repid = id im mapping reputations
+ *
+ * Returns:
+ *  1 = wurde erfolgreich deaktiviert
+ * -1 = Keine Zugriffsrechte
+ * -2 = Falsche Argumente
+ * -3 = Rep nicht vorhanden
+ * -4 = Ist bereits deaktiviert
+ */
+public int DeactivateReputation(string repid) {
+  if(!Sec())
+    return -1;
+  if(!stringp(repid) || !sizeof(repid))
+    return -2;
+  if(!member(reputations, repid))
+    return -3;
+  if(!(reputations[repid]["flags"] & REP_FLAG_ACTIVE)) 
+    return -4;
+  reputations[repid]["flags"] |= REP_FLAG_ACTIVE;
+  save_object(REP_SAVEFILE);
+  return 1;
+}
+
+/* Argumente:
+ * repid = id im mapping reputations
+ *
+ * Returns:
+ *  1 = wurde erfolgreich aktiviert
+ * -1 = Keine Zugriffsrechte
+ * -2 = Falsche Argumente
+ * -3 = Rep nicht vorhanden
+ * -4 = Ist bereits aktiviert
+ */
+public int ActivateReputation(string repid) {
+  if(!Sec())
+    return -1;
+  if(!stringp(repid) || !sizeof(repid))
+    return -2;
+  if(!member(reputations, repid))
+    return -3;
+  if(reputations[repid]["flags"] & REP_FLAG_ACTIVE) 
+    return -4;
+  reputations[repid]["flags"] ^= REP_FLAG_ACTIVE;
+  save_object(REP_SAVEFILE);
+  return 1;
+}
+
+/* Argumente:
+ * repid = id im mapping reputations
+ * uid = UID welche die Reputation veraendern darf
+ *
+ * Returns:
+ *  1 = UID erfolgreich hinzugefuegt
+ * -1 = Keine Zugriffsrechte
+ * -2 = Falsche Argumente
+ * -3 = Rep nicht vorhanden
+ * -4 = UID ist bereits vorhanden
+ */
+public int AddReputationUid(string repid, string uid) {
+  if(!Sec())
+    return -1;
+  if(!stringp(repid) || !sizeof(repid) || !stringp(uid) || !sizeof(uid))
+    return -2;
+  if(!member(reputations, repid))
+    return -3;
+  if(member(reputations[repid]["uids"], uid) != -1) 
+    return -4;
+  reputations[repid]["uids"] += ({ uid });
+  save_object(REP_SAVEFILE);
+  return 1;
+}
+
+/* Argumente:
+ * repid = id im mapping reputations
+ * uid = UID welche die Reputation nicht mehr veraendern darf
+ *
+ * Returns:
+ *  1 = UID erfolgreich geloescht
+ * -1 = Keine Zugriffsrechte
+ * -2 = Falsche Argumente
+ * -3 = Rep nicht vorhanden
+ * -4 = UID nicht vorhanden
+ */
+public int RemoveReputationUid(string repid, string uid) {
+  if(!Sec())
+    return -1;
+  if(!stringp(repid) || !sizeof(repid) || !stringp(uid) || !sizeof(uid))
+    return -2;
+  if(!member(reputations, repid))
+    return -3;
+  if(member(reputations[repid]["uids"], uid) == -1) 
+    return -4;
+  reputations[repid]["uids"] -= ({ uid });
+  save_object(REP_SAVEFILE);
+  return 1;
+}
+
+/* Argumente:
+ * repid = id im mapping reputations
+ * name = Neuer Name der Rep
+ *
+ * Returns:
+ *  1 = Neuer Name erfolgreich gesetzt
+ * -1 = Keine Zugriffsrechte
+ * -2 = Falsche Argumente
+ * -3 = Rep nicht vorhanden
+ * -4 = Name entspricht dem derzeitigen
+ */
+public int ChangeReputationName(string repid, string name) {
+  if(!Sec())
+    return -1;
+  if(!stringp(repid) || !sizeof(repid) || !stringp(name) || !sizeof(name))
+    return -2;
+  if(!member(reputations, repid))
+    return -3;
+  if(reputations[repid]["name"] == name) 
+    return -4;
+  reputations[repid]["name"] = name;
+  save_object(REP_SAVEFILE);
+  return 1;
+}
+
+/* Argumente:
+ * repid = id im mapping reputations
+ * msg = Neuer String in der Default-Changemsg
+ *
+ * Returns:
+ *  1 = Neue Msg erfolgreich gesetzt
+ * -1 = Keine Zugriffsrechte
+ * -2 = Falsche Argumente
+ * -3 = Rep nicht vorhanden
+ * -4 = Msg entspricht der derzeitigen
+ */
+public int ChangeReputationMsg(string repid, string msg) {
+  if(!Sec())
+    return -1;
+  if(!stringp(repid) || !sizeof(repid) || !stringp(msg) || !sizeof(msg))
+    return -2;
+  if(!member(reputations, repid))
+    return -3;
+  if(reputations[repid]["changemsg"] == msg) 
+    return -4;
+  reputations[repid]["changemsg"] = msg;
+  save_object(REP_SAVEFILE);
+  return 1;
+}
+
+/* Argumente:
+ * repid = id(s) im mapping reputations, ohne Argument werden alle aufgelistet,
+ *         String oder String-array
+ *
+ * Returns:
+ *  1 = Liste ausgegeben
+ * -1 = Falsche Argumente
+ * -2 = Rep nicht vorhanden
+ */
+public varargs int ViewReputationData(mixed repids) {
+  // Fuer alle sichtbar, damit man ggf. weiss, wo man nachfragen muss
+  if(!repids) repids = m_indices(reputations);
+  else if(stringp(repids)) repids = ({ repids });
+  if(!pointerp(repids) || !sizeof(repids))
+    return -1;
+  // Textausgabe fuer alle richtigen IDs
+  foreach(string repid : repids) {
+    if(!member(reputations, repid)) continue;
+    tell_object(this_interactive(), sprintf("%s %s\n  Name: %s\n  Changemsg: "
+      "%s\n  ValidUIDS: %O\n\n", repid, 
+      (reputations[repid]["flags"] & REP_FLAG_ACTIVE ? "" :
+      "(Inaktiv)"), reputations[repid]["name"], 
+      reputations[repid]["changemsg"], reputations[repid]["uids"]));
+  }
+  // Min. 1 nicht vorhanden: return -2, sonst 1
+  return (sizeof(repids - m_indices(reputations)) ? -2 : 1);
+}
+
+/* Argumente:
+ * repid = id im mapping reputations
+ *
+ * Returns:
+ * mapping = Daten der Reputation
+ * 0 = Falsches Argument / Reputation nicht vorhanden
+ */
+public mapping GetReputationData(string repid) {
+  if(!stringp(repid) || !sizeof(repid) || !member(reputations, repid))
+    return 0;
+  return deep_copy(reputations[repid]);
+}
+
+/* Argumente:
+ * repid = id im mapping reputations
+ * value = Wert um den die Rep veraendert wird (+ / -)
+ *
+ * Returns:
+ * string = "Dein Ansehen %s hat sich ... .\n"
+ * 0 = Falsche Argumente / Rep nicht vorhanden
+ */
+public string GetDefaultChangeMsg(string repid, int value) {
+  string strength;
+  if(!stringp(repid) || !sizeof(repid) || !intp(value) || !value ||
+     !member(reputations, repid))
+    return 0;
+  switch(abs(value)) {
+    case    0..25  : strength = "kaum merklich"; break;
+    case   26..50  : strength = "leicht"; break;
+    case   51..100 : strength = "deutlich"; break;
+    case  101..250 : strength = "erheblich"; break;
+    case  251..1000: strength = "sehr stark"; break;
+    default:         strength = "ausserordentlich stark"; break;
+  }
+  return sprintf("Dein Ansehen %s hat sich%s%s.\n", 
+    reputations[repid]["changemsg"], (!strength ? " " : " "+ strength +" "),
+    (value < 0 ? "verschlechtert" : "verbessert"));
+}
+
+/* Argumente:
+ * repid = id im mapping reputations
+ * ob = Objekt, dessen UID geprueft wird, ohne Argument -> prev_ob()
+ *
+ * Returns:
+ * 1 = UID valid
+ * 0 = UID invalid
+ * -1 = Falsche Argumente 
+ * -2 = Rep nicht vorhanden 
+ */
+public int CheckValidUid(string repid, object ob) {
+  if(!stringp(repid) || !sizeof(repid) || !objectp(ob))
+    return -1;
+  if(!member(reputations, repid))
+    return -2;
+  return (member(reputations[repid]["uids"], getuid(ob)) != -1);
+}
+
+/* Argumente:
+ * repid = id im mapping reputations
+ *
+ * Returns:
+ * 1 = Rep aktiv
+ * 0 = Rep inaktiv
+ * -1 = Falsche Argumente 
+ * -2 = Rep nicht vorhanden 
+ */
+public int CheckRepActive(string repid) {
+  if(!stringp(repid) || !sizeof(repid))
+    return -1;
+  if(!member(reputations, repid))
+    return -2;
+  // Verboolung
+  return !!(reputations[repid]["flags"] & REP_FLAG_ACTIVE);
+}
diff --git a/secure/scoremaster.c b/secure/scoremaster.c
new file mode 100644
index 0000000..576a4b9
--- /dev/null
+++ b/secure/scoremaster.c
@@ -0,0 +1,1465 @@
+// MorgenGrauen MUDlib
+//
+// scoremaster.c - Verwaltung der eindeutigen Nummernvergabe fuer NPCs und
+//       MiniQuests sowie der Stufenpunkte, die sie geben  
+//
+// $Id: scoremaster.c 9170 2015-03-05 20:18:54Z 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 "/secure/scoremaster.h"
+#include "/secure/wizlevels.h"
+#include <properties.h>
+#include <files.h>
+
+#define ZDEBUG(x) if (find_player("zesstra")) \
+  tell_object(find_player("zesstra"),sprintf("SCM: %s\n",x))
+
+// hoechste vergebene Nr.
+private int lastNum;
+
+// Liste alle EKs: ([obname: num; score; killcount])
+private mapping npcs = m_allocate(0,3);
+
+// Liste von Spielernamen-Wert-Paaren, die im Reset abgearbeitet wird:
+// ([plname: ({wert1, wert2, wert3, ...}) ]) 
+// wert > 0 bedeutet setzen des entsprechenden EKs, < 0 bedeutet loeschen.
+private mapping to_change = ([]);
+
+// Liste der EK-Tips: ([obname: Spruch])
+private mapping tipList = ([]);
+
+// Bit-Nr., die (wieder) vergeben werden duerfen.
+private int *free_num = ({});
+
+// zu entfernende EKs, Liste Bitnummern, also ints
+private int *to_be_removed = ({});
+
+// Liste von temporaeren EKs, die noch nicht bestaetigt wurden:
+// ([obname: ({plname1, plname2}) ])
+private mapping unconfirmed_scores = ([]);
+
+// alle Spieler kriegen diesen 
+// Nach Nr. sortierte NPC-Liste: ([num: key; score])
+private nosave mapping by_num = m_allocate(0,2);
+
+// Cache fuer EKs von Spielern: ([plname: scoresumme])
+private nosave mapping users_ek = ([]);
+
+// bitstring, der alle aktiven EKs als gesetztes Bit enthaelt.
+private nosave string active_eks="";
+
+// Prototypen
+public mapping getFreeEKsForPlayer(object player);
+public int addTip(mixed key,string tip);
+public int changeTip(mixed key,string tip);
+public int removeTip(mixed key);
+private string getTipFromList(mixed key);
+public string getTip(mixed key);
+
+public void CheckNPCs(int num);
+public void check_all_player(mapping allplayer);
+public varargs int DumpNPCs(int sortkey);
+
+private void make_num(string key, int num, int score) {
+  by_num += ([ num : key; score ]);
+  // fuer aktive EKs, die also einen Scorewert > 0 haben, wird das jeweilige
+  // Bit gesetzt. Wird spaeter zum Ausfiltern inaktiver EKs aus den Bitstrings
+  // in den Spieler gebraucht.
+  if (score>0 && !member(unconfirmed_scores,num))
+    active_eks = set_bit(active_eks, num);
+}
+
+private int allowed()
+{
+  if (previous_object() && geteuid(previous_object())==ROOTID)
+    return 1;
+  if (!process_call() && previous_object() && this_interactive() && ARCH_SECURITY)
+    return 1;
+  return 0;
+}
+
+protected void create()
+{
+  seteuid(getuid());
+  if (!restore_object(SCORESAVEFILE))
+  {
+    lastNum=0;
+    npcs=m_allocate(0,3);
+    to_change=m_allocate(0,1);
+    tipList=([]);
+  }
+  npcs-=([0]);
+  walk_mapping(npcs, #'make_num);
+}
+
+public int ClearUsersEKCache()
+{
+  if (!allowed())
+    return SCORE_NO_PERMISSION;
+  users_ek = ([]);
+  return 1;
+}
+
+public mixed QueryUsersEKCache()
+{
+  if (!allowed())
+    return SCORE_NO_PERMISSION;
+  return users_ek;
+}
+
+public mixed Query_free_num()
+{
+  if (!allowed())
+    return SCORE_NO_PERMISSION;
+  return free_num;
+}
+
+public mixed Add_free_num(int what)
+{
+  if (!allowed())
+    return SCORE_NO_PERMISSION;
+  if (!what || !intp(what) || by_num[what])
+    return SCORE_INVALID_ARG;
+  if (member(free_num,what)==-1)
+    free_num+=({what});
+  save_object(SCORESAVEFILE);
+  write_file(SCORELOGFILE,sprintf("ADDFREENUM: %s %5d (%s, %O)\n",     
+  strftime("%d%m%Y-%T",time()),what,
+  geteuid(previous_object()), this_interactive()));
+
+  return free_num;
+}
+
+public mixed Remove_free_num(int what)
+{
+  if (!allowed())
+    return SCORE_NO_PERMISSION;
+  if (!what || !intp(what))
+    return SCORE_INVALID_ARG;
+  free_num-=({what});
+  save_object(SCORESAVEFILE);
+  write_file(SCORELOGFILE,sprintf("REMOVEFREENUM: %s %5d (%s, %O)\n",     
+  strftime("%d%m%Y-%T",time()),what,
+  geteuid(previous_object()),this_interactive()));
+  return free_num;
+}
+
+public mixed Query_to_change(string who)
+{
+  if (!allowed())
+    return SCORE_NO_PERMISSION;
+  if (!who)
+    return to_change;
+  if (who=="")
+    return m_indices(to_change);
+  return to_change[who];
+}
+
+public mixed Add_to_change(string who, int what)
+{
+  if (!allowed())
+    return SCORE_NO_PERMISSION;
+  if (!who || !stringp(who) || !what || !intp(what))
+    return SCORE_INVALID_ARG;
+  if (member(to_change,who))
+  {
+    to_change[who]-=({-what});
+    if (member(to_change[who],what)==-1)
+      to_change[who]+=({what});
+  }
+  else
+     to_change[who]=({what});
+  save_object(SCORESAVEFILE);
+  write_file(SCORELOGFILE,sprintf("ADDTOCHANGE: %s %s %5d (%s, %O)\n",
+         strftime("%d%m%Y-%T",time()),who,what,
+         geteuid(previous_object()), this_interactive()));
+  return to_change[who];
+}
+
+public mixed Remove_to_change(string who, int what)
+{
+  if (!allowed())
+    return SCORE_NO_PERMISSION;
+  if (!who || !stringp(who) || !what || !intp(what))
+    return SCORE_INVALID_ARG;
+  if (member(to_change,who))
+  {
+     to_change[who]-=({what});
+     if (!sizeof(to_change[who]))
+        m_delete(to_change,who);
+  }
+  save_object(SCORESAVEFILE);
+  write_file(SCORELOGFILE,sprintf("REMOVETOCHANGE: %s %s %5d (%s, %O)\n",
+         strftime("%d%m%Y-%T",time()),who,what,
+         geteuid(previous_object()), this_interactive()));
+  return to_change[who];
+}
+
+void reset()
+{
+  string *whop,who,ek;
+  mixed what;
+  int i,j,value,changed;
+
+  // falls EKs global entfernt werden sollen, schonmal den noetigen Callout
+  // starten.
+  if (sizeof(to_be_removed) && find_call_out(#'check_all_player) == -1)
+      call_out(#'check_all_player, 10, 0);
+  // EK-Mainteiner ueber unbestaetigte EKs informieren
+  if (sizeof(unconfirmed_scores)) {
+    foreach(string n: SCOREMAINTAINERS) {
+      if (objectp(find_player(n)))
+          tell_object(find_player(n),break_string(
+      "Es gibt unbestaetigte EKs im Scoremaster. Schau Dir die doch "
+      "mal an. ;-)",78, "Der Scoremaster teilt Dir mit: "));
+    }
+  }
+
+  i=sizeof(whop=m_indices(to_change))-1;
+  while (i>=0 && get_eval_cost()>100000)
+  {
+    ek = (string)(MASTER->query_ek(who=whop[i]) || "");
+    for (j=sizeof(what=to_change[who])-1;j>=0;j--) {
+      if ((value=what[j])>0) {
+    // Vergabestatistik hochzaehlen.
+    npcs[by_num[value,BYNUM_KEY],NPC_COUNT]++;
+    ek=set_bit(ek,value);
+      }
+      else {
+    // Vergabestatistik hochzaehlen.
+    npcs[by_num[-value,BYNUM_KEY],NPC_COUNT]++;
+    ek=clear_bit(ek,-value);
+      }
+      // if (find_player("rikus")) 
+      //tell_object(find_player("rikus"),"SCOREMASTER "+who+" "+erg+"\n");
+
+      write_file(SCOREAUTOLOG,
+  sprintf("SET_CLEAR_BIT (reset): %s %4d %s\n",
+    who, j, strftime("%d%m%Y-%T",time()) ));
+    }
+    MASTER->update_ek(who, ek);
+
+    if (member(users_ek, who))
+      m_delete(users_ek, who);
+    
+    m_delete(to_change,who);
+    changed=1;
+    i--;
+  }
+  if (changed) save_object(SCORESAVEFILE);
+}
+
+public varargs mixed QueryNPC(int score)
+{
+  string key;
+  int val;
+
+  if (!previous_object())
+    return SCORE_INVALID_ARG;
+  
+  key = load_name(previous_object());
+
+  // schon bekannter EK?
+  if (member(npcs,key))
+    return ({npcs[key,NPC_NUMBER],npcs[key,NPC_SCORE]});
+
+  if (score<=0 || 
+      member(inherit_list(previous_object()),"/std/living/life.c") < 0)
+    return SCORE_INVALID_ARG;
+
+  if (key[0..8]=="/players/") return SCORE_INVALID_ARG;
+
+  if (score>2) score=2;
+
+  if (sizeof(free_num)) {
+      val = free_num[0];
+      free_num -= ({val});
+  }
+  else val=++lastNum;
+
+  npcs[key,NPC_SCORE] = score;
+  npcs[key,NPC_NUMBER] = val;
+  npcs[key,NPC_COUNT] = 0;
+  by_num += ([val: key; score]);
+  // werden noch nicht als aktive EKs gewertet, damit sie nicht als Ek-Tips
+  // vergben werden.
+  //active_eks = set_bit(active_eks, val);
+
+  unconfirmed_scores += ([ val: ({}) ]);
+
+  ClearUsersEKCache();
+  save_object(SCORESAVEFILE);
+  write_file(SCOREAUTOLOG,sprintf(
+  "ADDNPC: %s %5d %4d %s (UID: %s, TI: %O, TP: %O)\n",
+  strftime("%d%m%Y-%T",time()),val,score,key,
+  getuid(previous_object()), this_interactive(), this_player()));
+
+  while(remove_call_out("DumpNPCs") != -1) ;
+  call_out("DumpNPCs",60);
+  return ({val,score});
+}
+
+public varargs mixed NewNPC(string key,int score)
+{
+  int val;
+
+  if (!allowed())
+    return SCORE_NO_PERMISSION;
+  if (!key || !stringp(key))
+    return SCORE_INVALID_ARG;
+  
+  key = load_name(key);
+  if (val=npcs[key,NPC_NUMBER])
+    return ({val,npcs[key,NPC_SCORE]});
+  if (score<=0)
+    return SCORE_INVALID_ARG;
+
+  if (sizeof(free_num)) {
+      val=free_num[0];
+      free_num=free_num[1..];
+  }
+  else val=++lastNum;
+
+  npcs[key,NPC_SCORE] = score;
+  npcs[key,NPC_NUMBER] = val;
+  npcs[key,NPC_COUNT] = 0;
+  by_num += ([val: key; score]);
+  active_eks = set_bit(active_eks, val);
+
+  ClearUsersEKCache();
+  save_object(SCORESAVEFILE);
+  write_file(SCORELOGFILE,sprintf("NEWNPC: %s %5d %4d %s (%s, %O)\n",
+         strftime("%d%m%Y-%T",time()),val,score,key,
+         geteuid(previous_object()), this_interactive()));
+ while(remove_call_out("DumpNPCs") != -1) ; 
+  call_out("DumpNPCs",60);
+  return ({val,score});
+}
+
+public varargs mixed AddNPC(string key,int score) { return NewNPC(key,score); }
+
+// restauriert die Daten eines frueher geloeschten, in den Spielern noch
+// enthaltenen EKs. Moeglich, wenn man Pfad, Nr. und Punkte noch kennt.
+public int RestoreEK(string key, int bit, int score) {
+  if (!allowed())
+    return SCORE_NO_PERMISSION;
+  if (!stringp(key) || !sizeof(key) 
+      || !intp(bit) || bit < 0
+      || !intp(score) || score < 0)
+      return SCORE_INVALID_ARG;
+
+  if (member(npcs,key) || member(by_num,bit))
+      return SCORE_INVALID_ARG;
+
+  npcs += ([key: bit;score;0 ]);
+  by_num += ([bit: key;score ]);
+
+  ClearUsersEKCache();
+  save_object(SCORESAVEFILE);
+  write_file(SCORELOGFILE,sprintf("RESTOREEK: %s %5d %4d %s (%s, %O)\n",
+         strftime("%d%m%Y-%T",time()), bit, score, key,
+         geteuid(previous_object()), this_interactive()));
+  while(remove_call_out("DumpNPCs") != -1) ;
+  call_out("DumpNPCs",60);
+  return 1;
+
+}
+
+public int ConfirmScore(mixed key) {
+  // Bits in den Spielern in unconfirmed_scores setzen und Statistik
+  // hochzaehlen
+  // Bit in active_eks setzen
+  // Eintrag aus unconfirmed_scores loeschen
+  int bit;
+
+  if (!allowed()) return SCORE_NO_PERMISSION;
+  if (stringp(key) && member(npcs,key)) {
+      bit = npcs[key, NPC_NUMBER];
+  }
+  else if (intp(key) && member(by_num,key)) {
+      bit = key;
+  }
+  else
+      return SCORE_INVALID_ARG;
+
+  if (!member(unconfirmed_scores, bit)) 
+      return SCORE_INVALID_ARG;
+
+  string obname = by_num[bit, BYNUM_KEY];
+  int score = by_num[bit,BYNUM_SCORE];
+  
+  foreach(string pl: unconfirmed_scores[bit]) {
+      string eks = (string)master()->query_ek(pl);
+      eks = set_bit(eks, bit);
+      master()->update_ek(pl, eks);
+      write_file(SCOREAUTOLOG, sprintf(
+    "SETBIT: %s %5d %s\n",
+    strftime("%d%m%Y-%T",time()), bit, pl));
+  }
+  //Vergabestatistik hochzaehlen...
+  npcs[obname,NPC_COUNT]+=sizeof(unconfirmed_scores[bit]);
+
+  m_delete(unconfirmed_scores, bit);
+  active_eks = set_bit(active_eks, bit);
+  save_object(SCORESAVEFILE);
+
+  write_file(SCORELOGFILE,sprintf(
+      "CONFIRMNPC: %s %5d Score %3d %s [%s, %O]\n",
+       strftime("%d%m%Y-%T",time()), bit, score, obname,
+       getuid(previous_object()),this_interactive()));
+
+  return 1;
+}
+
+public int RejectScore(mixed key) {
+  // Eintrag aus unconfirmed_scores, npcs, by_num loeschen
+  // Bit-Nr. in free_num eintragen
+  // evtl. EK-Spruch entfernen?
+  int bit;
+
+  if (!allowed()) return SCORE_NO_PERMISSION;
+  if (stringp(key) && member(npcs,key)) {
+      bit = npcs[key, NPC_NUMBER];
+  }
+  else if (intp(key) && member(by_num,key)) {
+      bit = key;
+  }
+  else
+      return SCORE_INVALID_ARG;
+
+  if (!member(unconfirmed_scores, bit)) 
+      return SCORE_INVALID_ARG;
+
+  string obname = by_num[bit, BYNUM_KEY];
+  int score = by_num[bit,BYNUM_SCORE];
+
+  m_delete(by_num, bit);
+  m_delete(npcs, obname);
+  m_delete(unconfirmed_scores,bit);
+  removeTip(obname);
+  free_num += ({bit});
+
+  save_object(SCORESAVEFILE);
+
+  write_file(SCORELOGFILE,sprintf(
+      "REJECTNPC: %s %5d Score %3d %s [%s, %O]\n",
+       strftime("%d%m%Y-%T",time()), bit, score, obname,
+       getuid(previous_object()),this_interactive()));
+  return 1;
+}
+
+// unbestaetigte NPCs in ein File ausgeben
+public void DumpUnconfirmedScores() {
+  if (!objectp(this_player())) return;
+ 
+  write(sprintf("%5s  %5s  %4s   %s\n",
+  "Nr.", "Cnt", "Sc", "Objekt"));
+  foreach(int bit, string *pls: unconfirmed_scores) {
+    write(sprintf("%5d  %5d  %4d   %s\n",
+  bit, sizeof(pls), by_num[bit,BYNUM_SCORE], by_num[bit,BYNUM_KEY]));
+  }
+}
+
+public mapping _query_unconfirmed() {
+  if (allowed()) return unconfirmed_scores;
+  return 0;
+}
+
+public varargs int SetScore(mixed key,int score)
+{
+  int num;
+  string ob;
+  int oldscore;
+
+  if (!allowed())
+    return SCORE_NO_PERMISSION;
+  if (!key) return SCORE_INVALID_ARG;
+
+  if (stringp(key) && sizeof(key)) {
+    ob = load_name(key);
+    if (!member(npcs, ob)) return SCORE_INVALID_ARG;
+    num = npcs[ob, NPC_NUMBER];
+    if (ob != by_num[num, BYNUM_KEY])
+  return SCORE_INVALID_ARG;
+  }
+  else if (intp(key) && member(by_num,key) ) {
+    num = key;
+    ob = by_num[num, BYNUM_KEY];
+    if (!member(npcs, ob) || (npcs[ob, NPC_NUMBER] != num))
+  return SCORE_INVALID_ARG;
+  }
+  else
+    return SCORE_INVALID_ARG;
+
+  oldscore = by_num[num,BYNUM_SCORE];
+  by_num[num,BYNUM_SCORE] = score;
+  npcs[ob, NPC_SCORE] = score;
+
+  if (score > 0)
+      active_eks = set_bit(active_eks, num);
+  else
+      active_eks = clear_bit(active_eks, num);
+
+  ClearUsersEKCache();
+  save_object(SCORESAVEFILE);
+  write_file(SCORELOGFILE,sprintf(
+  "SETSCORE: %s %5d %.3d OSc: %.3d %s (%s, %O)\n",
+         strftime("%d%m%Y-%T",time()),num,score,oldscore, ob,
+         geteuid(previous_object()), this_interactive()));
+ while(remove_call_out("DumpNPCs") != -1) ; 
+  call_out("DumpNPCs",60);
+  return 1;
+}
+
+// entfernt einen EK endgueltig und unwiderruflich und gibt die Nr. wieder
+// frei.
+// Technisch wird der EK erstmal in eine Liste eingetragen. Im Reset iteriert
+// der Master ueber alle SPieler-Savefiles und loescht den Ek aus alle
+// Spielern. Nach Abschluss wird der Eintrag in npcs geloescht und seine Nr.
+// in die Liste freier Nummern eingetragen.
+public int* MarkEKForLiquidation(mixed key) {
+  int bit;
+  if (!allowed())
+      return 0;
+  // nicht in to_be_removed aendern, wenn check_all_player() laeuft.
+  if (find_call_out(#'check_all_player) != -1)
+      return 0;
+
+  if (stringp(key) && sizeof(key)) {
+    if (!member(npcs,key)) return 0;
+    bit = npcs[key,NPC_NUMBER];
+  }
+  else if (intp(key) && key>=0) {
+    bit = key;
+  }
+  else
+    return 0;
+
+  if (member(to_be_removed,bit) == -1)
+    to_be_removed += ({bit});
+  write_file(SCORELOGFILE,sprintf("DELETEFLAG: %s %5d %s (%s, %O)\n",
+  strftime("%d%m%Y-%T",time()), bit, by_num[bit,BYNUM_KEY] || "NoName",
+  geteuid(previous_object()), this_interactive()));
+  
+  save_object(SCORESAVEFILE);
+  
+  return to_be_removed;
+}
+
+// geht nur, solange nach einem RemoveEK() noch kein reset() gelaufen ist!
+public int* UnmarkEKForLiquidation(mixed key) {
+  int bit;
+  if (!allowed())
+      return 0;
+  // nicht in to_be_removed aendern, wenn check_all_player() laeuft.
+  if (find_call_out(#'check_all_player) != -1)
+      return 0;
+
+  if (stringp(key) && sizeof(key)) {
+    if (!member(npcs,key)) return 0;
+    bit = npcs[key,NPC_NUMBER];
+  }
+  else if (intp(key) && key>=0) {
+    bit = key;
+  }
+  else
+    return 0;
+ 
+  to_be_removed -= ({bit});
+  write_file(SCORELOGFILE,sprintf("UNDELETEFLAG: %s %5d %s (%s, %O\n",
+  strftime("%d%m%Y-%T",time()),bit, by_num[bit, BYNUM_KEY] || "NoName",
+  geteuid(previous_object()), this_interactive()));
+  
+  save_object(SCORESAVEFILE);
+
+  return to_be_removed;
+}
+
+public int* QueryLiquidationMarks() {
+  if (allowed())
+      return to_be_removed;
+  else
+      return 0;;
+}
+
+// setzt nur den Scorewert auf 0, sonst nix. Solche EKs koennen dann spaeter
+// durch Angabe eines neues Scorewertes reaktiviert werden.
+public int RemoveScore(mixed key) {
+  int changed;
+  int oldscore;
+
+  if (!allowed())
+    return SCORE_NO_PERMISSION;
+
+  if (stringp(key) && member(npcs,key)) {
+    int num = npcs[key, NPC_NUMBER];
+    if ( key == by_num[num, BYNUM_KEY]) {
+      oldscore = by_num[num, BYNUM_SCORE];
+      npcs[key, NPC_SCORE] = 0;
+      by_num[num, BYNUM_SCORE] = 0;
+      active_eks = clear_bit(active_eks,num); 
+      write_file(SCORELOGFILE,sprintf(
+      "REMOVESCORE: %s %5d OSc: %.3d %s (%s, %O)\n",
+        strftime("%d%m%Y-%T",time()), num, oldscore, key, 
+        geteuid(previous_object()), this_interactive()));
+      changed = 1;
+    }
+  }
+  else if (intp(key) && member(by_num, key)) {
+    string obname = by_num[key, BYNUM_KEY];
+    if (key == npcs[obname, NPC_NUMBER]) {
+      oldscore = by_num[key, BYNUM_SCORE];
+      npcs[obname, NPC_SCORE] = 0;
+      by_num[key, BYNUM_SCORE] = 0;
+      active_eks = clear_bit(active_eks,key); 
+      write_file(SCORELOGFILE,sprintf(
+      "REMOVESCORE: %s %5d OSc: %.3d %s (%s, %O)\n",
+        strftime("%d%m%Y-%T",time()),key, oldscore, obname,
+        geteuid(previous_object()), this_interactive()));
+      changed = 1;
+    }
+  }
+
+  if (changed) {
+    ClearUsersEKCache();
+    save_object(SCORESAVEFILE);
+    while(remove_call_out("DumpNPCs") != -1) ;
+    call_out("DumpNPCs",60);
+    return 1;
+  }
+  return SCORE_INVALID_ARG;
+}
+
+public varargs int MoveScore(mixed oldkey, string newpath)
+{
+  int num,score;
+  string oldpath;
+  string tip;
+  
+  if (!allowed())
+    return SCORE_NO_PERMISSION;
+  if (!stringp(newpath))
+    return SCORE_INVALID_ARG;
+
+  if (stringp(oldkey)) {
+    oldkey = load_name(oldkey); 
+    num=npcs[oldkey,NPC_NUMBER];
+  }
+  else if (intp(oldkey)) num=oldkey;
+  else return SCORE_INVALID_ARG;
+
+  if (!member(by_num,num)) return SCORE_INVALID_ARG;
+  
+  tip=getTipFromList(oldkey);
+  oldpath = by_num[num, BYNUM_KEY];
+  score = by_num[num, BYNUM_SCORE];
+
+  if (member(npcs, oldpath)) {
+    m_delete(npcs, oldpath);
+    removeTip(oldkey);
+    if(tip!="") addTip(newpath,tip);
+    npcs[newpath, NPC_SCORE] = score;
+    npcs[newpath, NPC_NUMBER] = num;
+  }
+  else return SCORE_INVALID_ARG;
+
+  by_num += ([num: newpath; score]);
+
+  ClearUsersEKCache();
+  save_object(SCORESAVEFILE);
+  write_file(SCORELOGFILE,sprintf("MOVESCORE: %s %s %s (%s, %O)\n",
+  strftime("%d%m%Y-%T",time()),oldpath,newpath,
+  geteuid(previous_object()),this_interactive()));
+
+  while(remove_call_out("DumpNPCs") != -1) ;
+  call_out("DumpNPCs",60);
+  return 1;
+}
+
+// liefert alle Kills des Spielers zurueck, auch solche, die momentan
+// ausgetragen/deaktiviet sind.
+public string QueryAllKills(string pl)
+{
+  return (MASTER->query_ek(pl) || "");
+}
+
+// filtert alle Eintraege aus dem Bitstring heraus, die fuer
+// ausgetragene/inaktive EKs stehen.
+public string QueryKills(string pl) {
+  string res = (string)MASTER->query_ek(pl) || "";
+  // vergleichen mit den aktiven EKs aus active_eks und nur jene Bits
+  // zurueckliefern, die in beiden Strings gesetzt sind.
+  return and_bits(res,active_eks);
+}
+
+public int QueryKillPoints(mixed pl) {
+  
+  if (!allowed() &&
+      (!previous_object() 
+       || strstr(object_name(previous_object()), "/gilden/") != 0) )
+     return 0;
+
+  if (!stringp(pl)) pl=getuid(pl);
+
+  if (member(users_ek,pl)) return users_ek[pl];
+
+  string s = (MASTER->query_ek(pl) || "");
+  
+  int p=-1;
+  int summe;
+  while ((p=next_bit(s,p)) != -1) {
+      summe+=by_num[p,BYNUM_SCORE];
+  }
+
+  users_ek += ([pl:summe]);
+  return summe;
+}
+
+public mixed *QueryNPCbyNumber(int num)
+{
+  if (!allowed())
+    return 0;
+
+  if (member(by_num, num))
+    return ({num, by_num[num, BYNUM_SCORE], by_num[num, BYNUM_KEY]});
+
+  return 0;
+}
+
+protected mixed *StaticQueryNPCbyNumber(int num)
+{
+  if (member(by_num, num))
+    return ({num, by_num[num, BYNUM_SCORE], by_num[num, BYNUM_KEY]});
+
+  return 0;
+}
+
+public mixed *QueryNPCbyObject(object o)
+{
+  string key;
+  int val;
+
+  key=load_name(o);
+  if (member(npcs,key)) {
+    val = npcs[key,NPC_NUMBER];
+    return ({val, by_num[val, BYNUM_SCORE], by_num[val, BYNUM_KEY]});
+  }
+  return 0;
+}
+
+public int GiveKill(object pl, int bit)
+{
+  mixed info;
+  object po;
+  int drin;
+  string pls, ek;
+
+
+  if (!pointerp(info = StaticQueryNPCbyNumber(bit)))
+    return -1;
+
+  if ((!po=previous_object()) 
+      || load_name(po) != info[SCORE_KEY])
+    return -2;
+
+  pls=getuid(pl);
+
+  // wenn unbestaetigt, Spieler fuer spaeter merken
+  if (member(unconfirmed_scores, bit)) {
+    if (member(unconfirmed_scores[bit], pls) == -1)
+  unconfirmed_scores[bit] += ({pls});
+  }
+  else {
+    // sonst wird das Bit direkt im Spieler gesetzt.
+    ek = (MASTER->query_ek(pls) || "");
+    if (test_bit(ek, bit))
+      return -3;
+    ek = set_bit(ek, bit);
+    MASTER->update_ek(pls, ek);
+    // Vergabestatistik hochzaehlen.
+    npcs[by_num[bit,BYNUM_KEY],NPC_COUNT]++;
+  }
+
+  if (member(users_ek, pls))
+    m_delete(users_ek, pls);
+
+  EK_GIVENLOG(sprintf("%s: %s", info[SCORE_KEY], pls)); 
+
+  return info[SCORE_SCORE];
+}
+
+public int HasKill(mixed pl, mixed npc)
+{
+  string fn, *pls;
+
+  if (!objectp(pl) && !stringp(pl) && 
+      !objectp(npc) && !stringp(npc) && !intp(npc))
+    return 0;
+  if (!stringp(pl)) 
+    pl=getuid(pl);
+
+  if (intp(npc))
+    npc=by_num[npc,BYNUM_KEY];
+  fn=load_name(npc);
+
+  if (!member(npcs, fn)) return 0;
+  
+  int bit = npcs[fn, NPC_NUMBER];
+  
+  if (pointerp(pls=unconfirmed_scores[bit]) &&
+      member(pls,pl) != -1)
+    return 1;
+
+  string eks = (MASTER->query_ek(pl) || "");
+
+  return test_bit(eks, bit);
+}
+
+private void WriteDumpFile(string *keys) {
+  int maxn;
+
+  if (!pointerp(keys)) return;
+
+  rm(SCOREDUMPFILE);
+
+  write_file(SCOREDUMPFILE,sprintf("%5s  %5s  %4s   %s\n",
+  "Nr.", "Cnt", "Sc", "Objekt"));
+  foreach(string key: keys) {
+    write_file(SCOREDUMPFILE,sprintf("%5d  %5d  %4d   %O\n",
+    npcs[key,NPC_NUMBER], npcs[key,NPC_COUNT],
+    npcs[key,NPC_SCORE], key));
+    maxn += npcs[key,NPC_SCORE];
+  }
+  write_file(SCOREDUMPFILE,sprintf(
+  "========================================================\n"
+  "NPCs gesamt: %d Punkte\n\n",maxn));
+}
+
+public varargs int DumpNPCs(int sortkey) {
+
+  if (extern_call() && !allowed()) return SCORE_NO_PERMISSION;
+  if (!intp(sortkey)) return SCORE_INVALID_ARG;
+
+  rm(SCOREDUMPFILE);
+
+  // sortieren
+  string *keys=sort_array(m_indices(npcs), function int (string a, string b) {
+        return(npcs[a,sortkey] < npcs[b,sortkey]); } );
+  call_out(#'WriteDumpFile, 2, keys);
+
+  return 1;
+}
+
+public int SetScoreBit(string pl, int bit)
+{
+  string ek;
+
+  if (!allowed())
+    return SCORE_NO_PERMISSION;
+
+  ek = (MASTER->query_ek(pl) || "");
+  ek = set_bit(ek, bit);
+  MASTER->update_ek(pl, ek);
+
+  // Vergabestatistik hochzaehlen.
+  npcs[by_num[bit,BYNUM_KEY],NPC_COUNT]++;
+
+  if (member(users_ek, pl))
+    m_delete(users_ek, pl);
+
+  write_file(SCORELOGFILE,sprintf("SETBIT: %s %s %5d Sc: %.3d %s (%s, %O)\n",
+         strftime("%d%m%Y-%T",time()),pl, bit,
+         by_num[bit,BYNUM_SCORE], by_num[bit,BYNUM_KEY],
+         geteuid(previous_object()), this_interactive()));
+  return 1;
+}
+
+public int ClearScoreBit(string pl, int bit)
+{
+  string ek;
+
+  if (!allowed())
+    return SCORE_NO_PERMISSION;
+
+  ek = (MASTER->query_ek(pl) || "");
+  ek = clear_bit(ek, bit);
+  MASTER->update_ek(pl, ek);
+
+  // Vergabestatistik runterzaehlen.
+  npcs[by_num[bit,BYNUM_KEY],NPC_COUNT]--;
+
+  if (member(users_ek, pl))
+    m_delete(users_ek, pl);
+
+  write_file(SCORELOGFILE,sprintf(
+  "CLEARBIT: %s %s %5d Sc: %.3d %s (%s, %O)\n",       
+  strftime("%d%m%Y-%T",time()),pl,bit,
+  by_num[bit,BYNUM_SCORE],by_num[bit,BYNUM_KEY],
+  geteuid(previous_object()), this_interactive()));
+  return 1;
+}
+
+private status ektipAllowed()
+{ 
+  status poOK;
+  string poName;
+  status ret;
+                
+  poName=load_name(previous_object());        
+  poOK=previous_object() &&     
+    ((previous_object()==find_object(EKTIPGIVER)) || (poName==EKTIPLIST) );
+
+  ret=allowed() || 
+    (this_player() && this_interactive() && previous_object() && 
+     this_interactive()==this_player() && poOK);
+  return ret;
+}
+
+// liefert alle EKs, die aktiv sind und die der Spieler noch nicht hat in
+// einem Mapping entsprechend npcs zurueck.
+public mapping getFreeEKsForPlayer(object player)
+{
+  if(!ektipAllowed() || !objectp(player) || !query_once_interactive(player)){
+      return ([]);
+  }
+  // alle EKs, die der Spieler hat
+  string eks = (string)master()->query_ek(getuid(player));
+  // als Tips kommen alle in Frage, die er nicht hat, vor dem Invertieren muss
+  // aber sichergestellt werden, dass eks min. so lang ist wie active_eks, da
+  // die Invertierung ja z.B. nur EKs 0-1700 beruecksichtigt, wenn 1700 der
+  // hoechste EK im Spieler ist und dann als Tips alle EKs ueber
+  // 1700 verlorengingen.
+  // hier wird das letzte Bit von active_eks ermittelt und das darauf folgende
+  // Bit im Spieler-EK-String gesetzt und wieder geloescht, woraufhin der
+  // EK-String min. so lang wie active_eks ist. (es ist egal, wenn er
+  // laenger ist, auch egal, wenn man ein Bit ueberschreibt, das faellt alles
+  // gleich beim and_bits() raus.
+  int lb = last_bit(active_eks) + 1;
+  eks = clear_bit(set_bit(eks, lb), lb);
+  // jetzt invertieren
+  string non_eks = invert_bits(eks);
+  // jetzt vorhande EK-Tips ausfiltern. Im Prinzip gleiches Spiel wie oben.
+  string ektips = (string)master()->query_ektips(getuid(player));
+  // jetzt alle nicht als Tip vergebenen NPC ermitteln, vor dem Invertieren
+  // wieder Laenge angleichen...
+  ektips = invert_bits(clear_bit(set_bit(ektips, lb), lb));
+  // verunden liefert EKs, die der Spieler nicht hat und nicht als Tip hat
+  ektips = and_bits(ektips, non_eks);
+
+  // und nun die inaktive EKs ausfiltern, nochmal verunden
+  ektips = and_bits(ektips, active_eks);
+
+  // mal Platz reservieren, entsprechend der Menge an Bits
+  mapping freeKills = m_allocate( count_bits(ektips), 2);
+  // durch alle jetzt gesetzten Bits laufen, d.h. alle EKs, die der Spieler
+  // nicht hat und ein Mapping a la npcs erstellen.
+  int p=-1;
+  while ( (p=next_bit(ektips, p)) != -1) {
+    freeKills += ([ by_num[p,0]: p; by_num[p,1] ]);
+  }
+
+  return freeKills;
+}
+
+public int addTip(mixed key,string tip)
+{
+  string fn;
+  
+  if (!allowed())
+    return SCORE_NO_PERMISSION;
+
+  if (!tip || (!objectp(key) && !stringp(key)))
+    return SCORE_INVALID_ARG;
+
+  fn=load_name(key);
+
+  if (!member(npcs, fn)) return SCORE_INVALID_ARG;
+  tipList+=([fn:tip]);
+  save_object(SCORESAVEFILE);
+    
+  return 1;
+}
+
+public int changeTip(mixed key,string tip)
+{
+    return addTip(key,tip);
+}
+
+public int removeTip(mixed key)
+{
+  string fn;
+  
+  if (!allowed())
+    return SCORE_NO_PERMISSION;
+  
+  if ((!objectp(key) && !stringp(key)))
+    return SCORE_INVALID_ARG;
+
+  fn=load_name(key);
+  
+  if (!member(tipList, fn)) return SCORE_INVALID_ARG;
+    
+  m_delete(tipList,fn);
+  save_object(SCORESAVEFILE);
+    
+  return 1;  
+}
+
+private string getTipFromList(mixed key)
+{
+  string fn;
+  
+  if (!ektipAllowed())
+    return "";
+  
+  if ((!objectp(key) && !stringp(key)))
+    return "";
+
+  fn=load_name(key);
+  
+  if (!member(tipList, fn)) return "";
+        
+  return tipList[fn];  
+}
+
+private string _getTip(mixed key)
+{
+  string fn;
+  string tip;
+  string* path;
+    
+  if ((!objectp(key) && !stringp(key)))
+    return "";
+
+  fn=load_name(key);
+  
+  if(!member(npcs,fn)) return "";
+  
+  tip=getTipFromList(fn);
+  if(!tip || tip==""){
+    path=explode(fn,"/")-({""});
+    if(sizeof(path)<3) return "";
+    if(path[0]=="players") {
+      string tiptext;
+      if ( path[1] == "ketos" )
+        tiptext = "Ketos im Gebirge";
+      else if ( path[1] == "boing" && path[2] == "friedhof" )
+        tiptext = "Boing im eisigen Polar";
+      else
+        tiptext = capitalize(path[1]);
+      return "Schau Dich doch mal bei "+tiptext+" um.";
+    }
+    
+    if(path[0]=="d")
+    {
+      tip+="Schau Dich doch mal ";
+    
+      if(file_size("/players/"+path[2])==-2)
+      {
+        tip+="bei "+capitalize(path[2]+" ");
+      }
+      if ( path[1]=="polar" && file_size("/players/"+path[3])==-2 )
+      {
+        tip+="bei "+capitalize(path[3])+" ";
+      }
+
+      if(path[1]=="anfaenger")
+        tip+="in den Anfaengergebieten ";
+      if(path[1]=="fernwest")
+        tip+="in Fernwest ";
+      if(path[1]=="dschungel")
+        tip+="im Dschungel ";
+      if(path[1]=="schattenwelt")
+        tip+="in der Welt der Schatten ";
+      if(path[1]=="unterwelt")
+        tip+="in der Unterwelt ";
+      if(path[1]=="gebirge")
+        tip+="im Gebirge ";
+      if(path[1]=="seher")
+        tip+="bei den Sehergebieten ";
+      if(path[1]=="vland")
+        tip+="auf dem Verlorenen Land ";
+      if(path[1]=="ebene")
+        tip+="in der Ebene ";
+      if(path[1]=="inseln")
+        tip+="auf den Inseln ";
+      if(path[1]=="wald")
+        tip+="im Wald ";
+      if(path[1]=="erzmagier")
+        tip+="bei den Erzmagiern ";
+      if(path[1]=="polar")
+      {
+        if (path[2]=="files.chaos")
+          tip+="in den Raeumen der Chaosgilde ";
+        tip+="im eisigen Polar ";
+      }
+      if(path[1]=="wueste")
+        tip+="in der Wueste ";
+      tip+="um.";
+    }
+    else if ( path[0]=="gilden" )
+    {
+      tip+="Schau Dich doch mal";
+      switch( path[1] )
+      {
+        case "mon.elementar": 
+          tip+=" unter den Anfuehrern der Elementargilde"; 
+          break;
+        case "files.dunkelelfen":
+          tip+=" unter den Anfuehrern der Dunkelelfengilde";
+          break;
+        case "files.klerus":
+          tip+=" beim Klerus"; 
+          break;
+        case "files.werwoelfe": 
+          tip+=" unter den Anfuehrern der Werwoelfe";
+          break;
+        case "files.chaos": 
+          tip+=" unter den Anfuehrern der Chaosgilde";
+          break;
+        default: 
+          tip+=" in einer der Gilden"; 
+          break;
+      }
+      tip+=" um.";
+    }
+    else if ( path[0] == "p" ) 
+    {
+      tip+="Schau Dich doch mal ";
+      switch( path[1] ) 
+      {
+        case "zauberer":
+          tip+="in der Zauberergilde zu Taramis";
+          break;
+        case "kaempfer":
+          tip+="bei den Angehoerigen des Koru-Tschakar-Struvs";
+          break;
+        case "katzenkrieger":
+          tip+="bei der Gilde der Katzenkrieger";
+          break;
+        case "tanjian":
+          tip+="unter den Meistern der Tanjiangilde";
+          break;
+      }
+      tip+=" um.";
+    }
+  }
+  return tip;
+}
+
+// return valid tips from database or existing 
+public string getTip(mixed key)
+{
+  string fn;
+  string tip;
+  string* path;
+  
+  if (!ektipAllowed())
+    return "";
+  
+  return _getTip(key);
+}
+
+// liefert ein Array mit allen Objekten zurueck, auf die bitstr verweist, also
+// eine Liste aller Objekte, die als Tip vergeben wurden.
+private string* makeTiplistFromBitString(string bitstr)
+{ 
+  string * ret= allocate(count_bits(bitstr));
+  // ueber alle gesetzten bits laufen und Array zusammensammeln
+  int i;
+  int p=-1;
+  while ((p=next_bit(bitstr,p)) != -1) {
+    ret[i] = by_num[p, 0];
+    i++;
+  }
+  // zur Sicherheit
+  ret -= ({0});
+
+  return ret;
+}
+
+// gibt die Objektnamen der EK-Tips vom jeweiligen Spieler zurueck.
+public string *QueryTipObjects(mixed player) {
+  
+  if (extern_call() && !allowed())
+      return 0;
+  if (objectp(player) && query_once_interactive(player))
+      player=getuid(player);
+  if (!stringp(player))
+    return 0;
+
+  string tipstr=(string)master()->query_ektips(player);
+  // jetzt EK-Tips ausfiltern, die erledigt sind. Dazu EK-Liste holen...
+  string eks=(string)master()->query_ek(player);
+  // als Tips kommen alle in Frage, die er nicht hat, vor dem Invertieren muss
+  // aber sichergestellt werden, dass eks min. so lang ist wie tipstr, da
+  // die Invertierung ja z.B. nur EKs 0-1700 beruecksichtigt, wenn 1700 der
+  // hoechste EK im Spieler ist und dann alle Tips ueber
+  // 1700 verlorengingen.
+  // hier wird das letzte Bit von tipstr ermittelt und das darauf folgende
+  // Bit im Spieler-EK-String gesetzt und wieder geloescht, woraufhin der
+  // EK-String min. so lang wie der Tipstring ist. (es ist egal, wenn er
+  // laenger ist, auch egal, wenn man ein Bit ueberschreibt, das faellt alles
+  // gleich beim and_bits() raus.
+  int lb = last_bit(tipstr) + 1;
+  eks = clear_bit(set_bit(eks, lb), lb);
+  // jetzt invertieren
+  string non_eks = invert_bits(eks);
+  // jetzt verunden und man hat die Tips, die noch nicht gehauen wurden.
+  tipstr = and_bits(tipstr, non_eks);
+  // noch inaktive EKs ausfiltern...
+  tipstr = and_bits(tipstr, active_eks);
+
+  return makeTiplistFromBitString(tipstr);
+}
+
+public string allTipsForPlayer(object player)
+{
+
+  if(!player || !this_interactive() || 
+      (this_interactive()!=player && !IS_ARCH(this_interactive())) )
+    return "";    
+ 
+  string *tips = QueryTipObjects(player);
+
+  tips = map(tips, #'_getTip);
+  tips -= ({0,""});
+
+  return implode(tips, "\n");
+}
+
+public status playerMayGetTip(object player)
+{
+  int numElegible;
+  int numReceived;
+  int lvl;
+  int i;
+  string tips;
+  
+  if(!ektipAllowed() || !player || !query_once_interactive(player))      
+      return 0;
+
+  if(!player || !query_once_interactive(player))    
+      return 0;
+  
+  lvl=(int)player->QueryProp(P_LEVEL);
+  numElegible=0;
+  i=sizeof(EKTIPS_LEVEL_LIMITS)-1;
+
+  if(lvl>EKTIPS_LEVEL_LIMITS[i])    
+      numElegible+=(lvl-EKTIPS_LEVEL_LIMITS[i]);
+
+  for(i;i>=0;i--){
+      if(lvl>=EKTIPS_LEVEL_LIMITS[i]) numElegible++;
+  }
+
+  tips=(string)MASTER->query_ektips(getuid(player)) || "";
+  // inaktive EKs ausfiltern.
+  tips = and_bits(tips, active_eks);
+  // und Gesamtzahl an Tips zaehlen. Hier werden erledigte Tips explizit nicht
+  // ausgefiltert!
+  numReceived=count_bits(tips);
+
+  return numElegible>numReceived;
+}
+
+public string giveTipForPlayer(object player)
+{
+  string* tmp;
+  mapping free;
+  string tip,pl,ektip;
+  int index;
+  
+  if(!ektipAllowed() || !player || 
+      !query_once_interactive(player) || !playerMayGetTip(player))  
+    return "";
+  
+  pl=getuid(player);
+  free=getFreeEKsForPlayer(player);
+
+  if(!mappingp(free) || sizeof(free)==0)
+    return "";
+
+  tmp=m_indices(free);
+
+  ektip=(string)MASTER->query_ektips(pl) || "";
+ 
+  foreach(int i: EKTIPS_MAX_RETRY) {
+      index=random(sizeof(tmp));
+      tip=getTip(tmp[index]);
+      if (stringp(tip) && sizeof(tip)) {
+    ektip=set_bit(ektip,npcs[tmp[index],NPC_NUMBER]);
+    MASTER->update_ektips(pl,ektip);
+    break; //fertig
+      }
+  }
+
+  return tip;  
+}
+
+// checkt NPCs auf Existenz und Ladbarkeit
+public void CheckNPCs(int num) {
+  string fn;
+  object ekob;
+  if (!num) rm(SCORECHECKFILE);
+  while (num <= lastNum && get_eval_cost() > 1480000) {
+    if (!by_num[num,1] || !by_num[num,0]) {
+      num++;
+      continue;
+    }
+    fn = by_num[num,0] + ".c";
+    if (file_size(fn) <= 0) {
+      // File nicht existent
+      write_file(SCORECHECKFILE, sprintf(
+    "EK %.4d ist nicht existent (%s)\n",num,fn));
+    }
+    else if (catch(ekob=load_object(fn)) || !objectp(ekob) ) {
+      // NPC offenbar nicht ladbar.
+      write_file(SCORECHECKFILE, sprintf(
+    "EK %.4d ist nicht ladbar (%s)\n",num,fn));
+    }
+    num++;
+  }
+  ZDEBUG(sprintf("%d NPC checked",num));
+  if (num <= lastNum)
+    call_out(#'CheckNPCs,4,num);
+  else
+    ZDEBUG("Finished!");
+}
+
+// liquidiert einen EK endgueltig. An diesem Punkt wird davon augegangen, dass
+// kein Spieler den EK mehr gesetzt hat!
+private void LiquidateEK(int bit) {
+
+  if (!intp(bit) || bit < 0) return;
+
+  string obname = by_num[bit, BYNUM_KEY];
+  int score = by_num[bit, BYNUM_SCORE];
+
+  if (member(npcs, obname) && (npcs[obname, NPC_NUMBER] == bit)) {
+    m_delete(by_num, bit);
+    m_delete(npcs, obname);
+    if (member(unconfirmed_scores,bit))
+      m_delete(unconfirmed_scores,bit);
+    active_eks = clear_bit(active_eks,bit);
+    removeTip(obname);
+    free_num += ({bit});
+    write_file(SCOREAUTOLOG,sprintf(
+    "LIQUIDATEEK: %s %5d Score %3d %s\n",
+    strftime("%d%m%Y-%T",time()), bit, score, obname));
+  }
+}
+
+private void check_player(string pl) {
+  int changed, changed2; 
+  
+  // EKs pruefen
+  string eks=(string)master()->query_ek(pl) || "";
+  string *opfer=allocate( (sizeof(eks)*6)+1, "");
+  int p=-1;
+  while ((p=next_bit(eks,p)) != -1) {
+    if (!member(by_num, p)) {
+  write_file(SCORECHECKFILE, sprintf(
+   "UNKNOWNEK %s %5d in %s gefunden.\n", 
+    strftime("%d%m%Y-%T",time()), p, pl));
+    }
+    // wenn das aktuelle Bit geloescht werden soll, also in to_be_removed
+    // steht...
+    if (member(to_be_removed,p) != -1) {
+  eks = clear_bit(eks,p);
+  changed=1;
+  write_file(EKCLEANLOG,sprintf(
+        "CLEARBIT: %s %O %5d %s\n",
+        strftime("%d%m%Y-%T",time()), pl, p, 
+        by_num[p,BYNUM_KEY] || "NoName"));
+    }
+    else {
+      // sonst statistikpflege
+      npcs[by_num[p,BYNUM_KEY],NPC_COUNT]++;
+      // loggen, welche NPC der Spieler hat
+      opfer[p]=to_string(p);
+    }
+  }
+  // und noch die Ek-Tips...
+  string ektips = (string)master()->query_ektips(pl) || "";
+  p = -1;
+  while ((p=next_bit(ektips,p)) != -1) {
+    if (!member(by_num, p)) {
+  write_file(EKCLEANLOG, sprintf(
+    "UNKNOWNEK %s %5d in %s (EKTips) gefunden - clearing.\n", 
+    strftime("%d%m%Y-%T",time()), p, pl));
+  ektips = clear_bit(ektips, p); // hier direkt loeschen.
+  changed2 = 1;
+    }
+    // wenn das aktuelle Bit geloescht werden soll, also in to_be_removed
+    // steht...
+    else if (member(to_be_removed,p) != -1) {
+  ektips = clear_bit(ektips,p);
+  changed2=1;
+  write_file(EKCLEANLOG,sprintf(
+        "CLEAREKTIP: %s %O %5d %s\n",
+        strftime("%d%m%Y-%T",time()), pl, p, 
+        by_num[p,BYNUM_KEY] || "NoName"));
+    }
+  }
+
+  if (changed) {
+      master()->update_ek(pl, eks);
+  }
+  if (changed2) {
+      master()->update_ektips(pl, ektips);
+  }
+  opfer-=({""});
+  write_file(WERKILLTWEN,sprintf("%s\n%=-78s\n\n",pl,CountUp(opfer)||""));
+}
+
+public void check_all_player(mapping allplayer) {
+ 
+  if (extern_call() && !allowed())
+      return;
+
+  if (!mappingp(allplayer)) {
+      foreach(string key: npcs) {
+  npcs[key,NPC_COUNT]=0;
+      }
+      allplayer=(mapping)master()->get_all_players();
+      rm(WERKILLTWEN);
+      call_out(#'check_all_player,2,allplayer);
+      return;
+  }
+
+  // offenbar fertig mit allen Spielern, jetzt noch den Rest erledigen.
+  if (!sizeof(allplayer)) {
+    foreach(int bit: to_be_removed) {
+      LiquidateEK(bit);
+    }
+    to_be_removed=({});
+    save_object(SCORESAVEFILE); 
+    ZDEBUG("Spielerchecks und EK-Liquidation fertig.\n");
+    return;
+  }
+
+  string dir=m_indices(allplayer)[0];
+  string *pls=allplayer[dir];
+  foreach(string pl: pls) {
+    if (get_eval_cost() < 1250000)
+      break; // spaeter weitermachen.
+    catch(check_player(pl) ; publish);
+    pls-=({pl});
+  }
+  allplayer[dir] = pls; 
+ 
+  if (!sizeof(allplayer[dir]))
+    m_delete(allplayer,dir);
+ 
+  call_out(#'check_all_player,2,allplayer);
+}
+
diff --git a/secure/scoremaster.h b/secure/scoremaster.h
new file mode 100644
index 0000000..900cf5f
--- /dev/null
+++ b/secure/scoremaster.h
@@ -0,0 +1,58 @@
+// MorgenGrauen MUDlib
+//
+// scoremaster.h -- Definitionen fuer den ScoreMaster
+//
+// $Id: scoremaster.h 6633 2007-12-06 21:49:02Z Zesstra $
+
+#ifndef _SCOREMASTER_H_
+#define _SCOREMASTER_H_ 1
+
+#define SCOREMASTER "/secure/scoremaster"
+
+#define SCORESAVEFILE    "/secure/ARCH/npcmaster"
+#define SCOREDUMPFILE    "/secure/ARCH/SCORES.dump"
+#define SCORELOGFILE     "/secure/ARCH/SCORES.LOG"
+#define SCOREAUTOLOG     "/secure/ARCH/SCORES_AUTO.LOG"
+#define SCORECHECKFILE   "/secure/ARCH/SCORES.kaputt"
+#define EKCLEANLOG       "/secure/ARCH/EKCLEANER.LOG"
+#define WERKILLTWEN      "/secure/ARCH/WERKILLTWEN.LOG"
+
+// wer ist fuer die EKs aktuell zustaendig?
+#define SCOREMAINTAINERS ({"arathorn", "zesstra"})
+
+// Elemente des von QueryScore() zurueckgegebenen Arrays
+#define SCORE_NUMBER	0   // (Bit-)Nummer des Objekts
+#define SCORE_SCORE	1   // Zahl der Stufenpunkte
+#define SCORE_KEY	2   // Filename des Obkekts
+
+// Indizes fuer das Mapping npcs
+#define NPC_NUMBER      0   // (Bit-)Nummer des EKs
+#define NPC_SCORE       1   // Zahl der Stufenpunkte
+#define NPC_COUNT       2   // wie oft der EK vergeben wurde
+// Indizes fuer das Mapping by_num
+#define BYNUM_KEY       0   // Filename des Objekts
+#define BYNUM_SCORE     1   // Zahl der Stufenpunkte
+
+#define SCORE_INVALID_ARG   -1
+#define SCORE_NO_PERMISSION -2
+
+#define SCORE_LOW_MARK	200000
+#define SCORE_HIGH_MARK 600000
+
+#define P_NO_SCORE "no_score"
+
+// moegliche Werte fuer 'flag' bei TestScore()
+#define SCORE_KILL  0x01
+#define SCORE_QUEST 0x02
+
+#define EK_GIVENLOG(x) log_file("ARCH/EK_GIVEN", \
+        dtime(time())+" "+x+"\n",100000)
+
+// ein paar defines fuer ektips
+#define EKTIPS_MAX_RETRY		10
+// one tip per level in list, above top entry one tip every level
+#define EKTIPS_LEVEL_LIMITS 	({60,65,70,74,78,82,85,88,91,93,95,97})
+#define EKTIPGIVER "/d/ebene/muadib/dragon/npc/brumni"
+#define EKTIPLIST  "/d/ebene/muadib/dragon/obj/eklist"
+
+#endif
diff --git a/secure/shadowmaster.c b/secure/shadowmaster.c
new file mode 100644
index 0000000..e1fd51d
--- /dev/null
+++ b/secure/shadowmaster.c
@@ -0,0 +1,262 @@
+// MorgenGrauen MUDlib
+/** \file /secure/shadowmaster.c
+* Objekt, welches eine Liste mit aktiven Shadows hat.
+* Die simul_efun shadow() meldet das beschattende Objekt hier an. Nuetzlich,
+* damit man als Magier endlich mal rauskriegen kann, welche Shadows es
+* eigentlich gibt.
+* \author Zesstra
+* \date 07.08.2009
+* \version $Id$
+*/
+/* Changelog:
+*/
+#pragma strong_types
+#pragma no_clone
+#pragma no_inherit
+#pragma no_shadow
+#pragma pedantic
+
+#include <defines.h>
+#include <wizlevels.h>
+
+#define HOME(x) (__PATH__(0)+x)
+
+/** shadows ist ein mapping, welches alle Schatten und ihre Opfer enthaelt.
+ *
+ * Struktur: Schatten als Schluessel (object) und Beschatteter (object) als
+ * Wert.
+*/
+private mapping shadows = ([]);
+
+#ifndef DEBUG
+#define DEBUG(x)  if (find_player("zesstra"))\
+            tell_object(find_player("zesstra"),\
+                                      "SMDBG: "+x+"\n")
+#endif
+
+#define MEMORY "/secure/memory"
+
+#define SHADOW_OK             1
+#define SHADOW_ACCESS_DENIED -1
+#define SHADOW_EXISTS        -2
+#define SHADOW_UNKNOWN       -3
+
+protected void create() {
+  mixed tmp;
+  seteuid(getuid());
+  if (mappingp(tmp=MEMORY->Load("Schatten"))) {
+    shadows = tmp;
+    DEBUG("Daten aus MEMORY geladen.\n");
+  }
+  else {
+    DEBUG("Keine Daten in MEMORY vorhanden - reinitialisiere.\n");
+    if (MEMORY->Save("Schatten", shadows) != 1)
+      raise_error("Konnte Daten nicht im Memory ablegen.\n");
+  }
+}
+
+/** Liefert einen String mit allen Schatten und beschatteten Objekten zurueck.
+  * @return string - Liste von Schatten und Opfern
+  */
+public void DumpShadows() {
+  if (extern_call() && !ELDER_SECURITY ) return 0;
+
+  string res = "";
+  foreach(object schatten, object opfer: shadows)
+    if (schatten && opfer)
+      res += sprintf("%O -> %O\n",schatten,opfer);
+
+  printf("Folgende Shadows sind bekannt: \n\n%s\n", res);
+}
+
+/** Findet zu einem beschatteten Objekt den zugehoerigen Schatten.
+  @param[in] object - ein beschattetes Objekt.
+  @return object - Den Schatten oder 0.
+  @sa FindShadowsObject(), QueryObject(), QueryInfo()
+*/
+public object FindShadow(object opfer) {
+  if (!objectp(opfer))
+    return 0;
+
+  foreach(object schatten, object victim: shadows) {
+    if (opfer == victim)
+      // Schatten gefunden.
+      return schatten;
+  }
+  return 0;
+}
+
+/** Findet zu einem Schatten das beschattete Objekt.
+  @param[in] object - ein beschattendes Objekt.
+  @return object - Das beschattete Objekt oder 0.
+  @sa FindShadow(), QueryObject(), QueryInfo()
+*/
+public object FindShadowedObject(object schatten) {
+  return shadows[schatten];
+}
+
+/** Gibt Schatten und Beschatteten zurueck, falls ob eines von beiden ist.
+  Ist ob ein Schatten oder ein beschattetes Objekt, wird ein Array aus
+  Objekten geliefert. Hierbei werden ggf. alle Objekte in der
+  Beschattungshierarchie geliefert, von der ob Bestandteil ist.
+  @param[in] object - ein Objekt.
+  @return object* - ({a, b, c, d}) oder 0.
+  @sa FindShadow(), FindShadowedObject, QueryInfo()
+*/
+public object* QueryObject(object ob) {
+
+  if (!objectp(ob)) return 0;
+ 
+  object *res = ({ob});
+  object sh = FindShadow(ob);
+
+  while(sh) {
+    // es gibt einen Schatten, also Kette nach oben verfolgen.
+    res = ({sh}) + res;
+    sh = FindShadow(sh);
+  }
+
+  object vic = FindShadowedObject(ob);
+  while(vic) {
+    // es gibt ein beschattetes Objekt, Kette nach unten verfolgen.
+    res = res + ({vic});
+    vic = FindShadowedObject(vic);
+  }
+
+  if (sizeof(res) < 2) {
+    // Offenbar wird ob weder beschattet noch beschattet selber.
+    // Moeglicherweise wurde jedoch durch wilde Zerstoerung die Hierarchie
+    // getrennt. Falls ob nen Schatten ist, laesst sich das reparieren. Falls
+    // ob beschattet wird, hilft eigentlich nur ein reset(). Den moechte ich
+    // hier aber nicht machen, weils u.U. teuer sein koennte.
+    if (query_shadowing(ob)) {
+      shadows[ob] = query_shadowing(ob);
+      // nochmal.
+      return QueryObject(ob);
+    }
+    // scheinbar nicht.
+    return 0;
+  }
+
+  return res;
+}
+
+/** Gibt einen String mit Infos ueber ob zurueck.
+  Ist ob ein beschattetes Objekt oder ein Schatten, wird ein beschreibender
+  String zurueckgeliefert (schatten -> beschattetes Objekt). Hierbei wird ggf.
+  die gesamte Beschattungshierarchie angegeben (a -> b -> c -> d).
+  @param[in] object - ein Objekt.
+  @return string - String mit Infos ueber ob.
+  @sa FindShadow(), FindShadowedObject(), QueryObject()
+*/
+public string QueryInfo(object ob) {
+  object *shs = QueryObject(ob);
+  if (!shs) return 0;
+
+  return CountUp(map(shs, #'object_name), " -> ", " -> ");
+}
+
+/** Registriert einen Schatten und sein Opfer.
+  Registriert den Schatten und sein Opfer. Sollte ausschliesslich durch die
+  simul_efun oder spare_simul_fun gerufen werden.
+  @sa UnregisterShadow()
+*/
+public int RegisterShadow(object schatten) {
+  object opfer;
+
+  //DEBUG(sprintf("[%O] %O\n", schatten, caller_stack()));
+
+  // Irgendein Sicherheitscheck ist eigentlich nicht noetig hier, da das Opfer
+  // ohnehin per efun ermittelt wird und kein Eintrag erfolgt, wenn kein
+  // beschattetes Objekt zu finden ist.
+
+  // das von schatten beschattete Objekt ermitteln 
+  if (objectp(opfer=query_shadowing(schatten))) {
+    if (shadows[schatten] == opfer)
+      return SHADOW_EXISTS;
+
+    // Neueintrag oder ggf. auch Aendern.
+    shadows[schatten] = opfer;
+    //DEBUG(DumpShadows());
+    return SHADOW_OK;
+  }
+  return SHADOW_ACCESS_DENIED;
+}
+
+/** Traegt einen Schatten wieder aus.
+  Der Schatten wird wieder ausgetragen. Sollte ausschliesslich durch die
+  simul_efun oder spare_simul_fun gerufen werden.
+  Werden Schatten oder beschattete Objekte zerstoert ohne vorher die
+  Schattierung zu beenden, fuehrt dies zu Inkonsistenzen und zerbrochenen
+  Beschattungshierarchien.
+  @sa RegisterShadow(), UnregisterOpfer()
+*/
+public int UnregisterShadow(object caller) {
+  object schatten, opfer;
+
+  // Ein Sicherheitscheck fuer den Aufruf ist eigentlich nicht noetig, da ein
+  // Eintrag nur entfernt wird, wenn die Beschattung nachweislich beendet
+  // wurde.
+
+  //DEBUG(sprintf("[%O] %O\n", caller, caller_stack()));
+  if (!objectp(caller)) return SHADOW_UNKNOWN;
+
+  // Schatten und beschatteten aus den lokalen Daten ermitteln.
+  if (member(shadows, caller)) {
+    schatten = caller;
+    opfer = shadows[schatten];
+  }
+  else if (objectp(schatten = FindShadow(caller))) {
+    opfer = caller;
+  }
+  // wenn nicht bekannt, ist jetzt eh Ende.
+  if (!schatten) return SHADOW_UNKNOWN;
+
+  //DEBUG(sprintf("%O -> %O (%O, %O)\n",
+  //      schatten, opfer, caller, query_shadowing(schatten)));
+
+  // Schattierung wirklich beendet? Wenn nicht -> Ende
+  if (opfer && query_shadowing(schatten) == opfer)
+    return SHADOW_ACCESS_DENIED;
+
+  // war schatten in einer Beschattungshierarchie?
+  object up = FindShadow(schatten);
+  if (up && query_shadowing(up) == opfer) {
+    shadows[up] = opfer; // Kette neu verlinken
+  }
+
+  // jetzt kann geloescht werden.
+  m_delete(shadows,schatten);
+  return SHADOW_OK;
+}
+
+public int ResetAll() {
+  if (!ARCH_SECURITY) return SHADOW_ACCESS_DENIED;
+
+  DEBUG("ResetAll() called.\n");
+
+  shadows = ([]);
+
+  if (MEMORY->Save("Schatten", shadows) != 1)
+    raise_error("Konnte Daten nicht im Memory ablegen.\n");
+
+  return SHADOW_OK;
+}
+
+/** Raeumt die Daten ueber die Schatten auf.
+    Zerstoeren sich beschattete Objekte, werden die Schatten nicht
+    ausgetragen, daher wird das von zeit zu zeit hier gemacht.
+*/
+public void reset() {
+  foreach(object schatten, object opfer: shadows) {
+    if (!objectp(opfer)) {
+      // war schatten evtl. in einer Hierarchie und beschattet jetzt was
+      // anderes?
+      if (query_shadowing(schatten))
+        shadows[schatten] = query_shadowing(schatten);
+      else
+        m_delete(shadows, schatten);
+    }
+  }
+}
+
diff --git a/secure/simul_efun/README b/secure/simul_efun/README
new file mode 100644
index 0000000..303429a
--- /dev/null
+++ b/secure/simul_efun/README
@@ -0,0 +1,20 @@
+simul_efun 
+---------- 
+Das simul_efun Objekt /secure/simul_efun/simul_efun benutzt die Dateien
+in /secure/simul_efun.
+Im Verzeichnis /secure/simul_efun/spare ist eine Sicherheitskopie von allen 
+Dateien in /secure/simul_efun.
+
+Falls /secure/simul_efun/simul_efun.c nicht ladbar ist, wird 
+/secure/simul_efun/spare/simul_efun.c als Ersatz versucht. Wenn auch das
+nicht ladbar ist, erfolgt ein Shutdown des Muds.
+
+Bei Aenderungen sollte _zuerst_ die normale simul efun editiert und
+anschliessend zerstoert/entladen werden, woraufhin sie implizit durch den
+Driver und Master neugeladen wird. Ist dies erfolgreich und die neue
+simul_efun laeuft (erst dann!) kopiert man die Dateien aus 
+/secure/simul_efun nach /secure/simul_efun/spare.
+
+Die Dateien hier sind mit Ausnahme der simul_efun.c selbst _nicht_ dazu 
+gedacht, geladen zu werden.
+
diff --git a/secure/simul_efun/comm.c b/secure/simul_efun/comm.c
new file mode 100644
index 0000000..595b852
--- /dev/null
+++ b/secure/simul_efun/comm.c
@@ -0,0 +1,124 @@
+#include <living/comm.h>
+
+// Sendet an alle Objekte in room und room selber durch Aufruf von
+// ReceiveMsg().
+varargs void send_room(object|string room, string msg, int msg_type,
+                       string msg_action, string msg_prefix, object *exclude,
+                       object origin)
+{
+  if (stringp(room))
+    room=load_object(room);
+
+  origin ||= previous_object();
+  object *dest = exclude ? all_inventory(room) - exclude :
+                           all_inventory(room);
+
+  dest->ReceiveMsg(msg, msg_type, msg_action, msg_prefix, origin);
+}
+
+static int _shout_filter( object ob, string pat )
+{
+    string *ignore;
+
+    if ( !environment(ob) )
+       return 0;
+
+    return sizeof( regexp( ({ object_name( environment(ob) ) }), pat ) );
+}
+
+varargs void shout( string s, mixed where ){
+    object *u;
+    string *pfade;
+
+    if ( !sizeof( u = users() - ({ this_player() }) ) )
+       return;
+
+    if ( !where )
+       pfade = ({ "/" });
+    else if ( intp(where) )
+       pfade =
+           ({ implode( efun::explode( object_name( environment(this_player()) ),
+                                   "/" )[0..2], "/" ) + "/" });
+    else if ( stringp(where) )
+       pfade = ({ where });
+    else
+       pfade = where;
+
+    u = filter( u, "_shout_filter", ME, implode( pfade, "|" ) );
+    u->ReceiveMsg(s, MT_COMM|MT_FAR|MSG_DONT_WRAP|MSG_DONT_STORE,
+                  MA_SHOUT_SEFUN, 0, previous_object());
+}
+
+
+#if __VERSION__ > "3.3.718"
+// This sefun replaces the deprecated efun cat().
+#define CAT_MAX_LINES 50
+varargs int cat(string file, int start, int num)
+{
+    if (extern_call())
+        set_this_object(previous_object());
+
+    int more;
+
+    if (num < 0 || !this_player())
+        return 0;
+
+    if (!start)
+        start = 1;
+
+    if (!num || num > CAT_MAX_LINES) {
+        num = CAT_MAX_LINES;
+        more = sizeof(read_file(file, start+num, 1));
+    }
+
+    string txt = read_file(file, start, num);
+    if (!txt)
+        return 0;
+
+    efun::tell_object(this_player(), txt);
+
+    if (more)
+        efun::tell_object(this_player(), "*****TRUNCATED****\n");
+
+    return sizeof(txt & "\n");
+}
+#undef CAT_MAX_LINES
+#endif // __VERSION__ > "3.3.718"
+
+#if __VERSION__ > "3.3.719"
+// This sefun replaces the deprecated efun tail().
+#define TAIL_MAX_BYTES 1000
+varargs int tail(string file)
+{
+    if (extern_call())
+        set_this_object(previous_object());
+
+    if (!stringp(file) || !this_player())
+        return 0;
+
+    string txt = read_bytes(file, -(TAIL_MAX_BYTES + 80), (TAIL_MAX_BYTES + 80));
+    // read_bytes() returns 0 if the start of the section given by
+    // parameter #2 lies beyond the beginning of the file.
+    // In this case we simply try and read the entire file.
+    if (!stringp(txt) && file_size(file) < TAIL_MAX_BYTES+80)
+      txt = read_file(file);
+    // Exit if still nothing could be read.
+    if (!stringp(txt))
+      return 0;
+
+    // cut off first (incomplete) line
+    int index = strstr(txt, "\n");
+    if (index > -1) {
+        if (index + 1 < sizeof(txt))
+            txt = txt[index+1..];
+        else
+            txt = "";
+    }
+
+    efun::tell_object(this_player(), txt);
+
+    return 1;
+}
+#undef TAIL_MAX_BYTES
+#endif // __VERSION__ > "3.3.719"
+
diff --git a/secure/simul_efun/debug_info.c b/secure/simul_efun/debug_info.c
new file mode 100644
index 0000000..d607c2f
--- /dev/null
+++ b/secure/simul_efun/debug_info.c
@@ -0,0 +1,434 @@
+/* This sefun is to provide a replacement for the efun debug_info().
+ * Feel free to add it to your mudlibs, if you have much code relying on that.
+ */
+
+#if ! __EFUN_DEFINED__(debug_info)
+
+#include <driver_info.h>
+#include <debug_info.h>
+
+mixed debug_info(int what, varargs mixed* args)
+{
+    if (sizeof(args) > 2)
+        raise_error("Too many arguments to debug_info\n");
+
+    switch(what)
+    {
+        default:
+            raise_error(sprintf("Illegal value %d for debug_info().\n", what));
+
+        case DINFO_OBJECT:
+        {
+            object ob;
+
+            if (sizeof(args) != 1)
+                raise_error("bad number of arguments to debug_info\n");
+            if (!objectp(args[0]))
+                raise_error("bag arg 2 to debug_info().\n");
+
+            ob = args[0];
+            printf("O_HEART_BEAT      : %s\n", efun::object_info(ob, OC_HEART_BEAT)       ? "TRUE" : "FALSE");
+            printf("O_ENABLE_COMMANDS : %s\n", efun::object_info(ob, OC_COMMANDS_ENABLED) ? "TRUE" : "FALSE");
+            printf("O_CLONE           : %s\n", efun::clonep(ob)                           ? "TRUE" : "FALSE");
+            printf("O_DESTRUCTED      : FALSE\n");
+            printf("O_SWAPPED         : %s\n", efun::object_info(ob, OI_SWAPPED)          ? "TRUE" : "FALSE");
+            printf("O_ONCE_INTERACTIVE: %s\n", efun::object_info(ob, OI_ONCE_INTERACTIVE) ? "TRUE" : "FALSE");
+            printf("O_RESET_STATE     : %s\n", efun::object_info(ob, OI_RESET_STATE)      ? "TRUE" : "FALSE");
+            printf("O_WILL_CLEAN_UP   : %s\n", efun::object_info(ob, OI_WILL_CLEAN_UP)    ? "TRUE" : "FALSE");
+            printf("O_REPLACED        : %s\n", efun::object_info(ob, OI_REPLACED)         ? "TRUE" : "FALSE");
+            printf("time_reset  : %d\n",       efun::object_info(ob, OI_NEXT_RESET_TIME));
+            printf("time_of_ref : %d\n",       efun::object_info(ob, OI_LAST_REF_TIME));
+            printf("ref         : %d\n",       efun::object_info(ob, OI_OBJECT_REFS));
+
+            int gticks = efun::object_info(ob, OI_GIGATICKS);
+            if(gticks)
+                printf("evalcost   :  %d%09d\n", gticks, efun::object_info(ob, OI_TICKS));
+            else
+                printf("evalcost   :  %d\n",   efun::object_info(ob, OI_TICKS));
+
+            printf("swap_num    : %d\n",       efun::object_info(ob, OI_SWAP_NUM));
+            printf("name        : '%s'\n",     efun::object_name(ob));
+            printf("load_name   : '%s'\n",     efun::load_name(ob));
+
+            object next_ob = efun::object_info(ob, OI_OBJECT_NEXT);
+            if (next_ob)
+                printf("next_all    : OBJ(%s)\n", efun::object_name(next_ob));
+
+            object prev_ob = efun::object_info(ob, OI_OBJECT_PREV);
+            if (prev_ob)
+                printf("Previous object in object list: OBJ(%s)\n", efun::object_name(prev_ob));
+            else
+                printf("This object is the head of the object list.\n");
+            break;
+        }
+
+        case DINFO_MEMORY:
+        {
+            object ob;
+
+            if (sizeof(args) != 1)
+                raise_error("bad number of arguments to debug_info\n");
+            if (!objectp(args[0]))
+                raise_error("bag arg 2 to debug_info().\n");
+
+            ob = args[0];
+
+            printf("program ref's %3d\n",   efun::object_info(ob, OI_PROG_REFS));
+            printf("Name: '%s'\n",          efun::program_name(ob));
+            printf("program size    %6d\n", efun::object_info(ob, OI_PROG_SIZE));
+            printf("num func's:  %3d (%4d)\n",
+                                            efun::object_info(ob, OI_NUM_FUNCTIONS),
+                                            efun::object_info(ob, OI_SIZE_FUNCTIONS));
+            printf("num vars:    %3d (%4d)\n",
+                                            efun::object_info(ob, OI_NUM_VARIABLES),
+                                            efun::object_info(ob, OI_SIZE_VARIABLES));
+
+            printf("num strings: %3d (%4d) : overhead %d + data %d (%d)\n",
+                                            efun::object_info(ob, OI_NUM_STRINGS),
+                                            efun::object_info(ob, OI_SIZE_STRINGS) + efun::object_info(ob, OI_SIZE_STRINGS_DATA),
+                                            efun::object_info(ob, OI_SIZE_STRINGS),
+                                            efun::object_info(ob, OI_SIZE_STRINGS_DATA),
+                                            efun::object_info(ob, OI_SIZE_STRINGS_DATA_TOTAL));
+
+            printf("num inherits %3d (%4d)\n",
+                                            efun::object_info(ob, OI_NUM_INHERITED),
+                                            efun::object_info(ob, OI_SIZE_INHERITED));
+
+            printf("total size      %6d\n", efun::object_info(ob, OI_PROG_SIZE_TOTAL));
+
+            printf("data size       %6d (%6d\n",
+                                            efun::object_info(ob, OI_DATA_SIZE),
+                                            efun::object_info(ob, OI_DATA_SIZE_TOTAL));
+            break;
+        }
+
+        case DINFO_OBJLIST:
+        {
+            if (sizeof(args) == 0)
+                raise_error("bad number of arguments to debug_info\n");
+
+            if (sizeof(args) == 1)
+            {
+                object * obs = efun::objects(args[0], 1);
+                return obs[0];
+            }
+            else
+                return efun::objects(args[0], args[1]);
+            break;
+        }
+
+        case DINFO_MALLOC:
+            write(debug_info(DINFO_STATUS, "malloc"));
+            break;
+
+        case DINFO_STATUS:
+        {
+            string which;
+            int opt;
+
+            if (sizeof(args) > 1)
+                raise_error("bad number of arguments to debug_info\n");
+
+            if (!sizeof(args) || !args[0])
+                which = "";
+            else if(stringp(args[0]))
+                which = args[0];
+            else
+                raise_error("bag arg 2 to debug_info().\n");
+
+            switch(which)
+            {
+                case "":
+                    opt = DI_STATUS_TEXT_MEMORY;
+                    break;
+
+                case "tables":
+                    opt = DI_STATUS_TEXT_TABLES;
+                    break;
+
+                case "swap":
+                    opt = DI_STATUS_TEXT_SWAP;
+                    break;
+
+                case "malloc":
+                    opt = DI_STATUS_TEXT_MALLOC;
+                    break;
+
+                case "malloc extstats":
+                    opt = DI_STATUS_TEXT_MALLOC_EXTENDED;
+                    break;
+
+                default:
+                    return 0;
+            }
+
+            return efun::driver_info(opt);
+        }
+
+        case DINFO_DUMP:
+        {
+            int opt;
+
+            if (!sizeof(args))
+                raise_error("bad number of arguments to debug_info\n");
+
+            if(!stringp(args[0]))
+                raise_error("bag arg 2 to debug_info().\n");
+
+            switch(args[0])
+            {
+                case "objects":
+                    opt = DDI_OBJECTS;
+                    break;
+
+                case "destructed":
+                    opt = DDI_OBJECTS_DESTRUCTED;
+                    break;
+
+                case "opcodes":
+                    opt = DDI_OPCODES;
+                    break;
+
+                case "memory":
+                    opt = DDI_MEMORY;
+                    break;
+
+                default:
+                    raise_error(sprintf("Bad argument '%s' to debug_info(DINFO_DUMP).\n", args[0]));
+                    return 0;
+            }
+
+            return efun::dump_driver_info(opt, args[1..1]...);
+        }
+
+        case DINFO_DATA:
+        {
+            mixed * result;
+
+            if (!sizeof(args))
+                raise_error("bad number of arguments to debug_info\n");
+
+            if (!intp(args[0]))
+                raise_error("bag arg 2 to debug_info().\n");
+
+            if (sizeof(args) == 2 && !intp(args[1]))
+                raise_error("bag arg 3 to debug_info().\n");
+
+            switch(args[0])
+            {
+                case DID_STATUS:
+                    result = allocate(DID_STATUS_MAX);
+
+                    result[DID_ST_ACTIONS]               = efun::driver_info(DI_NUM_ACTIONS);
+                    result[DID_ST_ACTIONS_SIZE]          = efun::driver_info(DI_SIZE_ACTIONS);
+                    result[DID_ST_SHADOWS]               = efun::driver_info(DI_NUM_SHADOWS);
+                    result[DID_ST_SHADOWS_SIZE]          = efun::driver_info(DI_SIZE_SHADOWS);
+
+                    result[DID_ST_OBJECTS]               = efun::driver_info(DI_NUM_OBJECTS);
+                    result[DID_ST_OBJECTS_SIZE]          = efun::driver_info(DI_SIZE_OBJECTS);
+                    result[DID_ST_OBJECTS_SWAPPED]       = efun::driver_info(DI_NUM_OBJECTS_SWAPPED);
+                    result[DID_ST_OBJECTS_SWAP_SIZE]     = efun::driver_info(DI_SIZE_OBJECTS_SWAPPED);
+                    result[DID_ST_OBJECTS_LIST]          = efun::driver_info(DI_NUM_OBJECTS_IN_LIST);
+                    result[DID_ST_OBJECTS_NEWLY_DEST]    = efun::driver_info(DI_NUM_OBJECTS_NEWLY_DESTRUCTED);
+                    result[DID_ST_OBJECTS_DESTRUCTED]    = efun::driver_info(DI_NUM_OBJECTS_DESTRUCTED);
+                    result[DID_ST_OBJECTS_PROCESSED]     = efun::driver_info(DI_NUM_OBJECTS_LAST_PROCESSED);
+                    result[DID_ST_OBJECTS_AVG_PROC]      = efun::driver_info(DI_LOAD_AVERAGE_PROCESSED_OBJECTS_RELATIVE);
+
+                    result[DID_ST_OTABLE]                = efun::driver_info(DI_NUM_OBJECTS_IN_TABLE);
+                    result[DID_ST_OTABLE_SLOTS]          = efun::driver_info(DI_NUM_OBJECT_TABLE_SLOTS);
+                    result[DID_ST_OTABLE_SIZE]           = efun::driver_info(DI_SIZE_OBJECT_TABLE);
+
+                    result[DID_ST_HBEAT_OBJS]            = efun::driver_info(DI_NUM_HEARTBEATS);
+                    result[DID_ST_HBEAT_CALLS]           = efun::driver_info(DI_NUM_HEARTBEAT_ACTIVE_CYCLES);
+                    result[DID_ST_HBEAT_CALLS_TOTAL]     = efun::driver_info(DI_NUM_HEARTBEAT_TOTAL_CYCLES);
+                    result[DID_ST_HBEAT_SLOTS]           = efun::driver_info(DI_NUM_HEARTBEATS);
+                    result[DID_ST_HBEAT_SIZE]            = efun::driver_info(DI_SIZE_HEARTBEATS);
+                    result[DID_ST_HBEAT_PROCESSED]       = efun::driver_info(DI_NUM_HEARTBEATS_LAST_PROCESSED);
+                    result[DID_ST_HBEAT_AVG_PROC]        = efun::driver_info(DI_LOAD_AVERAGE_PROCESSED_HEARTBEATS_RELATIVE);
+
+                    result[DID_ST_CALLOUTS]              = efun::driver_info(DI_NUM_CALLOUTS);
+                    result[DID_ST_CALLOUT_SIZE]          = efun::driver_info(DI_SIZE_CALLOUTS);
+
+                    result[DID_ST_ARRAYS]                = efun::driver_info(DI_NUM_ARRAYS);
+                    result[DID_ST_ARRAYS_SIZE]           = efun::driver_info(DI_SIZE_ARRAYS);
+
+                    result[DID_ST_MAPPINGS]              = efun::driver_info(DI_NUM_MAPPINGS);
+                    result[DID_ST_MAPPINGS_SIZE]         = efun::driver_info(DI_SIZE_MAPPINGS);
+                    result[DID_ST_HYBRID_MAPPINGS]       = efun::driver_info(DI_NUM_MAPPINGS_HYBRID);
+                    result[DID_ST_HASH_MAPPINGS]         = efun::driver_info(DI_NUM_MAPPINGS_HASH);
+
+                    result[DID_ST_STRUCTS]               = efun::driver_info(DI_NUM_STRUCTS);
+                    result[DID_ST_STRUCTS_SIZE]          = efun::driver_info(DI_SIZE_STRUCTS);
+                    result[DID_ST_STRUCT_TYPES]          = efun::driver_info(DI_NUM_STRUCT_TYPES);
+                    result[DID_ST_STRUCT_TYPES_SIZE]     = efun::driver_info(DI_SIZE_STRUCT_TYPES);
+
+                    result[DID_ST_PROGS]                 = efun::driver_info(DI_NUM_PROGS);
+                    result[DID_ST_PROGS_SIZE]            = efun::driver_info(DI_SIZE_PROGS);
+
+                    result[DID_ST_PROGS_SWAPPED]         = efun::driver_info(DI_NUM_PROGS_SWAPPED);
+                    result[DID_ST_PROGS_SWAP_SIZE]       = efun::driver_info(DI_SIZE_PROGS_SWAPPED);
+
+                    result[DID_ST_USER_RESERVE]          = efun::driver_info(DI_MEMORY_RESERVE_USER);
+                    result[DID_ST_MASTER_RESERVE]        = efun::driver_info(DI_MEMORY_RESERVE_MASTER);
+                    result[DID_ST_SYSTEM_RESERVE]        = efun::driver_info(DI_MEMORY_RESERVE_SYSTEM);
+
+                    result[DID_ST_ADD_MESSAGE]           = efun::driver_info(DI_NUM_MESSAGES_OUT);
+                    result[DID_ST_PACKETS]               = efun::driver_info(DI_NUM_PACKETS_OUT);
+                    result[DID_ST_PACKET_SIZE]           = efun::driver_info(DI_SIZE_PACKETS_OUT);
+                    result[DID_ST_PACKETS_IN]            = efun::driver_info(DI_NUM_PACKETS_IN);
+                    result[DID_ST_PACKET_SIZE_IN]        = efun::driver_info(DI_SIZE_PACKETS_IN);
+
+                    result[DID_ST_APPLY]                 = efun::driver_info(DI_NUM_FUNCTION_NAME_CALLS);
+                    result[DID_ST_APPLY_HITS]            = efun::driver_info(DI_NUM_FUNCTION_NAME_CALL_HITS);
+
+                    result[DID_ST_STRINGS]               = efun::driver_info(DI_NUM_VIRTUAL_STRINGS);
+                    result[DID_ST_STRING_SIZE]           = efun::driver_info(DI_SIZE_STRINGS);
+                    result[DID_ST_STR_TABLE_SIZE]        = efun::driver_info(DI_SIZE_STRING_TABLE);
+                    result[DID_ST_STR_OVERHEAD]          = efun::driver_info(DI_SIZE_STRING_OVERHEAD);
+                    result[DID_ST_UNTABLED]              = efun::driver_info(DI_NUM_STRINGS_UNTABLED);
+                    result[DID_ST_UNTABLED_SIZE]         = efun::driver_info(DI_SIZE_STRINGS_UNTABLED);
+                    result[DID_ST_TABLED]                = efun::driver_info(DI_NUM_STRINGS_TABLED);
+                    result[DID_ST_TABLED_SIZE]           = efun::driver_info(DI_SIZE_STRINGS_TABLED);
+                    result[DID_ST_STR_SEARCHES]          = efun::driver_info(DI_NUM_STRING_TABLE_LOOKUPS_BY_INDEX);
+                    result[DID_ST_STR_SEARCHLEN]         = efun::driver_info(DI_NUM_STRING_TABLE_LOOKUP_STEPS_BY_INDEX);
+                    result[DID_ST_STR_SEARCHES_BYVALUE]  = efun::driver_info(DI_NUM_STRING_TABLE_LOOKUPS_BY_VALUE);
+                    result[DID_ST_STR_SEARCHLEN_BYVALUE] = efun::driver_info(DI_NUM_STRING_TABLE_LOOKUP_STEPS_BY_VALUE);
+                    result[DID_ST_STR_CHAINS]            = efun::driver_info(DI_NUM_STRING_TABLE_SLOTS_USED);
+                    result[DID_ST_STR_ADDED]             = efun::driver_info(DI_NUM_STRING_TABLE_STRINGS_ADDED);
+                    result[DID_ST_STR_DELETED]           = efun::driver_info(DI_NUM_STRING_TABLE_STRINGS_REMOVED);
+                    result[DID_ST_STR_COLLISIONS]        = efun::driver_info(DI_NUM_STRING_TABLE_COLLISIONS);
+                    result[DID_ST_STR_FOUND]             = efun::driver_info(DI_NUM_STRING_TABLE_HITS_BY_INDEX);
+                    result[DID_ST_STR_FOUND_BYVALUE]     = efun::driver_info(DI_NUM_STRING_TABLE_HITS_BY_VALUE);
+
+                    result[DID_ST_RX_CACHED]             = efun::driver_info(DI_NUM_REGEX);
+                    result[DID_ST_RX_TABLE]              = efun::driver_info(DI_NUM_REGEX_TABLE_SLOTS);
+                    result[DID_ST_RX_TABLE_SIZE]         = efun::driver_info(DI_SIZE_REGEX);
+                    result[DID_ST_RX_REQUESTS]           = efun::driver_info(DI_NUM_REGEX_LOOKUPS);
+                    result[DID_ST_RX_REQ_FOUND]          = efun::driver_info(DI_NUM_REGEX_LOOKUP_HITS);
+                    result[DID_ST_RX_REQ_COLL]           = efun::driver_info(DI_NUM_REGEX_LOOKUP_COLLISIONS);
+
+                    result[DID_ST_MB_FILE]               = efun::driver_info(DI_SIZE_BUFFER_FILE);
+                    result[DID_ST_MB_SWAP]               = efun::driver_info(DI_SIZE_BUFFER_SWAP);
+
+                    result[DID_ST_BOOT_TIME]             = efun::driver_info(DI_BOOT_TIME);
+                    break;
+
+                case DID_SWAP:
+                    result = allocate(DID_SWAP_MAX);
+
+                    result[DID_SW_PROGS]                 = efun::driver_info(DI_NUM_PROGS_SWAPPED);
+                    result[DID_SW_PROG_SIZE]             = efun::driver_info(DI_SIZE_PROGS_SWAPPED);
+                    result[DID_SW_PROG_UNSWAPPED]        = efun::driver_info(DI_NUM_PROGS_UNSWAPPED);
+                    result[DID_SW_PROG_U_SIZE]           = efun::driver_info(DI_SIZE_PROGS_UNSWAPPED);
+                    result[DID_SW_VARS]                  = efun::driver_info(DI_NUM_OBJECTS_SWAPPED);
+                    result[DID_SW_VAR_SIZE]              = efun::driver_info(DI_SIZE_OBJECTS_SWAPPED);
+                    result[DID_SW_FREE]                  = efun::driver_info(DI_NUM_SWAP_BLOCKS_FREE);
+                    result[DID_SW_FREE_SIZE]             = efun::driver_info(DI_SIZE_SWAP_BLOCKS_FREE);
+                    result[DID_SW_FILE_SIZE]             = efun::driver_info(DI_SIZE_SWAP_BLOCKS);
+                    result[DID_SW_REUSED]                = efun::driver_info(DI_SIZE_SWAP_BLOCKS_REUSED);
+                    result[DID_SW_SEARCHES]              = efun::driver_info(DI_NUM_SWAP_BLOCKS_REUSE_LOOKUPS);
+                    result[DID_SW_SEARCH_LEN]            = efun::driver_info(DI_NUM_SWAP_BLOCKS_REUSE_LOOKUP_STEPS);
+                    result[DID_SW_F_SEARCHES]            = efun::driver_info(DI_NUM_SWAP_BLOCKS_FREE_LOOKUPS);
+                    result[DID_SW_F_SEARCH_LEN]          = efun::driver_info(DI_NUM_SWAP_BLOCKS_FREE_LOOKUP_STEPS);
+                    result[DID_SW_COMPACT]               = efun::driver_info(DC_SWAP_COMPACT_MODE);
+                    result[DID_SW_RECYCLE_FREE]          = efun::driver_info(DI_SWAP_RECYCLE_PHASE);
+                    break;
+
+                case DID_MEMORY:
+                    result = allocate(DID_MEMORY_MAX);
+
+                    result[DID_MEM_NAME]                 = efun::driver_info(DI_MEMORY_ALLOCATOR_NAME);
+                    result[DID_MEM_SBRK]                 = efun::driver_info(DI_NUM_SYS_ALLOCATED_BLOCKS);
+                    result[DID_MEM_SBRK_SIZE]            = efun::driver_info(DI_SIZE_SYS_ALLOCATED_BLOCKS);
+                    result[DID_MEM_LARGE]                = efun::driver_info(DI_NUM_LARGE_BLOCKS_ALLOCATED);
+                    result[DID_MEM_LARGE_SIZE]           = efun::driver_info(DI_SIZE_LARGE_BLOCKS_ALLOCATED);
+                    result[DID_MEM_LFREE]                = efun::driver_info(DI_NUM_LARGE_BLOCKS_FREE);
+                    result[DID_MEM_LFREE_SIZE]           = efun::driver_info(DI_SIZE_LARGE_BLOCKS_FREE);
+                    result[DID_MEM_LWASTED]              = efun::driver_info(DI_NUM_LARGE_BLOCKS_WASTE);
+                    result[DID_MEM_LWASTED_SIZE]         = efun::driver_info(DI_SIZE_LARGE_BLOCKS_WASTE);
+                    result[DID_MEM_CHUNK]                = efun::driver_info(DI_NUM_SMALL_BLOCK_CHUNKS);
+                    result[DID_MEM_CHUNK_SIZE]           = efun::driver_info(DI_SIZE_SMALL_BLOCK_CHUNKS);
+                    result[DID_MEM_SMALL]                = efun::driver_info(DI_NUM_SMALL_BLOCKS_ALLOCATED);
+                    result[DID_MEM_SMALL_SIZE]           = efun::driver_info(DI_SIZE_SMALL_BLOCKS_ALLOCATED);
+                    result[DID_MEM_SFREE]                = efun::driver_info(DI_NUM_SMALL_BLOCKS_FREE);
+                    result[DID_MEM_SFREE_SIZE]           = efun::driver_info(DI_SIZE_SMALL_BLOCKS_FREE);
+                    result[DID_MEM_SWASTED]              = efun::driver_info(DI_NUM_SMALL_BLOCKS_WASTE);
+                    result[DID_MEM_SWASTED_SIZE]         = efun::driver_info(DI_SIZE_SMALL_BLOCKS_WASTE);
+                    result[DID_MEM_MINC_CALLS]           = efun::driver_info(DI_NUM_INCREMENT_SIZE_CALLS);
+                    result[DID_MEM_MINC_SUCCESS]         = efun::driver_info(DI_NUM_INCREMENT_SIZE_CALL_SUCCESSES);
+                    result[DID_MEM_MINC_SIZE]            = efun::driver_info(DI_SIZE_INCREMENT_SIZE_CALL_DIFFS);
+                    result[DID_MEM_PERM]                 = efun::driver_info(DI_NUM_UNMANAGED_BLOCKS);
+                    result[DID_MEM_PERM_SIZE]            = efun::driver_info(DI_SIZE_UNMANAGED_BLOCKS);
+                    result[DID_MEM_CLIB]                 = efun::driver_info(DI_NUM_REPLACEMENT_MALLOC_CALLS);
+                    result[DID_MEM_CLIB_SIZE]            = efun::driver_info(DI_SIZE_REPLACEMENT_MALLOC_CALLS);
+                    result[DID_MEM_OVERHEAD]             = efun::driver_info(DI_SIZE_SMALL_BLOCK_OVERHEAD);
+                    result[DID_MEM_ALLOCATED]            = efun::driver_info(DI_SIZE_MEMORY_USED) + efun::driver_info(DI_SIZE_MEMORY_OVERHEAD);
+                    result[DID_MEM_USED]                 = efun::driver_info(DI_SIZE_MEMORY_USED);
+                    result[DID_MEM_TOTAL_UNUSED]         = efun::driver_info(DI_SIZE_MEMORY_UNUSED);
+                    result[DID_MEM_DEFRAG_CALLS]         = efun::driver_info(DI_NUM_MEMORY_DEFRAGMENTATION_CALLS_FULL) + efun::driver_info(DI_NUM_MEMORY_DEFRAGMENTATION_CALLS_TARGETED);
+                    result[DID_MEM_DEFRAG_CALLS_REQ]     = efun::driver_info(DI_NUM_MEMORY_DEFRAGMENTATION_CALLS_TARGETED);
+                    result[DID_MEM_DEFRAG_REQ_SUCCESS]   = efun::driver_info(DI_NUM_MEMORY_DEFRAGMENTATION_CALL_TARGET_HITS);
+                    result[DID_MEM_DEFRAG_BLOCKS_INSPECTED] = efun::driver_info(DI_NUM_MEMORY_DEFRAGMENTATION_BLOCKS_INSPECTED);
+                    result[DID_MEM_DEFRAG_BLOCKS_MERGED] = efun::driver_info(DI_NUM_MEMORY_DEFRAGMENTATION_BLOCKS_MERGED);
+                    result[DID_MEM_DEFRAG_BLOCKS_RESULT] = efun::driver_info(DI_NUM_MEMORY_DEFRAGMENTATION_BLOCKS_RESULTING);
+                    result[DID_MEM_AVL_NODES]            = efun::driver_info(DI_NUM_FREE_BLOCKS_AVL_NODES);
+                    result[DID_MEM_EXT_STATISTICS]       = efun::driver_info(DI_MEMORY_EXTENDED_STATISTICS);
+                    break;
+            }
+
+            if (sizeof(args) == 2)
+            {
+                int idx = args[0];
+                if (idx < 0 || idx >= sizeof(result))
+                    raise_error(sprintf("Illegal index for debug_info(): %d, expected 0..%d\n",
+                        idx, sizeof(result)-1));
+
+                return result[idx];
+            }
+            else
+                return result;
+        }
+
+        case DINFO_TRACE:
+        {
+            int which = DIT_CURRENT;
+
+            if (sizeof(args) > 1)
+                raise_error("bad number of arguments to debug_info\n");
+            if (sizeof(args))
+            {
+                if (!intp(args[0]))
+                    raise_error("bag arg 2 to debug_info().\n");
+                which = args[0];
+            }
+
+            switch (which)
+            {
+                case DIT_CURRENT:
+                    return efun::driver_info(DI_TRACE_CURRENT);
+
+                case DIT_ERROR:
+                    return efun::driver_info(DI_TRACE_LAST_ERROR) || ({ "No trace." });
+
+                case DIT_UNCAUGHT_ERROR:
+                    return efun::driver_info(DI_TRACE_LAST_UNCAUGHT_ERROR) || ({ "No trace." });
+
+                case DIT_STR_CURRENT:
+                    return efun::driver_info(DI_TRACE_CURRENT_AS_STRING);
+
+                case DIT_CURRENT_DEPTH:
+                    return efun::driver_info(DI_TRACE_CURRENT_DEPTH);
+
+                default:
+                    raise_error("bad arg 2 to debug_info().\n");
+            }
+
+        }
+
+        case DINFO_EVAL_NUMBER:
+            return efun::driver_info(DI_EVAL_NUMBER);
+    }
+    return 0;
+}
+
+#endif
diff --git a/secure/simul_efun/enable_commands.c b/secure/simul_efun/enable_commands.c
new file mode 100644
index 0000000..9d1c963
--- /dev/null
+++ b/secure/simul_efun/enable_commands.c
@@ -0,0 +1,30 @@
+/* These sefuns are to provide a replacement for the efuns enable_commands()
+ * and disable_commands().
+ * Feel free to add it to your mudlibs, if you have much code relying on that.
+ */
+
+#include <configuration.h>
+
+#if ! __EFUN_DEFINED__(enable_commands)
+
+void enable_commands()
+{
+    object ob = efun::previous_object();
+
+    efun::configure_object(ob, OC_COMMANDS_ENABLED, 1);
+    efun::set_this_player(ob);
+}
+
+#endif
+
+#if ! __EFUN_DEFINED__(disable_commands)
+
+void disable_commands()
+{
+    object ob = efun::previous_object();
+
+    efun::configure_object(ob, OC_COMMANDS_ENABLED, 0);
+    efun::set_this_player(0);
+}
+
+#endif
diff --git a/secure/simul_efun/hash.c b/secure/simul_efun/hash.c
new file mode 100644
index 0000000..51bbf60
--- /dev/null
+++ b/secure/simul_efun/hash.c
@@ -0,0 +1,17 @@
+#include "/sys/tls.h"
+
+deprecated string md5(mixed arg, varargs mixed* iterations)
+{
+    if (extern_call())
+         set_this_object(previous_object());
+
+    return hash(TLS_HASH_MD5, arg, iterations...);
+}
+
+deprecated string sha1(mixed arg, varargs mixed* iterations)
+{
+    if (extern_call())
+         set_this_object(previous_object());
+
+    return hash(TLS_HASH_SHA1, arg, iterations...);
+}
diff --git a/secure/simul_efun/livings.c b/secure/simul_efun/livings.c
new file mode 100644
index 0000000..160fcee
--- /dev/null
+++ b/secure/simul_efun/livings.c
@@ -0,0 +1,348 @@
+// * living_name-Behandlung
+
+#define clean_log(s)
+//#define clean_log(s) log_file("CLEAN_SIM",ctime(time())[4..18]+": "+(s));
+
+private nosave mapping living_name_m, name_living_m, netdead;
+
+private void InitLivingData(mixed wizinfo) {
+  // living_name ist ein Pointer der auch in der extra_wizinfo gehalten 
+  // wird, so kann die simul_efun neu geladen werden, ohne dass dieses
+  // Mapping verloren geht
+  if (!mappingp(living_name_m = wizinfo[LIVING_NAME]))
+    living_name_m = wizinfo[LIVING_NAME] = m_allocate(0, 1);
+
+  // Gleiches gilt fuer das Mapping Name-Living
+  if (!mappingp(name_living_m = wizinfo[NAME_LIVING]))
+    name_living_m = wizinfo[NAME_LIVING] = m_allocate(0, 1);
+
+  // Netztote sind ebenfalls in der extra_wizinfo
+  if (!mappingp(netdead = wizinfo[NETDEAD_MAP]))
+    netdead = wizinfo[NETDEAD_MAP] = ([]);
+}
+
+public varargs string getuuid( object ob )
+{
+    mixed *ret;
+
+    if ( !objectp(ob) )
+       ob = previous_object();
+
+    if ( !query_once_interactive(ob) )
+       return getuid(ob);
+
+    ret = (mixed)master()->get_userinfo( getuid(ob) );
+
+    if ( !pointerp(ret) || sizeof(ret) < 5 )
+       return getuid(ob);
+
+    // Username + "_" + CreationDate
+    return ret[0] + "_" + ret[5];
+}
+
+void set_object_living_name(string livname, object obj)
+{
+  string old;
+  mixed a;
+  int i;
+
+  if (previous_object()==obj || previous_object()==this_object() ||
+      previous_object()==master())
+  {
+    if(!livname || !stringp(livname)) {
+      set_this_object(previous_object());
+      raise_error(sprintf("%O: illegal living name: %O\n", obj, livname));
+    }
+    if (old = living_name_m[obj]) {
+      if (pointerp(a = name_living_m[old])) {
+       a[member(a, obj)] = 0;
+      } else {
+       m_delete(name_living_m, old);
+      }
+    }
+    living_name_m[obj] = livname;
+    if (a = name_living_m[livname]) {
+      if (!pointerp(a)) {
+       name_living_m[livname] = ({a, obj});
+       return;
+      }
+      /* Try to reallocate entry from destructed object */
+      if ((i = member(a, 0)) >= 0) {
+       a[i] = obj;
+       return;
+      }
+      name_living_m[livname] = a + ({obj});
+      return;
+    }
+    name_living_m[livname] = obj;
+  }
+}
+
+void set_living_name(string livname)
+{
+  set_object_living_name(livname,previous_object());
+}
+
+void remove_living_name()
+{
+  string livname;
+
+  if (!previous_object())
+    return;
+  if (livname=living_name_m[previous_object()])
+  {
+    m_delete(living_name_m,previous_object());
+    if (objectp(name_living_m[livname]))
+    {
+      if (name_living_m[livname]==previous_object())
+       m_delete(name_living_m,livname);
+      return;
+    }
+    if (pointerp(name_living_m[livname]))
+    {
+      name_living_m[livname]-=({previous_object()});
+      if (!sizeof(name_living_m[livname]))
+       m_delete(name_living_m,livname);
+    }
+  }
+}
+
+void _set_netdead()
+{
+  if (query_once_interactive(previous_object()))
+    netdead[getuid(previous_object())]=previous_object();
+}
+
+void _remove_netdead()
+{
+  m_delete(netdead,getuid(previous_object()));
+}
+
+object find_netdead(string uuid)
+{
+  int i;
+  string uid;
+  // Wenn sscanf() 2 liefert, ist uuid auch ne uuid.
+  int is_uuid = sscanf(uuid, "%s_%d", uid, i) == 2;
+  if (!is_uuid)
+    uid = uuid;
+ 
+  if (is_uuid && getuuid(netdead[uid]) != uuid)
+      return 0;
+
+  return netdead[uid];
+}
+
+string *dump_netdead()
+{
+  return m_indices(netdead);
+}
+
+object find_living(string livname) {
+  mixed *a, r;
+  int i;
+
+  if (pointerp(r = name_living_m[livname])) {
+    if (member(r,0)>=0)
+      r-=({0});
+    if (!sizeof(r)){
+      m_delete(name_living_m,livname);
+      clean_log(sprintf("find_living loescht %s\n",livname));
+      return 0;
+    }
+    if ( !living(r = (a = r)[0])) {
+      for (i = sizeof(a); --i;) {
+       if (living(a[<i])) {
+         r = a[<i];
+         a[<i] = a[0];
+         return a[0] = r;
+       }
+      }
+    }
+    return r;
+  }
+  return living(r) && r;
+}
+
+object *find_livings(string livname)
+{
+  mixed r;
+
+  if (pointerp(r=name_living_m[livname]))
+    r-=({0});
+  else
+    if (objectp(r))
+      r=({r});
+  if (!pointerp(r)||!sizeof(r))
+    return 0;
+  return r;
+}
+
+object find_player(string uuid) {
+  object *objs;
+  mixed res;
+  int i;
+  string uid;
+
+  if (!stringp(uuid))
+    return 0;
+  // Wenn sscanf() 2 liefert, ist uuid auch ne uuid.
+  int is_uuid = sscanf(uuid, "%s_%d", uid, i) == 2;
+  if (!is_uuid)
+    uid = uuid;
+
+  if (pointerp(res = name_living_m[uid])) {
+    // zerstoerte Objekte ggf. entfernen
+    if (member(res,0)>=0) {
+      res-=({0});
+      name_living_m[uid] = res;
+    }
+    // ggf. Namen ohne Objekte entfernen.
+    if (!sizeof(res)){
+      m_delete(name_living_m,uid);
+      clean_log(sprintf("find_player loescht %s\n",uid));
+      return 0;
+    }
+    objs = res;
+    res = objs[0];
+    // Wenn das 0. Element der Spieler ist, sind wir fertig. Ansonsten suchen
+    // wir den Spieler und schieben ihn an die 0. Stelle im Array.
+    if ( !query_once_interactive(res)) {
+      for (i = sizeof(objs); --i;) {
+       if (objs[<i] && query_once_interactive(objs[<i])) {
+         res = objs[<i];
+         objs[<i] = objs[0];
+         objs[0] = res;
+         break;
+       }
+      }
+      res = (objectp(res) && query_once_interactive(res)) ? res : 0;
+    }
+    // else: in res steht der Spieler schon.
+  }
+  else if (objectp(res)) // r ist nen Einzelobjekt
+    res = query_once_interactive(res) ? res : 0;
+
+  // res ist jetzt ggf. der Spieler. Aber wenn der ne andere als die gesuchte
+  // UUID hat, ist das Ergebnis trotzdem 0.
+  if (is_uuid && getuuid(res)!=uuid)
+      return 0;
+
+  // endlich gefunden
+  return res;
+}
+
+private int check_match( string str, int players_only )
+{
+    mixed match;
+
+    if ( !(match = name_living_m[str]) )
+       return 0;
+
+    if ( !pointerp(match) )
+       match = ({ match });
+
+    match -= ({0});
+
+    if ( sizeof(match) ){
+       if ( players_only )
+           return sizeof(filter( match, #'query_once_interactive/*'*/ ))
+              > 0;
+       else
+           return 1;
+    }
+
+    m_delete( name_living_m, str );
+    clean_log( sprintf("check_match loescht %s\n", str) );
+    return 0;
+}
+
+//TODO:: string|string* exclude
+varargs mixed match_living( string str, int players_only, mixed exclude )
+{
+    int i, s;
+    mixed match, *user;
+
+    if ( !str || str == "" )
+       return 0;
+
+    if ( !pointerp(exclude) )
+       exclude = ({ exclude });
+
+    if ( member(exclude, str) < 0 && check_match(str, players_only) )
+       return str;
+
+    user = m_indices(name_living_m);
+    s = sizeof(str);
+    match = 0;
+
+    for ( i = sizeof(user); i--; )
+       if ( str == user[i][0..s-1] && member( exclude, user[i] ) < 0 )
+           if ( match )
+              return -1;
+           else
+              if ( check_match(user[i], players_only) )
+                  match = user[i];
+
+    if ( !match )
+       return -2;
+
+    return match;
+}
+
+mixed *dump_livings()
+{
+  return sort_array(m_indices(name_living_m),#'>);
+}
+
+// * regelmaessig Listen von Ballast befreien
+private void clean_name_living_m(string *keys, int left, int num)
+{
+  int i, j;
+  mixed a;
+
+  while (left && num--)
+  {
+    if (pointerp(a = name_living_m[keys[--left]]) && member(a, 0)>=0)
+    {
+      a-=({0});
+      name_living_m[keys[left]] = a;
+    }
+    if (!a || (pointerp(a) && !sizeof(a)))
+    {
+      clean_log("Toasting "+keys[left]+"\n");
+      m_delete(name_living_m, keys[left]);
+    } else clean_log(sprintf("KEEPING %s (%O)\n",keys[left],pointerp(a)?sizeof(a):a));
+  }
+  if (left)
+    efun::call_out(#'clean_name_living_m, 1, keys, left, 80);
+  else
+    clean_log("Clean process finished\n");
+}
+
+private void clean_netdead() {
+  int i;
+  string *s;
+  object ob;
+
+  s=m_indices(netdead);
+  for (i=sizeof(s)-1;i>=0;i--)
+    if (!objectp(ob=netdead[s[i]]) || interactive(ob))
+      m_delete(netdead,s[i]);
+}
+
+private void CleanLivingData() {
+  if (find_call_out(#'clean_name_living_m) < 0) {
+    clean_log("Starting clean process\n");
+    efun::call_out(#'clean_name_living_m,
+                 1,
+                 m_indices(name_living_m),
+                 sizeof(name_living_m),
+                 80
+                 );
+  }
+  efun::call_out(#'clean_netdead,2);
+}
+
+#undef clean_log
+
diff --git a/secure/simul_efun/object_info.c b/secure/simul_efun/object_info.c
new file mode 100644
index 0000000..ed7c009
--- /dev/null
+++ b/secure/simul_efun/object_info.c
@@ -0,0 +1,106 @@
+/* This sefun is to provide the old semantics of the efun object_info().
+ * Feel free to add it to your mudlibs, if you have much code relying on that.
+ */
+
+#if __EFUN_DEFINED__(driver_info) /* Newer version is there */
+
+#include <objectinfo.h>
+#include <object_info.h>
+
+mixed object_info(object ob, int what, varargs int* index)
+{
+    mixed * result;
+
+    if (sizeof(index) > 1)
+        raise_error("Too many arguments to object_info\n");
+
+    switch(what)
+    {
+        default:
+            raise_error(sprintf("Illegal value %d for object_info().\n", what));
+
+        case OINFO_BASIC:
+        {
+            result = allocate(OIB_MAX);
+
+            result[OIB_HEART_BEAT]        = efun::object_info(ob, OC_HEART_BEAT);
+            result[OIB_IS_WIZARD]         = 0;   /* Not supported anymore. */
+            result[OIB_ENABLE_COMMANDS]   = efun::object_info(ob, OC_COMMANDS_ENABLED);
+            result[OIB_CLONE]             = efun::clonep(ob);
+            result[OIB_DESTRUCTED]        = 0;   /* Not possible anymore. */
+            result[OIB_SWAPPED]           = efun::object_info(ob, OI_SWAPPED);
+            result[OIB_ONCE_INTERACTIVE]  = efun::object_info(ob, OI_ONCE_INTERACTIVE);
+            result[OIB_RESET_STATE]       = efun::object_info(ob, OI_RESET_STATE);
+            result[OIB_WILL_CLEAN_UP]     = efun::object_info(ob, OI_WILL_CLEAN_UP);
+            result[OIB_LAMBDA_REFERENCED] = efun::object_info(ob, OI_LAMBDA_REFERENCED);
+            result[OIB_SHADOW]            = efun::object_info(ob, OI_SHADOW_PREV) && 1;
+            result[OIB_REPLACED]          = efun::object_info(ob, OI_REPLACED);
+            result[OIB_NEXT_RESET]        = efun::object_info(ob, OI_NEXT_RESET_TIME);
+            result[OIB_TIME_OF_REF]       = efun::object_info(ob, OI_LAST_REF_TIME);
+            result[OIB_REF]               = efun::object_info(ob, OI_OBJECT_REFS);
+            result[OIB_GIGATICKS]         = efun::object_info(ob, OI_GIGATICKS);
+            result[OIB_TICKS]             = efun::object_info(ob, OI_TICKS);
+            result[OIB_SWAP_NUM]          = efun::object_info(ob, OI_SWAP_NUM);
+            result[OIB_PROG_SWAPPED]      = efun::object_info(ob, OI_PROG_SWAPPED);
+            result[OIB_VAR_SWAPPED]       = efun::object_info(ob, OI_VAR_SWAPPED);
+            result[OIB_NAME]              = efun::object_name(ob);
+            result[OIB_LOAD_NAME]         = efun::load_name(ob);
+            result[OIB_NEXT_ALL]          = efun::object_info(ob, OI_OBJECT_NEXT);
+            result[OIB_PREV_ALL]          = efun::object_info(ob, OI_OBJECT_PREV);
+            result[OIB_NEXT_CLEANUP]      = efun::object_info(ob, OI_NEXT_CLEANUP_TIME);
+            break;
+        }
+
+        case OINFO_POSITION:
+        {
+            result = allocate(OIP_MAX);
+
+            result[OIP_NEXT] = efun::object_info(ob, OI_OBJECT_NEXT);
+            result[OIP_PREV] = efun::object_info(ob, OI_OBJECT_PREV);
+            result[OIP_POS]  = efun::object_info(ob, OI_OBJECT_POS);
+            break;
+        }
+
+        case OINFO_MEMORY:
+        {
+            result = allocate(OIM_MAX);
+
+            result[OIM_REF]                = efun::object_info(ob, OI_PROG_REFS);
+            result[OIM_NAME]               = efun::program_name(ob);
+            result[OIM_PROG_SIZE]          = efun::object_info(ob, OI_PROG_SIZE);
+            result[OIM_NUM_FUNCTIONS]      = efun::object_info(ob, OI_NUM_FUNCTIONS);
+            result[OIM_SIZE_FUNCTIONS]     = efun::object_info(ob, OI_SIZE_FUNCTIONS);
+            result[OIM_NUM_VARIABLES]      = efun::object_info(ob, OI_NUM_VARIABLES);
+            result[OIM_SIZE_VARIABLES]     = efun::object_info(ob, OI_SIZE_VARIABLES);
+            result[OIM_NUM_STRINGS]        = efun::object_info(ob, OI_NUM_STRINGS);
+            result[OIM_SIZE_STRINGS]       = efun::object_info(ob, OI_SIZE_STRINGS);
+            result[OIM_SIZE_STRINGS_DATA]  = efun::object_info(ob, OI_SIZE_STRINGS_DATA);
+            result[OIM_SIZE_STRINGS_TOTAL] = efun::object_info(ob, OI_SIZE_STRINGS_DATA_TOTAL);
+            result[OIM_NUM_INHERITED]      = efun::object_info(ob, OI_NUM_INHERITED);
+            result[OIM_SIZE_INHERITED]     = efun::object_info(ob, OI_SIZE_INHERITED);
+            result[OIM_TOTAL_SIZE]         = efun::object_info(ob, OI_PROG_SIZE_TOTAL);
+            result[OIM_DATA_SIZE]          = efun::object_info(ob, OI_DATA_SIZE);
+            result[OIM_TOTAL_DATA_SIZE]    = efun::object_info(ob, OI_DATA_SIZE_TOTAL);
+            result[OIM_NO_INHERIT]         = efun::object_info(ob, OI_NO_INHERIT);
+            result[OIM_NO_CLONE]           = efun::object_info(ob, OI_NO_CLONE);
+            result[OIM_NO_SHADOW]          = efun::object_info(ob, OI_NO_SHADOW);
+            result[OIM_NUM_INCLUDES]       = efun::object_info(ob, OI_NUM_INCLUDED);
+            result[OIM_SHARE_VARIABLES]    = efun::object_info(ob, OI_SHARE_VARIABLES);
+            break;
+        }
+    }
+
+    if (sizeof(index))
+    {
+        int idx = index[0];
+        if (idx < 0 || idx >= sizeof(result))
+            raise_error(sprintf("Illegal index for object_info(): %d, expected 0..%d\n",
+                idx, sizeof(result)-1));
+
+        return result[idx];
+    }
+    else
+        return result;
+}
+
+#endif
diff --git a/secure/simul_efun/query_editing.c b/secure/simul_efun/query_editing.c
new file mode 100644
index 0000000..5146dcd
--- /dev/null
+++ b/secure/simul_efun/query_editing.c
@@ -0,0 +1,17 @@
+/* This sefun is to provide a replacement for the efun query_editing().
+ * Feel free to add it to your mudlibs, if you have much code relying on that.
+ */
+
+#if ! __EFUN_DEFINED__(query_editing)
+
+#include <interactive_info.h>
+
+int|object query_editing(object ob)
+{
+    if(!efun::interactive(ob))
+        return 0;
+
+    return efun::interactive_info(ob, II_EDITING);
+}
+
+#endif
diff --git a/secure/simul_efun/query_idle.c b/secure/simul_efun/query_idle.c
new file mode 100644
index 0000000..93a32ae
--- /dev/null
+++ b/secure/simul_efun/query_idle.c
@@ -0,0 +1,14 @@
+/* This sefun is to provide a replacement for the efun query_idle().
+ * Feel free to add it to your mudlibs, if you have much code relying on that.
+ */
+
+#if ! __EFUN_DEFINED__(query_idle)
+
+#include <interactive_info.h>
+
+int query_idle(object ob)
+{
+    return efun::interactive_info(ob, II_IDLE);
+}
+
+#endif
diff --git a/secure/simul_efun/query_input_pending.c b/secure/simul_efun/query_input_pending.c
new file mode 100644
index 0000000..dd8c311
--- /dev/null
+++ b/secure/simul_efun/query_input_pending.c
@@ -0,0 +1,17 @@
+/* This sefun is to provide a replacement for the efun query_input_pending().
+ * Feel free to add it to your mudlibs, if you have much code relying on that.
+ */
+
+#if ! __EFUN_DEFINED__(query_input_pending)
+
+#include <interactive_info.h>
+
+object query_input_pending(object ob)
+{
+    if(!efun::interactive(ob))
+        return 0;
+
+    return efun::interactive_info(ob, II_INPUT_PENDING);
+}
+
+#endif
diff --git a/secure/simul_efun/query_ip_name.c b/secure/simul_efun/query_ip_name.c
new file mode 100644
index 0000000..ada6e07
--- /dev/null
+++ b/secure/simul_efun/query_ip_name.c
@@ -0,0 +1,48 @@
+/* This sefun is to provide a replacement for the efuns query_ip_name() and
+ * query_ip_number().
+ * Feel free to add it to your mudlibs, if you have much code relying on that.
+ */
+
+#if ! __EFUN_DEFINED__(query_ip_name)
+
+#include <interactive_info.h>
+
+private varargs string _query_ip_name(object player)
+{
+    object ob = player;
+    ob ||= efun::this_player();
+
+    player = efun::interactive_info(ob, II_IP_ADDRESS);
+    return efun::interactive_info(ob, II_IP_NAME);
+}
+
+private varargs string _query_ip_number(object player)
+{
+    object ob = player;
+    ob ||= efun::this_player();
+
+    player = efun::interactive_info(ob, II_IP_ADDRESS);
+    return efun::interactive_info(ob, II_IP_NUMBER);
+}
+
+// * Herkunfts-Ermittlung
+string query_ip_number(object ob)
+{
+  ob= ob || this_player();
+  if (!objectp(ob) || !interactive(ob)) return 0;
+  if(ob->query_realip() && (string)ob->query_realip()!="")
+  {
+    return (string)ob->query_realip();
+  }
+  return _query_ip_number(ob);
+}
+
+string query_ip_name(mixed ob)
+{
+  if ( !ob || objectp(ob) )
+      ob=query_ip_number(ob);
+  return (string)"/p/daemon/iplookup"->host(ob);
+}
+
+#endif
+
diff --git a/secure/simul_efun/query_limits.c b/secure/simul_efun/query_limits.c
new file mode 100644
index 0000000..ecbf390
--- /dev/null
+++ b/secure/simul_efun/query_limits.c
@@ -0,0 +1,14 @@
+/* This sefun is to provide a replacement for the efun query_limits().
+ * Feel free to add it to your mudlibs, if you have much code relying on that.
+ */
+
+#if ! __EFUN_DEFINED__(query_limits)
+
+#include <driver_info.h>
+
+varargs int* query_limits(int def)
+{
+    return efun::driver_info(def ? DC_DEFAULT_RUNTIME_LIMITS : DI_CURRENT_RUNTIME_LIMITS);
+}
+
+#endif
diff --git a/secure/simul_efun/query_load_average.c b/secure/simul_efun/query_load_average.c
new file mode 100644
index 0000000..3b36f0e
--- /dev/null
+++ b/secure/simul_efun/query_load_average.c
@@ -0,0 +1,16 @@
+/* This sefun is to provide a replacement for the efun query_load_average().
+ * Feel free to add it to your mudlibs, if you have much code relying on that.
+ */
+
+#if ! __EFUN_DEFINED__(query_load_average)
+
+#include <driver_info.h>
+
+string query_load_average()
+{
+    return efun::sprintf("%.2f cmds/s, %.2f comp lines/s",
+        efun::driver_info(DI_LOAD_AVERAGE_COMMANDS),
+        efun::driver_info(DI_LOAD_AVERAGE_LINES));
+}
+
+#endif
diff --git a/secure/simul_efun/query_mud_port.c b/secure/simul_efun/query_mud_port.c
new file mode 100644
index 0000000..bff009d
--- /dev/null
+++ b/secure/simul_efun/query_mud_port.c
@@ -0,0 +1,35 @@
+/* This sefun is to provide a replacement for the efun query_mud_port().
+ * Feel free to add it to your mudlibs, if you have much code relying on that.
+ */
+
+#if ! __EFUN_DEFINED__(query_mud_port)
+
+#include <interactive_info.h>
+#include <driver_info.h>
+
+int query_mud_port(varargs int*|object* args)
+{
+    if(!sizeof(args) && efun::this_player() && efun::interactive(this_player()))
+        return efun::interactive_info(this_player(), II_MUD_PORT);
+
+    if(sizeof(args) > 1)
+        raise_error("Too many arguments to query_mud_port\n");
+
+    if(sizeof(args) && efun::objectp(args[0]) && efun::interactive(args[0]))
+        return efun::interactive_info(args[0], II_MUD_PORT);
+
+    if(sizeof(args) && intp(args[0]))
+    {
+        int* ports = efun::driver_info(DI_MUD_PORTS);
+        if(args[0] < -1 || args[0] >= sizeof(ports))
+            raise_error(efun::sprintf("Bad arg 1 to query_mud_port(): value %d out of range.\n", args[0]));
+        else if(args[0] == -1)
+            return sizeof(ports);
+        else
+            return ports[args[0]];
+    }
+
+    return efun::driver_info(DI_MUD_PORTS)[0];
+}
+
+#endif
diff --git a/secure/simul_efun/query_once_interactive.c b/secure/simul_efun/query_once_interactive.c
new file mode 100644
index 0000000..bc7829f
--- /dev/null
+++ b/secure/simul_efun/query_once_interactive.c
@@ -0,0 +1,14 @@
+/* This sefun is to provide a replacement for the efun query_once_interactive().
+ * Feel free to add it to your mudlibs, if you have much code relying on that.
+ */
+
+#if ! __EFUN_DEFINED__(query_once_interactive)
+
+#include <object_info.h>
+
+int query_once_interactive(object ob)
+{
+    return efun::object_info(ob, OI_ONCE_INTERACTIVE);
+}
+
+#endif
diff --git a/secure/simul_efun/query_snoop.c b/secure/simul_efun/query_snoop.c
new file mode 100644
index 0000000..e6a69c0
--- /dev/null
+++ b/secure/simul_efun/query_snoop.c
@@ -0,0 +1,30 @@
+/* This sefun is to provide a replacement for the efun query_snoop().
+ * Feel free to add it to your mudlibs, if you have much code relying on that.
+ */
+
+
+#include <interactive_info.h>
+
+private object _query_snoop(object ob)
+{
+    if(!efun::interactive(ob))
+        return 0;
+
+    object prev = efun::previous_object();
+    efun::set_this_object(prev);
+#if ! __EFUN_DEFINED__(query_snoop)
+    return efun::interactive_info(ob, II_SNOOP_NEXT);
+#else
+    return efun::query_snoop(ob);
+#endif
+}
+
+nomask object query_snoop(object who) {
+  object snooper;
+
+  snooper=_query_snoop(who);
+  if (!snooper) return 0;
+  if (query_wiz_grp(snooper)>query_wiz_grp(getuid(previous_object())) &&
+      IS_ARCH(snooper)) return 0;
+  return snooper;
+}
diff --git a/secure/simul_efun/set_heart_beat.c b/secure/simul_efun/set_heart_beat.c
new file mode 100644
index 0000000..a3995eb
--- /dev/null
+++ b/secure/simul_efun/set_heart_beat.c
@@ -0,0 +1,24 @@
+/* This sefun is to provide a replacement for the efun set_heart_beat().
+ * Feel free to add it to your mudlibs, if you have much code relying on that.
+ */
+
+#if ! __EFUN_DEFINED__(set_heart_beat)
+
+#include <configuration.h>
+
+int set_heart_beat(int flag)
+{
+    object ob = efun::previous_object();
+    int hb = efun::object_info(ob, OC_HEART_BEAT);
+
+    if (!flag == !hb)
+        return 0;
+
+    efun::configure_object(ob, OC_HEART_BEAT, flag);
+
+    return 1;
+}
+
+#endif
+
+
diff --git a/secure/simul_efun/set_is_wizard.c b/secure/simul_efun/set_is_wizard.c
new file mode 100644
index 0000000..001ccdb
--- /dev/null
+++ b/secure/simul_efun/set_is_wizard.c
@@ -0,0 +1,137 @@
+/* These are the special commands from the driver that are activated with
+ * set_is_wizard(). These functions must be added to the player object.
+ * Also set_is_wizard() must be called in the corresponding player object,
+ * not as an (simul-)efun.
+ */
+
+#include <commands.h>
+#include <driver_info.h>
+
+/* is_wizard:
+ *  1: has actions,
+ *  0: never had actions,
+ * -1: had actions once.
+ */
+private nosave int is_wizard;
+
+private int driver_malloc(string str)
+{
+    if(is_wizard <= 0)
+        return 0;
+
+    if(!sizeof(str))
+    {
+        write(efun::driver_info(DI_STATUS_TEXT_MALLOC));
+        return 1;
+    }
+
+    if(str == "extstats")
+    {
+        write(efun::driver_info(DI_STATUS_TEXT_MALLOC_EXTENDED));
+        return 1;
+    }
+
+    return 0;
+}
+
+private int driver_dumpallobj(string str)
+{
+    if(is_wizard <= 0 || sizeof(str))
+        return 0;
+
+    write("Dumping to /OBJ_DUMP ... ");
+    efun::dump_driver_info(DDI_OBJECTS);
+    efun::dump_driver_info(DDI_OBJECTS_DESTRUCTED);
+    write("done\n");
+    return 1;
+}
+
+private int driver_opcdump(string str)
+{
+    if(is_wizard <= 0 || sizeof(str))
+        return 0;
+
+    efun::dump_driver_info(DDI_OPCODES);
+    return 1;
+}
+
+private int driver_status(string str)
+{
+    int opt;
+    if(is_wizard <= 0)
+        return 0;
+
+    switch(str || "")
+    {
+        case "":
+            opt = DI_STATUS_TEXT_MEMORY;
+            break;
+
+        case "tables":
+        case " tables":
+            opt = DI_STATUS_TEXT_TABLES;
+            break;
+
+        case "swap":
+        case " swap":
+            opt = DI_STATUS_TEXT_SWAP;
+            break;
+
+        case "malloc":
+        case " malloc":
+            opt = DI_STATUS_TEXT_MALLOC;
+            break;
+
+        case "malloc extstats":
+        case " malloc extstats":
+            opt = DI_STATUS_TEXT_MALLOC_EXTENDED;
+            break;
+
+        default:
+            return 0;
+    }
+
+    write(efun::driver_info(opt));
+    return 1;
+}
+
+int set_is_wizard(varargs <object|int>* args)
+{
+    int oldval = is_wizard;
+
+    if(!sizeof(args))
+        raise_error("Too few arguments to set_is_wizard\n");
+    if(sizeof(args) > 2)
+        raise_error("Too many arguments to set_is_wizard\n");
+    if(!objectp(args[0]))
+        raise_error("Bad arg 1 to set_is_wizard()\n");
+    if(args[0] != this_object())
+        raise_error("Only set_is_wizard for the current object supported\n");
+    if(this_player() != this_object())
+        raise_error("The current object must be this_player() for set_is_wizard()\n");
+    if(sizeof(args) == 2 && !intp(args[1]))
+        raise_error("Bad arg 2 to set_is_wizard()\n");
+
+    if(sizeof(args) == 2 && !args[1])
+    {
+        if(is_wizard > 0)
+            is_wizard = -1;
+    }
+    else if(sizeof(args) == 2 && args[1]<0)
+    {
+         // Just return the old value.
+    }
+    else
+    {
+        if(!is_wizard)
+        {
+            add_action(#'driver_malloc, "malloc");
+            add_action(#'driver_dumpallobj, "dumpallobj");
+            add_action(#'driver_opcdump, "opcdump");
+            add_action(#'driver_status, "status", AA_NOSPACE);
+        }
+        is_wizard = 1;
+    }
+
+    return oldval > 0;
+}
diff --git a/secure/simul_efun/set_prompt.c b/secure/simul_efun/set_prompt.c
new file mode 100644
index 0000000..0b59692
--- /dev/null
+++ b/secure/simul_efun/set_prompt.c
@@ -0,0 +1,21 @@
+/* This sefun is to provide a replacement for the efun set_prompt().
+ * Feel free to add it to your mudlibs, if you have much code relying on that.
+ */
+
+#if ! __EFUN_DEFINED__(set_prompt)
+
+#include <configuration.h>
+
+varargs string|closure set_prompt(string|closure|int prompt, object ob)
+{
+    ob ||= efun::this_player();
+
+    mixed oldprompt = efun::interactive_info(ob, IC_PROMPT);
+
+    if(!intp(prompt))
+        efun::configure_interactive(ob, IC_PROMPT, prompt);
+
+    return oldprompt;
+}
+
+#endif
diff --git a/secure/simul_efun/shadow.c b/secure/simul_efun/shadow.c
new file mode 100644
index 0000000..7608c73
--- /dev/null
+++ b/secure/simul_efun/shadow.c
@@ -0,0 +1,55 @@
+/* These sefuns are to provide a replacement for the efun query_shadowing()
+ * and the old semantics of the efun shadow().
+ * Feel free to add it to your mudlibs, if you have much code relying on that.
+ */
+
+#if ! __EFUN_DEFINED__(query_shadowing)
+
+#include <object_info.h>
+
+object query_shadowing(object ob)
+{
+    return efun::object_info(ob, OI_SHADOW_PREV);
+}
+
+private object _shadow(object ob, int flag)
+{
+    if(flag)
+    {
+        object shadower = efun::previous_object(1);
+        efun::set_this_object(shadower);
+
+        if (efun::shadow(ob))
+            return efun::object_info(shadower, OI_SHADOW_PREV);
+        else
+            return 0;
+    }
+    else
+        return efun::object_info(ob, OI_SHADOW_NEXT);
+}
+#else
+private object _shadow(object ob, int flag)
+{
+  set_this_object(previous_object(1));
+  return efun::shadow(ob, flag);
+}
+#endif
+
+
+public object shadow(object ob, int flag)
+{
+  object res = funcall(#'_shadow,ob, flag);
+  if (flag)
+    "/secure/shadowmaster"->RegisterShadow(previous_object());
+  return res;
+}
+
+private void _unshadow() {
+  set_this_object(previous_object(1));
+  efun::unshadow();
+}
+
+public void unshadow() {
+  funcall(#'_unshadow);
+  "/secure/shadowmaster"->UnregisterShadow(previous_object());
+}
diff --git a/secure/simul_efun/simul_efun.c b/secure/simul_efun/simul_efun.c
new file mode 100644
index 0000000..84e0b78
--- /dev/null
+++ b/secure/simul_efun/simul_efun.c
@@ -0,0 +1,2016 @@
+// MorgenGrauen MUDlib
+//
+// simul_efun.c -- simul efun's
+//
+// $Id: simul_efun.c 7408 2010-02-06 00:27:25Z Zesstra $
+#pragma strict_types,save_types,rtt_checks
+#pragma no_clone,no_shadow,no_inherit
+#pragma pedantic,range_check,warn_deprecated
+#pragma warn_empty_casts,warn_missing_return,warn_function_inconsistent
+
+// Absolute Pfade erforderlich - zum Zeitpunkt, wo der Master geladen
+// wird, sind noch keine Include-Pfade da ...
+
+#define SNOOPLOGFILE "SNOOP"
+#define ASNOOPLOGFILE "ARCH/SNOOP"
+
+#include "/secure/config.h"
+#include "/secure/wizlevels.h"
+#include "/sys/snooping.h"
+#include "/sys/language.h"
+#include "/sys/thing/properties.h"
+#include "/sys/wizlist.h"
+#include "/sys/erq.h"
+#include "/sys/lpctypes.h"
+#include "/sys/daemon.h"
+#include "/sys/player/base.h"
+#include "/sys/thing/description.h"
+#include "/sys/container.h"
+#include "/sys/defines.h"
+#include "/sys/telnet.h"
+#include "/sys/objectinfo.h"
+#include "/sys/files.h"
+#include "/sys/strings.h"
+#include "/sys/time.h"
+#include "/sys/lpctypes.h"
+#include "/sys/notify_fail.h"
+#include "/sys/tls.h"
+#include "/sys/input_to.h"
+#include "/sys/object_info.h"
+
+/* function prototypes
+ */
+string dtime(int wann);
+varargs int log_file(string file, string txt, int size_to_break);
+int query_wiz_level(mixed player);
+nomask varargs int snoop(object me, object you);
+varargs string country(mixed ip, string num);
+int query_wiz_grp(mixed wiz);
+public varargs object deep_present(mixed what, object ob);
+nomask int secure_level();
+nomask string secure_euid();
+public nomask int process_call();
+nomask mixed __create_player_dummy(string name);
+varargs string replace_personal(string str, mixed *obs, int caps);
+
+
+//replacements for dropped efuns in LD
+#if !__EFUN_DEFINED__(extract)
+varargs string extract(string str, int from, int to);
+#endif
+#if !__EFUN_DEFINED__(slice_array)
+varargs mixed slice_array(mixed array, int from, int to);
+#endif
+#if !__EFUN_DEFINED__(member_array)
+int member_array(mixed item, mixed arraystring);
+#endif
+
+// Include the different sub 'modules' of the simul_efun
+#include __DIR__"debug_info.c"
+#include __DIR__"enable_commands.c"
+#include __DIR__"hash.c"
+#include __DIR__"object_info.c"
+#include __DIR__"query_editing.c"
+#include __DIR__"query_idle.c"
+#include __DIR__"query_input_pending.c"
+#include __DIR__"query_ip_name.c"
+#include __DIR__"query_limits.c"
+#include __DIR__"query_load_average.c"
+#include __DIR__"query_mud_port.c"
+#include __DIR__"query_once_interactive.c"
+#include __DIR__"query_snoop.c"
+#include __DIR__"set_heart_beat.c"
+#if __BOOT_TIME__ < 1456261859
+#include __DIR__"set_prompt.c"
+#endif
+#include __DIR__"shadow.c"
+#include __DIR__"livings.c"
+#include __DIR__"comm.c"
+
+#define TO        efun::this_object()
+#define TI        efun::this_interactive()
+#define TP        efun::this_player()
+#define PO        efun::previous_object(0)
+#define LEVEL(x) query_wiz_level(x)
+#define NAME(x)  capitalize(getuid(x))
+
+#define DEBUG(x) if (find_player("zesstra")) \
+  tell_object(find_player("zesstra"),x)
+
+mixed dtime_cache = ({-1,""});
+
+#ifdef IOSTATS
+struct iostat_s {
+  string oname;
+  int time;
+  int size;
+};
+mixed saveo_stat = ({ 0,allocate(200, 0) });
+mixed restoreo_stat = ({ 0,allocate(200,0) });
+//mixed writefile_stat = ({ 0,allocate(100,(<iostat_s>)) });
+//mixed readfile_stat = ({ 0,allocate(100,(<iostat_s>)) });
+//mixed log_stat = ({ 0,allocate(100,(<iostat_s>)) });
+
+mixed ___iostats(int type) {
+  switch(type) {
+    case 1:
+      return saveo_stat;
+    case 2:
+      return restoreo_stat;
+/*    case 3:
+      return writefile_stat;
+    case 4:
+      return readfile_stat;
+    case 5:
+      return log_stat;
+      */
+  }
+  return 0;
+}
+#endif
+
+// Nicht jeder Magier muss die simul_efun entsorgen koennen.
+string NotifyDestruct(object caller) {
+    if( (caller!=this_object() && !ARCH_SECURITY) || process_call() ) {
+      return "Du darfst das simul_efun Objekt nicht zerstoeren!\n";
+    }
+    return 0;
+}
+
+public nomask void remove_interactive( object ob )
+{
+    if ( objectp(ob) && previous_object()
+        && object_name(previous_object())[0..7] != "/secure/"
+        && ((previous_object() != ob
+             && (ob != this_player() || ob != this_interactive()))
+            || (previous_object() == ob
+               && (this_player() && this_player() != ob
+                   || this_interactive() && this_interactive() != ob)) ) )
+
+       log_file( "PLAYERDEST",
+                sprintf( "%s: %O ausgeloggt von PO %O, TI %O, TP %O\n",
+                        dtime(time()), ob, previous_object(),
+                        this_interactive(), this_player() ) );
+
+    efun::remove_interactive(ob);
+}
+
+
+void ___updmaster()
+{
+    object ob;
+
+    //sollte nicht jeder duerfen.
+    if (process_call() || !ARCH_SECURITY)
+      raise_error("Illegal use of ___updmaster()!");
+
+    write("Removing old master ... ");
+    foreach(string file: 
+        get_dir("/secure/master/*.c",GETDIR_NAMES|GETDIR_UNSORTED|GETDIR_PATH)) {
+      if (ob = find_object(file))
+       efun::destruct(ob);
+    }
+    efun::destruct(efun::find_object("/secure/master"));
+    write("done.\nLoading again ... ");
+    load_object("/secure/master");
+
+    write("done.\n");
+}
+
+varargs string country(mixed ip, string num) {
+  mixed ret;
+
+  if(ret = (string)"/p/daemon/iplookup"->country(num || ip)) {
+    return ret;
+  } else return "???";
+}
+
+
+// * Snoopen und was dazugehoert
+static object find_snooped(object who)
+{
+  object *u;
+  int i;
+
+  for (i=sizeof(u=users())-1;i>=0;i--)
+    if (who==efun::interactive_info(u[i], II_SNOOP_NEXT))
+      return u[i];
+  return 0;
+}
+
+private string Lcut(string str) {
+  return str[5..11]+str[18..];
+}
+
+nomask varargs int snoop( object me, object you )
+{
+    int ret;
+    object snooper0, snooper, snooper2, snooper3;
+
+    if( !objectp(me) || me == you || !PO )
+       return 0;
+
+    snooper0 = find_snooped(me);
+
+     if(you){
+        if ( PO != me && query_wiz_grp(me) >= query_wiz_grp(geteuid(PO)) )
+            return 0;
+
+        if ( query_wiz_grp(me) <= query_wiz_grp(you) &&
+             !(you->QueryAllowSnoop(me)) )
+            if ( !IS_DEPUTY(me) || IS_ARCH(you) )
+               return 0;
+
+        if ( (snooper = efun::interactive_info(you, II_SNOOP_NEXT)) &&
+             query_wiz_grp(snooper) >= query_wiz_grp(me) ){
+            if ( (int)snooper->QueryProp(P_SNOOPFLAGS) & SF_LOCKED )
+               return 0;
+
+            tell_object( snooper, sprintf( "%s snooped jetzt %s.\n",
+                                       me->name(WER), you->name(WER) ) );
+
+            snooper2 = me;
+
+            while ( snooper3 = interactive_info(snooper2, II_SNOOP_NEXT) ){
+               tell_object( snooper,
+                           sprintf( "%s wird seinerseits von %s gesnooped.\n"
+                                   ,snooper2->name(WER),
+                                   snooper3->name(WEM) ) );
+               snooper2 = snooper3;
+            }
+
+            efun::snoop( snooper, snooper2 );
+
+            if ( efun::interactive_info(snooper2, II_SNOOP_NEXT) != snooper )
+               tell_object( snooper, sprintf( "Du kannst %s nicht snoopen.\n",
+                                          snooper2->name(WEN) ) );
+            else{
+               tell_object( snooper, sprintf( "Du snoopst jetzt %s.\n",
+                                          snooper2->name(WEN) ) );
+               if ( !IS_DEPUTY(snooper) ){
+                   log_file( SNOOPLOGFILE, sprintf("%s: %O %O %O\n",
+                                               dtime(time()),
+                                               snooper,
+                                               snooper2,
+                                               environment(snooper2) ),
+                            100000 );
+                   if (snooper0)
+                      CHMASTER->send( "Snoop", snooper,
+                                    sprintf( "%s *OFF* %s (%O)",
+                                            capitalize(getuid(snooper)),
+                                            capitalize(getuid(snooper0)),
+                                            environment(snooper0) ) );
+
+                   CHMASTER->send( "Snoop", snooper,
+                                 sprintf("%s -> %s (%O)",
+                                        capitalize(getuid(snooper)),
+                                        capitalize(getuid(snooper2)),
+                                        environment(snooper2)));
+               }
+               else{
+                   log_file( ASNOOPLOGFILE, sprintf( "%s: %O %O %O\n",
+                                                 dtime(time()),
+                                                 snooper,
+                                                 snooper2,
+                                                 environment(snooper2) )
+                            ,100000 );
+               }
+            }
+        }
+        else
+            if (snooper)
+               if ( !me->QueryProp(P_SNOOPFLAGS) & SF_LOCKED ){
+                   printf( "%s wird bereits von %s gesnooped. Benutze das "
+                          "\"f\"-Flag, wenn du dennoch snoopen willst.\n",
+                          you->name(WER), snooper->name(WEM) );
+                   return 0;
+               }
+
+        ret = efun::snoop( me, you );
+
+        if ( !IS_DEPUTY(me) && efun::interactive_info(you, II_SNOOP_NEXT) == me){
+            log_file( SNOOPLOGFILE, sprintf( "%s: %O %O %O\n",
+                                         Lcut(dtime(time())),
+                                         me, you, environment(you) ),
+                     100000 );
+
+            if (snooper0)
+               CHMASTER->send( "Snoop", me,
+                             sprintf( "%s *OFF* %s (%O).",
+                                     capitalize(getuid(me)),
+                                     capitalize(getuid(snooper0)),
+                                     environment(snooper0) ) );
+
+            CHMASTER->send( "Snoop", me, sprintf( "%s -> %s (%O).",
+                                             capitalize(getuid(me)),
+                                             capitalize(getuid(you)),
+                                             environment(you) ) );
+        }
+        else{
+            if ( efun::interactive_info(you, II_SNOOP_NEXT) == me ){
+               log_file( ASNOOPLOGFILE, sprintf( "%s: %O %O %O\n",
+                                             Lcut(dtime(time())),
+                                             me, you, environment(you) ),
+                        100000 );
+            }
+        }
+
+        if ( ret && query_wiz_grp(me) <= query_wiz_grp(you) &&
+             !IS_DEPUTY(me) )
+            tell_object( you, "*** " + NAME(me) + " snoopt Dich!\n" );
+
+        return ret;
+     }
+     else {
+        if ( (me == PO ||
+              query_wiz_grp(geteuid(PO)) > query_wiz_grp(me) ||
+              (query_wiz_grp(geteuid(PO)) == query_wiz_grp(me) &&
+              efun::interactive_info(PO, II_SNOOP_NEXT) == me)) && snooper0 ){
+            if ( !IS_DEPUTY(me) ){
+               log_file( SNOOPLOGFILE, sprintf( "%s: %O %O %O *OFF*\n",
+                                            Lcut(dtime(time())), me,
+                                            snooper0,
+                                            environment(snooper0) ),
+                        100000 );
+
+                CHMASTER->send( "Snoop", me,
+                              sprintf( "%s *OFF* %s (%O).",
+                                      capitalize(getuid(me)),
+                                      capitalize(getuid(snooper0)),
+                                      environment(snooper0) ) );
+            }
+            else{
+               log_file( ASNOOPLOGFILE, sprintf( "%s: %O %O %O *OFF*\n",
+                                             Lcut(dtime(time())), me,
+                                             snooper0,
+                                             environment(snooper0) ),
+                        100000 );
+            }
+
+            return efun::snoop(me);
+        }
+     }
+     return 0;
+}
+
+
+
+// * Emulation des 'alten' explode durch das neue
+string *old_explode(string str, string del) {
+  int s, t;
+  string *strs;
+
+  if (!stringp(str)) {
+      set_this_object(previous_object());
+      raise_error(sprintf(
+         "Invalid argument 1 to old_explode()! Expected <string>, got: "
+         "%.30O\n",str));
+  }
+  if (!stringp(del)) {
+      set_this_object(previous_object());
+      raise_error(sprintf(
+         "Invalid argument 2 to old_explode()! Expected <string>, got: "
+         "%.30O\n",del));
+  }
+  if(del == "")
+    return ({str});
+  strs=efun::explode(str, del);
+  t=sizeof(strs)-1;
+  while(s<=t && strs[s++] == "");s--;
+  while(t>=0 && strs[t--] == "");t++;
+  if(s<=t)
+    return strs[s..t];
+  return ({});
+}
+
+int file_time(string path) {
+  mixed *v;
+
+  set_this_object(previous_object());
+  if (sizeof(v=get_dir(path,GETDIR_DATES))) return v[0];
+  return(0); //sonst
+}
+
+// * Bei 50k Groesse Log-File rotieren
+varargs int log_file(string file, string txt, int size_to_break) {
+  mixed *st;
+
+  file="/log/"+file;
+  file=implode((efun::explode(file,"/")-({".."})),"/");
+//  tell_object(find_player("jof"),sprintf("LOG FILE: %O -> %O\n",previous_object(),file));
+  if (!funcall(bind_lambda(#'efun::call_other,PO),"secure/master",//')
+              "valid_write",file,geteuid(PO),"log_file",PO))
+      return 0;
+  if ( size_to_break >= 0 & (
+      sizeof(st = get_dir(file,2) ) && st[0] >= (size_to_break|MAX_LOG_SIZE)))
+      catch(rename(file, file + ".old");publish); /* No panic if failure */
+  return(write_file(file,txt));
+}
+
+// * Magier-Level abfragen
+int query_wiz_level(mixed player) {
+  return (int)"/secure/master"->query_wiz_level(player);
+}
+
+#ifdef __ALISTS__
+// * Element aus Alist loeschen (by key)
+mixed *remove_alist(mixed key,mixed *alist)
+{
+  int i,j;
+
+  if (!pointerp(alist) || !sizeof(alist))
+    return alist;
+  if (!pointerp(alist[0]))
+  {
+    if ((i=assoc(key,alist))<0)
+      return alist;
+    return alist[0..i-1]+alist[i+1..];
+  }
+  i = assoc(key,alist[0]);
+  if ((i=assoc(key,alist[0]))<0)
+    return alist;
+  alist=alist[0..];
+  for (j=sizeof(alist)-1;j>=0;j--)
+    alist[j]=alist[j][0..i-1]+alist[j][i+1..];
+  return alist;
+}
+
+// * Element aus Alist loeschen (by pos)
+mixed *exclude_alist(int i,mixed *alist)
+{
+  int j;
+  if (!pointerp(alist) || !sizeof(alist) || i<0)
+    return alist;
+  if (!pointerp(alist[0]))
+    return alist[0..i-1]+alist[i+1..];
+  alist=alist[0..]; /* Create PHYSICAL copy of alist */
+  for (j=sizeof(alist)-1;j>=0;j--)
+    alist[j]=alist[j][0..i-1]+alist[j][i+1..];
+  return alist; /* order_alist is NOT necessary - see /doc/LPC/alist */
+}
+#endif // __ALISTS__
+
+// * German version of ctime()
+#define TAGE ({"Son","Mon","Die","Mit","Don","Fre","Sam"})
+#define MONATE ({"Jan","Feb","Mar","Apr","Mai","Jun","Jul","Aug",\
+                 "Sep","Okt","Nov","Dez"})
+string dtime(int wann) {
+  
+  if (wann == dtime_cache[0])
+    return(dtime_cache[1]);
+
+  int *lt = localtime(wann);
+  return sprintf("%s, %2d. %s %d, %02d:%02d:%02d",
+      TAGE[lt[TM_WDAY]], lt[TM_MDAY], MONATE[lt[TM_MON]], 
+      lt[TM_YEAR],lt[TM_HOUR], lt[TM_MIN], lt[TM_SEC]);
+}
+
+// wenn strftime im Driver nicht existiert, ist dies hier ein Alias auf dtime(),
+// zwar stimmt das Format dann nicht, aber die Mudlib buggt nicht und schreibt
+// ein ordentliches Datum/Uhrzeit.
+#if !__EFUN_DEFINED__(strftime)
+varargs string strftime(mixed fmt, int clock, int localized) {
+  if (intp(clock) && clock >= 0)
+    return dtime(clock);
+  else if (intp(fmt) && fmt >= 0)
+    return dtime(fmt);
+  
+  return dtime(time());
+}
+#endif //!__EFUN_DEFINED__(strftime)
+
+// * Shutdown mit zusaetzlichem logging
+nomask int shutdown(string reason)
+{
+  string name;
+  string obname;
+  string output;
+
+  if (!reason)
+    return 0;
+  if ( !ARCH_SECURITY && getuid(previous_object())!=ROOTID &&
+          object_name(previous_object())!="/obj/shut" )
+  {
+    write("You have no permission to shut down the gamedriver!\n");
+    return 0;
+  }
+  if ((this_interactive())&&(name=getuid(this_interactive())))
+  {
+    name=capitalize(name);
+    filter(users(),#'tell_object,//'
+               capitalize(name)+" faehrt das Spiel herunter!\n");
+  }
+  else
+    name="ANONYMOUS";
+  if (previous_object()) obname=capitalize(getuid(previous_object()));
+  output=name;
+  if (obname && name!=obname) output=output+" ("+obname+")";
+  if (previous_object()&&object_name(previous_object())=="/obj/shut"){
+    output+=" faehrt das Spiel via Armageddon herunter.\n";
+    output=dtime(time())+": "+output;
+    log_file("GAME_LOG",output+"\n",-1);
+    efun::shutdown();
+    return 1;
+  }
+  output=ctime(time())+": "+output+" faehrt das Spiel herunter.\n";
+  output+="    Grund: "+reason;
+  log_file("GAME_LOG",output+"\n",-1);
+  efun::shutdown();
+  return 1;
+}
+
+// * lowerchar
+
+int lowerchar(int char) {
+  if (char<'A' || char>'Z') return char;
+  return char+32;
+}
+
+// * upperstring
+
+string upperstring(string s)
+{
+#if __EFUN_DEFINED__(upper_case)
+  return(upper_case(s));
+#else
+  int i;
+  if (!stringp(s)) return 0;
+  for (i=sizeof(s)-1;i>=0;i--) s[i]=((s[i]<'a'||s[i]>'z')?s[i]:s[i]-32);
+  return s;
+#endif
+}
+
+// * lowerstring
+
+string lowerstring(string s)
+{
+  if (!stringp(s)) return 0;
+  return lower_case(s);
+}
+
+
+// * GD version
+string version()
+{
+  return __VERSION__;
+}
+
+// * break_string
+// stretch() -- stretch a line to fill a given width 
+private string stretch(string s, int width) {
+  int len=sizeof(s);
+  if (len==width) return s;
+
+  // reine Leerzeilen, direkt zurueckgeben
+  string trimmed=trim(s,TRIM_LEFT," ");
+  if (trimmed=="") return s; 
+  int start_spaces = len - sizeof(trimmed);
+
+  string* words = explode(trimmed, " ");
+  // der letzte kriegt keine Spaces
+  int word_count=sizeof(words) - 1;
+  // wenn Zeile nur aus einem Wort, wird das Wort zurueckgegeben
+  if (!word_count)
+    return " "*start_spaces + words[0];
+
+  int space_count = width - len;
+
+  int space_per_word=(word_count+space_count) / word_count;
+  // Anz.Woerter mit Zusatz-Space
+  int rest=(word_count+space_count) % word_count; 
+  // Rest-Spaces Verteilen
+  foreach (int pos : rest) words[pos]+=" ";
+  return (" "*start_spaces) + implode( words, " "*space_per_word );
+}
+
+// aus Geschwindigkeitsgruenden hat der Blocksatz fuer break_string eine
+// eigene Funktion bekommen:
+private varargs string block_string(string s, int width, int flags) {
+  // wenn BS_LEAVE_MY_LFS, aber kein BS_NO_PARINDENT, dann werden Zeilen mit
+  // einem Leerzeichen begonnen.
+  // BTW: Wenn !BS_LEAVE_MY_LFS, hat der Aufrufer bereits alle \n durch " "
+  // ersetzt.
+  if ( (flags & BS_LEAVE_MY_LFS)
+      && !(flags & BS_NO_PARINDENT))
+  {
+      s = " "+regreplace(s,"\n","\n ",1);
+  }
+
+  // sprintf fuellt die letzte Zeile auf die Feldbreite (hier also
+  // Zeilenbreite) mit Fuellzeichen auf, wenn sie NICHT mit \n umgebrochen
+  // ist. Es wird an die letzte Zeile aber kein Zeilenumbruch angehaengt.
+  // Eigentlich ist das Auffuellen doof, aber vermutlich ist es unnoetig, es
+  // wieder rueckgaengig zu machen.
+  s = sprintf( "%-*=s", width, s);
+
+  string *tmp=explode(s, "\n");
+  // Nur wenn s mehrzeilig ist, Blocksatz draus machen. Die letzte Zeile wird
+  // natuerlich nicht gedehnt. Sie ist dafuer schon von sprintf() aufgefuellt
+  // worden. BTW: Die letzte Zeile endet u.U. noch nicht mit einem \n (bzw.
+  // nur dann, wenn BS_LEAVE_MY_LFS und der uebergebene Text schon nen \n am
+  // Ende der letzten Zeile hat), das macht der Aufrufer...
+  if (sizeof(tmp) > 1)
+    return implode( map( tmp[0..<2], #'stretch/*'*/, width ), "\n" ) 
+      + "\n" + tmp[<1];
+
+  return s;
+}
+
+public varargs string break_string(string s, int w, mixed indent, int flags)
+{
+    if ( !s || s == "" ) return "";
+
+    if ( !w ) w=78;
+
+    if( intp(indent) )
+       indent = indent ? " "*indent : "";
+
+    int indentlen=stringp(indent) ? sizeof(indent) : 0;
+
+    if (indentlen>w) {
+      set_this_object(previous_object());
+      raise_error(sprintf("break_string: indent longer %d than width %d\n",
+                  indentlen,w));
+      // w=((indentlen/w)+1)*w;
+    }
+
+    if (!(flags & BS_LEAVE_MY_LFS)) 
+      s=regreplace( s, "\n", " ", 1 );
+
+    if ( flags & BS_SINGLE_SPACE )
+       s = regreplace( s, "(^|\n| )  *", "\\1", 1 );
+ 
+    string prefix="";
+    if (indentlen && flags & BS_PREPEND_INDENT) {
+      if (indentlen+sizeof(s) > w || 
+         (flags & BS_LEAVE_MY_LFS) && strstr(s,"\n")>-1) {
+       prefix=indent+"\n";
+       indent=(flags & BS_NO_PARINDENT) ? "" : " ";
+       indentlen=sizeof(indent);
+      }
+    }
+
+    if ( flags & BS_BLOCK ) {
+      /*
+           s = implode( map( explode( s, "\n" ),
+                               #'block_string, w, indentlen, flags),
+                      "" );
+      */
+      s = block_string( s , w - indentlen, flags );
+    }
+    else {
+      s = sprintf("%-1.*=s",w-indentlen,s);
+    }
+    if ( s[<1] != '\n' ) s += "\n";
+
+    if ( !indentlen ) return prefix + s;
+    
+    string indent2 = ( flags & BS_INDENT_ONCE ) ? (" "*indentlen) :indent;
+      
+    return prefix + indent + 
+      regreplace( s[0..<2], "\n", "\n"+indent2, 1 ) + "\n";
+      /*
+       string *buf;
+
+       buf = explode( s, "\n" );
+       return prefix + indent + implode( buf[0..<2], "\n"+indent2 ) + buf[<1] + "\n";
+      */
+}
+
+// * Elemente aus mapping loeschen - mapping vorher kopieren
+
+mapping m_copy_delete(mapping m, mixed key) {
+  return m_delete(copy(m), key);
+}
+
+// * times
+int last_reboot_time()
+{
+  return __BOOT_TIME__;
+}
+
+int first_boot_time()
+{
+  return 701517600;
+}
+
+int exist_days()
+{
+  return (((time()-first_boot_time())/8640)+5)/10;
+}
+
+// * uptime :)
+string uptime()
+{
+  int t;
+  int tmp;
+  string s;
+
+  t=time()-__BOOT_TIME__;
+  s="";
+  if (t>=86400)
+    s+=sprintf("%d Tag%s, ",tmp=t/86400,(tmp==1?"":"e"));
+  if (t>=3600)
+    s+=sprintf("%d Stunde%s, ",tmp=(t=t%86400)/3600,(tmp==1?"":"n"));
+  if (t>60)
+    s+=sprintf("%d Minute%s und ",tmp=(t=t%3600)/60,(tmp==1?"":"n"));
+  return s+sprintf("%d Sekunde%s",t=t%60,(t==1?"":"n"));
+}
+
+// * Was tun bei 'dangling' lfun-closures ?
+void dangling_lfun_closure() {
+  raise_error("dangling lfun closure\n");
+}
+
+// * Sperren ausser fuer master/simul_efun
+
+#if __EFUN_DEFINED__(set_environment)
+nomask void set_environment(object o1, object o2) {
+  raise_error("Available only for root\n");
+}
+#endif
+
+nomask void set_this_player(object pl) {
+  raise_error("Available only for root\n");
+}
+
+#if __EFUN_DEFINED__(export_uid)
+nomask void export_uid(object ob) {
+  raise_error("Available only for root\n");
+}
+#endif
+
+// * Jetzt auch closures
+int process_flag;
+
+public nomask int process_call()
+{
+  if (process_flag>0)
+    return process_flag;
+  else return(0);
+}
+
+private nomask string _process_string(string str,object po) {
+              set_this_object(po);
+              return(efun::process_string(str));
+}
+
+nomask string process_string( mixed str )
+{
+  string tmp, err;
+  int flag; 
+
+  if ( closurep(str) ) {
+      set_this_object( previous_object() );
+      return funcall(str);
+  }
+  else if (str==0)
+      return((string)str);
+  else if ( !stringp(str) ) {
+      return to_string(str);
+  }
+
+  if ( !(flag = process_flag > time() - 60))                     
+      process_flag=time();
+
+  err = catch(tmp = funcall(#'_process_string,str,previous_object()); publish);
+
+  if ( !flag )
+    process_flag=0;
+
+  if (err) {
+    // Verarbeitung abbrechen
+    set_this_object(previous_object());
+    raise_error(err);
+  }
+  return tmp;
+}
+
+// 'mkdir -p' - erzeugt eine komplette Hierarchie von Verzeichnissen.
+// wenn das Verzeichnis angelegt wurde oder schon existiert, wird 1
+// zurueckgeliefert, sonst 0.
+// Wirft einen Fehler, wenn das angebene Verzeichnis nicht absolut ist!
+public int mkdirp(string dir) {
+  // wenn es nur keinen fuehrenden / gibt, ist das ein Fehler.
+  if (strstr(dir, "/") != 0)
+    raise_error("mkdirp(): Pfad ist nicht absolute.\n");
+  // cut off trailing /...
+  if (dir[<1]=='/')
+      dir = dir[0..<2];
+
+  int fstat = file_size(dir);
+  // wenn es schon existiert, tun wir einfach so, als haetten wir es angelegt.
+  if (fstat == FSIZE_DIR)
+    return 1;
+  // wenn schon ne Datei existiert, geht es nicht.
+  if (fstat != FSIZE_NOFILE)
+    return 0;
+  // wenn es nur einen / gibt (den fuehrenden), dann ist es ein
+  // toplevel-verzeichnis, was direkt angelegt wird.
+  if (strrstr(dir,"/")==0) {
+    return funcall(bind_lambda(#'efun::mkdir, previous_object()), dir);
+  }
+
+  // mkdir() nicht direkt rufen, sondern vorher als closure ans aufrufende
+  // Objekt binden. Sonst laeuft die Rechtepruefung in valid_write() im Master
+  // unter der Annahme, dass die simul_efun.c mit ihrer root id was will.
+
+  // jetzt rekursiv die Verzeichnishierarchie anlegen. Wenn das erfolgreich
+  // ist, dann koennen wir jetzt mit mkdir das tiefste Verzeichnis anlegen
+  if (mkdirp(dir[0..strrstr(dir,"/")-1]) == 1)
+    return funcall(bind_lambda(#'efun::mkdir, previous_object()), dir);
+}
+
+
+// * Properties ggfs. mitspeichern
+mixed save_object(mixed name)
+{
+  mapping properties;
+  mapping save;
+  mixed index, res;
+  int i;
+
+  // nur Strings und 0 zulassen
+  if ((!stringp(name) || !sizeof(name)) && 
+      (!intp(name) || name!=0)) {
+      set_this_object(previous_object());
+      raise_error(sprintf(
+         "Only non-empty strings and 0 may be used as filename in "
+         "sefun::save_object()! Argument was %O\n",name));
+  }
+
+  save = m_allocate(0, 2);
+  properties = (mapping)previous_object()->QueryProperties();
+
+  if(mappingp(properties))
+  {
+    // delete all entries in mapping properties without SAVE flag!
+    index = m_indices(properties);
+    for(i = sizeof(index)-1; i>=0;i--)
+    {
+      if(properties[index[i], F_MODE] & SAVE)
+      {
+       save[index[i]] = properties[index[i]];
+       save[index[i], F_MODE] =
+       properties[index[i], F_MODE] &
+                    (~(SETMNOTFOUND|QUERYMNOTFOUND|QUERYCACHED|SETCACHED));
+      }
+    }
+  }
+  else save = ([]);
+
+  // save object!
+  previous_object()->_set_save_data(save);
+  // format: wie definiert in config.h
+  if (stringp(name))
+    res = funcall(bind_lambda(#'efun::save_object, previous_object()), name,
+       __LIB__SAVE_FORMAT_VERSION__);
+  else
+    res = funcall(bind_lambda(#'efun::save_object, previous_object()),
+       __LIB__SAVE_FORMAT_VERSION__);
+  previous_object()->_set_save_data(0);
+
+#ifdef IOSTATS
+  // Stats...
+  struct iostat_s stat = (<iostat_s>);
+  stat->oname = object_name(previous_object());
+  stat->time = time();
+  //stat->size = (int)object_info(previous_object(),OINFO_MEMORY,
+  //    OIM_TOTAL_DATA_SIZE);
+  if (stringp(name))
+      stat->size = file_size(name + ".o");
+  else
+      stat->sizeof(res);
+  //debug_message("saveo: "+saveo_stat[0]+"\n");
+  saveo_stat[1][saveo_stat[0]] = stat;
+  saveo_stat[0] = (saveo_stat[0] + 1) % sizeof(saveo_stat[1]);
+  //debug_message("saveo 2: "+saveo_stat[0]+"\n");
+#endif
+
+  return res;
+}
+
+// * Auch Properties laden
+int restore_object(string name)
+{
+  int result;
+  mixed index;
+  mixed save;
+  mapping properties;
+  int i;
+  closure cl;
+
+  // get actual property settings (by create())
+  properties = (mapping)previous_object()->QueryProperties();
+
+//  DEBUG(sprintf("RESTORE %O\n",name));
+  // restore object
+  result=funcall(bind_lambda(#'efun::restore_object, previous_object()), name);
+  //'))
+  //_get_save_data liefert tatsaechlich mixed zurueck, wenn das auch immer ein 
+  //mapping sein sollte.
+  save = (mixed)previous_object()->_get_save_data();
+  if(mappingp(save))
+  {
+    index = m_indices(save);
+    for(i = sizeof(index)-1; i>=0; i--)
+    {
+      properties[index[i]] = save[index[i]];
+      properties[index[i], F_MODE] = save[index[i], F_MODE]
+                            &~(SETCACHED|QUERYCACHED);
+    }
+  }
+  else properties = ([]);
+
+  // restore properties
+  funcall(
+          bind_lambda(
+                     unbound_lambda(({'arg}), //'})
+                                  ({#'call_other,({#'this_object}),
+                                  "SetProperties",'arg})),//')
+                     previous_object()),properties);
+  previous_object()->_set_save_data(0);
+
+#ifdef IOSTATS
+  // Stats...
+  //debug_message("restoreo: "+restoreo_stat[0]+"\n");
+  struct iostat_s stat = (<iostat_s>);
+  stat->oname = object_name(previous_object());
+  stat->time = time();
+  //stat->size = (int)object_info(previous_object(),OINFO_MEMORY,
+  //    OIM_TOTAL_DATA_SIZE);
+  stat->size = file_size(name + ".o");
+  restoreo_stat[1][restoreo_stat[0]] = stat;
+
+  restoreo_stat[0] = (restoreo_stat[0] + 1) % sizeof(restoreo_stat[1]);
+#endif
+
+  return result;
+}
+
+// * HB eines Objektes ein/ausschalten
+int set_object_heart_beat(object ob, int flag)
+{
+  if (objectp(ob))
+    return funcall(bind_lambda(#'efun::configure_object,ob), ob, OC_HEART_BEAT, flag);
+}
+
+// * Magierlevelgruppen ermitteln
+int query_wiz_grp(mixed wiz)
+{
+  int lev;
+
+  lev=query_wiz_level(wiz);
+  if (lev<SEER_LVL) return 0;
+  if (lev>=GOD_LVL) return lev;
+  if (lev>=ARCH_LVL) return ARCH_GRP;
+  if (lev>=ELDER_LVL) return ELDER_GRP;
+  if (lev>=LORD_LVL) return LORD_GRP;
+  if (lev>=SPECIAL_LVL) return SPECIAL_GRP;
+  if (lev>=DOMAINMEMBER_LVL) return DOMAINMEMBER_GRP;
+  if (lev>=WIZARD_LVL) return WIZARD_GRP;
+  if (lev>=LEARNER_LVL) return LEARNER_GRP;
+  return SEER_GRP;
+}
+
+mixed *wizlist_info()
+{
+  if (ARCH_SECURITY || !extern_call())
+            return efun::wizlist_info();
+  return 0;
+}
+
+// * wizlist ausgeben
+varargs void wizlist(string name, int sortkey ) {
+
+  if (!name)
+  {
+    if (this_player())
+      name = getuid(this_player());
+    if (!name)
+      return;
+  }
+
+  // Schluessel darf nur in einem gueltigen Bereich sein
+  if (sortkey<WL_NAME || sortkey>=WL_SIZE) sortkey=WL_COST;
+
+  mixed** wl = efun::wizlist_info();
+  // nach <sortkey> sortieren
+  wl = sort_array(wl, function int (mixed a, mixed b)
+      {return a[sortkey] < b[sortkey]; } );
+
+  // Summe ueber alle Kommandos ermitteln.
+  int total_cmd, i;
+  int pos=-1;
+  foreach(mixed entry : wl)
+  {
+    total_cmd += entry[WL_COMMANDS];
+    if (entry[WL_NAME] == name)
+      pos = i;
+    ++i;
+  }
+
+  if (pos < 0 && name != "ALL" && name != "TOP100")
+    return;
+
+  if (name == "TOP100")
+  {
+    if (sizeof(wl) > 100)
+      wl = wl[0..100];
+    else
+      wl = wl;
+  }
+  // um name herum schneiden
+  else if (name != "ALL")
+  {
+    if (sizeof(wl) <= 21)
+      wl = wl;
+    else if (pos + 10 < sizeof(wl) && pos - 10 > 0)
+      wl = wl[pos-10..pos+10];
+    else if (pos <=21)
+      wl = wl[0..20];
+    else if (pos >= sizeof(wl) - 21)
+      wl = wl[<21..];
+    else
+      wl = wl;
+  }
+
+  write("\nWizard top score list\n\n");
+  if (total_cmd == 0)
+    total_cmd = 1;
+  printf("%-20s %-6s %-3s %-17s %-6s %-6s %-6s\n",
+         "EUID", "cmds", "%", "Costs", "HB", "Arrays","Mapp.");
+  foreach(mixed e: wl)
+  {
+    printf("%-:20s %:6d %:2d%% [%:6dk,%:6dG] %:6d %:6d %:6d\n",
+          e[WL_NAME], e[WL_COMMANDS], e[WL_COMMANDS] * 100 / total_cmd,
+          e[WL_COST] / 1000, e[WL_TOTAL_GIGACOST],
+          e[WL_HEART_BEATS], e[WL_ARRAY_TOTAL], e[WL_MAPPING_TOTAL]
+          );
+  }
+  printf("\nTotal         %7d         (%d)\n\n", total_cmd, sizeof(wl));
+}
+
+
+// Ab hier folgen Hilfsfunktionen fuer call_out() bzw. fuer deren Begrenzung
+
+// ermittelt das Objekt des Callouts.
+private object _call_out_obj( mixed call_out ) {
+    return pointerp(call_out) ? call_out[0] : 0;
+}
+
+private void _same_object( object ob, mapping m ) {
+  // ist nicht so bloed, wie es aussieht, s. nachfolgede Verwendung von m
+  if ( m[ob] )
+    m[ob] += ({ ob });
+  else
+    m[ob] = ({ ob }); 
+}
+
+// alle Objekte im Mapping m zusammenfassen, die den gleichen Loadname (Name der
+// Blueprint) haben, also alle Clones der BP, die Callouts laufen haben.
+// Objekte werden auch mehrfach erfasst, je nach Anzahl ihrer Callouts.
+private void _same_path( object key, object *obs, mapping m ) {
+  string path;
+  if (!objectp(key) || !pointerp(obs)) return;
+
+  path = load_name(key);
+
+  if ( m[path] )
+    m[path] += obs;
+  else
+    m[path] = obs;
+}
+
+// key kann object oder string sein.
+private int _too_many( mixed key, mapping m, int i ) {
+    return sizeof(m[key]) >= i;
+}
+
+// alle Objekte in obs zerstoeren und Fehlermeldung ausgeben. ACHTUNG: Die
+// Objekte werden idR zu einer BP gehoeren, muessen aber nicht! In dem Fall
+// wird auf der Ebene aber nur ein Objekt in der Fehlermeldung erwaehnt.
+private void _destroy( mixed key, object *obs, string text, int uid ) {
+    if (!pointerp(obs)) return;
+    // Array mit unique Eintraege erzeugen.
+    obs = m_indices( mkmapping(obs) );
+    // Fehlermeldung auf der Ebene ausgeben, im catch() mit publish, damit es
+    // auf der Ebene direkt scrollt, der backtrace verfuegbar ist (im
+    // gegensatz zur Loesung mittels Callout), die Ausfuehrung aber weiter
+    // laeuft.
+    catch( efun::raise_error(           
+         sprintf( text,                   
+           uid ? (string)master()->creator_file(key) : key,                   
+           sizeof(obs), object_name(obs[<1]) ) );publish);
+    // Und weg mit dem Kram...
+    filter( obs, #'efun::destruct/*'*/ );
+}
+
+// Alle Objekt einer UID im Mapping m mit UID als KEys zusammenfassen. Objekt
+// sind dabei nicht unique.
+private void _same_uid( string key, object *obs, mapping m, closure cf ) {
+  string uid;
+
+  if ( !pointerp(obs) || !sizeof(obs) )
+    return;
+
+  uid = funcall( cf, key );
+
+  if ( m[uid] )
+    m[uid] += obs; // obs ist nen Array
+  else
+    m[uid] = obs;
+}
+
+private int _log_call_out(mixed co)
+{
+  log_file("TOO_MANY_CALLOUTS",
+      sprintf("%s::%O (%d)\n",object_name(co[0]),co[1],co[2]),
+      200000);
+  return 0;
+}
+
+private int last_callout_log=0;
+
+nomask varargs void call_out( varargs mixed *args )
+{
+    mixed tmp, *call_outs;
+
+    // Bei >600 Callouts alle Objekte killen, die mehr als 30 Callouts laufen
+    // haben.
+    if ( efun::driver_info(DI_NUM_CALLOUTS) > 600
+        && geteuid(previous_object()) != ROOTID )
+    {
+       // Log erzeugen...
+       if (last_callout_log <
+           efun::driver_info(DI_NUM_HEARTBEAT_TOTAL_CYCLES) - 10
+           && get_eval_cost() > 200000)
+       {
+         last_callout_log = efun::driver_info(DI_NUM_HEARTBEAT_TOTAL_CYCLES);
+         log_file("TOO_MANY_CALLOUTS",
+             sprintf("\n%s: ############ Too many callouts: %d ##############\n",
+                     strftime("%y%m%d-%H%M%S"),
+                     efun::driver_info(DI_NUM_CALLOUTS)));
+         filter(efun::call_out_info(), #'_log_call_out);
+       }
+       // Objekte aller Callouts ermitteln
+       call_outs = map( efun::call_out_info(), #'_call_out_obj );
+       mapping objectmap = ([]);
+       filter( call_outs, #'_same_object, &objectmap );
+       // Master nicht grillen...
+       efun::m_delete( objectmap, master(1) );
+       // alle Objekte raussuchen, die zuviele haben...
+       mapping res = filter_indices( objectmap, #'_too_many, objectmap, 29 );
+       // und ueber alle Keys gehen, an _destroy() werden Key und Array mit
+       // Objekten uebergeben (in diesem Fall sind Keys und Array mit
+       // Objekten jeweils das gleiche Objekt).
+       if ( sizeof(res) )       
+           walk_mapping(res, #'_destroy, "CALL_OUT overflow by single "             
+              "object [%O]. Destructed %d objects. [%s]\n", 0 );
+
+       // Bei (auch nach dem ersten Aufraeumen noch) >800 Callouts alle
+       // Objekte killen, die mehr als 50 Callouts laufen haben - und
+       // diesmal zaehlen Clones nicht eigenstaendig! D.h. es werden alle
+       // Clones einer BP gekillt, die Callouts laufen haben, falls alle
+       // diese Objekte _zusammen_ mehr als 50 Callouts haben!
+       if ( efun::driver_info(DI_NUM_CALLOUTS) > 800 ) {
+           // zerstoerte Objekte von der letzten Aktion sind in objectmap nicht
+           // mehr drin, da sie dort als Keys verwendet wurden.
+           mapping pathmap=([]);
+           // alle Objekt einer BP in res sortieren, BP-Name als Key, Arrays
+           // von Objekten als Werte.
+           walk_mapping( objectmap, #'_same_path, &pathmap);
+           // alle BPs (und ihre Objekte) raussuchen, die zuviele haben...
+           res = filter_indices( pathmap, #'_too_many/*'*/, pathmap, 50 );
+           // und ueber alle Keys gehen, an _destroy() werden die Clones
+           // uebergeben, die Callouts haben.
+           if ( sizeof(res) )
+              walk_mapping( res, #'_destroy/*'*/, "CALL_OUT overflow by file "
+                           "'%s'. Destructed %d objects. [%s]\n", 0 );
+
+           // Wenn beide Aufraeumarbeiten nichts gebracht haben und immer
+           // noch >1000 Callouts laufen, werden diesmal alle Callouts
+           // einer UID zusammengezaehlt.
+           // Alle Objekte einer UID, die es in Summe aller ihrer Objekt mit
+           // Callouts auf mehr als 100 Callouts bringt, werden geroestet.
+           if (efun::driver_info(DI_NUM_CALLOUTS) > 1000)
+           {
+              // das nach BP-Namen vorgefilterte Mapping jetzt nach UIDs
+              // zusammensortieren. Zerstoerte Clones filter _same_uid()
+              // raus.
+              mapping uidmap=([]);
+              walk_mapping( pathmap, #'_same_uid, &uidmap,
+                           symbol_function( "creator_file",
+                                          "/secure/master" ) );
+              // In res nun UIDs als Keys und Arrays von Objekten als Werte.
+              // Die rausfiltern, die mehr als 100 Objekte (non-unique, d.h.
+              // 100 Callouts!) haben.
+              res = filter_indices( uidmap, #'_too_many, uidmap, 100 );
+              // und erneut ueber die Keys laufen und jeweils die Arrays mit
+              // den Objekten zur Zerstoerung an _destroy()...
+              if ( sizeof(res) )
+                  walk_mapping( res, #'_destroy, "CALL_OUT overflow by "
+                              "UID '%s'. Destructed %d objects. [%s]\n",
+                              1 );
+           }
+       }
+    }
+
+    // Falls das aufrufende Objekt zerstoert wurde beim Aufraeumen
+    if ( !previous_object() )
+       return;
+
+    set_this_object( previous_object() );
+    apply( #'efun::call_out, args );
+    return;
+}
+
+mixed call_out_info() {
+  
+  object po = previous_object();
+  mixed coi = efun::call_out_info();
+
+  // ungefilterten Output nur fuer bestimmte Objekte, Objekte in /std oder
+  // /obj haben die BackboneID.
+  if (query_wiz_level(getuid(po)) >= ARCH_LVL
+       || (string)master()->creator_file(load_name(po)) == BACKBONEID ) {
+      return coi;
+  }
+  else {
+      return filter(coi, function mixed (mixed arr) {
+              if (pointerp(arr) && arr[0]==po)
+                 return 1;
+              else return 0; });
+  }
+}
+
+// * Zu einer closure das Objekt, an das sie gebunden ist, suchen
+// NICHT das Objekt, was ggf. die lfun definiert!
+mixed query_closure_object(closure c) {
+  return
+    CLOSURE_IS_UNBOUND_LAMBDA(get_type_info(c, 1)) ?
+      0 :
+  (to_object(c) || -1);
+}
+
+// * Wir wollen nur EIN Argument ... ausserdem checks fuer den Netztotenraum
+varargs void move_object(mixed what, mixed where)
+{
+  object po,tmp;
+
+  po=previous_object();
+  if (!where)
+  {
+    where=what;
+    what=po;
+  }
+  if (((stringp(where) && where==NETDEAD_ROOM ) ||
+       (objectp(where) && where==find_object(NETDEAD_ROOM))) &&
+       objectp(what) && object_name(what)!="/obj/sperrer")
+  {
+    if (!query_once_interactive(what))
+    {
+      what->remove();
+      if (what) destruct(what);
+      return;
+    }
+    if (living(what) || interactive(what))
+    {
+      log_file("NDEAD2",sprintf("TRYED TO MOVE TO NETDEAD: %O\n",what));
+      return;
+    }
+    set_object_heart_beat(what,0);
+  }
+  tmp=what;
+  while (tmp=environment(tmp))
+      // Ja. Man ruft die _set_xxx()-Funktionen eigentlich nicht direkt auf.
+      // Aber das Lichtsystem ist schon *so* rechenintensiv und gerade der
+      // P_LAST_CONTENT_CHANGE-Cache wird *so* oft benoetigt, dass es mir
+      // da um jedes bisschen Rechenzeit geht.
+      // Der Zweck heiligt ja bekanntlich die Mittel. ;-)
+      //
+      // Tiamak
+    tmp->_set_last_content_change();
+  funcall(bind_lambda(#'efun::move_object,po),what,where);
+  if (tmp=what)
+    while (tmp=environment(tmp))
+      tmp->_set_last_content_change();
+}
+
+
+void start_simul_efun() {
+  mixed *info;
+
+  // Falls noch nicht getan, extra_wizinfo initialisieren
+  if ( !pointerp(info = get_extra_wizinfo(0)) )
+    set_extra_wizinfo(0, info = allocate(BACKBONE_WIZINFO_SIZE));
+
+  InitLivingData(info);
+
+  set_next_reset(10); // direkt mal aufraeumen
+}
+
+protected void reset() {
+  set_next_reset(7200);
+  CleanLivingData();
+}
+
+#if !__EFUN_DEFINED__(absolute_hb_count)
+int absolute_hb_count() {
+  return efun::driver_info(DI_NUM_HEARTBEAT_TOTAL_CYCLES);
+}
+#endif
+
+void __set_environment(object ob, mixed target)
+{
+  string path;
+  object obj;
+
+  if (!objectp(ob))
+    return;
+  if (!IS_ARCH(geteuid(previous_object())) || !ARCH_SECURITY )
+    return;
+  if (objectp(target))
+  {
+    efun::set_environment(ob,target);
+    return;
+  }
+  path=(string)MASTER->_get_path(target,this_interactive());
+  if (stringp(path) && file_size(path+".c")>=0 &&
+      !catch(load_object(path);publish) )
+  {
+    obj=find_object(path);
+    efun::set_environment(ob,obj);
+    return;
+  }
+}
+
+void _dump_wizlist(string file, int sortby) {
+  int i;
+  mixed *a;
+
+  if (!LORD_SECURITY)
+    return;
+  if (!master()->valid_write(file,geteuid(previous_object()),"write_file"))
+  {
+    write("NO WRITE PERMISSION\n");
+    return;
+  }
+  a = wizlist_info();
+  a = sort_array(a, lambda( ({'a,'b}),
+                        ({#'<,
+                          ({#'[,'a,sortby}),
+                          ({#'[,'b,sortby})
+                         })));
+  rm(file);
+  for (i=sizeof(a)-1;i>=0;i--)
+    write_file(file,sprintf("%-11s: eval=%-8d cmds=%-6d HBs=%-5d array=%-5d mapping=%-7d\n",
+      a[i][WL_NAME],a[i][WL_TOTAL_COST],a[i][WL_COMMANDS],a[i][WL_HEART_BEATS],
+      a[i][WL_ARRAY_TOTAL],a[i][WL_CALL_OUT]));
+}
+
+public varargs object deep_present(mixed what, object ob) {
+
+  if(!objectp(ob))
+    ob=previous_object();
+  // Wenn ein Objekt gesucht wird: Alle Envs dieses Objekts ermitteln und
+  // schauen, ob in diesen ob vorkommt. Dann ist what in ob enthalten.
+  if(objectp(what)) {
+    object *envs=all_environment(what);
+    // wenn ob kein Environment hat, ist es offensichtlich nicht in what
+    // enthalten.
+    if (!pointerp(envs)) return 0;
+    if (member(envs, ob) != -1) return what;
+  }
+  // sonst wirds teurer, ueber alle Objekte im (deep) Inv laufen und per id()
+  // testen. Dabei muss aber die gewuenschte Nr. ("flasche 3") abgeschnitten
+  // werden und selber gezaehlt werden, welche das entsprechende Objekt ist.
+  else if (stringp(what)) {
+      int cnt;
+      string newwhat;
+      if(sscanf(what,"%s %d",newwhat,cnt)!=2)
+       cnt=1;
+      else
+       what=newwhat;
+      foreach(object invob: deep_inventory(ob)) {
+       if (invob->id(what) && !--cnt)
+           return invob;
+      }
+  }
+  else {
+    set_this_object(previous_object());
+    raise_error(sprintf("Wrong argument 1 to deep_present(). "
+         "Expected \"object\" or \"string\", got %.50O.\n",
+         what));
+  }
+  return 0;
+}
+
+mapping dump_ip_mapping()
+{
+  return 0;
+}
+
+nomask void swap(object obj)
+{
+  write("Your are not allowed to swap objects by hand!\n");
+  return;
+}
+
+nomask varargs void garbage_collection(string str)
+{
+  if(previous_object()==0 || !IS_ARCH(geteuid(previous_object())) 
+      || !ARCH_SECURITY)
+  {
+    write("Call GC now and the mud will crash in 5-6 hours. DONT DO IT!\n");
+    return;
+  }
+  else if (stringp(str))
+  {
+    return efun::garbage_collection(str);
+  }
+  else 
+    return efun::garbage_collection();
+}
+
+varargs void notify_fail(mixed nf, int prio) {
+  object po,oldo;
+  int oldprio;
+  
+  if (!PL || !objectp(po=previous_object())) return;
+  if (!stringp(nf) && !closurep(nf)) {
+      set_this_object(po);
+      raise_error(sprintf(
+         "Only strings and closures allowed for notify_fail! "
+         "Argument was: %.50O...\n",nf));
+  }
+
+  // falls ein Objekt bereits nen notify_fail() setzte, Prioritaeten abschaetzen
+  // und vergleichen.
+  if (objectp(oldo=query_notify_fail(1)) && po!=oldo) {
+    if (!prio) {       
+      //Prioritaet dieses notify_fail() 'abschaetzen'
+      if (po==PL) // Spieler-interne (soul-cmds)
+        prio=NF_NL_OWN;
+      else if (living(po))
+        prio=NF_NL_LIVING;
+      else if ((int)po->IsRoom())
+        prio=NF_NL_ROOM;
+      else
+        prio=NF_NL_THING;
+    }
+    //Prioritaet des alten Setzers abschaetzen
+    if (oldo==PL)
+      oldprio=NF_NL_OWN;
+    else if (living(oldo))
+      oldprio=NF_NL_LIVING;
+    else if ((int)oldo->IsRoom())
+      oldprio=NF_NL_ROOM;
+    else
+      oldprio=NF_NL_THING;
+  }
+  else // wenn es noch kein Notify_fail gibt:
+    oldprio=NF_NL_NONE;
+
+  //vergleichen und ggf. setzen
+  if (prio >= oldprio) { 
+    set_this_object(po);
+    efun::notify_fail(nf);
+  }
+
+  return;
+}
+
+void _notify_fail(string str)
+{
+  //query_notify_fail() benutzen, um das Objekt
+  //des letzten notify_fail() zu ermitteln
+  object o;
+  if ((o=query_notify_fail(1)) && o!=previous_object())
+    return;
+  //noch kein notify_fail() fuer dieses Kommando gesetzt, auf gehts.
+  set_this_object(previous_object());
+  efun::notify_fail(str);
+  return;
+}
+
+string time2string( string format, int zeit )
+{
+  int i,ch,maxunit,dummy;
+  string *parts, fmt;
+
+  int secs = zeit;
+  int mins = (zeit/60);
+  int hours = (zeit/3600);
+  int days = (zeit/86400);
+  int weeks =  (zeit/604800);
+  int months = (zeit/2419200);
+  int abbr = 0;
+
+  parts = regexplode( format, "\(%\(-|\)[0-9]*[nwdhmsxNWDHMSX]\)|\(%%\)" );
+
+  for( i=1; i<sizeof(parts); i+=2 )
+  {
+    ch = parts[i][<1];
+    switch( parts[i][<1] )
+    {
+    case 'x': case 'X':
+       abbr = sscanf( parts[i], "%%%d", dummy ) && dummy==0;
+       // NO break !
+    case 'n': case 'N':
+       maxunit |= 31;
+       break;
+    case 'w': case 'W':
+       maxunit |= 15;
+       break;
+    case 'd': case 'D':
+       maxunit |= 7;
+       break;
+    case 'h': case 'H':
+       maxunit |= 3;
+       break;
+    case 'm': case 'M':
+       maxunit |= 1;
+       break;
+    }
+  }
+  if( maxunit & 16 ) weeks %= 4;
+  if( maxunit & 8 ) days %= 7;
+  if( maxunit & 4 ) hours %= 24;
+  if( maxunit & 2 ) mins %= 60;
+  if( maxunit ) secs %= 60;
+
+  for( i=1; i<sizeof(parts); i+=2 )
+  {
+    fmt = parts[i][0..<2];
+    ch = parts[i][<1];
+    if( ch=='x' )
+    {
+      if (months > 0) ch='n';
+      else if( weeks>0 ) ch='w';
+      else if( days>0 ) ch='d';
+      else if( hours>0 ) ch='h'; 
+      else if(mins > 0) ch = 'm';
+      else ch = 's';
+    }
+    else if( ch=='X' )
+    {
+      if (months > 0) ch='N';
+      else if( weeks>0 ) ch='W';
+      else if( days>0 ) ch='D';
+      else if( hours>0 ) ch='H'; 
+      else if(mins > 0) ch = 'M';
+      else ch = 'S';
+    }
+    
+    switch( ch )
+    {
+      case 'n': parts[i] = sprintf( fmt+"d", months ); break;
+      case 'w': parts[i] = sprintf( fmt+"d", weeks ); break;
+      case 'd': parts[i] = sprintf( fmt+"d", days ); break;
+      case 'h': parts[i] = sprintf( fmt+"d", hours ); break;
+      case 'm': parts[i] = sprintf( fmt+"d", mins ); break;
+      case 's': parts[i] = sprintf( fmt+"d", secs ); break;
+      case 'N':
+       if(abbr) parts[i] = "M";
+       else parts[i] = sprintf( fmt+"s", (months==1) ? "Monat" : "Monate" );
+       break;
+      case 'W':
+       if(abbr) parts[i] = "w"; else
+       parts[i] = sprintf( fmt+"s", (weeks==1) ? "Woche" : "Wochen" );
+       break;
+      case 'D':
+       if(abbr) parts[i] = "d"; else
+       parts[i] = sprintf( fmt+"s", (days==1) ? "Tag" : "Tage" );
+       break;
+      case 'H':
+       if(abbr) parts[i] = "h"; else
+       parts[i] = sprintf( fmt+"s", (hours==1) ? "Stunde" : "Stunden" );
+       break;
+      case 'M':
+       if(abbr) parts[i] = "m"; else
+       parts[i] = sprintf( fmt+"s", (mins==1) ? "Minute" : "Minuten" );
+       break;
+      case 'S':
+       if(abbr) parts[i] = "s"; else
+       parts[i] = sprintf( fmt+"s", (secs==1) ? "Sekunde" : "Sekunden" );
+       break;
+      case '%':
+       parts[i] = "%";
+       break;
+      }
+    }
+    return implode( parts, "" );
+}
+
+nomask mixed __create_player_dummy(string name)
+{
+  string err;
+  object ob;
+  mixed m;
+  //hat nen Scherzkeks die Blueprint bewegt?
+  if ((ob=find_object("/secure/login")) && environment(ob))
+      catch(destruct(ob);publish);
+  err = catch(ob = clone_object("secure/login");publish);
+  if (err)
+  {
+    write("Fehler beim Laden von /secure/login.c\n"+err+"\n");
+    return 0;
+  }
+  if (objectp(m=(mixed)ob->new_logon(name))) netdead[name]=m;
+  return m;
+}
+
+nomask int secure_level()
+{
+  int *level;
+  //kette der Caller durchlaufen, den niedrigsten Level in der Kette
+  //zurueckgeben. Zerstoerte Objekte (Selbstzerstoerer) fuehren zur Rueckgabe
+  //von 0.
+  //caller_stack(1) fuegt dem Rueckgabearray this_interactive() hinzu bzw. 0,
+  //wenn es keinen Interactive gibt. Die 0 fuehrt dann wie bei zerstoerten
+  //Objekten zur Rueckgabe von 0, was gewuenscht ist, da es hier einen
+  //INteractive geben muss.
+  level=map(caller_stack(1),function int (object caller)
+      {if (objectp(caller))
+       return(query_wiz_level(geteuid(caller)));
+       return(0); // kein Objekt da, 0.
+      } );
+  return(min(level)); //den kleinsten Wert im Array zurueckgeben (ggf. 0)
+}
+
+nomask string secure_euid()
+{
+  string euid;
+
+  if (!this_interactive()) // Es muss einen interactive geben
+     return 0;
+  euid=geteuid(this_interactive());
+  // ueber alle Caller iterieren. Wenn eines davon eine andere euid hat als
+  // der Interactive und diese nicht die ROOTID ist, wird 0 zurueckgeben.
+  // Ebenso, falls ein Selbstzerstoerer irgendwo in der Kette ist.
+  foreach(object caller: caller_stack()) {
+      if (!objectp(caller) ||
+       (geteuid(caller)!=euid && geteuid(caller)!=ROOTID))
+         return 0;
+  }
+  return euid; // 'sichere' euid zurueckgeben
+}
+
+// INPUT_PROMPT und nen Leerprompt hinzufuegen, wenn keins uebergeben wird.
+// Das soll dazu dienen, dass alle ggf. ein EOR am Promptende kriegen...
+//#if __BOOT_TIME__ < 1360017213
+varargs void input_to( mixed fun, int flags, varargs mixed *args )
+{
+    mixed *arr;
+    int i;
+
+    if ( !this_player() || !previous_object() )
+       return;
+
+    // TODO: input_to(...,INPUT_PROMPT, "", ...), wenn kein INPUT_PROMPT
+    // vorkommt...
+    if ( flags&INPUT_PROMPT ) {    
+        arr = ({ fun, flags }) + args;
+    }
+    else {
+        // ggf. ein INPUT_PROMPT hinzufuegen und nen Leerstring als Prompt.
+        flags |= INPUT_PROMPT;
+        arr = ({ fun, flags, "" }) + args;
+    }
+
+    // Arrays gegen flatten quoten.
+    for ( i = sizeof(arr) - 1; i > 1; i-- )
+       if ( pointerp(arr[i]) )
+           arr[i] = quote(arr[i]);
+
+    apply( bind_lambda( unbound_lambda( ({}),
+                                     ({ #'efun::input_to/*'*/ }) + arr ),
+                       previous_object() ) );
+}
+//#endif
+
+nomask int set_light(int i)
+// erhoeht das Lichtlevel eines Objekts um i
+// result: das Lichtlevel innerhalb des Objekts
+{
+    object ob, *inv;
+    int lall, light, dark, tmp;
+
+    if (!(ob=previous_object())) return 0; // ohne das gehts nicht.
+
+    // aus kompatibilitaetsgruenden kann man auch den Lichtlevel noch setzen
+    if (i!=0) ob->SetProp(P_LIGHT, ob->QueryProp(P_LIGHT)+i);
+
+    // Lichtberechnung findet eigentlich in der Mudlib statt.
+    return (int)ob->QueryProp(P_INT_LIGHT);
+}
+
+
+public string iso2ascii( string str )
+{
+    if ( !stringp(str) || !sizeof(str) )
+       return "";
+
+    str = regreplace( str, "ä", "ae", 1 );
+    str = regreplace( str, "ö", "oe", 1 );
+    str = regreplace( str, "ü", "ue", 1 );
+    str = regreplace( str, "Ä", "Ae", 1 );
+    str = regreplace( str, "Ö", "Oe", 1 );
+    str = regreplace( str, "Ü", "Ue", 1 );
+    str = regreplace( str, "ß", "ss", 1 );
+    str = regreplace( str, "[^ -~]", "?", 1 );
+
+    return str;
+}
+
+
+public varargs string CountUp( string *s, string sep, string lastsep )
+{
+    string ret;
+
+    if ( !pointerp(s) )
+       return "";
+    
+    if (!sep) sep = ", ";
+    if (!lastsep) lastsep = " und ";
+
+    switch (sizeof(s))  {
+       case 0: ret=""; break;
+       case 1: ret=s[0]; break;
+       default:
+              ret = implode(s[0..<2], sep);
+              ret += lastsep + s[<1];
+    }
+    return ret;
+}
+
+nomask varargs int query_next_reset(object ob) {
+
+    // Typpruefung: etwas anderes als Objekte oder 0 sollen Fehler sein.
+    if (ob && !objectp(ob))
+      raise_error(sprintf("Bad arg 1 to query_next_reset(): got %.20O, "
+           "expected object.\n",ob));
+
+    // Defaultobjekt PO, wenn 0 uebergeben.
+    if ( !objectp(ob) )
+      ob = previous_object();
+
+    return efun::object_info(ob, OI_NEXT_RESET_TIME);
+}
+
+
+#if !__EFUN_DEFINED__(copy_file)
+#define MAXLEN 50000
+nomask int copy_file(string source, string dest)
+{
+
+  int ptr;
+  string bytes;
+
+  set_this_object(previous_object());
+  if (!sizeof(source)||!sizeof(dest)||source==dest||(file_size(source)==-1)||
+      (!call_other(master(),"valid_read",source,
+                   getuid(this_interactive()||
+                 previous_object()),"read_file",previous_object()))||
+      (!call_other(master(),"valid_read",source,
+                   getuid(this_interactive()||
+                 previous_object()),"write_file",previous_object())))
+    return 1;
+  switch (file_size(dest))
+  {
+  case -1:
+    break;
+  case -2:
+    if (dest[<1]!='/') dest+="/";
+    dest+=efun::explode(source,"/")[<1];
+    if (file_size(dest)==-2) return 1;
+    if (file_size(dest)!=-1) break;
+  default:
+    if (!rm(dest)) return 1;
+    break;
+  }
+  do
+  {
+    bytes = read_bytes(source, ptr, MAXLEN); ptr += MAXLEN;
+    if (!bytes) bytes="";
+    write_file(dest, bytes);
+  }
+  while(sizeof(bytes) == MAXLEN);
+  return 0;
+}
+#endif //!__EFUN_DEFINED__(copy_file)
+
+
+// ### Ersatzaufloesung in Strings ###
+varargs string replace_personal(string str, mixed *obs, int caps) {
+  int i;
+  string *parts;
+
+  parts = regexplode(str, "@WE[A-SU]*[0-9]");
+  i = sizeof(parts);
+
+  if (i>1) {
+    int j, t;
+    closure *name_cls;
+
+    t = j = sizeof(obs);
+
+    name_cls  =  allocate(j);
+    while (j--)
+      if (objectp(obs[j]))
+        name_cls[j] = symbol_function("name", obs[j]);
+      else if (stringp(obs[j]))
+        name_cls[j] = obs[j];
+
+    while ((i-= 2)>0) {
+      int ob_nr;
+      // zu ersetzendes Token in Fall und Objektindex aufspalten
+      ob_nr = parts[i][<1]-'1';
+      if (ob_nr<0 || ob_nr>=t) {
+        set_this_object(previous_object());
+        raise_error(sprintf("replace_personal: using wrong object index %d\n",
+                    ob_nr));
+        return implode(parts, "");
+      }
+
+      // casus kann man schon hier entscheiden
+      int casus;
+      string part = parts[i];
+      switch (part[3]) {
+        case 'R': casus = WER;    break;
+        case 'S': casus = WESSEN; break;
+        case 'M': casus = WEM;    break;
+        case 'N': casus = WEN;    break;
+        default:  continue; // passt schon jetzt nicht in das Hauptmuster
+      }
+
+      // und jetzt die einzelnen Keywords ohne fuehrendes "@WE", beendende Id
+      mixed tmp;
+      switch (part[3..<2]) {
+        case "R": case "SSEN": case "M": case "N":               // Name
+          parts[i] = funcall(name_cls[ob_nr], casus, 1);  break;
+        case "RU": case "SSENU": case "MU": case "NU":           // unbestimmt
+          parts[i] = funcall(name_cls[ob_nr], casus);     break;
+        case "RQP": case "SSENQP": case "MQP": case "NQP":       // Pronoun
+          if (objectp(tmp = obs[ob_nr]))
+            parts[i] = (string)tmp->QueryPronoun(casus);
+          break;
+        case "RQA": case "SSENQA": case "MQA": case "NQA":       // Article
+          if (objectp(tmp = obs[ob_nr]))
+            tmp = (string)tmp->QueryArticle(casus, 1, 1);
+          if (stringp(tmp) && !(tmp[<1]^' ')) 
+            tmp = tmp[0..<2];                // Extra-Space wieder loeschen
+          break;
+        case "RQPPMS": case "SSENQPPMS": case "MQPPMS": case "NQPPMS":
+          if (objectp(tmp = obs[ob_nr]))
+            parts[i] = (string)tmp->QueryPossPronoun(MALE, casus, SINGULAR);
+          break;
+        case "RQPPFS": case "SSENQPPFS": case "MQPPFS": case "NQPPFS":
+          if (objectp(tmp = obs[ob_nr]))
+            parts[i] = (string)tmp->QueryPossPronoun(FEMALE, casus, SINGULAR);
+          break;
+        case "RQPPNS": case "SSENQPPNS": case "MQPPNS": case "NQPPNS":
+          if (objectp(tmp = obs[ob_nr]))
+            parts[i] = (string)tmp->QueryPossPronoun(NEUTER, casus, SINGULAR);
+          break;
+        case "RQPPMP": case "SSENQPPMP": case "MQPPMP": case "NQPPMP":
+          if (objectp(tmp = obs[ob_nr]))
+            parts[i] = (string)tmp->QueryPossPronoun(MALE, casus, PLURAL);
+          break;
+        case "RQPPFP": case "SSENQPPFP": case "MQPPFP": case "NQPPFP":
+          if (objectp(tmp = obs[ob_nr]))
+            parts[i] = (string)tmp->QueryPossPronoun(FEMALE, casus, PLURAL);
+          break;
+        case "RQPPNP": case "SSENQPPNP": case "MQPPNP": case "NQPPNP":
+          if (objectp(tmp = obs[ob_nr]))
+            parts[i] = (string)tmp->QueryPossPronoun(NEUTER, casus, PLURAL);
+          break;
+        default:
+          continue;
+      }
+      
+      // wenn tmp ein String war, weisen wir es hier pauschal zu
+      if (stringp(tmp))
+        parts[i] = tmp;
+
+      // auf Wunsch wird nach Satzenden gross geschrieben
+      if (caps)
+        switch (parts[i-1][<2..]) {
+          case ". ":  case "! ":  case "? ":
+          case ".":   case "!":   case "?":
+          case ".\n": case "!\n": case "?\n":
+          case "\" ": case "\"\n":
+            parts[i] = capitalize(parts[i]);
+            break;
+        }
+    }
+    return implode(parts, "");
+  }
+  return str;
+}
+
+
+//replacements for dropped efuns in LD
+#if !__EFUN_DEFINED__(extract)
+deprecated varargs string extract(string str, int from, int to) {
+
+  if(!stringp(str)) {
+    set_this_object(previous_object());
+    raise_error(sprintf("Bad argument 1 to extract(): %O",str));
+  }
+  if (intp(from) && intp(to)) {
+    if (from>=0 && to>=0)
+      return(str[from .. to]);
+    else if (from>=0 && to<0)
+      return(str[from .. <abs(to)]);
+    else if (from<0 && to>=0)
+      return(str[<abs(from) .. to]);
+    else
+      return(str[<abs(from) .. <abs(to)]);
+  }
+  else if (intp(from)) {
+    if (from>=0)
+      return(str[from .. ]);
+    else
+      return(str[<abs(from) .. ]);
+  }
+  else {
+    return(str);
+  }
+}
+#endif // !__EFUN_DEFINED__(extract)
+
+#if !__EFUN_DEFINED__(slice_array)
+deprecated varargs mixed slice_array(mixed array, int from, int to) {
+
+  if(!pointerp(array)) {
+    set_this_object(previous_object());
+    raise_error(sprintf("Bad argument 1 to slice_array(): %O",array));
+  }
+  if (intp(from) && intp(to)) {
+    if (from>=0 && to>=0)
+      return(array[from .. to]);
+    else if (from>=0 && to<0)
+      return(array[from .. <abs(to)]);
+    else if (from<0 && to>=0)
+      return(array[<abs(from) .. to]);
+    else
+      return(array[<abs(from) .. <abs(to)]);
+  }
+  else if (intp(from)) {
+    if (from>=0)
+      return(array[from .. ]);
+    else
+      return(array[<abs(from) .. ]);
+  }
+  else {
+    return(array);
+  }
+}
+#endif // !__EFUN_DEFINED__(slice_array)
+
+#if !__EFUN_DEFINED__(member_array)
+deprecated int member_array(mixed item, mixed arraystring) {
+
+  if (pointerp(arraystring)) {
+    return(efun::member(arraystring,item));
+  }
+  else if (stringp(arraystring)) {
+    return(efun::member(arraystring,to_int(item)));
+  }
+  else {
+    set_this_object(previous_object());
+    raise_error(sprintf("Bad argument 1 to member_array(): %O",arraystring));
+  }
+}
+#endif // !__EFUN_DEFINED__(member_array)
+
+// The digit at the i'th position is the number of bits set in 'i'.
+string count_table =
+    "0112122312232334122323342334344512232334233434452334344534454556";
+int broken_count_bits( string s ) {
+    int i, res;
+    if( !stringp(s) || !(i=sizeof(s)) ) return 0;
+    for( ; i-->0; ) {
+        // We are counting 6 bits at a time using a precompiled table.
+        res += count_table[(s[i]-' ')&63]-'0';
+    }
+    return res;
+}
+
+#if !__EFUN_DEFINED__(count_bits)
+int count_bits( string s ) {
+    return(broken_count_bits(s));
+}
+#endif
+
+
+// * Teile aus einem Array entfernen *** OBSOLETE
+deprecated mixed *exclude_array(mixed *arr,int from,int to)
+{
+  if (to<from)
+    to = from;
+  return arr[0..from-1]+arr[to+1..];
+}
+
diff --git a/secure/simul_efun/spare/README b/secure/simul_efun/spare/README
new file mode 100644
index 0000000..303429a
--- /dev/null
+++ b/secure/simul_efun/spare/README
@@ -0,0 +1,20 @@
+simul_efun 
+---------- 
+Das simul_efun Objekt /secure/simul_efun/simul_efun benutzt die Dateien
+in /secure/simul_efun.
+Im Verzeichnis /secure/simul_efun/spare ist eine Sicherheitskopie von allen 
+Dateien in /secure/simul_efun.
+
+Falls /secure/simul_efun/simul_efun.c nicht ladbar ist, wird 
+/secure/simul_efun/spare/simul_efun.c als Ersatz versucht. Wenn auch das
+nicht ladbar ist, erfolgt ein Shutdown des Muds.
+
+Bei Aenderungen sollte _zuerst_ die normale simul efun editiert und
+anschliessend zerstoert/entladen werden, woraufhin sie implizit durch den
+Driver und Master neugeladen wird. Ist dies erfolgreich und die neue
+simul_efun laeuft (erst dann!) kopiert man die Dateien aus 
+/secure/simul_efun nach /secure/simul_efun/spare.
+
+Die Dateien hier sind mit Ausnahme der simul_efun.c selbst _nicht_ dazu 
+gedacht, geladen zu werden.
+
diff --git a/secure/simul_efun/spare/comm.c b/secure/simul_efun/spare/comm.c
new file mode 100644
index 0000000..595b852
--- /dev/null
+++ b/secure/simul_efun/spare/comm.c
@@ -0,0 +1,124 @@
+#include <living/comm.h>
+
+// Sendet an alle Objekte in room und room selber durch Aufruf von
+// ReceiveMsg().
+varargs void send_room(object|string room, string msg, int msg_type,
+                       string msg_action, string msg_prefix, object *exclude,
+                       object origin)
+{
+  if (stringp(room))
+    room=load_object(room);
+
+  origin ||= previous_object();
+  object *dest = exclude ? all_inventory(room) - exclude :
+                           all_inventory(room);
+
+  dest->ReceiveMsg(msg, msg_type, msg_action, msg_prefix, origin);
+}
+
+static int _shout_filter( object ob, string pat )
+{
+    string *ignore;
+
+    if ( !environment(ob) )
+       return 0;
+
+    return sizeof( regexp( ({ object_name( environment(ob) ) }), pat ) );
+}
+
+varargs void shout( string s, mixed where ){
+    object *u;
+    string *pfade;
+
+    if ( !sizeof( u = users() - ({ this_player() }) ) )
+       return;
+
+    if ( !where )
+       pfade = ({ "/" });
+    else if ( intp(where) )
+       pfade =
+           ({ implode( efun::explode( object_name( environment(this_player()) ),
+                                   "/" )[0..2], "/" ) + "/" });
+    else if ( stringp(where) )
+       pfade = ({ where });
+    else
+       pfade = where;
+
+    u = filter( u, "_shout_filter", ME, implode( pfade, "|" ) );
+    u->ReceiveMsg(s, MT_COMM|MT_FAR|MSG_DONT_WRAP|MSG_DONT_STORE,
+                  MA_SHOUT_SEFUN, 0, previous_object());
+}
+
+
+#if __VERSION__ > "3.3.718"
+// This sefun replaces the deprecated efun cat().
+#define CAT_MAX_LINES 50
+varargs int cat(string file, int start, int num)
+{
+    if (extern_call())
+        set_this_object(previous_object());
+
+    int more;
+
+    if (num < 0 || !this_player())
+        return 0;
+
+    if (!start)
+        start = 1;
+
+    if (!num || num > CAT_MAX_LINES) {
+        num = CAT_MAX_LINES;
+        more = sizeof(read_file(file, start+num, 1));
+    }
+
+    string txt = read_file(file, start, num);
+    if (!txt)
+        return 0;
+
+    efun::tell_object(this_player(), txt);
+
+    if (more)
+        efun::tell_object(this_player(), "*****TRUNCATED****\n");
+
+    return sizeof(txt & "\n");
+}
+#undef CAT_MAX_LINES
+#endif // __VERSION__ > "3.3.718"
+
+#if __VERSION__ > "3.3.719"
+// This sefun replaces the deprecated efun tail().
+#define TAIL_MAX_BYTES 1000
+varargs int tail(string file)
+{
+    if (extern_call())
+        set_this_object(previous_object());
+
+    if (!stringp(file) || !this_player())
+        return 0;
+
+    string txt = read_bytes(file, -(TAIL_MAX_BYTES + 80), (TAIL_MAX_BYTES + 80));
+    // read_bytes() returns 0 if the start of the section given by
+    // parameter #2 lies beyond the beginning of the file.
+    // In this case we simply try and read the entire file.
+    if (!stringp(txt) && file_size(file) < TAIL_MAX_BYTES+80)
+      txt = read_file(file);
+    // Exit if still nothing could be read.
+    if (!stringp(txt))
+      return 0;
+
+    // cut off first (incomplete) line
+    int index = strstr(txt, "\n");
+    if (index > -1) {
+        if (index + 1 < sizeof(txt))
+            txt = txt[index+1..];
+        else
+            txt = "";
+    }
+
+    efun::tell_object(this_player(), txt);
+
+    return 1;
+}
+#undef TAIL_MAX_BYTES
+#endif // __VERSION__ > "3.3.719"
+
diff --git a/secure/simul_efun/spare/debug_info.c b/secure/simul_efun/spare/debug_info.c
new file mode 100644
index 0000000..d607c2f
--- /dev/null
+++ b/secure/simul_efun/spare/debug_info.c
@@ -0,0 +1,434 @@
+/* This sefun is to provide a replacement for the efun debug_info().
+ * Feel free to add it to your mudlibs, if you have much code relying on that.
+ */
+
+#if ! __EFUN_DEFINED__(debug_info)
+
+#include <driver_info.h>
+#include <debug_info.h>
+
+mixed debug_info(int what, varargs mixed* args)
+{
+    if (sizeof(args) > 2)
+        raise_error("Too many arguments to debug_info\n");
+
+    switch(what)
+    {
+        default:
+            raise_error(sprintf("Illegal value %d for debug_info().\n", what));
+
+        case DINFO_OBJECT:
+        {
+            object ob;
+
+            if (sizeof(args) != 1)
+                raise_error("bad number of arguments to debug_info\n");
+            if (!objectp(args[0]))
+                raise_error("bag arg 2 to debug_info().\n");
+
+            ob = args[0];
+            printf("O_HEART_BEAT      : %s\n", efun::object_info(ob, OC_HEART_BEAT)       ? "TRUE" : "FALSE");
+            printf("O_ENABLE_COMMANDS : %s\n", efun::object_info(ob, OC_COMMANDS_ENABLED) ? "TRUE" : "FALSE");
+            printf("O_CLONE           : %s\n", efun::clonep(ob)                           ? "TRUE" : "FALSE");
+            printf("O_DESTRUCTED      : FALSE\n");
+            printf("O_SWAPPED         : %s\n", efun::object_info(ob, OI_SWAPPED)          ? "TRUE" : "FALSE");
+            printf("O_ONCE_INTERACTIVE: %s\n", efun::object_info(ob, OI_ONCE_INTERACTIVE) ? "TRUE" : "FALSE");
+            printf("O_RESET_STATE     : %s\n", efun::object_info(ob, OI_RESET_STATE)      ? "TRUE" : "FALSE");
+            printf("O_WILL_CLEAN_UP   : %s\n", efun::object_info(ob, OI_WILL_CLEAN_UP)    ? "TRUE" : "FALSE");
+            printf("O_REPLACED        : %s\n", efun::object_info(ob, OI_REPLACED)         ? "TRUE" : "FALSE");
+            printf("time_reset  : %d\n",       efun::object_info(ob, OI_NEXT_RESET_TIME));
+            printf("time_of_ref : %d\n",       efun::object_info(ob, OI_LAST_REF_TIME));
+            printf("ref         : %d\n",       efun::object_info(ob, OI_OBJECT_REFS));
+
+            int gticks = efun::object_info(ob, OI_GIGATICKS);
+            if(gticks)
+                printf("evalcost   :  %d%09d\n", gticks, efun::object_info(ob, OI_TICKS));
+            else
+                printf("evalcost   :  %d\n",   efun::object_info(ob, OI_TICKS));
+
+            printf("swap_num    : %d\n",       efun::object_info(ob, OI_SWAP_NUM));
+            printf("name        : '%s'\n",     efun::object_name(ob));
+            printf("load_name   : '%s'\n",     efun::load_name(ob));
+
+            object next_ob = efun::object_info(ob, OI_OBJECT_NEXT);
+            if (next_ob)
+                printf("next_all    : OBJ(%s)\n", efun::object_name(next_ob));
+
+            object prev_ob = efun::object_info(ob, OI_OBJECT_PREV);
+            if (prev_ob)
+                printf("Previous object in object list: OBJ(%s)\n", efun::object_name(prev_ob));
+            else
+                printf("This object is the head of the object list.\n");
+            break;
+        }
+
+        case DINFO_MEMORY:
+        {
+            object ob;
+
+            if (sizeof(args) != 1)
+                raise_error("bad number of arguments to debug_info\n");
+            if (!objectp(args[0]))
+                raise_error("bag arg 2 to debug_info().\n");
+
+            ob = args[0];
+
+            printf("program ref's %3d\n",   efun::object_info(ob, OI_PROG_REFS));
+            printf("Name: '%s'\n",          efun::program_name(ob));
+            printf("program size    %6d\n", efun::object_info(ob, OI_PROG_SIZE));
+            printf("num func's:  %3d (%4d)\n",
+                                            efun::object_info(ob, OI_NUM_FUNCTIONS),
+                                            efun::object_info(ob, OI_SIZE_FUNCTIONS));
+            printf("num vars:    %3d (%4d)\n",
+                                            efun::object_info(ob, OI_NUM_VARIABLES),
+                                            efun::object_info(ob, OI_SIZE_VARIABLES));
+
+            printf("num strings: %3d (%4d) : overhead %d + data %d (%d)\n",
+                                            efun::object_info(ob, OI_NUM_STRINGS),
+                                            efun::object_info(ob, OI_SIZE_STRINGS) + efun::object_info(ob, OI_SIZE_STRINGS_DATA),
+                                            efun::object_info(ob, OI_SIZE_STRINGS),
+                                            efun::object_info(ob, OI_SIZE_STRINGS_DATA),
+                                            efun::object_info(ob, OI_SIZE_STRINGS_DATA_TOTAL));
+
+            printf("num inherits %3d (%4d)\n",
+                                            efun::object_info(ob, OI_NUM_INHERITED),
+                                            efun::object_info(ob, OI_SIZE_INHERITED));
+
+            printf("total size      %6d\n", efun::object_info(ob, OI_PROG_SIZE_TOTAL));
+
+            printf("data size       %6d (%6d\n",
+                                            efun::object_info(ob, OI_DATA_SIZE),
+                                            efun::object_info(ob, OI_DATA_SIZE_TOTAL));
+            break;
+        }
+
+        case DINFO_OBJLIST:
+        {
+            if (sizeof(args) == 0)
+                raise_error("bad number of arguments to debug_info\n");
+
+            if (sizeof(args) == 1)
+            {
+                object * obs = efun::objects(args[0], 1);
+                return obs[0];
+            }
+            else
+                return efun::objects(args[0], args[1]);
+            break;
+        }
+
+        case DINFO_MALLOC:
+            write(debug_info(DINFO_STATUS, "malloc"));
+            break;
+
+        case DINFO_STATUS:
+        {
+            string which;
+            int opt;
+
+            if (sizeof(args) > 1)
+                raise_error("bad number of arguments to debug_info\n");
+
+            if (!sizeof(args) || !args[0])
+                which = "";
+            else if(stringp(args[0]))
+                which = args[0];
+            else
+                raise_error("bag arg 2 to debug_info().\n");
+
+            switch(which)
+            {
+                case "":
+                    opt = DI_STATUS_TEXT_MEMORY;
+                    break;
+
+                case "tables":
+                    opt = DI_STATUS_TEXT_TABLES;
+                    break;
+
+                case "swap":
+                    opt = DI_STATUS_TEXT_SWAP;
+                    break;
+
+                case "malloc":
+                    opt = DI_STATUS_TEXT_MALLOC;
+                    break;
+
+                case "malloc extstats":
+                    opt = DI_STATUS_TEXT_MALLOC_EXTENDED;
+                    break;
+
+                default:
+                    return 0;
+            }
+
+            return efun::driver_info(opt);
+        }
+
+        case DINFO_DUMP:
+        {
+            int opt;
+
+            if (!sizeof(args))
+                raise_error("bad number of arguments to debug_info\n");
+
+            if(!stringp(args[0]))
+                raise_error("bag arg 2 to debug_info().\n");
+
+            switch(args[0])
+            {
+                case "objects":
+                    opt = DDI_OBJECTS;
+                    break;
+
+                case "destructed":
+                    opt = DDI_OBJECTS_DESTRUCTED;
+                    break;
+
+                case "opcodes":
+                    opt = DDI_OPCODES;
+                    break;
+
+                case "memory":
+                    opt = DDI_MEMORY;
+                    break;
+
+                default:
+                    raise_error(sprintf("Bad argument '%s' to debug_info(DINFO_DUMP).\n", args[0]));
+                    return 0;
+            }
+
+            return efun::dump_driver_info(opt, args[1..1]...);
+        }
+
+        case DINFO_DATA:
+        {
+            mixed * result;
+
+            if (!sizeof(args))
+                raise_error("bad number of arguments to debug_info\n");
+
+            if (!intp(args[0]))
+                raise_error("bag arg 2 to debug_info().\n");
+
+            if (sizeof(args) == 2 && !intp(args[1]))
+                raise_error("bag arg 3 to debug_info().\n");
+
+            switch(args[0])
+            {
+                case DID_STATUS:
+                    result = allocate(DID_STATUS_MAX);
+
+                    result[DID_ST_ACTIONS]               = efun::driver_info(DI_NUM_ACTIONS);
+                    result[DID_ST_ACTIONS_SIZE]          = efun::driver_info(DI_SIZE_ACTIONS);
+                    result[DID_ST_SHADOWS]               = efun::driver_info(DI_NUM_SHADOWS);
+                    result[DID_ST_SHADOWS_SIZE]          = efun::driver_info(DI_SIZE_SHADOWS);
+
+                    result[DID_ST_OBJECTS]               = efun::driver_info(DI_NUM_OBJECTS);
+                    result[DID_ST_OBJECTS_SIZE]          = efun::driver_info(DI_SIZE_OBJECTS);
+                    result[DID_ST_OBJECTS_SWAPPED]       = efun::driver_info(DI_NUM_OBJECTS_SWAPPED);
+                    result[DID_ST_OBJECTS_SWAP_SIZE]     = efun::driver_info(DI_SIZE_OBJECTS_SWAPPED);
+                    result[DID_ST_OBJECTS_LIST]          = efun::driver_info(DI_NUM_OBJECTS_IN_LIST);
+                    result[DID_ST_OBJECTS_NEWLY_DEST]    = efun::driver_info(DI_NUM_OBJECTS_NEWLY_DESTRUCTED);
+                    result[DID_ST_OBJECTS_DESTRUCTED]    = efun::driver_info(DI_NUM_OBJECTS_DESTRUCTED);
+                    result[DID_ST_OBJECTS_PROCESSED]     = efun::driver_info(DI_NUM_OBJECTS_LAST_PROCESSED);
+                    result[DID_ST_OBJECTS_AVG_PROC]      = efun::driver_info(DI_LOAD_AVERAGE_PROCESSED_OBJECTS_RELATIVE);
+
+                    result[DID_ST_OTABLE]                = efun::driver_info(DI_NUM_OBJECTS_IN_TABLE);
+                    result[DID_ST_OTABLE_SLOTS]          = efun::driver_info(DI_NUM_OBJECT_TABLE_SLOTS);
+                    result[DID_ST_OTABLE_SIZE]           = efun::driver_info(DI_SIZE_OBJECT_TABLE);
+
+                    result[DID_ST_HBEAT_OBJS]            = efun::driver_info(DI_NUM_HEARTBEATS);
+                    result[DID_ST_HBEAT_CALLS]           = efun::driver_info(DI_NUM_HEARTBEAT_ACTIVE_CYCLES);
+                    result[DID_ST_HBEAT_CALLS_TOTAL]     = efun::driver_info(DI_NUM_HEARTBEAT_TOTAL_CYCLES);
+                    result[DID_ST_HBEAT_SLOTS]           = efun::driver_info(DI_NUM_HEARTBEATS);
+                    result[DID_ST_HBEAT_SIZE]            = efun::driver_info(DI_SIZE_HEARTBEATS);
+                    result[DID_ST_HBEAT_PROCESSED]       = efun::driver_info(DI_NUM_HEARTBEATS_LAST_PROCESSED);
+                    result[DID_ST_HBEAT_AVG_PROC]        = efun::driver_info(DI_LOAD_AVERAGE_PROCESSED_HEARTBEATS_RELATIVE);
+
+                    result[DID_ST_CALLOUTS]              = efun::driver_info(DI_NUM_CALLOUTS);
+                    result[DID_ST_CALLOUT_SIZE]          = efun::driver_info(DI_SIZE_CALLOUTS);
+
+                    result[DID_ST_ARRAYS]                = efun::driver_info(DI_NUM_ARRAYS);
+                    result[DID_ST_ARRAYS_SIZE]           = efun::driver_info(DI_SIZE_ARRAYS);
+
+                    result[DID_ST_MAPPINGS]              = efun::driver_info(DI_NUM_MAPPINGS);
+                    result[DID_ST_MAPPINGS_SIZE]         = efun::driver_info(DI_SIZE_MAPPINGS);
+                    result[DID_ST_HYBRID_MAPPINGS]       = efun::driver_info(DI_NUM_MAPPINGS_HYBRID);
+                    result[DID_ST_HASH_MAPPINGS]         = efun::driver_info(DI_NUM_MAPPINGS_HASH);
+
+                    result[DID_ST_STRUCTS]               = efun::driver_info(DI_NUM_STRUCTS);
+                    result[DID_ST_STRUCTS_SIZE]          = efun::driver_info(DI_SIZE_STRUCTS);
+                    result[DID_ST_STRUCT_TYPES]          = efun::driver_info(DI_NUM_STRUCT_TYPES);
+                    result[DID_ST_STRUCT_TYPES_SIZE]     = efun::driver_info(DI_SIZE_STRUCT_TYPES);
+
+                    result[DID_ST_PROGS]                 = efun::driver_info(DI_NUM_PROGS);
+                    result[DID_ST_PROGS_SIZE]            = efun::driver_info(DI_SIZE_PROGS);
+
+                    result[DID_ST_PROGS_SWAPPED]         = efun::driver_info(DI_NUM_PROGS_SWAPPED);
+                    result[DID_ST_PROGS_SWAP_SIZE]       = efun::driver_info(DI_SIZE_PROGS_SWAPPED);
+
+                    result[DID_ST_USER_RESERVE]          = efun::driver_info(DI_MEMORY_RESERVE_USER);
+                    result[DID_ST_MASTER_RESERVE]        = efun::driver_info(DI_MEMORY_RESERVE_MASTER);
+                    result[DID_ST_SYSTEM_RESERVE]        = efun::driver_info(DI_MEMORY_RESERVE_SYSTEM);
+
+                    result[DID_ST_ADD_MESSAGE]           = efun::driver_info(DI_NUM_MESSAGES_OUT);
+                    result[DID_ST_PACKETS]               = efun::driver_info(DI_NUM_PACKETS_OUT);
+                    result[DID_ST_PACKET_SIZE]           = efun::driver_info(DI_SIZE_PACKETS_OUT);
+                    result[DID_ST_PACKETS_IN]            = efun::driver_info(DI_NUM_PACKETS_IN);
+                    result[DID_ST_PACKET_SIZE_IN]        = efun::driver_info(DI_SIZE_PACKETS_IN);
+
+                    result[DID_ST_APPLY]                 = efun::driver_info(DI_NUM_FUNCTION_NAME_CALLS);
+                    result[DID_ST_APPLY_HITS]            = efun::driver_info(DI_NUM_FUNCTION_NAME_CALL_HITS);
+
+                    result[DID_ST_STRINGS]               = efun::driver_info(DI_NUM_VIRTUAL_STRINGS);
+                    result[DID_ST_STRING_SIZE]           = efun::driver_info(DI_SIZE_STRINGS);
+                    result[DID_ST_STR_TABLE_SIZE]        = efun::driver_info(DI_SIZE_STRING_TABLE);
+                    result[DID_ST_STR_OVERHEAD]          = efun::driver_info(DI_SIZE_STRING_OVERHEAD);
+                    result[DID_ST_UNTABLED]              = efun::driver_info(DI_NUM_STRINGS_UNTABLED);
+                    result[DID_ST_UNTABLED_SIZE]         = efun::driver_info(DI_SIZE_STRINGS_UNTABLED);
+                    result[DID_ST_TABLED]                = efun::driver_info(DI_NUM_STRINGS_TABLED);
+                    result[DID_ST_TABLED_SIZE]           = efun::driver_info(DI_SIZE_STRINGS_TABLED);
+                    result[DID_ST_STR_SEARCHES]          = efun::driver_info(DI_NUM_STRING_TABLE_LOOKUPS_BY_INDEX);
+                    result[DID_ST_STR_SEARCHLEN]         = efun::driver_info(DI_NUM_STRING_TABLE_LOOKUP_STEPS_BY_INDEX);
+                    result[DID_ST_STR_SEARCHES_BYVALUE]  = efun::driver_info(DI_NUM_STRING_TABLE_LOOKUPS_BY_VALUE);
+                    result[DID_ST_STR_SEARCHLEN_BYVALUE] = efun::driver_info(DI_NUM_STRING_TABLE_LOOKUP_STEPS_BY_VALUE);
+                    result[DID_ST_STR_CHAINS]            = efun::driver_info(DI_NUM_STRING_TABLE_SLOTS_USED);
+                    result[DID_ST_STR_ADDED]             = efun::driver_info(DI_NUM_STRING_TABLE_STRINGS_ADDED);
+                    result[DID_ST_STR_DELETED]           = efun::driver_info(DI_NUM_STRING_TABLE_STRINGS_REMOVED);
+                    result[DID_ST_STR_COLLISIONS]        = efun::driver_info(DI_NUM_STRING_TABLE_COLLISIONS);
+                    result[DID_ST_STR_FOUND]             = efun::driver_info(DI_NUM_STRING_TABLE_HITS_BY_INDEX);
+                    result[DID_ST_STR_FOUND_BYVALUE]     = efun::driver_info(DI_NUM_STRING_TABLE_HITS_BY_VALUE);
+
+                    result[DID_ST_RX_CACHED]             = efun::driver_info(DI_NUM_REGEX);
+                    result[DID_ST_RX_TABLE]              = efun::driver_info(DI_NUM_REGEX_TABLE_SLOTS);
+                    result[DID_ST_RX_TABLE_SIZE]         = efun::driver_info(DI_SIZE_REGEX);
+                    result[DID_ST_RX_REQUESTS]           = efun::driver_info(DI_NUM_REGEX_LOOKUPS);
+                    result[DID_ST_RX_REQ_FOUND]          = efun::driver_info(DI_NUM_REGEX_LOOKUP_HITS);
+                    result[DID_ST_RX_REQ_COLL]           = efun::driver_info(DI_NUM_REGEX_LOOKUP_COLLISIONS);
+
+                    result[DID_ST_MB_FILE]               = efun::driver_info(DI_SIZE_BUFFER_FILE);
+                    result[DID_ST_MB_SWAP]               = efun::driver_info(DI_SIZE_BUFFER_SWAP);
+
+                    result[DID_ST_BOOT_TIME]             = efun::driver_info(DI_BOOT_TIME);
+                    break;
+
+                case DID_SWAP:
+                    result = allocate(DID_SWAP_MAX);
+
+                    result[DID_SW_PROGS]                 = efun::driver_info(DI_NUM_PROGS_SWAPPED);
+                    result[DID_SW_PROG_SIZE]             = efun::driver_info(DI_SIZE_PROGS_SWAPPED);
+                    result[DID_SW_PROG_UNSWAPPED]        = efun::driver_info(DI_NUM_PROGS_UNSWAPPED);
+                    result[DID_SW_PROG_U_SIZE]           = efun::driver_info(DI_SIZE_PROGS_UNSWAPPED);
+                    result[DID_SW_VARS]                  = efun::driver_info(DI_NUM_OBJECTS_SWAPPED);
+                    result[DID_SW_VAR_SIZE]              = efun::driver_info(DI_SIZE_OBJECTS_SWAPPED);
+                    result[DID_SW_FREE]                  = efun::driver_info(DI_NUM_SWAP_BLOCKS_FREE);
+                    result[DID_SW_FREE_SIZE]             = efun::driver_info(DI_SIZE_SWAP_BLOCKS_FREE);
+                    result[DID_SW_FILE_SIZE]             = efun::driver_info(DI_SIZE_SWAP_BLOCKS);
+                    result[DID_SW_REUSED]                = efun::driver_info(DI_SIZE_SWAP_BLOCKS_REUSED);
+                    result[DID_SW_SEARCHES]              = efun::driver_info(DI_NUM_SWAP_BLOCKS_REUSE_LOOKUPS);
+                    result[DID_SW_SEARCH_LEN]            = efun::driver_info(DI_NUM_SWAP_BLOCKS_REUSE_LOOKUP_STEPS);
+                    result[DID_SW_F_SEARCHES]            = efun::driver_info(DI_NUM_SWAP_BLOCKS_FREE_LOOKUPS);
+                    result[DID_SW_F_SEARCH_LEN]          = efun::driver_info(DI_NUM_SWAP_BLOCKS_FREE_LOOKUP_STEPS);
+                    result[DID_SW_COMPACT]               = efun::driver_info(DC_SWAP_COMPACT_MODE);
+                    result[DID_SW_RECYCLE_FREE]          = efun::driver_info(DI_SWAP_RECYCLE_PHASE);
+                    break;
+
+                case DID_MEMORY:
+                    result = allocate(DID_MEMORY_MAX);
+
+                    result[DID_MEM_NAME]                 = efun::driver_info(DI_MEMORY_ALLOCATOR_NAME);
+                    result[DID_MEM_SBRK]                 = efun::driver_info(DI_NUM_SYS_ALLOCATED_BLOCKS);
+                    result[DID_MEM_SBRK_SIZE]            = efun::driver_info(DI_SIZE_SYS_ALLOCATED_BLOCKS);
+                    result[DID_MEM_LARGE]                = efun::driver_info(DI_NUM_LARGE_BLOCKS_ALLOCATED);
+                    result[DID_MEM_LARGE_SIZE]           = efun::driver_info(DI_SIZE_LARGE_BLOCKS_ALLOCATED);
+                    result[DID_MEM_LFREE]                = efun::driver_info(DI_NUM_LARGE_BLOCKS_FREE);
+                    result[DID_MEM_LFREE_SIZE]           = efun::driver_info(DI_SIZE_LARGE_BLOCKS_FREE);
+                    result[DID_MEM_LWASTED]              = efun::driver_info(DI_NUM_LARGE_BLOCKS_WASTE);
+                    result[DID_MEM_LWASTED_SIZE]         = efun::driver_info(DI_SIZE_LARGE_BLOCKS_WASTE);
+                    result[DID_MEM_CHUNK]                = efun::driver_info(DI_NUM_SMALL_BLOCK_CHUNKS);
+                    result[DID_MEM_CHUNK_SIZE]           = efun::driver_info(DI_SIZE_SMALL_BLOCK_CHUNKS);
+                    result[DID_MEM_SMALL]                = efun::driver_info(DI_NUM_SMALL_BLOCKS_ALLOCATED);
+                    result[DID_MEM_SMALL_SIZE]           = efun::driver_info(DI_SIZE_SMALL_BLOCKS_ALLOCATED);
+                    result[DID_MEM_SFREE]                = efun::driver_info(DI_NUM_SMALL_BLOCKS_FREE);
+                    result[DID_MEM_SFREE_SIZE]           = efun::driver_info(DI_SIZE_SMALL_BLOCKS_FREE);
+                    result[DID_MEM_SWASTED]              = efun::driver_info(DI_NUM_SMALL_BLOCKS_WASTE);
+                    result[DID_MEM_SWASTED_SIZE]         = efun::driver_info(DI_SIZE_SMALL_BLOCKS_WASTE);
+                    result[DID_MEM_MINC_CALLS]           = efun::driver_info(DI_NUM_INCREMENT_SIZE_CALLS);
+                    result[DID_MEM_MINC_SUCCESS]         = efun::driver_info(DI_NUM_INCREMENT_SIZE_CALL_SUCCESSES);
+                    result[DID_MEM_MINC_SIZE]            = efun::driver_info(DI_SIZE_INCREMENT_SIZE_CALL_DIFFS);
+                    result[DID_MEM_PERM]                 = efun::driver_info(DI_NUM_UNMANAGED_BLOCKS);
+                    result[DID_MEM_PERM_SIZE]            = efun::driver_info(DI_SIZE_UNMANAGED_BLOCKS);
+                    result[DID_MEM_CLIB]                 = efun::driver_info(DI_NUM_REPLACEMENT_MALLOC_CALLS);
+                    result[DID_MEM_CLIB_SIZE]            = efun::driver_info(DI_SIZE_REPLACEMENT_MALLOC_CALLS);
+                    result[DID_MEM_OVERHEAD]             = efun::driver_info(DI_SIZE_SMALL_BLOCK_OVERHEAD);
+                    result[DID_MEM_ALLOCATED]            = efun::driver_info(DI_SIZE_MEMORY_USED) + efun::driver_info(DI_SIZE_MEMORY_OVERHEAD);
+                    result[DID_MEM_USED]                 = efun::driver_info(DI_SIZE_MEMORY_USED);
+                    result[DID_MEM_TOTAL_UNUSED]         = efun::driver_info(DI_SIZE_MEMORY_UNUSED);
+                    result[DID_MEM_DEFRAG_CALLS]         = efun::driver_info(DI_NUM_MEMORY_DEFRAGMENTATION_CALLS_FULL) + efun::driver_info(DI_NUM_MEMORY_DEFRAGMENTATION_CALLS_TARGETED);
+                    result[DID_MEM_DEFRAG_CALLS_REQ]     = efun::driver_info(DI_NUM_MEMORY_DEFRAGMENTATION_CALLS_TARGETED);
+                    result[DID_MEM_DEFRAG_REQ_SUCCESS]   = efun::driver_info(DI_NUM_MEMORY_DEFRAGMENTATION_CALL_TARGET_HITS);
+                    result[DID_MEM_DEFRAG_BLOCKS_INSPECTED] = efun::driver_info(DI_NUM_MEMORY_DEFRAGMENTATION_BLOCKS_INSPECTED);
+                    result[DID_MEM_DEFRAG_BLOCKS_MERGED] = efun::driver_info(DI_NUM_MEMORY_DEFRAGMENTATION_BLOCKS_MERGED);
+                    result[DID_MEM_DEFRAG_BLOCKS_RESULT] = efun::driver_info(DI_NUM_MEMORY_DEFRAGMENTATION_BLOCKS_RESULTING);
+                    result[DID_MEM_AVL_NODES]            = efun::driver_info(DI_NUM_FREE_BLOCKS_AVL_NODES);
+                    result[DID_MEM_EXT_STATISTICS]       = efun::driver_info(DI_MEMORY_EXTENDED_STATISTICS);
+                    break;
+            }
+
+            if (sizeof(args) == 2)
+            {
+                int idx = args[0];
+                if (idx < 0 || idx >= sizeof(result))
+                    raise_error(sprintf("Illegal index for debug_info(): %d, expected 0..%d\n",
+                        idx, sizeof(result)-1));
+
+                return result[idx];
+            }
+            else
+                return result;
+        }
+
+        case DINFO_TRACE:
+        {
+            int which = DIT_CURRENT;
+
+            if (sizeof(args) > 1)
+                raise_error("bad number of arguments to debug_info\n");
+            if (sizeof(args))
+            {
+                if (!intp(args[0]))
+                    raise_error("bag arg 2 to debug_info().\n");
+                which = args[0];
+            }
+
+            switch (which)
+            {
+                case DIT_CURRENT:
+                    return efun::driver_info(DI_TRACE_CURRENT);
+
+                case DIT_ERROR:
+                    return efun::driver_info(DI_TRACE_LAST_ERROR) || ({ "No trace." });
+
+                case DIT_UNCAUGHT_ERROR:
+                    return efun::driver_info(DI_TRACE_LAST_UNCAUGHT_ERROR) || ({ "No trace." });
+
+                case DIT_STR_CURRENT:
+                    return efun::driver_info(DI_TRACE_CURRENT_AS_STRING);
+
+                case DIT_CURRENT_DEPTH:
+                    return efun::driver_info(DI_TRACE_CURRENT_DEPTH);
+
+                default:
+                    raise_error("bad arg 2 to debug_info().\n");
+            }
+
+        }
+
+        case DINFO_EVAL_NUMBER:
+            return efun::driver_info(DI_EVAL_NUMBER);
+    }
+    return 0;
+}
+
+#endif
diff --git a/secure/simul_efun/spare/enable_commands.c b/secure/simul_efun/spare/enable_commands.c
new file mode 100644
index 0000000..9d1c963
--- /dev/null
+++ b/secure/simul_efun/spare/enable_commands.c
@@ -0,0 +1,30 @@
+/* These sefuns are to provide a replacement for the efuns enable_commands()
+ * and disable_commands().
+ * Feel free to add it to your mudlibs, if you have much code relying on that.
+ */
+
+#include <configuration.h>
+
+#if ! __EFUN_DEFINED__(enable_commands)
+
+void enable_commands()
+{
+    object ob = efun::previous_object();
+
+    efun::configure_object(ob, OC_COMMANDS_ENABLED, 1);
+    efun::set_this_player(ob);
+}
+
+#endif
+
+#if ! __EFUN_DEFINED__(disable_commands)
+
+void disable_commands()
+{
+    object ob = efun::previous_object();
+
+    efun::configure_object(ob, OC_COMMANDS_ENABLED, 0);
+    efun::set_this_player(0);
+}
+
+#endif
diff --git a/secure/simul_efun/spare/hash.c b/secure/simul_efun/spare/hash.c
new file mode 100644
index 0000000..51bbf60
--- /dev/null
+++ b/secure/simul_efun/spare/hash.c
@@ -0,0 +1,17 @@
+#include "/sys/tls.h"
+
+deprecated string md5(mixed arg, varargs mixed* iterations)
+{
+    if (extern_call())
+         set_this_object(previous_object());
+
+    return hash(TLS_HASH_MD5, arg, iterations...);
+}
+
+deprecated string sha1(mixed arg, varargs mixed* iterations)
+{
+    if (extern_call())
+         set_this_object(previous_object());
+
+    return hash(TLS_HASH_SHA1, arg, iterations...);
+}
diff --git a/secure/simul_efun/spare/livings.c b/secure/simul_efun/spare/livings.c
new file mode 100644
index 0000000..160fcee
--- /dev/null
+++ b/secure/simul_efun/spare/livings.c
@@ -0,0 +1,348 @@
+// * living_name-Behandlung
+
+#define clean_log(s)
+//#define clean_log(s) log_file("CLEAN_SIM",ctime(time())[4..18]+": "+(s));
+
+private nosave mapping living_name_m, name_living_m, netdead;
+
+private void InitLivingData(mixed wizinfo) {
+  // living_name ist ein Pointer der auch in der extra_wizinfo gehalten 
+  // wird, so kann die simul_efun neu geladen werden, ohne dass dieses
+  // Mapping verloren geht
+  if (!mappingp(living_name_m = wizinfo[LIVING_NAME]))
+    living_name_m = wizinfo[LIVING_NAME] = m_allocate(0, 1);
+
+  // Gleiches gilt fuer das Mapping Name-Living
+  if (!mappingp(name_living_m = wizinfo[NAME_LIVING]))
+    name_living_m = wizinfo[NAME_LIVING] = m_allocate(0, 1);
+
+  // Netztote sind ebenfalls in der extra_wizinfo
+  if (!mappingp(netdead = wizinfo[NETDEAD_MAP]))
+    netdead = wizinfo[NETDEAD_MAP] = ([]);
+}
+
+public varargs string getuuid( object ob )
+{
+    mixed *ret;
+
+    if ( !objectp(ob) )
+       ob = previous_object();
+
+    if ( !query_once_interactive(ob) )
+       return getuid(ob);
+
+    ret = (mixed)master()->get_userinfo( getuid(ob) );
+
+    if ( !pointerp(ret) || sizeof(ret) < 5 )
+       return getuid(ob);
+
+    // Username + "_" + CreationDate
+    return ret[0] + "_" + ret[5];
+}
+
+void set_object_living_name(string livname, object obj)
+{
+  string old;
+  mixed a;
+  int i;
+
+  if (previous_object()==obj || previous_object()==this_object() ||
+      previous_object()==master())
+  {
+    if(!livname || !stringp(livname)) {
+      set_this_object(previous_object());
+      raise_error(sprintf("%O: illegal living name: %O\n", obj, livname));
+    }
+    if (old = living_name_m[obj]) {
+      if (pointerp(a = name_living_m[old])) {
+       a[member(a, obj)] = 0;
+      } else {
+       m_delete(name_living_m, old);
+      }
+    }
+    living_name_m[obj] = livname;
+    if (a = name_living_m[livname]) {
+      if (!pointerp(a)) {
+       name_living_m[livname] = ({a, obj});
+       return;
+      }
+      /* Try to reallocate entry from destructed object */
+      if ((i = member(a, 0)) >= 0) {
+       a[i] = obj;
+       return;
+      }
+      name_living_m[livname] = a + ({obj});
+      return;
+    }
+    name_living_m[livname] = obj;
+  }
+}
+
+void set_living_name(string livname)
+{
+  set_object_living_name(livname,previous_object());
+}
+
+void remove_living_name()
+{
+  string livname;
+
+  if (!previous_object())
+    return;
+  if (livname=living_name_m[previous_object()])
+  {
+    m_delete(living_name_m,previous_object());
+    if (objectp(name_living_m[livname]))
+    {
+      if (name_living_m[livname]==previous_object())
+       m_delete(name_living_m,livname);
+      return;
+    }
+    if (pointerp(name_living_m[livname]))
+    {
+      name_living_m[livname]-=({previous_object()});
+      if (!sizeof(name_living_m[livname]))
+       m_delete(name_living_m,livname);
+    }
+  }
+}
+
+void _set_netdead()
+{
+  if (query_once_interactive(previous_object()))
+    netdead[getuid(previous_object())]=previous_object();
+}
+
+void _remove_netdead()
+{
+  m_delete(netdead,getuid(previous_object()));
+}
+
+object find_netdead(string uuid)
+{
+  int i;
+  string uid;
+  // Wenn sscanf() 2 liefert, ist uuid auch ne uuid.
+  int is_uuid = sscanf(uuid, "%s_%d", uid, i) == 2;
+  if (!is_uuid)
+    uid = uuid;
+ 
+  if (is_uuid && getuuid(netdead[uid]) != uuid)
+      return 0;
+
+  return netdead[uid];
+}
+
+string *dump_netdead()
+{
+  return m_indices(netdead);
+}
+
+object find_living(string livname) {
+  mixed *a, r;
+  int i;
+
+  if (pointerp(r = name_living_m[livname])) {
+    if (member(r,0)>=0)
+      r-=({0});
+    if (!sizeof(r)){
+      m_delete(name_living_m,livname);
+      clean_log(sprintf("find_living loescht %s\n",livname));
+      return 0;
+    }
+    if ( !living(r = (a = r)[0])) {
+      for (i = sizeof(a); --i;) {
+       if (living(a[<i])) {
+         r = a[<i];
+         a[<i] = a[0];
+         return a[0] = r;
+       }
+      }
+    }
+    return r;
+  }
+  return living(r) && r;
+}
+
+object *find_livings(string livname)
+{
+  mixed r;
+
+  if (pointerp(r=name_living_m[livname]))
+    r-=({0});
+  else
+    if (objectp(r))
+      r=({r});
+  if (!pointerp(r)||!sizeof(r))
+    return 0;
+  return r;
+}
+
+object find_player(string uuid) {
+  object *objs;
+  mixed res;
+  int i;
+  string uid;
+
+  if (!stringp(uuid))
+    return 0;
+  // Wenn sscanf() 2 liefert, ist uuid auch ne uuid.
+  int is_uuid = sscanf(uuid, "%s_%d", uid, i) == 2;
+  if (!is_uuid)
+    uid = uuid;
+
+  if (pointerp(res = name_living_m[uid])) {
+    // zerstoerte Objekte ggf. entfernen
+    if (member(res,0)>=0) {
+      res-=({0});
+      name_living_m[uid] = res;
+    }
+    // ggf. Namen ohne Objekte entfernen.
+    if (!sizeof(res)){
+      m_delete(name_living_m,uid);
+      clean_log(sprintf("find_player loescht %s\n",uid));
+      return 0;
+    }
+    objs = res;
+    res = objs[0];
+    // Wenn das 0. Element der Spieler ist, sind wir fertig. Ansonsten suchen
+    // wir den Spieler und schieben ihn an die 0. Stelle im Array.
+    if ( !query_once_interactive(res)) {
+      for (i = sizeof(objs); --i;) {
+       if (objs[<i] && query_once_interactive(objs[<i])) {
+         res = objs[<i];
+         objs[<i] = objs[0];
+         objs[0] = res;
+         break;
+       }
+      }
+      res = (objectp(res) && query_once_interactive(res)) ? res : 0;
+    }
+    // else: in res steht der Spieler schon.
+  }
+  else if (objectp(res)) // r ist nen Einzelobjekt
+    res = query_once_interactive(res) ? res : 0;
+
+  // res ist jetzt ggf. der Spieler. Aber wenn der ne andere als die gesuchte
+  // UUID hat, ist das Ergebnis trotzdem 0.
+  if (is_uuid && getuuid(res)!=uuid)
+      return 0;
+
+  // endlich gefunden
+  return res;
+}
+
+private int check_match( string str, int players_only )
+{
+    mixed match;
+
+    if ( !(match = name_living_m[str]) )
+       return 0;
+
+    if ( !pointerp(match) )
+       match = ({ match });
+
+    match -= ({0});
+
+    if ( sizeof(match) ){
+       if ( players_only )
+           return sizeof(filter( match, #'query_once_interactive/*'*/ ))
+              > 0;
+       else
+           return 1;
+    }
+
+    m_delete( name_living_m, str );
+    clean_log( sprintf("check_match loescht %s\n", str) );
+    return 0;
+}
+
+//TODO:: string|string* exclude
+varargs mixed match_living( string str, int players_only, mixed exclude )
+{
+    int i, s;
+    mixed match, *user;
+
+    if ( !str || str == "" )
+       return 0;
+
+    if ( !pointerp(exclude) )
+       exclude = ({ exclude });
+
+    if ( member(exclude, str) < 0 && check_match(str, players_only) )
+       return str;
+
+    user = m_indices(name_living_m);
+    s = sizeof(str);
+    match = 0;
+
+    for ( i = sizeof(user); i--; )
+       if ( str == user[i][0..s-1] && member( exclude, user[i] ) < 0 )
+           if ( match )
+              return -1;
+           else
+              if ( check_match(user[i], players_only) )
+                  match = user[i];
+
+    if ( !match )
+       return -2;
+
+    return match;
+}
+
+mixed *dump_livings()
+{
+  return sort_array(m_indices(name_living_m),#'>);
+}
+
+// * regelmaessig Listen von Ballast befreien
+private void clean_name_living_m(string *keys, int left, int num)
+{
+  int i, j;
+  mixed a;
+
+  while (left && num--)
+  {
+    if (pointerp(a = name_living_m[keys[--left]]) && member(a, 0)>=0)
+    {
+      a-=({0});
+      name_living_m[keys[left]] = a;
+    }
+    if (!a || (pointerp(a) && !sizeof(a)))
+    {
+      clean_log("Toasting "+keys[left]+"\n");
+      m_delete(name_living_m, keys[left]);
+    } else clean_log(sprintf("KEEPING %s (%O)\n",keys[left],pointerp(a)?sizeof(a):a));
+  }
+  if (left)
+    efun::call_out(#'clean_name_living_m, 1, keys, left, 80);
+  else
+    clean_log("Clean process finished\n");
+}
+
+private void clean_netdead() {
+  int i;
+  string *s;
+  object ob;
+
+  s=m_indices(netdead);
+  for (i=sizeof(s)-1;i>=0;i--)
+    if (!objectp(ob=netdead[s[i]]) || interactive(ob))
+      m_delete(netdead,s[i]);
+}
+
+private void CleanLivingData() {
+  if (find_call_out(#'clean_name_living_m) < 0) {
+    clean_log("Starting clean process\n");
+    efun::call_out(#'clean_name_living_m,
+                 1,
+                 m_indices(name_living_m),
+                 sizeof(name_living_m),
+                 80
+                 );
+  }
+  efun::call_out(#'clean_netdead,2);
+}
+
+#undef clean_log
+
diff --git a/secure/simul_efun/spare/object_info.c b/secure/simul_efun/spare/object_info.c
new file mode 100644
index 0000000..ed7c009
--- /dev/null
+++ b/secure/simul_efun/spare/object_info.c
@@ -0,0 +1,106 @@
+/* This sefun is to provide the old semantics of the efun object_info().
+ * Feel free to add it to your mudlibs, if you have much code relying on that.
+ */
+
+#if __EFUN_DEFINED__(driver_info) /* Newer version is there */
+
+#include <objectinfo.h>
+#include <object_info.h>
+
+mixed object_info(object ob, int what, varargs int* index)
+{
+    mixed * result;
+
+    if (sizeof(index) > 1)
+        raise_error("Too many arguments to object_info\n");
+
+    switch(what)
+    {
+        default:
+            raise_error(sprintf("Illegal value %d for object_info().\n", what));
+
+        case OINFO_BASIC:
+        {
+            result = allocate(OIB_MAX);
+
+            result[OIB_HEART_BEAT]        = efun::object_info(ob, OC_HEART_BEAT);
+            result[OIB_IS_WIZARD]         = 0;   /* Not supported anymore. */
+            result[OIB_ENABLE_COMMANDS]   = efun::object_info(ob, OC_COMMANDS_ENABLED);
+            result[OIB_CLONE]             = efun::clonep(ob);
+            result[OIB_DESTRUCTED]        = 0;   /* Not possible anymore. */
+            result[OIB_SWAPPED]           = efun::object_info(ob, OI_SWAPPED);
+            result[OIB_ONCE_INTERACTIVE]  = efun::object_info(ob, OI_ONCE_INTERACTIVE);
+            result[OIB_RESET_STATE]       = efun::object_info(ob, OI_RESET_STATE);
+            result[OIB_WILL_CLEAN_UP]     = efun::object_info(ob, OI_WILL_CLEAN_UP);
+            result[OIB_LAMBDA_REFERENCED] = efun::object_info(ob, OI_LAMBDA_REFERENCED);
+            result[OIB_SHADOW]            = efun::object_info(ob, OI_SHADOW_PREV) && 1;
+            result[OIB_REPLACED]          = efun::object_info(ob, OI_REPLACED);
+            result[OIB_NEXT_RESET]        = efun::object_info(ob, OI_NEXT_RESET_TIME);
+            result[OIB_TIME_OF_REF]       = efun::object_info(ob, OI_LAST_REF_TIME);
+            result[OIB_REF]               = efun::object_info(ob, OI_OBJECT_REFS);
+            result[OIB_GIGATICKS]         = efun::object_info(ob, OI_GIGATICKS);
+            result[OIB_TICKS]             = efun::object_info(ob, OI_TICKS);
+            result[OIB_SWAP_NUM]          = efun::object_info(ob, OI_SWAP_NUM);
+            result[OIB_PROG_SWAPPED]      = efun::object_info(ob, OI_PROG_SWAPPED);
+            result[OIB_VAR_SWAPPED]       = efun::object_info(ob, OI_VAR_SWAPPED);
+            result[OIB_NAME]              = efun::object_name(ob);
+            result[OIB_LOAD_NAME]         = efun::load_name(ob);
+            result[OIB_NEXT_ALL]          = efun::object_info(ob, OI_OBJECT_NEXT);
+            result[OIB_PREV_ALL]          = efun::object_info(ob, OI_OBJECT_PREV);
+            result[OIB_NEXT_CLEANUP]      = efun::object_info(ob, OI_NEXT_CLEANUP_TIME);
+            break;
+        }
+
+        case OINFO_POSITION:
+        {
+            result = allocate(OIP_MAX);
+
+            result[OIP_NEXT] = efun::object_info(ob, OI_OBJECT_NEXT);
+            result[OIP_PREV] = efun::object_info(ob, OI_OBJECT_PREV);
+            result[OIP_POS]  = efun::object_info(ob, OI_OBJECT_POS);
+            break;
+        }
+
+        case OINFO_MEMORY:
+        {
+            result = allocate(OIM_MAX);
+
+            result[OIM_REF]                = efun::object_info(ob, OI_PROG_REFS);
+            result[OIM_NAME]               = efun::program_name(ob);
+            result[OIM_PROG_SIZE]          = efun::object_info(ob, OI_PROG_SIZE);
+            result[OIM_NUM_FUNCTIONS]      = efun::object_info(ob, OI_NUM_FUNCTIONS);
+            result[OIM_SIZE_FUNCTIONS]     = efun::object_info(ob, OI_SIZE_FUNCTIONS);
+            result[OIM_NUM_VARIABLES]      = efun::object_info(ob, OI_NUM_VARIABLES);
+            result[OIM_SIZE_VARIABLES]     = efun::object_info(ob, OI_SIZE_VARIABLES);
+            result[OIM_NUM_STRINGS]        = efun::object_info(ob, OI_NUM_STRINGS);
+            result[OIM_SIZE_STRINGS]       = efun::object_info(ob, OI_SIZE_STRINGS);
+            result[OIM_SIZE_STRINGS_DATA]  = efun::object_info(ob, OI_SIZE_STRINGS_DATA);
+            result[OIM_SIZE_STRINGS_TOTAL] = efun::object_info(ob, OI_SIZE_STRINGS_DATA_TOTAL);
+            result[OIM_NUM_INHERITED]      = efun::object_info(ob, OI_NUM_INHERITED);
+            result[OIM_SIZE_INHERITED]     = efun::object_info(ob, OI_SIZE_INHERITED);
+            result[OIM_TOTAL_SIZE]         = efun::object_info(ob, OI_PROG_SIZE_TOTAL);
+            result[OIM_DATA_SIZE]          = efun::object_info(ob, OI_DATA_SIZE);
+            result[OIM_TOTAL_DATA_SIZE]    = efun::object_info(ob, OI_DATA_SIZE_TOTAL);
+            result[OIM_NO_INHERIT]         = efun::object_info(ob, OI_NO_INHERIT);
+            result[OIM_NO_CLONE]           = efun::object_info(ob, OI_NO_CLONE);
+            result[OIM_NO_SHADOW]          = efun::object_info(ob, OI_NO_SHADOW);
+            result[OIM_NUM_INCLUDES]       = efun::object_info(ob, OI_NUM_INCLUDED);
+            result[OIM_SHARE_VARIABLES]    = efun::object_info(ob, OI_SHARE_VARIABLES);
+            break;
+        }
+    }
+
+    if (sizeof(index))
+    {
+        int idx = index[0];
+        if (idx < 0 || idx >= sizeof(result))
+            raise_error(sprintf("Illegal index for object_info(): %d, expected 0..%d\n",
+                idx, sizeof(result)-1));
+
+        return result[idx];
+    }
+    else
+        return result;
+}
+
+#endif
diff --git a/secure/simul_efun/spare/query_editing.c b/secure/simul_efun/spare/query_editing.c
new file mode 100644
index 0000000..5146dcd
--- /dev/null
+++ b/secure/simul_efun/spare/query_editing.c
@@ -0,0 +1,17 @@
+/* This sefun is to provide a replacement for the efun query_editing().
+ * Feel free to add it to your mudlibs, if you have much code relying on that.
+ */
+
+#if ! __EFUN_DEFINED__(query_editing)
+
+#include <interactive_info.h>
+
+int|object query_editing(object ob)
+{
+    if(!efun::interactive(ob))
+        return 0;
+
+    return efun::interactive_info(ob, II_EDITING);
+}
+
+#endif
diff --git a/secure/simul_efun/spare/query_idle.c b/secure/simul_efun/spare/query_idle.c
new file mode 100644
index 0000000..93a32ae
--- /dev/null
+++ b/secure/simul_efun/spare/query_idle.c
@@ -0,0 +1,14 @@
+/* This sefun is to provide a replacement for the efun query_idle().
+ * Feel free to add it to your mudlibs, if you have much code relying on that.
+ */
+
+#if ! __EFUN_DEFINED__(query_idle)
+
+#include <interactive_info.h>
+
+int query_idle(object ob)
+{
+    return efun::interactive_info(ob, II_IDLE);
+}
+
+#endif
diff --git a/secure/simul_efun/spare/query_input_pending.c b/secure/simul_efun/spare/query_input_pending.c
new file mode 100644
index 0000000..dd8c311
--- /dev/null
+++ b/secure/simul_efun/spare/query_input_pending.c
@@ -0,0 +1,17 @@
+/* This sefun is to provide a replacement for the efun query_input_pending().
+ * Feel free to add it to your mudlibs, if you have much code relying on that.
+ */
+
+#if ! __EFUN_DEFINED__(query_input_pending)
+
+#include <interactive_info.h>
+
+object query_input_pending(object ob)
+{
+    if(!efun::interactive(ob))
+        return 0;
+
+    return efun::interactive_info(ob, II_INPUT_PENDING);
+}
+
+#endif
diff --git a/secure/simul_efun/spare/query_ip_name.c b/secure/simul_efun/spare/query_ip_name.c
new file mode 100644
index 0000000..ada6e07
--- /dev/null
+++ b/secure/simul_efun/spare/query_ip_name.c
@@ -0,0 +1,48 @@
+/* This sefun is to provide a replacement for the efuns query_ip_name() and
+ * query_ip_number().
+ * Feel free to add it to your mudlibs, if you have much code relying on that.
+ */
+
+#if ! __EFUN_DEFINED__(query_ip_name)
+
+#include <interactive_info.h>
+
+private varargs string _query_ip_name(object player)
+{
+    object ob = player;
+    ob ||= efun::this_player();
+
+    player = efun::interactive_info(ob, II_IP_ADDRESS);
+    return efun::interactive_info(ob, II_IP_NAME);
+}
+
+private varargs string _query_ip_number(object player)
+{
+    object ob = player;
+    ob ||= efun::this_player();
+
+    player = efun::interactive_info(ob, II_IP_ADDRESS);
+    return efun::interactive_info(ob, II_IP_NUMBER);
+}
+
+// * Herkunfts-Ermittlung
+string query_ip_number(object ob)
+{
+  ob= ob || this_player();
+  if (!objectp(ob) || !interactive(ob)) return 0;
+  if(ob->query_realip() && (string)ob->query_realip()!="")
+  {
+    return (string)ob->query_realip();
+  }
+  return _query_ip_number(ob);
+}
+
+string query_ip_name(mixed ob)
+{
+  if ( !ob || objectp(ob) )
+      ob=query_ip_number(ob);
+  return (string)"/p/daemon/iplookup"->host(ob);
+}
+
+#endif
+
diff --git a/secure/simul_efun/spare/query_limits.c b/secure/simul_efun/spare/query_limits.c
new file mode 100644
index 0000000..ecbf390
--- /dev/null
+++ b/secure/simul_efun/spare/query_limits.c
@@ -0,0 +1,14 @@
+/* This sefun is to provide a replacement for the efun query_limits().
+ * Feel free to add it to your mudlibs, if you have much code relying on that.
+ */
+
+#if ! __EFUN_DEFINED__(query_limits)
+
+#include <driver_info.h>
+
+varargs int* query_limits(int def)
+{
+    return efun::driver_info(def ? DC_DEFAULT_RUNTIME_LIMITS : DI_CURRENT_RUNTIME_LIMITS);
+}
+
+#endif
diff --git a/secure/simul_efun/spare/query_load_average.c b/secure/simul_efun/spare/query_load_average.c
new file mode 100644
index 0000000..3b36f0e
--- /dev/null
+++ b/secure/simul_efun/spare/query_load_average.c
@@ -0,0 +1,16 @@
+/* This sefun is to provide a replacement for the efun query_load_average().
+ * Feel free to add it to your mudlibs, if you have much code relying on that.
+ */
+
+#if ! __EFUN_DEFINED__(query_load_average)
+
+#include <driver_info.h>
+
+string query_load_average()
+{
+    return efun::sprintf("%.2f cmds/s, %.2f comp lines/s",
+        efun::driver_info(DI_LOAD_AVERAGE_COMMANDS),
+        efun::driver_info(DI_LOAD_AVERAGE_LINES));
+}
+
+#endif
diff --git a/secure/simul_efun/spare/query_mud_port.c b/secure/simul_efun/spare/query_mud_port.c
new file mode 100644
index 0000000..bff009d
--- /dev/null
+++ b/secure/simul_efun/spare/query_mud_port.c
@@ -0,0 +1,35 @@
+/* This sefun is to provide a replacement for the efun query_mud_port().
+ * Feel free to add it to your mudlibs, if you have much code relying on that.
+ */
+
+#if ! __EFUN_DEFINED__(query_mud_port)
+
+#include <interactive_info.h>
+#include <driver_info.h>
+
+int query_mud_port(varargs int*|object* args)
+{
+    if(!sizeof(args) && efun::this_player() && efun::interactive(this_player()))
+        return efun::interactive_info(this_player(), II_MUD_PORT);
+
+    if(sizeof(args) > 1)
+        raise_error("Too many arguments to query_mud_port\n");
+
+    if(sizeof(args) && efun::objectp(args[0]) && efun::interactive(args[0]))
+        return efun::interactive_info(args[0], II_MUD_PORT);
+
+    if(sizeof(args) && intp(args[0]))
+    {
+        int* ports = efun::driver_info(DI_MUD_PORTS);
+        if(args[0] < -1 || args[0] >= sizeof(ports))
+            raise_error(efun::sprintf("Bad arg 1 to query_mud_port(): value %d out of range.\n", args[0]));
+        else if(args[0] == -1)
+            return sizeof(ports);
+        else
+            return ports[args[0]];
+    }
+
+    return efun::driver_info(DI_MUD_PORTS)[0];
+}
+
+#endif
diff --git a/secure/simul_efun/spare/query_once_interactive.c b/secure/simul_efun/spare/query_once_interactive.c
new file mode 100644
index 0000000..bc7829f
--- /dev/null
+++ b/secure/simul_efun/spare/query_once_interactive.c
@@ -0,0 +1,14 @@
+/* This sefun is to provide a replacement for the efun query_once_interactive().
+ * Feel free to add it to your mudlibs, if you have much code relying on that.
+ */
+
+#if ! __EFUN_DEFINED__(query_once_interactive)
+
+#include <object_info.h>
+
+int query_once_interactive(object ob)
+{
+    return efun::object_info(ob, OI_ONCE_INTERACTIVE);
+}
+
+#endif
diff --git a/secure/simul_efun/spare/query_snoop.c b/secure/simul_efun/spare/query_snoop.c
new file mode 100644
index 0000000..e6a69c0
--- /dev/null
+++ b/secure/simul_efun/spare/query_snoop.c
@@ -0,0 +1,30 @@
+/* This sefun is to provide a replacement for the efun query_snoop().
+ * Feel free to add it to your mudlibs, if you have much code relying on that.
+ */
+
+
+#include <interactive_info.h>
+
+private object _query_snoop(object ob)
+{
+    if(!efun::interactive(ob))
+        return 0;
+
+    object prev = efun::previous_object();
+    efun::set_this_object(prev);
+#if ! __EFUN_DEFINED__(query_snoop)
+    return efun::interactive_info(ob, II_SNOOP_NEXT);
+#else
+    return efun::query_snoop(ob);
+#endif
+}
+
+nomask object query_snoop(object who) {
+  object snooper;
+
+  snooper=_query_snoop(who);
+  if (!snooper) return 0;
+  if (query_wiz_grp(snooper)>query_wiz_grp(getuid(previous_object())) &&
+      IS_ARCH(snooper)) return 0;
+  return snooper;
+}
diff --git a/secure/simul_efun/spare/set_heart_beat.c b/secure/simul_efun/spare/set_heart_beat.c
new file mode 100644
index 0000000..a3995eb
--- /dev/null
+++ b/secure/simul_efun/spare/set_heart_beat.c
@@ -0,0 +1,24 @@
+/* This sefun is to provide a replacement for the efun set_heart_beat().
+ * Feel free to add it to your mudlibs, if you have much code relying on that.
+ */
+
+#if ! __EFUN_DEFINED__(set_heart_beat)
+
+#include <configuration.h>
+
+int set_heart_beat(int flag)
+{
+    object ob = efun::previous_object();
+    int hb = efun::object_info(ob, OC_HEART_BEAT);
+
+    if (!flag == !hb)
+        return 0;
+
+    efun::configure_object(ob, OC_HEART_BEAT, flag);
+
+    return 1;
+}
+
+#endif
+
+
diff --git a/secure/simul_efun/spare/set_is_wizard.c b/secure/simul_efun/spare/set_is_wizard.c
new file mode 100644
index 0000000..001ccdb
--- /dev/null
+++ b/secure/simul_efun/spare/set_is_wizard.c
@@ -0,0 +1,137 @@
+/* These are the special commands from the driver that are activated with
+ * set_is_wizard(). These functions must be added to the player object.
+ * Also set_is_wizard() must be called in the corresponding player object,
+ * not as an (simul-)efun.
+ */
+
+#include <commands.h>
+#include <driver_info.h>
+
+/* is_wizard:
+ *  1: has actions,
+ *  0: never had actions,
+ * -1: had actions once.
+ */
+private nosave int is_wizard;
+
+private int driver_malloc(string str)
+{
+    if(is_wizard <= 0)
+        return 0;
+
+    if(!sizeof(str))
+    {
+        write(efun::driver_info(DI_STATUS_TEXT_MALLOC));
+        return 1;
+    }
+
+    if(str == "extstats")
+    {
+        write(efun::driver_info(DI_STATUS_TEXT_MALLOC_EXTENDED));
+        return 1;
+    }
+
+    return 0;
+}
+
+private int driver_dumpallobj(string str)
+{
+    if(is_wizard <= 0 || sizeof(str))
+        return 0;
+
+    write("Dumping to /OBJ_DUMP ... ");
+    efun::dump_driver_info(DDI_OBJECTS);
+    efun::dump_driver_info(DDI_OBJECTS_DESTRUCTED);
+    write("done\n");
+    return 1;
+}
+
+private int driver_opcdump(string str)
+{
+    if(is_wizard <= 0 || sizeof(str))
+        return 0;
+
+    efun::dump_driver_info(DDI_OPCODES);
+    return 1;
+}
+
+private int driver_status(string str)
+{
+    int opt;
+    if(is_wizard <= 0)
+        return 0;
+
+    switch(str || "")
+    {
+        case "":
+            opt = DI_STATUS_TEXT_MEMORY;
+            break;
+
+        case "tables":
+        case " tables":
+            opt = DI_STATUS_TEXT_TABLES;
+            break;
+
+        case "swap":
+        case " swap":
+            opt = DI_STATUS_TEXT_SWAP;
+            break;
+
+        case "malloc":
+        case " malloc":
+            opt = DI_STATUS_TEXT_MALLOC;
+            break;
+
+        case "malloc extstats":
+        case " malloc extstats":
+            opt = DI_STATUS_TEXT_MALLOC_EXTENDED;
+            break;
+
+        default:
+            return 0;
+    }
+
+    write(efun::driver_info(opt));
+    return 1;
+}
+
+int set_is_wizard(varargs <object|int>* args)
+{
+    int oldval = is_wizard;
+
+    if(!sizeof(args))
+        raise_error("Too few arguments to set_is_wizard\n");
+    if(sizeof(args) > 2)
+        raise_error("Too many arguments to set_is_wizard\n");
+    if(!objectp(args[0]))
+        raise_error("Bad arg 1 to set_is_wizard()\n");
+    if(args[0] != this_object())
+        raise_error("Only set_is_wizard for the current object supported\n");
+    if(this_player() != this_object())
+        raise_error("The current object must be this_player() for set_is_wizard()\n");
+    if(sizeof(args) == 2 && !intp(args[1]))
+        raise_error("Bad arg 2 to set_is_wizard()\n");
+
+    if(sizeof(args) == 2 && !args[1])
+    {
+        if(is_wizard > 0)
+            is_wizard = -1;
+    }
+    else if(sizeof(args) == 2 && args[1]<0)
+    {
+         // Just return the old value.
+    }
+    else
+    {
+        if(!is_wizard)
+        {
+            add_action(#'driver_malloc, "malloc");
+            add_action(#'driver_dumpallobj, "dumpallobj");
+            add_action(#'driver_opcdump, "opcdump");
+            add_action(#'driver_status, "status", AA_NOSPACE);
+        }
+        is_wizard = 1;
+    }
+
+    return oldval > 0;
+}
diff --git a/secure/simul_efun/spare/set_prompt.c b/secure/simul_efun/spare/set_prompt.c
new file mode 100644
index 0000000..0b59692
--- /dev/null
+++ b/secure/simul_efun/spare/set_prompt.c
@@ -0,0 +1,21 @@
+/* This sefun is to provide a replacement for the efun set_prompt().
+ * Feel free to add it to your mudlibs, if you have much code relying on that.
+ */
+
+#if ! __EFUN_DEFINED__(set_prompt)
+
+#include <configuration.h>
+
+varargs string|closure set_prompt(string|closure|int prompt, object ob)
+{
+    ob ||= efun::this_player();
+
+    mixed oldprompt = efun::interactive_info(ob, IC_PROMPT);
+
+    if(!intp(prompt))
+        efun::configure_interactive(ob, IC_PROMPT, prompt);
+
+    return oldprompt;
+}
+
+#endif
diff --git a/secure/simul_efun/spare/shadow.c b/secure/simul_efun/spare/shadow.c
new file mode 100644
index 0000000..7608c73
--- /dev/null
+++ b/secure/simul_efun/spare/shadow.c
@@ -0,0 +1,55 @@
+/* These sefuns are to provide a replacement for the efun query_shadowing()
+ * and the old semantics of the efun shadow().
+ * Feel free to add it to your mudlibs, if you have much code relying on that.
+ */
+
+#if ! __EFUN_DEFINED__(query_shadowing)
+
+#include <object_info.h>
+
+object query_shadowing(object ob)
+{
+    return efun::object_info(ob, OI_SHADOW_PREV);
+}
+
+private object _shadow(object ob, int flag)
+{
+    if(flag)
+    {
+        object shadower = efun::previous_object(1);
+        efun::set_this_object(shadower);
+
+        if (efun::shadow(ob))
+            return efun::object_info(shadower, OI_SHADOW_PREV);
+        else
+            return 0;
+    }
+    else
+        return efun::object_info(ob, OI_SHADOW_NEXT);
+}
+#else
+private object _shadow(object ob, int flag)
+{
+  set_this_object(previous_object(1));
+  return efun::shadow(ob, flag);
+}
+#endif
+
+
+public object shadow(object ob, int flag)
+{
+  object res = funcall(#'_shadow,ob, flag);
+  if (flag)
+    "/secure/shadowmaster"->RegisterShadow(previous_object());
+  return res;
+}
+
+private void _unshadow() {
+  set_this_object(previous_object(1));
+  efun::unshadow();
+}
+
+public void unshadow() {
+  funcall(#'_unshadow);
+  "/secure/shadowmaster"->UnregisterShadow(previous_object());
+}
diff --git a/secure/simul_efun/spare/simul_efun.c b/secure/simul_efun/spare/simul_efun.c
new file mode 100644
index 0000000..84e0b78
--- /dev/null
+++ b/secure/simul_efun/spare/simul_efun.c
@@ -0,0 +1,2016 @@
+// MorgenGrauen MUDlib
+//
+// simul_efun.c -- simul efun's
+//
+// $Id: simul_efun.c 7408 2010-02-06 00:27:25Z Zesstra $
+#pragma strict_types,save_types,rtt_checks
+#pragma no_clone,no_shadow,no_inherit
+#pragma pedantic,range_check,warn_deprecated
+#pragma warn_empty_casts,warn_missing_return,warn_function_inconsistent
+
+// Absolute Pfade erforderlich - zum Zeitpunkt, wo der Master geladen
+// wird, sind noch keine Include-Pfade da ...
+
+#define SNOOPLOGFILE "SNOOP"
+#define ASNOOPLOGFILE "ARCH/SNOOP"
+
+#include "/secure/config.h"
+#include "/secure/wizlevels.h"
+#include "/sys/snooping.h"
+#include "/sys/language.h"
+#include "/sys/thing/properties.h"
+#include "/sys/wizlist.h"
+#include "/sys/erq.h"
+#include "/sys/lpctypes.h"
+#include "/sys/daemon.h"
+#include "/sys/player/base.h"
+#include "/sys/thing/description.h"
+#include "/sys/container.h"
+#include "/sys/defines.h"
+#include "/sys/telnet.h"
+#include "/sys/objectinfo.h"
+#include "/sys/files.h"
+#include "/sys/strings.h"
+#include "/sys/time.h"
+#include "/sys/lpctypes.h"
+#include "/sys/notify_fail.h"
+#include "/sys/tls.h"
+#include "/sys/input_to.h"
+#include "/sys/object_info.h"
+
+/* function prototypes
+ */
+string dtime(int wann);
+varargs int log_file(string file, string txt, int size_to_break);
+int query_wiz_level(mixed player);
+nomask varargs int snoop(object me, object you);
+varargs string country(mixed ip, string num);
+int query_wiz_grp(mixed wiz);
+public varargs object deep_present(mixed what, object ob);
+nomask int secure_level();
+nomask string secure_euid();
+public nomask int process_call();
+nomask mixed __create_player_dummy(string name);
+varargs string replace_personal(string str, mixed *obs, int caps);
+
+
+//replacements for dropped efuns in LD
+#if !__EFUN_DEFINED__(extract)
+varargs string extract(string str, int from, int to);
+#endif
+#if !__EFUN_DEFINED__(slice_array)
+varargs mixed slice_array(mixed array, int from, int to);
+#endif
+#if !__EFUN_DEFINED__(member_array)
+int member_array(mixed item, mixed arraystring);
+#endif
+
+// Include the different sub 'modules' of the simul_efun
+#include __DIR__"debug_info.c"
+#include __DIR__"enable_commands.c"
+#include __DIR__"hash.c"
+#include __DIR__"object_info.c"
+#include __DIR__"query_editing.c"
+#include __DIR__"query_idle.c"
+#include __DIR__"query_input_pending.c"
+#include __DIR__"query_ip_name.c"
+#include __DIR__"query_limits.c"
+#include __DIR__"query_load_average.c"
+#include __DIR__"query_mud_port.c"
+#include __DIR__"query_once_interactive.c"
+#include __DIR__"query_snoop.c"
+#include __DIR__"set_heart_beat.c"
+#if __BOOT_TIME__ < 1456261859
+#include __DIR__"set_prompt.c"
+#endif
+#include __DIR__"shadow.c"
+#include __DIR__"livings.c"
+#include __DIR__"comm.c"
+
+#define TO        efun::this_object()
+#define TI        efun::this_interactive()
+#define TP        efun::this_player()
+#define PO        efun::previous_object(0)
+#define LEVEL(x) query_wiz_level(x)
+#define NAME(x)  capitalize(getuid(x))
+
+#define DEBUG(x) if (find_player("zesstra")) \
+  tell_object(find_player("zesstra"),x)
+
+mixed dtime_cache = ({-1,""});
+
+#ifdef IOSTATS
+struct iostat_s {
+  string oname;
+  int time;
+  int size;
+};
+mixed saveo_stat = ({ 0,allocate(200, 0) });
+mixed restoreo_stat = ({ 0,allocate(200,0) });
+//mixed writefile_stat = ({ 0,allocate(100,(<iostat_s>)) });
+//mixed readfile_stat = ({ 0,allocate(100,(<iostat_s>)) });
+//mixed log_stat = ({ 0,allocate(100,(<iostat_s>)) });
+
+mixed ___iostats(int type) {
+  switch(type) {
+    case 1:
+      return saveo_stat;
+    case 2:
+      return restoreo_stat;
+/*    case 3:
+      return writefile_stat;
+    case 4:
+      return readfile_stat;
+    case 5:
+      return log_stat;
+      */
+  }
+  return 0;
+}
+#endif
+
+// Nicht jeder Magier muss die simul_efun entsorgen koennen.
+string NotifyDestruct(object caller) {
+    if( (caller!=this_object() && !ARCH_SECURITY) || process_call() ) {
+      return "Du darfst das simul_efun Objekt nicht zerstoeren!\n";
+    }
+    return 0;
+}
+
+public nomask void remove_interactive( object ob )
+{
+    if ( objectp(ob) && previous_object()
+        && object_name(previous_object())[0..7] != "/secure/"
+        && ((previous_object() != ob
+             && (ob != this_player() || ob != this_interactive()))
+            || (previous_object() == ob
+               && (this_player() && this_player() != ob
+                   || this_interactive() && this_interactive() != ob)) ) )
+
+       log_file( "PLAYERDEST",
+                sprintf( "%s: %O ausgeloggt von PO %O, TI %O, TP %O\n",
+                        dtime(time()), ob, previous_object(),
+                        this_interactive(), this_player() ) );
+
+    efun::remove_interactive(ob);
+}
+
+
+void ___updmaster()
+{
+    object ob;
+
+    //sollte nicht jeder duerfen.
+    if (process_call() || !ARCH_SECURITY)
+      raise_error("Illegal use of ___updmaster()!");
+
+    write("Removing old master ... ");
+    foreach(string file: 
+        get_dir("/secure/master/*.c",GETDIR_NAMES|GETDIR_UNSORTED|GETDIR_PATH)) {
+      if (ob = find_object(file))
+       efun::destruct(ob);
+    }
+    efun::destruct(efun::find_object("/secure/master"));
+    write("done.\nLoading again ... ");
+    load_object("/secure/master");
+
+    write("done.\n");
+}
+
+varargs string country(mixed ip, string num) {
+  mixed ret;
+
+  if(ret = (string)"/p/daemon/iplookup"->country(num || ip)) {
+    return ret;
+  } else return "???";
+}
+
+
+// * Snoopen und was dazugehoert
+static object find_snooped(object who)
+{
+  object *u;
+  int i;
+
+  for (i=sizeof(u=users())-1;i>=0;i--)
+    if (who==efun::interactive_info(u[i], II_SNOOP_NEXT))
+      return u[i];
+  return 0;
+}
+
+private string Lcut(string str) {
+  return str[5..11]+str[18..];
+}
+
+nomask varargs int snoop( object me, object you )
+{
+    int ret;
+    object snooper0, snooper, snooper2, snooper3;
+
+    if( !objectp(me) || me == you || !PO )
+       return 0;
+
+    snooper0 = find_snooped(me);
+
+     if(you){
+        if ( PO != me && query_wiz_grp(me) >= query_wiz_grp(geteuid(PO)) )
+            return 0;
+
+        if ( query_wiz_grp(me) <= query_wiz_grp(you) &&
+             !(you->QueryAllowSnoop(me)) )
+            if ( !IS_DEPUTY(me) || IS_ARCH(you) )
+               return 0;
+
+        if ( (snooper = efun::interactive_info(you, II_SNOOP_NEXT)) &&
+             query_wiz_grp(snooper) >= query_wiz_grp(me) ){
+            if ( (int)snooper->QueryProp(P_SNOOPFLAGS) & SF_LOCKED )
+               return 0;
+
+            tell_object( snooper, sprintf( "%s snooped jetzt %s.\n",
+                                       me->name(WER), you->name(WER) ) );
+
+            snooper2 = me;
+
+            while ( snooper3 = interactive_info(snooper2, II_SNOOP_NEXT) ){
+               tell_object( snooper,
+                           sprintf( "%s wird seinerseits von %s gesnooped.\n"
+                                   ,snooper2->name(WER),
+                                   snooper3->name(WEM) ) );
+               snooper2 = snooper3;
+            }
+
+            efun::snoop( snooper, snooper2 );
+
+            if ( efun::interactive_info(snooper2, II_SNOOP_NEXT) != snooper )
+               tell_object( snooper, sprintf( "Du kannst %s nicht snoopen.\n",
+                                          snooper2->name(WEN) ) );
+            else{
+               tell_object( snooper, sprintf( "Du snoopst jetzt %s.\n",
+                                          snooper2->name(WEN) ) );
+               if ( !IS_DEPUTY(snooper) ){
+                   log_file( SNOOPLOGFILE, sprintf("%s: %O %O %O\n",
+                                               dtime(time()),
+                                               snooper,
+                                               snooper2,
+                                               environment(snooper2) ),
+                            100000 );
+                   if (snooper0)
+                      CHMASTER->send( "Snoop", snooper,
+                                    sprintf( "%s *OFF* %s (%O)",
+                                            capitalize(getuid(snooper)),
+                                            capitalize(getuid(snooper0)),
+                                            environment(snooper0) ) );
+
+                   CHMASTER->send( "Snoop", snooper,
+                                 sprintf("%s -> %s (%O)",
+                                        capitalize(getuid(snooper)),
+                                        capitalize(getuid(snooper2)),
+                                        environment(snooper2)));
+               }
+               else{
+                   log_file( ASNOOPLOGFILE, sprintf( "%s: %O %O %O\n",
+                                                 dtime(time()),
+                                                 snooper,
+                                                 snooper2,
+                                                 environment(snooper2) )
+                            ,100000 );
+               }
+            }
+        }
+        else
+            if (snooper)
+               if ( !me->QueryProp(P_SNOOPFLAGS) & SF_LOCKED ){
+                   printf( "%s wird bereits von %s gesnooped. Benutze das "
+                          "\"f\"-Flag, wenn du dennoch snoopen willst.\n",
+                          you->name(WER), snooper->name(WEM) );
+                   return 0;
+               }
+
+        ret = efun::snoop( me, you );
+
+        if ( !IS_DEPUTY(me) && efun::interactive_info(you, II_SNOOP_NEXT) == me){
+            log_file( SNOOPLOGFILE, sprintf( "%s: %O %O %O\n",
+                                         Lcut(dtime(time())),
+                                         me, you, environment(you) ),
+                     100000 );
+
+            if (snooper0)
+               CHMASTER->send( "Snoop", me,
+                             sprintf( "%s *OFF* %s (%O).",
+                                     capitalize(getuid(me)),
+                                     capitalize(getuid(snooper0)),
+                                     environment(snooper0) ) );
+
+            CHMASTER->send( "Snoop", me, sprintf( "%s -> %s (%O).",
+                                             capitalize(getuid(me)),
+                                             capitalize(getuid(you)),
+                                             environment(you) ) );
+        }
+        else{
+            if ( efun::interactive_info(you, II_SNOOP_NEXT) == me ){
+               log_file( ASNOOPLOGFILE, sprintf( "%s: %O %O %O\n",
+                                             Lcut(dtime(time())),
+                                             me, you, environment(you) ),
+                        100000 );
+            }
+        }
+
+        if ( ret && query_wiz_grp(me) <= query_wiz_grp(you) &&
+             !IS_DEPUTY(me) )
+            tell_object( you, "*** " + NAME(me) + " snoopt Dich!\n" );
+
+        return ret;
+     }
+     else {
+        if ( (me == PO ||
+              query_wiz_grp(geteuid(PO)) > query_wiz_grp(me) ||
+              (query_wiz_grp(geteuid(PO)) == query_wiz_grp(me) &&
+              efun::interactive_info(PO, II_SNOOP_NEXT) == me)) && snooper0 ){
+            if ( !IS_DEPUTY(me) ){
+               log_file( SNOOPLOGFILE, sprintf( "%s: %O %O %O *OFF*\n",
+                                            Lcut(dtime(time())), me,
+                                            snooper0,
+                                            environment(snooper0) ),
+                        100000 );
+
+                CHMASTER->send( "Snoop", me,
+                              sprintf( "%s *OFF* %s (%O).",
+                                      capitalize(getuid(me)),
+                                      capitalize(getuid(snooper0)),
+                                      environment(snooper0) ) );
+            }
+            else{
+               log_file( ASNOOPLOGFILE, sprintf( "%s: %O %O %O *OFF*\n",
+                                             Lcut(dtime(time())), me,
+                                             snooper0,
+                                             environment(snooper0) ),
+                        100000 );
+            }
+
+            return efun::snoop(me);
+        }
+     }
+     return 0;
+}
+
+
+
+// * Emulation des 'alten' explode durch das neue
+string *old_explode(string str, string del) {
+  int s, t;
+  string *strs;
+
+  if (!stringp(str)) {
+      set_this_object(previous_object());
+      raise_error(sprintf(
+         "Invalid argument 1 to old_explode()! Expected <string>, got: "
+         "%.30O\n",str));
+  }
+  if (!stringp(del)) {
+      set_this_object(previous_object());
+      raise_error(sprintf(
+         "Invalid argument 2 to old_explode()! Expected <string>, got: "
+         "%.30O\n",del));
+  }
+  if(del == "")
+    return ({str});
+  strs=efun::explode(str, del);
+  t=sizeof(strs)-1;
+  while(s<=t && strs[s++] == "");s--;
+  while(t>=0 && strs[t--] == "");t++;
+  if(s<=t)
+    return strs[s..t];
+  return ({});
+}
+
+int file_time(string path) {
+  mixed *v;
+
+  set_this_object(previous_object());
+  if (sizeof(v=get_dir(path,GETDIR_DATES))) return v[0];
+  return(0); //sonst
+}
+
+// * Bei 50k Groesse Log-File rotieren
+varargs int log_file(string file, string txt, int size_to_break) {
+  mixed *st;
+
+  file="/log/"+file;
+  file=implode((efun::explode(file,"/")-({".."})),"/");
+//  tell_object(find_player("jof"),sprintf("LOG FILE: %O -> %O\n",previous_object(),file));
+  if (!funcall(bind_lambda(#'efun::call_other,PO),"secure/master",//')
+              "valid_write",file,geteuid(PO),"log_file",PO))
+      return 0;
+  if ( size_to_break >= 0 & (
+      sizeof(st = get_dir(file,2) ) && st[0] >= (size_to_break|MAX_LOG_SIZE)))
+      catch(rename(file, file + ".old");publish); /* No panic if failure */
+  return(write_file(file,txt));
+}
+
+// * Magier-Level abfragen
+int query_wiz_level(mixed player) {
+  return (int)"/secure/master"->query_wiz_level(player);
+}
+
+#ifdef __ALISTS__
+// * Element aus Alist loeschen (by key)
+mixed *remove_alist(mixed key,mixed *alist)
+{
+  int i,j;
+
+  if (!pointerp(alist) || !sizeof(alist))
+    return alist;
+  if (!pointerp(alist[0]))
+  {
+    if ((i=assoc(key,alist))<0)
+      return alist;
+    return alist[0..i-1]+alist[i+1..];
+  }
+  i = assoc(key,alist[0]);
+  if ((i=assoc(key,alist[0]))<0)
+    return alist;
+  alist=alist[0..];
+  for (j=sizeof(alist)-1;j>=0;j--)
+    alist[j]=alist[j][0..i-1]+alist[j][i+1..];
+  return alist;
+}
+
+// * Element aus Alist loeschen (by pos)
+mixed *exclude_alist(int i,mixed *alist)
+{
+  int j;
+  if (!pointerp(alist) || !sizeof(alist) || i<0)
+    return alist;
+  if (!pointerp(alist[0]))
+    return alist[0..i-1]+alist[i+1..];
+  alist=alist[0..]; /* Create PHYSICAL copy of alist */
+  for (j=sizeof(alist)-1;j>=0;j--)
+    alist[j]=alist[j][0..i-1]+alist[j][i+1..];
+  return alist; /* order_alist is NOT necessary - see /doc/LPC/alist */
+}
+#endif // __ALISTS__
+
+// * German version of ctime()
+#define TAGE ({"Son","Mon","Die","Mit","Don","Fre","Sam"})
+#define MONATE ({"Jan","Feb","Mar","Apr","Mai","Jun","Jul","Aug",\
+                 "Sep","Okt","Nov","Dez"})
+string dtime(int wann) {
+  
+  if (wann == dtime_cache[0])
+    return(dtime_cache[1]);
+
+  int *lt = localtime(wann);
+  return sprintf("%s, %2d. %s %d, %02d:%02d:%02d",
+      TAGE[lt[TM_WDAY]], lt[TM_MDAY], MONATE[lt[TM_MON]], 
+      lt[TM_YEAR],lt[TM_HOUR], lt[TM_MIN], lt[TM_SEC]);
+}
+
+// wenn strftime im Driver nicht existiert, ist dies hier ein Alias auf dtime(),
+// zwar stimmt das Format dann nicht, aber die Mudlib buggt nicht und schreibt
+// ein ordentliches Datum/Uhrzeit.
+#if !__EFUN_DEFINED__(strftime)
+varargs string strftime(mixed fmt, int clock, int localized) {
+  if (intp(clock) && clock >= 0)
+    return dtime(clock);
+  else if (intp(fmt) && fmt >= 0)
+    return dtime(fmt);
+  
+  return dtime(time());
+}
+#endif //!__EFUN_DEFINED__(strftime)
+
+// * Shutdown mit zusaetzlichem logging
+nomask int shutdown(string reason)
+{
+  string name;
+  string obname;
+  string output;
+
+  if (!reason)
+    return 0;
+  if ( !ARCH_SECURITY && getuid(previous_object())!=ROOTID &&
+          object_name(previous_object())!="/obj/shut" )
+  {
+    write("You have no permission to shut down the gamedriver!\n");
+    return 0;
+  }
+  if ((this_interactive())&&(name=getuid(this_interactive())))
+  {
+    name=capitalize(name);
+    filter(users(),#'tell_object,//'
+               capitalize(name)+" faehrt das Spiel herunter!\n");
+  }
+  else
+    name="ANONYMOUS";
+  if (previous_object()) obname=capitalize(getuid(previous_object()));
+  output=name;
+  if (obname && name!=obname) output=output+" ("+obname+")";
+  if (previous_object()&&object_name(previous_object())=="/obj/shut"){
+    output+=" faehrt das Spiel via Armageddon herunter.\n";
+    output=dtime(time())+": "+output;
+    log_file("GAME_LOG",output+"\n",-1);
+    efun::shutdown();
+    return 1;
+  }
+  output=ctime(time())+": "+output+" faehrt das Spiel herunter.\n";
+  output+="    Grund: "+reason;
+  log_file("GAME_LOG",output+"\n",-1);
+  efun::shutdown();
+  return 1;
+}
+
+// * lowerchar
+
+int lowerchar(int char) {
+  if (char<'A' || char>'Z') return char;
+  return char+32;
+}
+
+// * upperstring
+
+string upperstring(string s)
+{
+#if __EFUN_DEFINED__(upper_case)
+  return(upper_case(s));
+#else
+  int i;
+  if (!stringp(s)) return 0;
+  for (i=sizeof(s)-1;i>=0;i--) s[i]=((s[i]<'a'||s[i]>'z')?s[i]:s[i]-32);
+  return s;
+#endif
+}
+
+// * lowerstring
+
+string lowerstring(string s)
+{
+  if (!stringp(s)) return 0;
+  return lower_case(s);
+}
+
+
+// * GD version
+string version()
+{
+  return __VERSION__;
+}
+
+// * break_string
+// stretch() -- stretch a line to fill a given width 
+private string stretch(string s, int width) {
+  int len=sizeof(s);
+  if (len==width) return s;
+
+  // reine Leerzeilen, direkt zurueckgeben
+  string trimmed=trim(s,TRIM_LEFT," ");
+  if (trimmed=="") return s; 
+  int start_spaces = len - sizeof(trimmed);
+
+  string* words = explode(trimmed, " ");
+  // der letzte kriegt keine Spaces
+  int word_count=sizeof(words) - 1;
+  // wenn Zeile nur aus einem Wort, wird das Wort zurueckgegeben
+  if (!word_count)
+    return " "*start_spaces + words[0];
+
+  int space_count = width - len;
+
+  int space_per_word=(word_count+space_count) / word_count;
+  // Anz.Woerter mit Zusatz-Space
+  int rest=(word_count+space_count) % word_count; 
+  // Rest-Spaces Verteilen
+  foreach (int pos : rest) words[pos]+=" ";
+  return (" "*start_spaces) + implode( words, " "*space_per_word );
+}
+
+// aus Geschwindigkeitsgruenden hat der Blocksatz fuer break_string eine
+// eigene Funktion bekommen:
+private varargs string block_string(string s, int width, int flags) {
+  // wenn BS_LEAVE_MY_LFS, aber kein BS_NO_PARINDENT, dann werden Zeilen mit
+  // einem Leerzeichen begonnen.
+  // BTW: Wenn !BS_LEAVE_MY_LFS, hat der Aufrufer bereits alle \n durch " "
+  // ersetzt.
+  if ( (flags & BS_LEAVE_MY_LFS)
+      && !(flags & BS_NO_PARINDENT))
+  {
+      s = " "+regreplace(s,"\n","\n ",1);
+  }
+
+  // sprintf fuellt die letzte Zeile auf die Feldbreite (hier also
+  // Zeilenbreite) mit Fuellzeichen auf, wenn sie NICHT mit \n umgebrochen
+  // ist. Es wird an die letzte Zeile aber kein Zeilenumbruch angehaengt.
+  // Eigentlich ist das Auffuellen doof, aber vermutlich ist es unnoetig, es
+  // wieder rueckgaengig zu machen.
+  s = sprintf( "%-*=s", width, s);
+
+  string *tmp=explode(s, "\n");
+  // Nur wenn s mehrzeilig ist, Blocksatz draus machen. Die letzte Zeile wird
+  // natuerlich nicht gedehnt. Sie ist dafuer schon von sprintf() aufgefuellt
+  // worden. BTW: Die letzte Zeile endet u.U. noch nicht mit einem \n (bzw.
+  // nur dann, wenn BS_LEAVE_MY_LFS und der uebergebene Text schon nen \n am
+  // Ende der letzten Zeile hat), das macht der Aufrufer...
+  if (sizeof(tmp) > 1)
+    return implode( map( tmp[0..<2], #'stretch/*'*/, width ), "\n" ) 
+      + "\n" + tmp[<1];
+
+  return s;
+}
+
+public varargs string break_string(string s, int w, mixed indent, int flags)
+{
+    if ( !s || s == "" ) return "";
+
+    if ( !w ) w=78;
+
+    if( intp(indent) )
+       indent = indent ? " "*indent : "";
+
+    int indentlen=stringp(indent) ? sizeof(indent) : 0;
+
+    if (indentlen>w) {
+      set_this_object(previous_object());
+      raise_error(sprintf("break_string: indent longer %d than width %d\n",
+                  indentlen,w));
+      // w=((indentlen/w)+1)*w;
+    }
+
+    if (!(flags & BS_LEAVE_MY_LFS)) 
+      s=regreplace( s, "\n", " ", 1 );
+
+    if ( flags & BS_SINGLE_SPACE )
+       s = regreplace( s, "(^|\n| )  *", "\\1", 1 );
+ 
+    string prefix="";
+    if (indentlen && flags & BS_PREPEND_INDENT) {
+      if (indentlen+sizeof(s) > w || 
+         (flags & BS_LEAVE_MY_LFS) && strstr(s,"\n")>-1) {
+       prefix=indent+"\n";
+       indent=(flags & BS_NO_PARINDENT) ? "" : " ";
+       indentlen=sizeof(indent);
+      }
+    }
+
+    if ( flags & BS_BLOCK ) {
+      /*
+           s = implode( map( explode( s, "\n" ),
+                               #'block_string, w, indentlen, flags),
+                      "" );
+      */
+      s = block_string( s , w - indentlen, flags );
+    }
+    else {
+      s = sprintf("%-1.*=s",w-indentlen,s);
+    }
+    if ( s[<1] != '\n' ) s += "\n";
+
+    if ( !indentlen ) return prefix + s;
+    
+    string indent2 = ( flags & BS_INDENT_ONCE ) ? (" "*indentlen) :indent;
+      
+    return prefix + indent + 
+      regreplace( s[0..<2], "\n", "\n"+indent2, 1 ) + "\n";
+      /*
+       string *buf;
+
+       buf = explode( s, "\n" );
+       return prefix + indent + implode( buf[0..<2], "\n"+indent2 ) + buf[<1] + "\n";
+      */
+}
+
+// * Elemente aus mapping loeschen - mapping vorher kopieren
+
+mapping m_copy_delete(mapping m, mixed key) {
+  return m_delete(copy(m), key);
+}
+
+// * times
+int last_reboot_time()
+{
+  return __BOOT_TIME__;
+}
+
+int first_boot_time()
+{
+  return 701517600;
+}
+
+int exist_days()
+{
+  return (((time()-first_boot_time())/8640)+5)/10;
+}
+
+// * uptime :)
+string uptime()
+{
+  int t;
+  int tmp;
+  string s;
+
+  t=time()-__BOOT_TIME__;
+  s="";
+  if (t>=86400)
+    s+=sprintf("%d Tag%s, ",tmp=t/86400,(tmp==1?"":"e"));
+  if (t>=3600)
+    s+=sprintf("%d Stunde%s, ",tmp=(t=t%86400)/3600,(tmp==1?"":"n"));
+  if (t>60)
+    s+=sprintf("%d Minute%s und ",tmp=(t=t%3600)/60,(tmp==1?"":"n"));
+  return s+sprintf("%d Sekunde%s",t=t%60,(t==1?"":"n"));
+}
+
+// * Was tun bei 'dangling' lfun-closures ?
+void dangling_lfun_closure() {
+  raise_error("dangling lfun closure\n");
+}
+
+// * Sperren ausser fuer master/simul_efun
+
+#if __EFUN_DEFINED__(set_environment)
+nomask void set_environment(object o1, object o2) {
+  raise_error("Available only for root\n");
+}
+#endif
+
+nomask void set_this_player(object pl) {
+  raise_error("Available only for root\n");
+}
+
+#if __EFUN_DEFINED__(export_uid)
+nomask void export_uid(object ob) {
+  raise_error("Available only for root\n");
+}
+#endif
+
+// * Jetzt auch closures
+int process_flag;
+
+public nomask int process_call()
+{
+  if (process_flag>0)
+    return process_flag;
+  else return(0);
+}
+
+private nomask string _process_string(string str,object po) {
+              set_this_object(po);
+              return(efun::process_string(str));
+}
+
+nomask string process_string( mixed str )
+{
+  string tmp, err;
+  int flag; 
+
+  if ( closurep(str) ) {
+      set_this_object( previous_object() );
+      return funcall(str);
+  }
+  else if (str==0)
+      return((string)str);
+  else if ( !stringp(str) ) {
+      return to_string(str);
+  }
+
+  if ( !(flag = process_flag > time() - 60))                     
+      process_flag=time();
+
+  err = catch(tmp = funcall(#'_process_string,str,previous_object()); publish);
+
+  if ( !flag )
+    process_flag=0;
+
+  if (err) {
+    // Verarbeitung abbrechen
+    set_this_object(previous_object());
+    raise_error(err);
+  }
+  return tmp;
+}
+
+// 'mkdir -p' - erzeugt eine komplette Hierarchie von Verzeichnissen.
+// wenn das Verzeichnis angelegt wurde oder schon existiert, wird 1
+// zurueckgeliefert, sonst 0.
+// Wirft einen Fehler, wenn das angebene Verzeichnis nicht absolut ist!
+public int mkdirp(string dir) {
+  // wenn es nur keinen fuehrenden / gibt, ist das ein Fehler.
+  if (strstr(dir, "/") != 0)
+    raise_error("mkdirp(): Pfad ist nicht absolute.\n");
+  // cut off trailing /...
+  if (dir[<1]=='/')
+      dir = dir[0..<2];
+
+  int fstat = file_size(dir);
+  // wenn es schon existiert, tun wir einfach so, als haetten wir es angelegt.
+  if (fstat == FSIZE_DIR)
+    return 1;
+  // wenn schon ne Datei existiert, geht es nicht.
+  if (fstat != FSIZE_NOFILE)
+    return 0;
+  // wenn es nur einen / gibt (den fuehrenden), dann ist es ein
+  // toplevel-verzeichnis, was direkt angelegt wird.
+  if (strrstr(dir,"/")==0) {
+    return funcall(bind_lambda(#'efun::mkdir, previous_object()), dir);
+  }
+
+  // mkdir() nicht direkt rufen, sondern vorher als closure ans aufrufende
+  // Objekt binden. Sonst laeuft die Rechtepruefung in valid_write() im Master
+  // unter der Annahme, dass die simul_efun.c mit ihrer root id was will.
+
+  // jetzt rekursiv die Verzeichnishierarchie anlegen. Wenn das erfolgreich
+  // ist, dann koennen wir jetzt mit mkdir das tiefste Verzeichnis anlegen
+  if (mkdirp(dir[0..strrstr(dir,"/")-1]) == 1)
+    return funcall(bind_lambda(#'efun::mkdir, previous_object()), dir);
+}
+
+
+// * Properties ggfs. mitspeichern
+mixed save_object(mixed name)
+{
+  mapping properties;
+  mapping save;
+  mixed index, res;
+  int i;
+
+  // nur Strings und 0 zulassen
+  if ((!stringp(name) || !sizeof(name)) && 
+      (!intp(name) || name!=0)) {
+      set_this_object(previous_object());
+      raise_error(sprintf(
+         "Only non-empty strings and 0 may be used as filename in "
+         "sefun::save_object()! Argument was %O\n",name));
+  }
+
+  save = m_allocate(0, 2);
+  properties = (mapping)previous_object()->QueryProperties();
+
+  if(mappingp(properties))
+  {
+    // delete all entries in mapping properties without SAVE flag!
+    index = m_indices(properties);
+    for(i = sizeof(index)-1; i>=0;i--)
+    {
+      if(properties[index[i], F_MODE] & SAVE)
+      {
+       save[index[i]] = properties[index[i]];
+       save[index[i], F_MODE] =
+       properties[index[i], F_MODE] &
+                    (~(SETMNOTFOUND|QUERYMNOTFOUND|QUERYCACHED|SETCACHED));
+      }
+    }
+  }
+  else save = ([]);
+
+  // save object!
+  previous_object()->_set_save_data(save);
+  // format: wie definiert in config.h
+  if (stringp(name))
+    res = funcall(bind_lambda(#'efun::save_object, previous_object()), name,
+       __LIB__SAVE_FORMAT_VERSION__);
+  else
+    res = funcall(bind_lambda(#'efun::save_object, previous_object()),
+       __LIB__SAVE_FORMAT_VERSION__);
+  previous_object()->_set_save_data(0);
+
+#ifdef IOSTATS
+  // Stats...
+  struct iostat_s stat = (<iostat_s>);
+  stat->oname = object_name(previous_object());
+  stat->time = time();
+  //stat->size = (int)object_info(previous_object(),OINFO_MEMORY,
+  //    OIM_TOTAL_DATA_SIZE);
+  if (stringp(name))
+      stat->size = file_size(name + ".o");
+  else
+      stat->sizeof(res);
+  //debug_message("saveo: "+saveo_stat[0]+"\n");
+  saveo_stat[1][saveo_stat[0]] = stat;
+  saveo_stat[0] = (saveo_stat[0] + 1) % sizeof(saveo_stat[1]);
+  //debug_message("saveo 2: "+saveo_stat[0]+"\n");
+#endif
+
+  return res;
+}
+
+// * Auch Properties laden
+int restore_object(string name)
+{
+  int result;
+  mixed index;
+  mixed save;
+  mapping properties;
+  int i;
+  closure cl;
+
+  // get actual property settings (by create())
+  properties = (mapping)previous_object()->QueryProperties();
+
+//  DEBUG(sprintf("RESTORE %O\n",name));
+  // restore object
+  result=funcall(bind_lambda(#'efun::restore_object, previous_object()), name);
+  //'))
+  //_get_save_data liefert tatsaechlich mixed zurueck, wenn das auch immer ein 
+  //mapping sein sollte.
+  save = (mixed)previous_object()->_get_save_data();
+  if(mappingp(save))
+  {
+    index = m_indices(save);
+    for(i = sizeof(index)-1; i>=0; i--)
+    {
+      properties[index[i]] = save[index[i]];
+      properties[index[i], F_MODE] = save[index[i], F_MODE]
+                            &~(SETCACHED|QUERYCACHED);
+    }
+  }
+  else properties = ([]);
+
+  // restore properties
+  funcall(
+          bind_lambda(
+                     unbound_lambda(({'arg}), //'})
+                                  ({#'call_other,({#'this_object}),
+                                  "SetProperties",'arg})),//')
+                     previous_object()),properties);
+  previous_object()->_set_save_data(0);
+
+#ifdef IOSTATS
+  // Stats...
+  //debug_message("restoreo: "+restoreo_stat[0]+"\n");
+  struct iostat_s stat = (<iostat_s>);
+  stat->oname = object_name(previous_object());
+  stat->time = time();
+  //stat->size = (int)object_info(previous_object(),OINFO_MEMORY,
+  //    OIM_TOTAL_DATA_SIZE);
+  stat->size = file_size(name + ".o");
+  restoreo_stat[1][restoreo_stat[0]] = stat;
+
+  restoreo_stat[0] = (restoreo_stat[0] + 1) % sizeof(restoreo_stat[1]);
+#endif
+
+  return result;
+}
+
+// * HB eines Objektes ein/ausschalten
+int set_object_heart_beat(object ob, int flag)
+{
+  if (objectp(ob))
+    return funcall(bind_lambda(#'efun::configure_object,ob), ob, OC_HEART_BEAT, flag);
+}
+
+// * Magierlevelgruppen ermitteln
+int query_wiz_grp(mixed wiz)
+{
+  int lev;
+
+  lev=query_wiz_level(wiz);
+  if (lev<SEER_LVL) return 0;
+  if (lev>=GOD_LVL) return lev;
+  if (lev>=ARCH_LVL) return ARCH_GRP;
+  if (lev>=ELDER_LVL) return ELDER_GRP;
+  if (lev>=LORD_LVL) return LORD_GRP;
+  if (lev>=SPECIAL_LVL) return SPECIAL_GRP;
+  if (lev>=DOMAINMEMBER_LVL) return DOMAINMEMBER_GRP;
+  if (lev>=WIZARD_LVL) return WIZARD_GRP;
+  if (lev>=LEARNER_LVL) return LEARNER_GRP;
+  return SEER_GRP;
+}
+
+mixed *wizlist_info()
+{
+  if (ARCH_SECURITY || !extern_call())
+            return efun::wizlist_info();
+  return 0;
+}
+
+// * wizlist ausgeben
+varargs void wizlist(string name, int sortkey ) {
+
+  if (!name)
+  {
+    if (this_player())
+      name = getuid(this_player());
+    if (!name)
+      return;
+  }
+
+  // Schluessel darf nur in einem gueltigen Bereich sein
+  if (sortkey<WL_NAME || sortkey>=WL_SIZE) sortkey=WL_COST;
+
+  mixed** wl = efun::wizlist_info();
+  // nach <sortkey> sortieren
+  wl = sort_array(wl, function int (mixed a, mixed b)
+      {return a[sortkey] < b[sortkey]; } );
+
+  // Summe ueber alle Kommandos ermitteln.
+  int total_cmd, i;
+  int pos=-1;
+  foreach(mixed entry : wl)
+  {
+    total_cmd += entry[WL_COMMANDS];
+    if (entry[WL_NAME] == name)
+      pos = i;
+    ++i;
+  }
+
+  if (pos < 0 && name != "ALL" && name != "TOP100")
+    return;
+
+  if (name == "TOP100")
+  {
+    if (sizeof(wl) > 100)
+      wl = wl[0..100];
+    else
+      wl = wl;
+  }
+  // um name herum schneiden
+  else if (name != "ALL")
+  {
+    if (sizeof(wl) <= 21)
+      wl = wl;
+    else if (pos + 10 < sizeof(wl) && pos - 10 > 0)
+      wl = wl[pos-10..pos+10];
+    else if (pos <=21)
+      wl = wl[0..20];
+    else if (pos >= sizeof(wl) - 21)
+      wl = wl[<21..];
+    else
+      wl = wl;
+  }
+
+  write("\nWizard top score list\n\n");
+  if (total_cmd == 0)
+    total_cmd = 1;
+  printf("%-20s %-6s %-3s %-17s %-6s %-6s %-6s\n",
+         "EUID", "cmds", "%", "Costs", "HB", "Arrays","Mapp.");
+  foreach(mixed e: wl)
+  {
+    printf("%-:20s %:6d %:2d%% [%:6dk,%:6dG] %:6d %:6d %:6d\n",
+          e[WL_NAME], e[WL_COMMANDS], e[WL_COMMANDS] * 100 / total_cmd,
+          e[WL_COST] / 1000, e[WL_TOTAL_GIGACOST],
+          e[WL_HEART_BEATS], e[WL_ARRAY_TOTAL], e[WL_MAPPING_TOTAL]
+          );
+  }
+  printf("\nTotal         %7d         (%d)\n\n", total_cmd, sizeof(wl));
+}
+
+
+// Ab hier folgen Hilfsfunktionen fuer call_out() bzw. fuer deren Begrenzung
+
+// ermittelt das Objekt des Callouts.
+private object _call_out_obj( mixed call_out ) {
+    return pointerp(call_out) ? call_out[0] : 0;
+}
+
+private void _same_object( object ob, mapping m ) {
+  // ist nicht so bloed, wie es aussieht, s. nachfolgede Verwendung von m
+  if ( m[ob] )
+    m[ob] += ({ ob });
+  else
+    m[ob] = ({ ob }); 
+}
+
+// alle Objekte im Mapping m zusammenfassen, die den gleichen Loadname (Name der
+// Blueprint) haben, also alle Clones der BP, die Callouts laufen haben.
+// Objekte werden auch mehrfach erfasst, je nach Anzahl ihrer Callouts.
+private void _same_path( object key, object *obs, mapping m ) {
+  string path;
+  if (!objectp(key) || !pointerp(obs)) return;
+
+  path = load_name(key);
+
+  if ( m[path] )
+    m[path] += obs;
+  else
+    m[path] = obs;
+}
+
+// key kann object oder string sein.
+private int _too_many( mixed key, mapping m, int i ) {
+    return sizeof(m[key]) >= i;
+}
+
+// alle Objekte in obs zerstoeren und Fehlermeldung ausgeben. ACHTUNG: Die
+// Objekte werden idR zu einer BP gehoeren, muessen aber nicht! In dem Fall
+// wird auf der Ebene aber nur ein Objekt in der Fehlermeldung erwaehnt.
+private void _destroy( mixed key, object *obs, string text, int uid ) {
+    if (!pointerp(obs)) return;
+    // Array mit unique Eintraege erzeugen.
+    obs = m_indices( mkmapping(obs) );
+    // Fehlermeldung auf der Ebene ausgeben, im catch() mit publish, damit es
+    // auf der Ebene direkt scrollt, der backtrace verfuegbar ist (im
+    // gegensatz zur Loesung mittels Callout), die Ausfuehrung aber weiter
+    // laeuft.
+    catch( efun::raise_error(           
+         sprintf( text,                   
+           uid ? (string)master()->creator_file(key) : key,                   
+           sizeof(obs), object_name(obs[<1]) ) );publish);
+    // Und weg mit dem Kram...
+    filter( obs, #'efun::destruct/*'*/ );
+}
+
+// Alle Objekt einer UID im Mapping m mit UID als KEys zusammenfassen. Objekt
+// sind dabei nicht unique.
+private void _same_uid( string key, object *obs, mapping m, closure cf ) {
+  string uid;
+
+  if ( !pointerp(obs) || !sizeof(obs) )
+    return;
+
+  uid = funcall( cf, key );
+
+  if ( m[uid] )
+    m[uid] += obs; // obs ist nen Array
+  else
+    m[uid] = obs;
+}
+
+private int _log_call_out(mixed co)
+{
+  log_file("TOO_MANY_CALLOUTS",
+      sprintf("%s::%O (%d)\n",object_name(co[0]),co[1],co[2]),
+      200000);
+  return 0;
+}
+
+private int last_callout_log=0;
+
+nomask varargs void call_out( varargs mixed *args )
+{
+    mixed tmp, *call_outs;
+
+    // Bei >600 Callouts alle Objekte killen, die mehr als 30 Callouts laufen
+    // haben.
+    if ( efun::driver_info(DI_NUM_CALLOUTS) > 600
+        && geteuid(previous_object()) != ROOTID )
+    {
+       // Log erzeugen...
+       if (last_callout_log <
+           efun::driver_info(DI_NUM_HEARTBEAT_TOTAL_CYCLES) - 10
+           && get_eval_cost() > 200000)
+       {
+         last_callout_log = efun::driver_info(DI_NUM_HEARTBEAT_TOTAL_CYCLES);
+         log_file("TOO_MANY_CALLOUTS",
+             sprintf("\n%s: ############ Too many callouts: %d ##############\n",
+                     strftime("%y%m%d-%H%M%S"),
+                     efun::driver_info(DI_NUM_CALLOUTS)));
+         filter(efun::call_out_info(), #'_log_call_out);
+       }
+       // Objekte aller Callouts ermitteln
+       call_outs = map( efun::call_out_info(), #'_call_out_obj );
+       mapping objectmap = ([]);
+       filter( call_outs, #'_same_object, &objectmap );
+       // Master nicht grillen...
+       efun::m_delete( objectmap, master(1) );
+       // alle Objekte raussuchen, die zuviele haben...
+       mapping res = filter_indices( objectmap, #'_too_many, objectmap, 29 );
+       // und ueber alle Keys gehen, an _destroy() werden Key und Array mit
+       // Objekten uebergeben (in diesem Fall sind Keys und Array mit
+       // Objekten jeweils das gleiche Objekt).
+       if ( sizeof(res) )       
+           walk_mapping(res, #'_destroy, "CALL_OUT overflow by single "             
+              "object [%O]. Destructed %d objects. [%s]\n", 0 );
+
+       // Bei (auch nach dem ersten Aufraeumen noch) >800 Callouts alle
+       // Objekte killen, die mehr als 50 Callouts laufen haben - und
+       // diesmal zaehlen Clones nicht eigenstaendig! D.h. es werden alle
+       // Clones einer BP gekillt, die Callouts laufen haben, falls alle
+       // diese Objekte _zusammen_ mehr als 50 Callouts haben!
+       if ( efun::driver_info(DI_NUM_CALLOUTS) > 800 ) {
+           // zerstoerte Objekte von der letzten Aktion sind in objectmap nicht
+           // mehr drin, da sie dort als Keys verwendet wurden.
+           mapping pathmap=([]);
+           // alle Objekt einer BP in res sortieren, BP-Name als Key, Arrays
+           // von Objekten als Werte.
+           walk_mapping( objectmap, #'_same_path, &pathmap);
+           // alle BPs (und ihre Objekte) raussuchen, die zuviele haben...
+           res = filter_indices( pathmap, #'_too_many/*'*/, pathmap, 50 );
+           // und ueber alle Keys gehen, an _destroy() werden die Clones
+           // uebergeben, die Callouts haben.
+           if ( sizeof(res) )
+              walk_mapping( res, #'_destroy/*'*/, "CALL_OUT overflow by file "
+                           "'%s'. Destructed %d objects. [%s]\n", 0 );
+
+           // Wenn beide Aufraeumarbeiten nichts gebracht haben und immer
+           // noch >1000 Callouts laufen, werden diesmal alle Callouts
+           // einer UID zusammengezaehlt.
+           // Alle Objekte einer UID, die es in Summe aller ihrer Objekt mit
+           // Callouts auf mehr als 100 Callouts bringt, werden geroestet.
+           if (efun::driver_info(DI_NUM_CALLOUTS) > 1000)
+           {
+              // das nach BP-Namen vorgefilterte Mapping jetzt nach UIDs
+              // zusammensortieren. Zerstoerte Clones filter _same_uid()
+              // raus.
+              mapping uidmap=([]);
+              walk_mapping( pathmap, #'_same_uid, &uidmap,
+                           symbol_function( "creator_file",
+                                          "/secure/master" ) );
+              // In res nun UIDs als Keys und Arrays von Objekten als Werte.
+              // Die rausfiltern, die mehr als 100 Objekte (non-unique, d.h.
+              // 100 Callouts!) haben.
+              res = filter_indices( uidmap, #'_too_many, uidmap, 100 );
+              // und erneut ueber die Keys laufen und jeweils die Arrays mit
+              // den Objekten zur Zerstoerung an _destroy()...
+              if ( sizeof(res) )
+                  walk_mapping( res, #'_destroy, "CALL_OUT overflow by "
+                              "UID '%s'. Destructed %d objects. [%s]\n",
+                              1 );
+           }
+       }
+    }
+
+    // Falls das aufrufende Objekt zerstoert wurde beim Aufraeumen
+    if ( !previous_object() )
+       return;
+
+    set_this_object( previous_object() );
+    apply( #'efun::call_out, args );
+    return;
+}
+
+mixed call_out_info() {
+  
+  object po = previous_object();
+  mixed coi = efun::call_out_info();
+
+  // ungefilterten Output nur fuer bestimmte Objekte, Objekte in /std oder
+  // /obj haben die BackboneID.
+  if (query_wiz_level(getuid(po)) >= ARCH_LVL
+       || (string)master()->creator_file(load_name(po)) == BACKBONEID ) {
+      return coi;
+  }
+  else {
+      return filter(coi, function mixed (mixed arr) {
+              if (pointerp(arr) && arr[0]==po)
+                 return 1;
+              else return 0; });
+  }
+}
+
+// * Zu einer closure das Objekt, an das sie gebunden ist, suchen
+// NICHT das Objekt, was ggf. die lfun definiert!
+mixed query_closure_object(closure c) {
+  return
+    CLOSURE_IS_UNBOUND_LAMBDA(get_type_info(c, 1)) ?
+      0 :
+  (to_object(c) || -1);
+}
+
+// * Wir wollen nur EIN Argument ... ausserdem checks fuer den Netztotenraum
+varargs void move_object(mixed what, mixed where)
+{
+  object po,tmp;
+
+  po=previous_object();
+  if (!where)
+  {
+    where=what;
+    what=po;
+  }
+  if (((stringp(where) && where==NETDEAD_ROOM ) ||
+       (objectp(where) && where==find_object(NETDEAD_ROOM))) &&
+       objectp(what) && object_name(what)!="/obj/sperrer")
+  {
+    if (!query_once_interactive(what))
+    {
+      what->remove();
+      if (what) destruct(what);
+      return;
+    }
+    if (living(what) || interactive(what))
+    {
+      log_file("NDEAD2",sprintf("TRYED TO MOVE TO NETDEAD: %O\n",what));
+      return;
+    }
+    set_object_heart_beat(what,0);
+  }
+  tmp=what;
+  while (tmp=environment(tmp))
+      // Ja. Man ruft die _set_xxx()-Funktionen eigentlich nicht direkt auf.
+      // Aber das Lichtsystem ist schon *so* rechenintensiv und gerade der
+      // P_LAST_CONTENT_CHANGE-Cache wird *so* oft benoetigt, dass es mir
+      // da um jedes bisschen Rechenzeit geht.
+      // Der Zweck heiligt ja bekanntlich die Mittel. ;-)
+      //
+      // Tiamak
+    tmp->_set_last_content_change();
+  funcall(bind_lambda(#'efun::move_object,po),what,where);
+  if (tmp=what)
+    while (tmp=environment(tmp))
+      tmp->_set_last_content_change();
+}
+
+
+void start_simul_efun() {
+  mixed *info;
+
+  // Falls noch nicht getan, extra_wizinfo initialisieren
+  if ( !pointerp(info = get_extra_wizinfo(0)) )
+    set_extra_wizinfo(0, info = allocate(BACKBONE_WIZINFO_SIZE));
+
+  InitLivingData(info);
+
+  set_next_reset(10); // direkt mal aufraeumen
+}
+
+protected void reset() {
+  set_next_reset(7200);
+  CleanLivingData();
+}
+
+#if !__EFUN_DEFINED__(absolute_hb_count)
+int absolute_hb_count() {
+  return efun::driver_info(DI_NUM_HEARTBEAT_TOTAL_CYCLES);
+}
+#endif
+
+void __set_environment(object ob, mixed target)
+{
+  string path;
+  object obj;
+
+  if (!objectp(ob))
+    return;
+  if (!IS_ARCH(geteuid(previous_object())) || !ARCH_SECURITY )
+    return;
+  if (objectp(target))
+  {
+    efun::set_environment(ob,target);
+    return;
+  }
+  path=(string)MASTER->_get_path(target,this_interactive());
+  if (stringp(path) && file_size(path+".c")>=0 &&
+      !catch(load_object(path);publish) )
+  {
+    obj=find_object(path);
+    efun::set_environment(ob,obj);
+    return;
+  }
+}
+
+void _dump_wizlist(string file, int sortby) {
+  int i;
+  mixed *a;
+
+  if (!LORD_SECURITY)
+    return;
+  if (!master()->valid_write(file,geteuid(previous_object()),"write_file"))
+  {
+    write("NO WRITE PERMISSION\n");
+    return;
+  }
+  a = wizlist_info();
+  a = sort_array(a, lambda( ({'a,'b}),
+                        ({#'<,
+                          ({#'[,'a,sortby}),
+                          ({#'[,'b,sortby})
+                         })));
+  rm(file);
+  for (i=sizeof(a)-1;i>=0;i--)
+    write_file(file,sprintf("%-11s: eval=%-8d cmds=%-6d HBs=%-5d array=%-5d mapping=%-7d\n",
+      a[i][WL_NAME],a[i][WL_TOTAL_COST],a[i][WL_COMMANDS],a[i][WL_HEART_BEATS],
+      a[i][WL_ARRAY_TOTAL],a[i][WL_CALL_OUT]));
+}
+
+public varargs object deep_present(mixed what, object ob) {
+
+  if(!objectp(ob))
+    ob=previous_object();
+  // Wenn ein Objekt gesucht wird: Alle Envs dieses Objekts ermitteln und
+  // schauen, ob in diesen ob vorkommt. Dann ist what in ob enthalten.
+  if(objectp(what)) {
+    object *envs=all_environment(what);
+    // wenn ob kein Environment hat, ist es offensichtlich nicht in what
+    // enthalten.
+    if (!pointerp(envs)) return 0;
+    if (member(envs, ob) != -1) return what;
+  }
+  // sonst wirds teurer, ueber alle Objekte im (deep) Inv laufen und per id()
+  // testen. Dabei muss aber die gewuenschte Nr. ("flasche 3") abgeschnitten
+  // werden und selber gezaehlt werden, welche das entsprechende Objekt ist.
+  else if (stringp(what)) {
+      int cnt;
+      string newwhat;
+      if(sscanf(what,"%s %d",newwhat,cnt)!=2)
+       cnt=1;
+      else
+       what=newwhat;
+      foreach(object invob: deep_inventory(ob)) {
+       if (invob->id(what) && !--cnt)
+           return invob;
+      }
+  }
+  else {
+    set_this_object(previous_object());
+    raise_error(sprintf("Wrong argument 1 to deep_present(). "
+         "Expected \"object\" or \"string\", got %.50O.\n",
+         what));
+  }
+  return 0;
+}
+
+mapping dump_ip_mapping()
+{
+  return 0;
+}
+
+nomask void swap(object obj)
+{
+  write("Your are not allowed to swap objects by hand!\n");
+  return;
+}
+
+nomask varargs void garbage_collection(string str)
+{
+  if(previous_object()==0 || !IS_ARCH(geteuid(previous_object())) 
+      || !ARCH_SECURITY)
+  {
+    write("Call GC now and the mud will crash in 5-6 hours. DONT DO IT!\n");
+    return;
+  }
+  else if (stringp(str))
+  {
+    return efun::garbage_collection(str);
+  }
+  else 
+    return efun::garbage_collection();
+}
+
+varargs void notify_fail(mixed nf, int prio) {
+  object po,oldo;
+  int oldprio;
+  
+  if (!PL || !objectp(po=previous_object())) return;
+  if (!stringp(nf) && !closurep(nf)) {
+      set_this_object(po);
+      raise_error(sprintf(
+         "Only strings and closures allowed for notify_fail! "
+         "Argument was: %.50O...\n",nf));
+  }
+
+  // falls ein Objekt bereits nen notify_fail() setzte, Prioritaeten abschaetzen
+  // und vergleichen.
+  if (objectp(oldo=query_notify_fail(1)) && po!=oldo) {
+    if (!prio) {       
+      //Prioritaet dieses notify_fail() 'abschaetzen'
+      if (po==PL) // Spieler-interne (soul-cmds)
+        prio=NF_NL_OWN;
+      else if (living(po))
+        prio=NF_NL_LIVING;
+      else if ((int)po->IsRoom())
+        prio=NF_NL_ROOM;
+      else
+        prio=NF_NL_THING;
+    }
+    //Prioritaet des alten Setzers abschaetzen
+    if (oldo==PL)
+      oldprio=NF_NL_OWN;
+    else if (living(oldo))
+      oldprio=NF_NL_LIVING;
+    else if ((int)oldo->IsRoom())
+      oldprio=NF_NL_ROOM;
+    else
+      oldprio=NF_NL_THING;
+  }
+  else // wenn es noch kein Notify_fail gibt:
+    oldprio=NF_NL_NONE;
+
+  //vergleichen und ggf. setzen
+  if (prio >= oldprio) { 
+    set_this_object(po);
+    efun::notify_fail(nf);
+  }
+
+  return;
+}
+
+void _notify_fail(string str)
+{
+  //query_notify_fail() benutzen, um das Objekt
+  //des letzten notify_fail() zu ermitteln
+  object o;
+  if ((o=query_notify_fail(1)) && o!=previous_object())
+    return;
+  //noch kein notify_fail() fuer dieses Kommando gesetzt, auf gehts.
+  set_this_object(previous_object());
+  efun::notify_fail(str);
+  return;
+}
+
+string time2string( string format, int zeit )
+{
+  int i,ch,maxunit,dummy;
+  string *parts, fmt;
+
+  int secs = zeit;
+  int mins = (zeit/60);
+  int hours = (zeit/3600);
+  int days = (zeit/86400);
+  int weeks =  (zeit/604800);
+  int months = (zeit/2419200);
+  int abbr = 0;
+
+  parts = regexplode( format, "\(%\(-|\)[0-9]*[nwdhmsxNWDHMSX]\)|\(%%\)" );
+
+  for( i=1; i<sizeof(parts); i+=2 )
+  {
+    ch = parts[i][<1];
+    switch( parts[i][<1] )
+    {
+    case 'x': case 'X':
+       abbr = sscanf( parts[i], "%%%d", dummy ) && dummy==0;
+       // NO break !
+    case 'n': case 'N':
+       maxunit |= 31;
+       break;
+    case 'w': case 'W':
+       maxunit |= 15;
+       break;
+    case 'd': case 'D':
+       maxunit |= 7;
+       break;
+    case 'h': case 'H':
+       maxunit |= 3;
+       break;
+    case 'm': case 'M':
+       maxunit |= 1;
+       break;
+    }
+  }
+  if( maxunit & 16 ) weeks %= 4;
+  if( maxunit & 8 ) days %= 7;
+  if( maxunit & 4 ) hours %= 24;
+  if( maxunit & 2 ) mins %= 60;
+  if( maxunit ) secs %= 60;
+
+  for( i=1; i<sizeof(parts); i+=2 )
+  {
+    fmt = parts[i][0..<2];
+    ch = parts[i][<1];
+    if( ch=='x' )
+    {
+      if (months > 0) ch='n';
+      else if( weeks>0 ) ch='w';
+      else if( days>0 ) ch='d';
+      else if( hours>0 ) ch='h'; 
+      else if(mins > 0) ch = 'm';
+      else ch = 's';
+    }
+    else if( ch=='X' )
+    {
+      if (months > 0) ch='N';
+      else if( weeks>0 ) ch='W';
+      else if( days>0 ) ch='D';
+      else if( hours>0 ) ch='H'; 
+      else if(mins > 0) ch = 'M';
+      else ch = 'S';
+    }
+    
+    switch( ch )
+    {
+      case 'n': parts[i] = sprintf( fmt+"d", months ); break;
+      case 'w': parts[i] = sprintf( fmt+"d", weeks ); break;
+      case 'd': parts[i] = sprintf( fmt+"d", days ); break;
+      case 'h': parts[i] = sprintf( fmt+"d", hours ); break;
+      case 'm': parts[i] = sprintf( fmt+"d", mins ); break;
+      case 's': parts[i] = sprintf( fmt+"d", secs ); break;
+      case 'N':
+       if(abbr) parts[i] = "M";
+       else parts[i] = sprintf( fmt+"s", (months==1) ? "Monat" : "Monate" );
+       break;
+      case 'W':
+       if(abbr) parts[i] = "w"; else
+       parts[i] = sprintf( fmt+"s", (weeks==1) ? "Woche" : "Wochen" );
+       break;
+      case 'D':
+       if(abbr) parts[i] = "d"; else
+       parts[i] = sprintf( fmt+"s", (days==1) ? "Tag" : "Tage" );
+       break;
+      case 'H':
+       if(abbr) parts[i] = "h"; else
+       parts[i] = sprintf( fmt+"s", (hours==1) ? "Stunde" : "Stunden" );
+       break;
+      case 'M':
+       if(abbr) parts[i] = "m"; else
+       parts[i] = sprintf( fmt+"s", (mins==1) ? "Minute" : "Minuten" );
+       break;
+      case 'S':
+       if(abbr) parts[i] = "s"; else
+       parts[i] = sprintf( fmt+"s", (secs==1) ? "Sekunde" : "Sekunden" );
+       break;
+      case '%':
+       parts[i] = "%";
+       break;
+      }
+    }
+    return implode( parts, "" );
+}
+
+nomask mixed __create_player_dummy(string name)
+{
+  string err;
+  object ob;
+  mixed m;
+  //hat nen Scherzkeks die Blueprint bewegt?
+  if ((ob=find_object("/secure/login")) && environment(ob))
+      catch(destruct(ob);publish);
+  err = catch(ob = clone_object("secure/login");publish);
+  if (err)
+  {
+    write("Fehler beim Laden von /secure/login.c\n"+err+"\n");
+    return 0;
+  }
+  if (objectp(m=(mixed)ob->new_logon(name))) netdead[name]=m;
+  return m;
+}
+
+nomask int secure_level()
+{
+  int *level;
+  //kette der Caller durchlaufen, den niedrigsten Level in der Kette
+  //zurueckgeben. Zerstoerte Objekte (Selbstzerstoerer) fuehren zur Rueckgabe
+  //von 0.
+  //caller_stack(1) fuegt dem Rueckgabearray this_interactive() hinzu bzw. 0,
+  //wenn es keinen Interactive gibt. Die 0 fuehrt dann wie bei zerstoerten
+  //Objekten zur Rueckgabe von 0, was gewuenscht ist, da es hier einen
+  //INteractive geben muss.
+  level=map(caller_stack(1),function int (object caller)
+      {if (objectp(caller))
+       return(query_wiz_level(geteuid(caller)));
+       return(0); // kein Objekt da, 0.
+      } );
+  return(min(level)); //den kleinsten Wert im Array zurueckgeben (ggf. 0)
+}
+
+nomask string secure_euid()
+{
+  string euid;
+
+  if (!this_interactive()) // Es muss einen interactive geben
+     return 0;
+  euid=geteuid(this_interactive());
+  // ueber alle Caller iterieren. Wenn eines davon eine andere euid hat als
+  // der Interactive und diese nicht die ROOTID ist, wird 0 zurueckgeben.
+  // Ebenso, falls ein Selbstzerstoerer irgendwo in der Kette ist.
+  foreach(object caller: caller_stack()) {
+      if (!objectp(caller) ||
+       (geteuid(caller)!=euid && geteuid(caller)!=ROOTID))
+         return 0;
+  }
+  return euid; // 'sichere' euid zurueckgeben
+}
+
+// INPUT_PROMPT und nen Leerprompt hinzufuegen, wenn keins uebergeben wird.
+// Das soll dazu dienen, dass alle ggf. ein EOR am Promptende kriegen...
+//#if __BOOT_TIME__ < 1360017213
+varargs void input_to( mixed fun, int flags, varargs mixed *args )
+{
+    mixed *arr;
+    int i;
+
+    if ( !this_player() || !previous_object() )
+       return;
+
+    // TODO: input_to(...,INPUT_PROMPT, "", ...), wenn kein INPUT_PROMPT
+    // vorkommt...
+    if ( flags&INPUT_PROMPT ) {    
+        arr = ({ fun, flags }) + args;
+    }
+    else {
+        // ggf. ein INPUT_PROMPT hinzufuegen und nen Leerstring als Prompt.
+        flags |= INPUT_PROMPT;
+        arr = ({ fun, flags, "" }) + args;
+    }
+
+    // Arrays gegen flatten quoten.
+    for ( i = sizeof(arr) - 1; i > 1; i-- )
+       if ( pointerp(arr[i]) )
+           arr[i] = quote(arr[i]);
+
+    apply( bind_lambda( unbound_lambda( ({}),
+                                     ({ #'efun::input_to/*'*/ }) + arr ),
+                       previous_object() ) );
+}
+//#endif
+
+nomask int set_light(int i)
+// erhoeht das Lichtlevel eines Objekts um i
+// result: das Lichtlevel innerhalb des Objekts
+{
+    object ob, *inv;
+    int lall, light, dark, tmp;
+
+    if (!(ob=previous_object())) return 0; // ohne das gehts nicht.
+
+    // aus kompatibilitaetsgruenden kann man auch den Lichtlevel noch setzen
+    if (i!=0) ob->SetProp(P_LIGHT, ob->QueryProp(P_LIGHT)+i);
+
+    // Lichtberechnung findet eigentlich in der Mudlib statt.
+    return (int)ob->QueryProp(P_INT_LIGHT);
+}
+
+
+public string iso2ascii( string str )
+{
+    if ( !stringp(str) || !sizeof(str) )
+       return "";
+
+    str = regreplace( str, "ä", "ae", 1 );
+    str = regreplace( str, "ö", "oe", 1 );
+    str = regreplace( str, "ü", "ue", 1 );
+    str = regreplace( str, "Ä", "Ae", 1 );
+    str = regreplace( str, "Ö", "Oe", 1 );
+    str = regreplace( str, "Ü", "Ue", 1 );
+    str = regreplace( str, "ß", "ss", 1 );
+    str = regreplace( str, "[^ -~]", "?", 1 );
+
+    return str;
+}
+
+
+public varargs string CountUp( string *s, string sep, string lastsep )
+{
+    string ret;
+
+    if ( !pointerp(s) )
+       return "";
+    
+    if (!sep) sep = ", ";
+    if (!lastsep) lastsep = " und ";
+
+    switch (sizeof(s))  {
+       case 0: ret=""; break;
+       case 1: ret=s[0]; break;
+       default:
+              ret = implode(s[0..<2], sep);
+              ret += lastsep + s[<1];
+    }
+    return ret;
+}
+
+nomask varargs int query_next_reset(object ob) {
+
+    // Typpruefung: etwas anderes als Objekte oder 0 sollen Fehler sein.
+    if (ob && !objectp(ob))
+      raise_error(sprintf("Bad arg 1 to query_next_reset(): got %.20O, "
+           "expected object.\n",ob));
+
+    // Defaultobjekt PO, wenn 0 uebergeben.
+    if ( !objectp(ob) )
+      ob = previous_object();
+
+    return efun::object_info(ob, OI_NEXT_RESET_TIME);
+}
+
+
+#if !__EFUN_DEFINED__(copy_file)
+#define MAXLEN 50000
+nomask int copy_file(string source, string dest)
+{
+
+  int ptr;
+  string bytes;
+
+  set_this_object(previous_object());
+  if (!sizeof(source)||!sizeof(dest)||source==dest||(file_size(source)==-1)||
+      (!call_other(master(),"valid_read",source,
+                   getuid(this_interactive()||
+                 previous_object()),"read_file",previous_object()))||
+      (!call_other(master(),"valid_read",source,
+                   getuid(this_interactive()||
+                 previous_object()),"write_file",previous_object())))
+    return 1;
+  switch (file_size(dest))
+  {
+  case -1:
+    break;
+  case -2:
+    if (dest[<1]!='/') dest+="/";
+    dest+=efun::explode(source,"/")[<1];
+    if (file_size(dest)==-2) return 1;
+    if (file_size(dest)!=-1) break;
+  default:
+    if (!rm(dest)) return 1;
+    break;
+  }
+  do
+  {
+    bytes = read_bytes(source, ptr, MAXLEN); ptr += MAXLEN;
+    if (!bytes) bytes="";
+    write_file(dest, bytes);
+  }
+  while(sizeof(bytes) == MAXLEN);
+  return 0;
+}
+#endif //!__EFUN_DEFINED__(copy_file)
+
+
+// ### Ersatzaufloesung in Strings ###
+varargs string replace_personal(string str, mixed *obs, int caps) {
+  int i;
+  string *parts;
+
+  parts = regexplode(str, "@WE[A-SU]*[0-9]");
+  i = sizeof(parts);
+
+  if (i>1) {
+    int j, t;
+    closure *name_cls;
+
+    t = j = sizeof(obs);
+
+    name_cls  =  allocate(j);
+    while (j--)
+      if (objectp(obs[j]))
+        name_cls[j] = symbol_function("name", obs[j]);
+      else if (stringp(obs[j]))
+        name_cls[j] = obs[j];
+
+    while ((i-= 2)>0) {
+      int ob_nr;
+      // zu ersetzendes Token in Fall und Objektindex aufspalten
+      ob_nr = parts[i][<1]-'1';
+      if (ob_nr<0 || ob_nr>=t) {
+        set_this_object(previous_object());
+        raise_error(sprintf("replace_personal: using wrong object index %d\n",
+                    ob_nr));
+        return implode(parts, "");
+      }
+
+      // casus kann man schon hier entscheiden
+      int casus;
+      string part = parts[i];
+      switch (part[3]) {
+        case 'R': casus = WER;    break;
+        case 'S': casus = WESSEN; break;
+        case 'M': casus = WEM;    break;
+        case 'N': casus = WEN;    break;
+        default:  continue; // passt schon jetzt nicht in das Hauptmuster
+      }
+
+      // und jetzt die einzelnen Keywords ohne fuehrendes "@WE", beendende Id
+      mixed tmp;
+      switch (part[3..<2]) {
+        case "R": case "SSEN": case "M": case "N":               // Name
+          parts[i] = funcall(name_cls[ob_nr], casus, 1);  break;
+        case "RU": case "SSENU": case "MU": case "NU":           // unbestimmt
+          parts[i] = funcall(name_cls[ob_nr], casus);     break;
+        case "RQP": case "SSENQP": case "MQP": case "NQP":       // Pronoun
+          if (objectp(tmp = obs[ob_nr]))
+            parts[i] = (string)tmp->QueryPronoun(casus);
+          break;
+        case "RQA": case "SSENQA": case "MQA": case "NQA":       // Article
+          if (objectp(tmp = obs[ob_nr]))
+            tmp = (string)tmp->QueryArticle(casus, 1, 1);
+          if (stringp(tmp) && !(tmp[<1]^' ')) 
+            tmp = tmp[0..<2];                // Extra-Space wieder loeschen
+          break;
+        case "RQPPMS": case "SSENQPPMS": case "MQPPMS": case "NQPPMS":
+          if (objectp(tmp = obs[ob_nr]))
+            parts[i] = (string)tmp->QueryPossPronoun(MALE, casus, SINGULAR);
+          break;
+        case "RQPPFS": case "SSENQPPFS": case "MQPPFS": case "NQPPFS":
+          if (objectp(tmp = obs[ob_nr]))
+            parts[i] = (string)tmp->QueryPossPronoun(FEMALE, casus, SINGULAR);
+          break;
+        case "RQPPNS": case "SSENQPPNS": case "MQPPNS": case "NQPPNS":
+          if (objectp(tmp = obs[ob_nr]))
+            parts[i] = (string)tmp->QueryPossPronoun(NEUTER, casus, SINGULAR);
+          break;
+        case "RQPPMP": case "SSENQPPMP": case "MQPPMP": case "NQPPMP":
+          if (objectp(tmp = obs[ob_nr]))
+            parts[i] = (string)tmp->QueryPossPronoun(MALE, casus, PLURAL);
+          break;
+        case "RQPPFP": case "SSENQPPFP": case "MQPPFP": case "NQPPFP":
+          if (objectp(tmp = obs[ob_nr]))
+            parts[i] = (string)tmp->QueryPossPronoun(FEMALE, casus, PLURAL);
+          break;
+        case "RQPPNP": case "SSENQPPNP": case "MQPPNP": case "NQPPNP":
+          if (objectp(tmp = obs[ob_nr]))
+            parts[i] = (string)tmp->QueryPossPronoun(NEUTER, casus, PLURAL);
+          break;
+        default:
+          continue;
+      }
+      
+      // wenn tmp ein String war, weisen wir es hier pauschal zu
+      if (stringp(tmp))
+        parts[i] = tmp;
+
+      // auf Wunsch wird nach Satzenden gross geschrieben
+      if (caps)
+        switch (parts[i-1][<2..]) {
+          case ". ":  case "! ":  case "? ":
+          case ".":   case "!":   case "?":
+          case ".\n": case "!\n": case "?\n":
+          case "\" ": case "\"\n":
+            parts[i] = capitalize(parts[i]);
+            break;
+        }
+    }
+    return implode(parts, "");
+  }
+  return str;
+}
+
+
+//replacements for dropped efuns in LD
+#if !__EFUN_DEFINED__(extract)
+deprecated varargs string extract(string str, int from, int to) {
+
+  if(!stringp(str)) {
+    set_this_object(previous_object());
+    raise_error(sprintf("Bad argument 1 to extract(): %O",str));
+  }
+  if (intp(from) && intp(to)) {
+    if (from>=0 && to>=0)
+      return(str[from .. to]);
+    else if (from>=0 && to<0)
+      return(str[from .. <abs(to)]);
+    else if (from<0 && to>=0)
+      return(str[<abs(from) .. to]);
+    else
+      return(str[<abs(from) .. <abs(to)]);
+  }
+  else if (intp(from)) {
+    if (from>=0)
+      return(str[from .. ]);
+    else
+      return(str[<abs(from) .. ]);
+  }
+  else {
+    return(str);
+  }
+}
+#endif // !__EFUN_DEFINED__(extract)
+
+#if !__EFUN_DEFINED__(slice_array)
+deprecated varargs mixed slice_array(mixed array, int from, int to) {
+
+  if(!pointerp(array)) {
+    set_this_object(previous_object());
+    raise_error(sprintf("Bad argument 1 to slice_array(): %O",array));
+  }
+  if (intp(from) && intp(to)) {
+    if (from>=0 && to>=0)
+      return(array[from .. to]);
+    else if (from>=0 && to<0)
+      return(array[from .. <abs(to)]);
+    else if (from<0 && to>=0)
+      return(array[<abs(from) .. to]);
+    else
+      return(array[<abs(from) .. <abs(to)]);
+  }
+  else if (intp(from)) {
+    if (from>=0)
+      return(array[from .. ]);
+    else
+      return(array[<abs(from) .. ]);
+  }
+  else {
+    return(array);
+  }
+}
+#endif // !__EFUN_DEFINED__(slice_array)
+
+#if !__EFUN_DEFINED__(member_array)
+deprecated int member_array(mixed item, mixed arraystring) {
+
+  if (pointerp(arraystring)) {
+    return(efun::member(arraystring,item));
+  }
+  else if (stringp(arraystring)) {
+    return(efun::member(arraystring,to_int(item)));
+  }
+  else {
+    set_this_object(previous_object());
+    raise_error(sprintf("Bad argument 1 to member_array(): %O",arraystring));
+  }
+}
+#endif // !__EFUN_DEFINED__(member_array)
+
+// The digit at the i'th position is the number of bits set in 'i'.
+string count_table =
+    "0112122312232334122323342334344512232334233434452334344534454556";
+int broken_count_bits( string s ) {
+    int i, res;
+    if( !stringp(s) || !(i=sizeof(s)) ) return 0;
+    for( ; i-->0; ) {
+        // We are counting 6 bits at a time using a precompiled table.
+        res += count_table[(s[i]-' ')&63]-'0';
+    }
+    return res;
+}
+
+#if !__EFUN_DEFINED__(count_bits)
+int count_bits( string s ) {
+    return(broken_count_bits(s));
+}
+#endif
+
+
+// * Teile aus einem Array entfernen *** OBSOLETE
+deprecated mixed *exclude_array(mixed *arr,int from,int to)
+{
+  if (to<from)
+    to = from;
+  return arr[0..from-1]+arr[to+1..];
+}
+
diff --git a/secure/simul_efun/spare/spare/README b/secure/simul_efun/spare/spare/README
new file mode 100644
index 0000000..303429a
--- /dev/null
+++ b/secure/simul_efun/spare/spare/README
@@ -0,0 +1,20 @@
+simul_efun 
+---------- 
+Das simul_efun Objekt /secure/simul_efun/simul_efun benutzt die Dateien
+in /secure/simul_efun.
+Im Verzeichnis /secure/simul_efun/spare ist eine Sicherheitskopie von allen 
+Dateien in /secure/simul_efun.
+
+Falls /secure/simul_efun/simul_efun.c nicht ladbar ist, wird 
+/secure/simul_efun/spare/simul_efun.c als Ersatz versucht. Wenn auch das
+nicht ladbar ist, erfolgt ein Shutdown des Muds.
+
+Bei Aenderungen sollte _zuerst_ die normale simul efun editiert und
+anschliessend zerstoert/entladen werden, woraufhin sie implizit durch den
+Driver und Master neugeladen wird. Ist dies erfolgreich und die neue
+simul_efun laeuft (erst dann!) kopiert man die Dateien aus 
+/secure/simul_efun nach /secure/simul_efun/spare.
+
+Die Dateien hier sind mit Ausnahme der simul_efun.c selbst _nicht_ dazu 
+gedacht, geladen zu werden.
+
diff --git a/secure/simul_efun/spare/spare/comm.c b/secure/simul_efun/spare/spare/comm.c
new file mode 100644
index 0000000..ffb598c
--- /dev/null
+++ b/secure/simul_efun/spare/spare/comm.c
@@ -0,0 +1,125 @@
+#include <living/comm.h>
+
+// Sendet an alle Objekte in room und room selber durch Aufruf von
+// ReceiveMsg().
+varargs void send_room(object|string room, string msg, int msg_type,
+                       string msg_action, string msg_prefix, object *exclude,
+                       object origin)
+{
+  if (stringp(room))
+    room=load_object(room);
+  
+  origin ||= previous_object();
+  object *dest = exclude ? all_inventory(room) - exclude :
+                           all_inventory(room);
+
+  dest->ReceiveMsg(msg, msg_type, msg_action, msg_prefix, origin);
+  room->ReceiveMsg(msg, msg_type, msg_action, msg_prefix, origin);
+}
+
+static int _shout_filter( object ob, string pat )
+{
+    string *ignore;
+
+    if ( !environment(ob) )
+       return 0;
+
+    return sizeof( regexp( ({ object_name( environment(ob) ) }), pat ) );
+}
+
+varargs void shout( string s, mixed where ){
+    object *u;
+    string *pfade;
+
+    if ( !sizeof( u = users() - ({ this_player() }) ) )
+       return;
+
+    if ( !where )
+       pfade = ({ "/" });
+    else if ( intp(where) )
+       pfade =
+           ({ implode( efun::explode( object_name( environment(this_player()) ),
+                                   "/" )[0..2], "/" ) + "/" });
+    else if ( stringp(where) )
+       pfade = ({ where });
+    else
+       pfade = where;
+
+    u = filter( u, "_shout_filter", ME, implode( pfade, "|" ) );
+    u->ReceiveMsg(s, MT_COMM|MT_FAR|MSG_DONT_WRAP|MSG_DONT_STORE,
+                  MA_SHOUT_SEFUN, 0, previous_object());
+}
+
+
+#if __VERSION__ > "3.3.718"
+// This sefun replaces the deprecated efun cat().
+#define CAT_MAX_LINES 50
+varargs int cat(string file, int start, int num)
+{
+    if (extern_call())
+        set_this_object(previous_object());
+
+    int more;
+
+    if (num < 0 || !this_player())
+        return 0;
+
+    if (!start)
+        start = 1;
+
+    if (!num || num > CAT_MAX_LINES) {
+        num = CAT_MAX_LINES;
+        more = sizeof(read_file(file, start+num, 1));
+    }
+
+    string txt = read_file(file, start, num);
+    if (!txt)
+        return 0;
+
+    efun::tell_object(this_player(), txt);
+
+    if (more)
+        efun::tell_object(this_player(), "*****TRUNCATED****\n");
+
+    return sizeof(txt & "\n");
+}
+#undef CAT_MAX_LINES
+#endif // __VERSION__ > "3.3.718"
+
+#if __VERSION__ > "3.3.719"
+// This sefun replaces the deprecated efun tail().
+#define TAIL_MAX_BYTES 1000
+varargs int tail(string file)
+{
+    if (extern_call())
+        set_this_object(previous_object());
+
+    if (!stringp(file) || !this_player())
+        return 0;
+
+    string txt = read_bytes(file, -(TAIL_MAX_BYTES + 80), (TAIL_MAX_BYTES + 80));
+    // read_bytes() returns 0 if the start of the section given by
+    // parameter #2 lies beyond the beginning of the file.
+    // In this case we simply try and read the entire file.
+    if (!stringp(txt) && file_size(file) < TAIL_MAX_BYTES+80)
+      txt = read_file(file);
+    // Exit if still nothing could be read.
+    if (!stringp(txt))
+      return 0;
+
+    // cut off first (incomplete) line
+    int index = strstr(txt, "\n");
+    if (index > -1) {
+        if (index + 1 < sizeof(txt))
+            txt = txt[index+1..];
+        else
+            txt = "";
+    }
+
+    efun::tell_object(this_player(), txt);
+
+    return 1;
+}
+#undef TAIL_MAX_BYTES
+#endif // __VERSION__ > "3.3.719"
+
diff --git a/secure/simul_efun/spare/spare/debug_info.c b/secure/simul_efun/spare/spare/debug_info.c
new file mode 100644
index 0000000..d607c2f
--- /dev/null
+++ b/secure/simul_efun/spare/spare/debug_info.c
@@ -0,0 +1,434 @@
+/* This sefun is to provide a replacement for the efun debug_info().
+ * Feel free to add it to your mudlibs, if you have much code relying on that.
+ */
+
+#if ! __EFUN_DEFINED__(debug_info)
+
+#include <driver_info.h>
+#include <debug_info.h>
+
+mixed debug_info(int what, varargs mixed* args)
+{
+    if (sizeof(args) > 2)
+        raise_error("Too many arguments to debug_info\n");
+
+    switch(what)
+    {
+        default:
+            raise_error(sprintf("Illegal value %d for debug_info().\n", what));
+
+        case DINFO_OBJECT:
+        {
+            object ob;
+
+            if (sizeof(args) != 1)
+                raise_error("bad number of arguments to debug_info\n");
+            if (!objectp(args[0]))
+                raise_error("bag arg 2 to debug_info().\n");
+
+            ob = args[0];
+            printf("O_HEART_BEAT      : %s\n", efun::object_info(ob, OC_HEART_BEAT)       ? "TRUE" : "FALSE");
+            printf("O_ENABLE_COMMANDS : %s\n", efun::object_info(ob, OC_COMMANDS_ENABLED) ? "TRUE" : "FALSE");
+            printf("O_CLONE           : %s\n", efun::clonep(ob)                           ? "TRUE" : "FALSE");
+            printf("O_DESTRUCTED      : FALSE\n");
+            printf("O_SWAPPED         : %s\n", efun::object_info(ob, OI_SWAPPED)          ? "TRUE" : "FALSE");
+            printf("O_ONCE_INTERACTIVE: %s\n", efun::object_info(ob, OI_ONCE_INTERACTIVE) ? "TRUE" : "FALSE");
+            printf("O_RESET_STATE     : %s\n", efun::object_info(ob, OI_RESET_STATE)      ? "TRUE" : "FALSE");
+            printf("O_WILL_CLEAN_UP   : %s\n", efun::object_info(ob, OI_WILL_CLEAN_UP)    ? "TRUE" : "FALSE");
+            printf("O_REPLACED        : %s\n", efun::object_info(ob, OI_REPLACED)         ? "TRUE" : "FALSE");
+            printf("time_reset  : %d\n",       efun::object_info(ob, OI_NEXT_RESET_TIME));
+            printf("time_of_ref : %d\n",       efun::object_info(ob, OI_LAST_REF_TIME));
+            printf("ref         : %d\n",       efun::object_info(ob, OI_OBJECT_REFS));
+
+            int gticks = efun::object_info(ob, OI_GIGATICKS);
+            if(gticks)
+                printf("evalcost   :  %d%09d\n", gticks, efun::object_info(ob, OI_TICKS));
+            else
+                printf("evalcost   :  %d\n",   efun::object_info(ob, OI_TICKS));
+
+            printf("swap_num    : %d\n",       efun::object_info(ob, OI_SWAP_NUM));
+            printf("name        : '%s'\n",     efun::object_name(ob));
+            printf("load_name   : '%s'\n",     efun::load_name(ob));
+
+            object next_ob = efun::object_info(ob, OI_OBJECT_NEXT);
+            if (next_ob)
+                printf("next_all    : OBJ(%s)\n", efun::object_name(next_ob));
+
+            object prev_ob = efun::object_info(ob, OI_OBJECT_PREV);
+            if (prev_ob)
+                printf("Previous object in object list: OBJ(%s)\n", efun::object_name(prev_ob));
+            else
+                printf("This object is the head of the object list.\n");
+            break;
+        }
+
+        case DINFO_MEMORY:
+        {
+            object ob;
+
+            if (sizeof(args) != 1)
+                raise_error("bad number of arguments to debug_info\n");
+            if (!objectp(args[0]))
+                raise_error("bag arg 2 to debug_info().\n");
+
+            ob = args[0];
+
+            printf("program ref's %3d\n",   efun::object_info(ob, OI_PROG_REFS));
+            printf("Name: '%s'\n",          efun::program_name(ob));
+            printf("program size    %6d\n", efun::object_info(ob, OI_PROG_SIZE));
+            printf("num func's:  %3d (%4d)\n",
+                                            efun::object_info(ob, OI_NUM_FUNCTIONS),
+                                            efun::object_info(ob, OI_SIZE_FUNCTIONS));
+            printf("num vars:    %3d (%4d)\n",
+                                            efun::object_info(ob, OI_NUM_VARIABLES),
+                                            efun::object_info(ob, OI_SIZE_VARIABLES));
+
+            printf("num strings: %3d (%4d) : overhead %d + data %d (%d)\n",
+                                            efun::object_info(ob, OI_NUM_STRINGS),
+                                            efun::object_info(ob, OI_SIZE_STRINGS) + efun::object_info(ob, OI_SIZE_STRINGS_DATA),
+                                            efun::object_info(ob, OI_SIZE_STRINGS),
+                                            efun::object_info(ob, OI_SIZE_STRINGS_DATA),
+                                            efun::object_info(ob, OI_SIZE_STRINGS_DATA_TOTAL));
+
+            printf("num inherits %3d (%4d)\n",
+                                            efun::object_info(ob, OI_NUM_INHERITED),
+                                            efun::object_info(ob, OI_SIZE_INHERITED));
+
+            printf("total size      %6d\n", efun::object_info(ob, OI_PROG_SIZE_TOTAL));
+
+            printf("data size       %6d (%6d\n",
+                                            efun::object_info(ob, OI_DATA_SIZE),
+                                            efun::object_info(ob, OI_DATA_SIZE_TOTAL));
+            break;
+        }
+
+        case DINFO_OBJLIST:
+        {
+            if (sizeof(args) == 0)
+                raise_error("bad number of arguments to debug_info\n");
+
+            if (sizeof(args) == 1)
+            {
+                object * obs = efun::objects(args[0], 1);
+                return obs[0];
+            }
+            else
+                return efun::objects(args[0], args[1]);
+            break;
+        }
+
+        case DINFO_MALLOC:
+            write(debug_info(DINFO_STATUS, "malloc"));
+            break;
+
+        case DINFO_STATUS:
+        {
+            string which;
+            int opt;
+
+            if (sizeof(args) > 1)
+                raise_error("bad number of arguments to debug_info\n");
+
+            if (!sizeof(args) || !args[0])
+                which = "";
+            else if(stringp(args[0]))
+                which = args[0];
+            else
+                raise_error("bag arg 2 to debug_info().\n");
+
+            switch(which)
+            {
+                case "":
+                    opt = DI_STATUS_TEXT_MEMORY;
+                    break;
+
+                case "tables":
+                    opt = DI_STATUS_TEXT_TABLES;
+                    break;
+
+                case "swap":
+                    opt = DI_STATUS_TEXT_SWAP;
+                    break;
+
+                case "malloc":
+                    opt = DI_STATUS_TEXT_MALLOC;
+                    break;
+
+                case "malloc extstats":
+                    opt = DI_STATUS_TEXT_MALLOC_EXTENDED;
+                    break;
+
+                default:
+                    return 0;
+            }
+
+            return efun::driver_info(opt);
+        }
+
+        case DINFO_DUMP:
+        {
+            int opt;
+
+            if (!sizeof(args))
+                raise_error("bad number of arguments to debug_info\n");
+
+            if(!stringp(args[0]))
+                raise_error("bag arg 2 to debug_info().\n");
+
+            switch(args[0])
+            {
+                case "objects":
+                    opt = DDI_OBJECTS;
+                    break;
+
+                case "destructed":
+                    opt = DDI_OBJECTS_DESTRUCTED;
+                    break;
+
+                case "opcodes":
+                    opt = DDI_OPCODES;
+                    break;
+
+                case "memory":
+                    opt = DDI_MEMORY;
+                    break;
+
+                default:
+                    raise_error(sprintf("Bad argument '%s' to debug_info(DINFO_DUMP).\n", args[0]));
+                    return 0;
+            }
+
+            return efun::dump_driver_info(opt, args[1..1]...);
+        }
+
+        case DINFO_DATA:
+        {
+            mixed * result;
+
+            if (!sizeof(args))
+                raise_error("bad number of arguments to debug_info\n");
+
+            if (!intp(args[0]))
+                raise_error("bag arg 2 to debug_info().\n");
+
+            if (sizeof(args) == 2 && !intp(args[1]))
+                raise_error("bag arg 3 to debug_info().\n");
+
+            switch(args[0])
+            {
+                case DID_STATUS:
+                    result = allocate(DID_STATUS_MAX);
+
+                    result[DID_ST_ACTIONS]               = efun::driver_info(DI_NUM_ACTIONS);
+                    result[DID_ST_ACTIONS_SIZE]          = efun::driver_info(DI_SIZE_ACTIONS);
+                    result[DID_ST_SHADOWS]               = efun::driver_info(DI_NUM_SHADOWS);
+                    result[DID_ST_SHADOWS_SIZE]          = efun::driver_info(DI_SIZE_SHADOWS);
+
+                    result[DID_ST_OBJECTS]               = efun::driver_info(DI_NUM_OBJECTS);
+                    result[DID_ST_OBJECTS_SIZE]          = efun::driver_info(DI_SIZE_OBJECTS);
+                    result[DID_ST_OBJECTS_SWAPPED]       = efun::driver_info(DI_NUM_OBJECTS_SWAPPED);
+                    result[DID_ST_OBJECTS_SWAP_SIZE]     = efun::driver_info(DI_SIZE_OBJECTS_SWAPPED);
+                    result[DID_ST_OBJECTS_LIST]          = efun::driver_info(DI_NUM_OBJECTS_IN_LIST);
+                    result[DID_ST_OBJECTS_NEWLY_DEST]    = efun::driver_info(DI_NUM_OBJECTS_NEWLY_DESTRUCTED);
+                    result[DID_ST_OBJECTS_DESTRUCTED]    = efun::driver_info(DI_NUM_OBJECTS_DESTRUCTED);
+                    result[DID_ST_OBJECTS_PROCESSED]     = efun::driver_info(DI_NUM_OBJECTS_LAST_PROCESSED);
+                    result[DID_ST_OBJECTS_AVG_PROC]      = efun::driver_info(DI_LOAD_AVERAGE_PROCESSED_OBJECTS_RELATIVE);
+
+                    result[DID_ST_OTABLE]                = efun::driver_info(DI_NUM_OBJECTS_IN_TABLE);
+                    result[DID_ST_OTABLE_SLOTS]          = efun::driver_info(DI_NUM_OBJECT_TABLE_SLOTS);
+                    result[DID_ST_OTABLE_SIZE]           = efun::driver_info(DI_SIZE_OBJECT_TABLE);
+
+                    result[DID_ST_HBEAT_OBJS]            = efun::driver_info(DI_NUM_HEARTBEATS);
+                    result[DID_ST_HBEAT_CALLS]           = efun::driver_info(DI_NUM_HEARTBEAT_ACTIVE_CYCLES);
+                    result[DID_ST_HBEAT_CALLS_TOTAL]     = efun::driver_info(DI_NUM_HEARTBEAT_TOTAL_CYCLES);
+                    result[DID_ST_HBEAT_SLOTS]           = efun::driver_info(DI_NUM_HEARTBEATS);
+                    result[DID_ST_HBEAT_SIZE]            = efun::driver_info(DI_SIZE_HEARTBEATS);
+                    result[DID_ST_HBEAT_PROCESSED]       = efun::driver_info(DI_NUM_HEARTBEATS_LAST_PROCESSED);
+                    result[DID_ST_HBEAT_AVG_PROC]        = efun::driver_info(DI_LOAD_AVERAGE_PROCESSED_HEARTBEATS_RELATIVE);
+
+                    result[DID_ST_CALLOUTS]              = efun::driver_info(DI_NUM_CALLOUTS);
+                    result[DID_ST_CALLOUT_SIZE]          = efun::driver_info(DI_SIZE_CALLOUTS);
+
+                    result[DID_ST_ARRAYS]                = efun::driver_info(DI_NUM_ARRAYS);
+                    result[DID_ST_ARRAYS_SIZE]           = efun::driver_info(DI_SIZE_ARRAYS);
+
+                    result[DID_ST_MAPPINGS]              = efun::driver_info(DI_NUM_MAPPINGS);
+                    result[DID_ST_MAPPINGS_SIZE]         = efun::driver_info(DI_SIZE_MAPPINGS);
+                    result[DID_ST_HYBRID_MAPPINGS]       = efun::driver_info(DI_NUM_MAPPINGS_HYBRID);
+                    result[DID_ST_HASH_MAPPINGS]         = efun::driver_info(DI_NUM_MAPPINGS_HASH);
+
+                    result[DID_ST_STRUCTS]               = efun::driver_info(DI_NUM_STRUCTS);
+                    result[DID_ST_STRUCTS_SIZE]          = efun::driver_info(DI_SIZE_STRUCTS);
+                    result[DID_ST_STRUCT_TYPES]          = efun::driver_info(DI_NUM_STRUCT_TYPES);
+                    result[DID_ST_STRUCT_TYPES_SIZE]     = efun::driver_info(DI_SIZE_STRUCT_TYPES);
+
+                    result[DID_ST_PROGS]                 = efun::driver_info(DI_NUM_PROGS);
+                    result[DID_ST_PROGS_SIZE]            = efun::driver_info(DI_SIZE_PROGS);
+
+                    result[DID_ST_PROGS_SWAPPED]         = efun::driver_info(DI_NUM_PROGS_SWAPPED);
+                    result[DID_ST_PROGS_SWAP_SIZE]       = efun::driver_info(DI_SIZE_PROGS_SWAPPED);
+
+                    result[DID_ST_USER_RESERVE]          = efun::driver_info(DI_MEMORY_RESERVE_USER);
+                    result[DID_ST_MASTER_RESERVE]        = efun::driver_info(DI_MEMORY_RESERVE_MASTER);
+                    result[DID_ST_SYSTEM_RESERVE]        = efun::driver_info(DI_MEMORY_RESERVE_SYSTEM);
+
+                    result[DID_ST_ADD_MESSAGE]           = efun::driver_info(DI_NUM_MESSAGES_OUT);
+                    result[DID_ST_PACKETS]               = efun::driver_info(DI_NUM_PACKETS_OUT);
+                    result[DID_ST_PACKET_SIZE]           = efun::driver_info(DI_SIZE_PACKETS_OUT);
+                    result[DID_ST_PACKETS_IN]            = efun::driver_info(DI_NUM_PACKETS_IN);
+                    result[DID_ST_PACKET_SIZE_IN]        = efun::driver_info(DI_SIZE_PACKETS_IN);
+
+                    result[DID_ST_APPLY]                 = efun::driver_info(DI_NUM_FUNCTION_NAME_CALLS);
+                    result[DID_ST_APPLY_HITS]            = efun::driver_info(DI_NUM_FUNCTION_NAME_CALL_HITS);
+
+                    result[DID_ST_STRINGS]               = efun::driver_info(DI_NUM_VIRTUAL_STRINGS);
+                    result[DID_ST_STRING_SIZE]           = efun::driver_info(DI_SIZE_STRINGS);
+                    result[DID_ST_STR_TABLE_SIZE]        = efun::driver_info(DI_SIZE_STRING_TABLE);
+                    result[DID_ST_STR_OVERHEAD]          = efun::driver_info(DI_SIZE_STRING_OVERHEAD);
+                    result[DID_ST_UNTABLED]              = efun::driver_info(DI_NUM_STRINGS_UNTABLED);
+                    result[DID_ST_UNTABLED_SIZE]         = efun::driver_info(DI_SIZE_STRINGS_UNTABLED);
+                    result[DID_ST_TABLED]                = efun::driver_info(DI_NUM_STRINGS_TABLED);
+                    result[DID_ST_TABLED_SIZE]           = efun::driver_info(DI_SIZE_STRINGS_TABLED);
+                    result[DID_ST_STR_SEARCHES]          = efun::driver_info(DI_NUM_STRING_TABLE_LOOKUPS_BY_INDEX);
+                    result[DID_ST_STR_SEARCHLEN]         = efun::driver_info(DI_NUM_STRING_TABLE_LOOKUP_STEPS_BY_INDEX);
+                    result[DID_ST_STR_SEARCHES_BYVALUE]  = efun::driver_info(DI_NUM_STRING_TABLE_LOOKUPS_BY_VALUE);
+                    result[DID_ST_STR_SEARCHLEN_BYVALUE] = efun::driver_info(DI_NUM_STRING_TABLE_LOOKUP_STEPS_BY_VALUE);
+                    result[DID_ST_STR_CHAINS]            = efun::driver_info(DI_NUM_STRING_TABLE_SLOTS_USED);
+                    result[DID_ST_STR_ADDED]             = efun::driver_info(DI_NUM_STRING_TABLE_STRINGS_ADDED);
+                    result[DID_ST_STR_DELETED]           = efun::driver_info(DI_NUM_STRING_TABLE_STRINGS_REMOVED);
+                    result[DID_ST_STR_COLLISIONS]        = efun::driver_info(DI_NUM_STRING_TABLE_COLLISIONS);
+                    result[DID_ST_STR_FOUND]             = efun::driver_info(DI_NUM_STRING_TABLE_HITS_BY_INDEX);
+                    result[DID_ST_STR_FOUND_BYVALUE]     = efun::driver_info(DI_NUM_STRING_TABLE_HITS_BY_VALUE);
+
+                    result[DID_ST_RX_CACHED]             = efun::driver_info(DI_NUM_REGEX);
+                    result[DID_ST_RX_TABLE]              = efun::driver_info(DI_NUM_REGEX_TABLE_SLOTS);
+                    result[DID_ST_RX_TABLE_SIZE]         = efun::driver_info(DI_SIZE_REGEX);
+                    result[DID_ST_RX_REQUESTS]           = efun::driver_info(DI_NUM_REGEX_LOOKUPS);
+                    result[DID_ST_RX_REQ_FOUND]          = efun::driver_info(DI_NUM_REGEX_LOOKUP_HITS);
+                    result[DID_ST_RX_REQ_COLL]           = efun::driver_info(DI_NUM_REGEX_LOOKUP_COLLISIONS);
+
+                    result[DID_ST_MB_FILE]               = efun::driver_info(DI_SIZE_BUFFER_FILE);
+                    result[DID_ST_MB_SWAP]               = efun::driver_info(DI_SIZE_BUFFER_SWAP);
+
+                    result[DID_ST_BOOT_TIME]             = efun::driver_info(DI_BOOT_TIME);
+                    break;
+
+                case DID_SWAP:
+                    result = allocate(DID_SWAP_MAX);
+
+                    result[DID_SW_PROGS]                 = efun::driver_info(DI_NUM_PROGS_SWAPPED);
+                    result[DID_SW_PROG_SIZE]             = efun::driver_info(DI_SIZE_PROGS_SWAPPED);
+                    result[DID_SW_PROG_UNSWAPPED]        = efun::driver_info(DI_NUM_PROGS_UNSWAPPED);
+                    result[DID_SW_PROG_U_SIZE]           = efun::driver_info(DI_SIZE_PROGS_UNSWAPPED);
+                    result[DID_SW_VARS]                  = efun::driver_info(DI_NUM_OBJECTS_SWAPPED);
+                    result[DID_SW_VAR_SIZE]              = efun::driver_info(DI_SIZE_OBJECTS_SWAPPED);
+                    result[DID_SW_FREE]                  = efun::driver_info(DI_NUM_SWAP_BLOCKS_FREE);
+                    result[DID_SW_FREE_SIZE]             = efun::driver_info(DI_SIZE_SWAP_BLOCKS_FREE);
+                    result[DID_SW_FILE_SIZE]             = efun::driver_info(DI_SIZE_SWAP_BLOCKS);
+                    result[DID_SW_REUSED]                = efun::driver_info(DI_SIZE_SWAP_BLOCKS_REUSED);
+                    result[DID_SW_SEARCHES]              = efun::driver_info(DI_NUM_SWAP_BLOCKS_REUSE_LOOKUPS);
+                    result[DID_SW_SEARCH_LEN]            = efun::driver_info(DI_NUM_SWAP_BLOCKS_REUSE_LOOKUP_STEPS);
+                    result[DID_SW_F_SEARCHES]            = efun::driver_info(DI_NUM_SWAP_BLOCKS_FREE_LOOKUPS);
+                    result[DID_SW_F_SEARCH_LEN]          = efun::driver_info(DI_NUM_SWAP_BLOCKS_FREE_LOOKUP_STEPS);
+                    result[DID_SW_COMPACT]               = efun::driver_info(DC_SWAP_COMPACT_MODE);
+                    result[DID_SW_RECYCLE_FREE]          = efun::driver_info(DI_SWAP_RECYCLE_PHASE);
+                    break;
+
+                case DID_MEMORY:
+                    result = allocate(DID_MEMORY_MAX);
+
+                    result[DID_MEM_NAME]                 = efun::driver_info(DI_MEMORY_ALLOCATOR_NAME);
+                    result[DID_MEM_SBRK]                 = efun::driver_info(DI_NUM_SYS_ALLOCATED_BLOCKS);
+                    result[DID_MEM_SBRK_SIZE]            = efun::driver_info(DI_SIZE_SYS_ALLOCATED_BLOCKS);
+                    result[DID_MEM_LARGE]                = efun::driver_info(DI_NUM_LARGE_BLOCKS_ALLOCATED);
+                    result[DID_MEM_LARGE_SIZE]           = efun::driver_info(DI_SIZE_LARGE_BLOCKS_ALLOCATED);
+                    result[DID_MEM_LFREE]                = efun::driver_info(DI_NUM_LARGE_BLOCKS_FREE);
+                    result[DID_MEM_LFREE_SIZE]           = efun::driver_info(DI_SIZE_LARGE_BLOCKS_FREE);
+                    result[DID_MEM_LWASTED]              = efun::driver_info(DI_NUM_LARGE_BLOCKS_WASTE);
+                    result[DID_MEM_LWASTED_SIZE]         = efun::driver_info(DI_SIZE_LARGE_BLOCKS_WASTE);
+                    result[DID_MEM_CHUNK]                = efun::driver_info(DI_NUM_SMALL_BLOCK_CHUNKS);
+                    result[DID_MEM_CHUNK_SIZE]           = efun::driver_info(DI_SIZE_SMALL_BLOCK_CHUNKS);
+                    result[DID_MEM_SMALL]                = efun::driver_info(DI_NUM_SMALL_BLOCKS_ALLOCATED);
+                    result[DID_MEM_SMALL_SIZE]           = efun::driver_info(DI_SIZE_SMALL_BLOCKS_ALLOCATED);
+                    result[DID_MEM_SFREE]                = efun::driver_info(DI_NUM_SMALL_BLOCKS_FREE);
+                    result[DID_MEM_SFREE_SIZE]           = efun::driver_info(DI_SIZE_SMALL_BLOCKS_FREE);
+                    result[DID_MEM_SWASTED]              = efun::driver_info(DI_NUM_SMALL_BLOCKS_WASTE);
+                    result[DID_MEM_SWASTED_SIZE]         = efun::driver_info(DI_SIZE_SMALL_BLOCKS_WASTE);
+                    result[DID_MEM_MINC_CALLS]           = efun::driver_info(DI_NUM_INCREMENT_SIZE_CALLS);
+                    result[DID_MEM_MINC_SUCCESS]         = efun::driver_info(DI_NUM_INCREMENT_SIZE_CALL_SUCCESSES);
+                    result[DID_MEM_MINC_SIZE]            = efun::driver_info(DI_SIZE_INCREMENT_SIZE_CALL_DIFFS);
+                    result[DID_MEM_PERM]                 = efun::driver_info(DI_NUM_UNMANAGED_BLOCKS);
+                    result[DID_MEM_PERM_SIZE]            = efun::driver_info(DI_SIZE_UNMANAGED_BLOCKS);
+                    result[DID_MEM_CLIB]                 = efun::driver_info(DI_NUM_REPLACEMENT_MALLOC_CALLS);
+                    result[DID_MEM_CLIB_SIZE]            = efun::driver_info(DI_SIZE_REPLACEMENT_MALLOC_CALLS);
+                    result[DID_MEM_OVERHEAD]             = efun::driver_info(DI_SIZE_SMALL_BLOCK_OVERHEAD);
+                    result[DID_MEM_ALLOCATED]            = efun::driver_info(DI_SIZE_MEMORY_USED) + efun::driver_info(DI_SIZE_MEMORY_OVERHEAD);
+                    result[DID_MEM_USED]                 = efun::driver_info(DI_SIZE_MEMORY_USED);
+                    result[DID_MEM_TOTAL_UNUSED]         = efun::driver_info(DI_SIZE_MEMORY_UNUSED);
+                    result[DID_MEM_DEFRAG_CALLS]         = efun::driver_info(DI_NUM_MEMORY_DEFRAGMENTATION_CALLS_FULL) + efun::driver_info(DI_NUM_MEMORY_DEFRAGMENTATION_CALLS_TARGETED);
+                    result[DID_MEM_DEFRAG_CALLS_REQ]     = efun::driver_info(DI_NUM_MEMORY_DEFRAGMENTATION_CALLS_TARGETED);
+                    result[DID_MEM_DEFRAG_REQ_SUCCESS]   = efun::driver_info(DI_NUM_MEMORY_DEFRAGMENTATION_CALL_TARGET_HITS);
+                    result[DID_MEM_DEFRAG_BLOCKS_INSPECTED] = efun::driver_info(DI_NUM_MEMORY_DEFRAGMENTATION_BLOCKS_INSPECTED);
+                    result[DID_MEM_DEFRAG_BLOCKS_MERGED] = efun::driver_info(DI_NUM_MEMORY_DEFRAGMENTATION_BLOCKS_MERGED);
+                    result[DID_MEM_DEFRAG_BLOCKS_RESULT] = efun::driver_info(DI_NUM_MEMORY_DEFRAGMENTATION_BLOCKS_RESULTING);
+                    result[DID_MEM_AVL_NODES]            = efun::driver_info(DI_NUM_FREE_BLOCKS_AVL_NODES);
+                    result[DID_MEM_EXT_STATISTICS]       = efun::driver_info(DI_MEMORY_EXTENDED_STATISTICS);
+                    break;
+            }
+
+            if (sizeof(args) == 2)
+            {
+                int idx = args[0];
+                if (idx < 0 || idx >= sizeof(result))
+                    raise_error(sprintf("Illegal index for debug_info(): %d, expected 0..%d\n",
+                        idx, sizeof(result)-1));
+
+                return result[idx];
+            }
+            else
+                return result;
+        }
+
+        case DINFO_TRACE:
+        {
+            int which = DIT_CURRENT;
+
+            if (sizeof(args) > 1)
+                raise_error("bad number of arguments to debug_info\n");
+            if (sizeof(args))
+            {
+                if (!intp(args[0]))
+                    raise_error("bag arg 2 to debug_info().\n");
+                which = args[0];
+            }
+
+            switch (which)
+            {
+                case DIT_CURRENT:
+                    return efun::driver_info(DI_TRACE_CURRENT);
+
+                case DIT_ERROR:
+                    return efun::driver_info(DI_TRACE_LAST_ERROR) || ({ "No trace." });
+
+                case DIT_UNCAUGHT_ERROR:
+                    return efun::driver_info(DI_TRACE_LAST_UNCAUGHT_ERROR) || ({ "No trace." });
+
+                case DIT_STR_CURRENT:
+                    return efun::driver_info(DI_TRACE_CURRENT_AS_STRING);
+
+                case DIT_CURRENT_DEPTH:
+                    return efun::driver_info(DI_TRACE_CURRENT_DEPTH);
+
+                default:
+                    raise_error("bad arg 2 to debug_info().\n");
+            }
+
+        }
+
+        case DINFO_EVAL_NUMBER:
+            return efun::driver_info(DI_EVAL_NUMBER);
+    }
+    return 0;
+}
+
+#endif
diff --git a/secure/simul_efun/spare/spare/enable_commands.c b/secure/simul_efun/spare/spare/enable_commands.c
new file mode 100644
index 0000000..9d1c963
--- /dev/null
+++ b/secure/simul_efun/spare/spare/enable_commands.c
@@ -0,0 +1,30 @@
+/* These sefuns are to provide a replacement for the efuns enable_commands()
+ * and disable_commands().
+ * Feel free to add it to your mudlibs, if you have much code relying on that.
+ */
+
+#include <configuration.h>
+
+#if ! __EFUN_DEFINED__(enable_commands)
+
+void enable_commands()
+{
+    object ob = efun::previous_object();
+
+    efun::configure_object(ob, OC_COMMANDS_ENABLED, 1);
+    efun::set_this_player(ob);
+}
+
+#endif
+
+#if ! __EFUN_DEFINED__(disable_commands)
+
+void disable_commands()
+{
+    object ob = efun::previous_object();
+
+    efun::configure_object(ob, OC_COMMANDS_ENABLED, 0);
+    efun::set_this_player(0);
+}
+
+#endif
diff --git a/secure/simul_efun/spare/spare/hash.c b/secure/simul_efun/spare/spare/hash.c
new file mode 100644
index 0000000..51bbf60
--- /dev/null
+++ b/secure/simul_efun/spare/spare/hash.c
@@ -0,0 +1,17 @@
+#include "/sys/tls.h"
+
+deprecated string md5(mixed arg, varargs mixed* iterations)
+{
+    if (extern_call())
+         set_this_object(previous_object());
+
+    return hash(TLS_HASH_MD5, arg, iterations...);
+}
+
+deprecated string sha1(mixed arg, varargs mixed* iterations)
+{
+    if (extern_call())
+         set_this_object(previous_object());
+
+    return hash(TLS_HASH_SHA1, arg, iterations...);
+}
diff --git a/secure/simul_efun/spare/spare/livings.c b/secure/simul_efun/spare/spare/livings.c
new file mode 100644
index 0000000..160fcee
--- /dev/null
+++ b/secure/simul_efun/spare/spare/livings.c
@@ -0,0 +1,348 @@
+// * living_name-Behandlung
+
+#define clean_log(s)
+//#define clean_log(s) log_file("CLEAN_SIM",ctime(time())[4..18]+": "+(s));
+
+private nosave mapping living_name_m, name_living_m, netdead;
+
+private void InitLivingData(mixed wizinfo) {
+  // living_name ist ein Pointer der auch in der extra_wizinfo gehalten 
+  // wird, so kann die simul_efun neu geladen werden, ohne dass dieses
+  // Mapping verloren geht
+  if (!mappingp(living_name_m = wizinfo[LIVING_NAME]))
+    living_name_m = wizinfo[LIVING_NAME] = m_allocate(0, 1);
+
+  // Gleiches gilt fuer das Mapping Name-Living
+  if (!mappingp(name_living_m = wizinfo[NAME_LIVING]))
+    name_living_m = wizinfo[NAME_LIVING] = m_allocate(0, 1);
+
+  // Netztote sind ebenfalls in der extra_wizinfo
+  if (!mappingp(netdead = wizinfo[NETDEAD_MAP]))
+    netdead = wizinfo[NETDEAD_MAP] = ([]);
+}
+
+public varargs string getuuid( object ob )
+{
+    mixed *ret;
+
+    if ( !objectp(ob) )
+       ob = previous_object();
+
+    if ( !query_once_interactive(ob) )
+       return getuid(ob);
+
+    ret = (mixed)master()->get_userinfo( getuid(ob) );
+
+    if ( !pointerp(ret) || sizeof(ret) < 5 )
+       return getuid(ob);
+
+    // Username + "_" + CreationDate
+    return ret[0] + "_" + ret[5];
+}
+
+void set_object_living_name(string livname, object obj)
+{
+  string old;
+  mixed a;
+  int i;
+
+  if (previous_object()==obj || previous_object()==this_object() ||
+      previous_object()==master())
+  {
+    if(!livname || !stringp(livname)) {
+      set_this_object(previous_object());
+      raise_error(sprintf("%O: illegal living name: %O\n", obj, livname));
+    }
+    if (old = living_name_m[obj]) {
+      if (pointerp(a = name_living_m[old])) {
+       a[member(a, obj)] = 0;
+      } else {
+       m_delete(name_living_m, old);
+      }
+    }
+    living_name_m[obj] = livname;
+    if (a = name_living_m[livname]) {
+      if (!pointerp(a)) {
+       name_living_m[livname] = ({a, obj});
+       return;
+      }
+      /* Try to reallocate entry from destructed object */
+      if ((i = member(a, 0)) >= 0) {
+       a[i] = obj;
+       return;
+      }
+      name_living_m[livname] = a + ({obj});
+      return;
+    }
+    name_living_m[livname] = obj;
+  }
+}
+
+void set_living_name(string livname)
+{
+  set_object_living_name(livname,previous_object());
+}
+
+void remove_living_name()
+{
+  string livname;
+
+  if (!previous_object())
+    return;
+  if (livname=living_name_m[previous_object()])
+  {
+    m_delete(living_name_m,previous_object());
+    if (objectp(name_living_m[livname]))
+    {
+      if (name_living_m[livname]==previous_object())
+       m_delete(name_living_m,livname);
+      return;
+    }
+    if (pointerp(name_living_m[livname]))
+    {
+      name_living_m[livname]-=({previous_object()});
+      if (!sizeof(name_living_m[livname]))
+       m_delete(name_living_m,livname);
+    }
+  }
+}
+
+void _set_netdead()
+{
+  if (query_once_interactive(previous_object()))
+    netdead[getuid(previous_object())]=previous_object();
+}
+
+void _remove_netdead()
+{
+  m_delete(netdead,getuid(previous_object()));
+}
+
+object find_netdead(string uuid)
+{
+  int i;
+  string uid;
+  // Wenn sscanf() 2 liefert, ist uuid auch ne uuid.
+  int is_uuid = sscanf(uuid, "%s_%d", uid, i) == 2;
+  if (!is_uuid)
+    uid = uuid;
+ 
+  if (is_uuid && getuuid(netdead[uid]) != uuid)
+      return 0;
+
+  return netdead[uid];
+}
+
+string *dump_netdead()
+{
+  return m_indices(netdead);
+}
+
+object find_living(string livname) {
+  mixed *a, r;
+  int i;
+
+  if (pointerp(r = name_living_m[livname])) {
+    if (member(r,0)>=0)
+      r-=({0});
+    if (!sizeof(r)){
+      m_delete(name_living_m,livname);
+      clean_log(sprintf("find_living loescht %s\n",livname));
+      return 0;
+    }
+    if ( !living(r = (a = r)[0])) {
+      for (i = sizeof(a); --i;) {
+       if (living(a[<i])) {
+         r = a[<i];
+         a[<i] = a[0];
+         return a[0] = r;
+       }
+      }
+    }
+    return r;
+  }
+  return living(r) && r;
+}
+
+object *find_livings(string livname)
+{
+  mixed r;
+
+  if (pointerp(r=name_living_m[livname]))
+    r-=({0});
+  else
+    if (objectp(r))
+      r=({r});
+  if (!pointerp(r)||!sizeof(r))
+    return 0;
+  return r;
+}
+
+object find_player(string uuid) {
+  object *objs;
+  mixed res;
+  int i;
+  string uid;
+
+  if (!stringp(uuid))
+    return 0;
+  // Wenn sscanf() 2 liefert, ist uuid auch ne uuid.
+  int is_uuid = sscanf(uuid, "%s_%d", uid, i) == 2;
+  if (!is_uuid)
+    uid = uuid;
+
+  if (pointerp(res = name_living_m[uid])) {
+    // zerstoerte Objekte ggf. entfernen
+    if (member(res,0)>=0) {
+      res-=({0});
+      name_living_m[uid] = res;
+    }
+    // ggf. Namen ohne Objekte entfernen.
+    if (!sizeof(res)){
+      m_delete(name_living_m,uid);
+      clean_log(sprintf("find_player loescht %s\n",uid));
+      return 0;
+    }
+    objs = res;
+    res = objs[0];
+    // Wenn das 0. Element der Spieler ist, sind wir fertig. Ansonsten suchen
+    // wir den Spieler und schieben ihn an die 0. Stelle im Array.
+    if ( !query_once_interactive(res)) {
+      for (i = sizeof(objs); --i;) {
+       if (objs[<i] && query_once_interactive(objs[<i])) {
+         res = objs[<i];
+         objs[<i] = objs[0];
+         objs[0] = res;
+         break;
+       }
+      }
+      res = (objectp(res) && query_once_interactive(res)) ? res : 0;
+    }
+    // else: in res steht der Spieler schon.
+  }
+  else if (objectp(res)) // r ist nen Einzelobjekt
+    res = query_once_interactive(res) ? res : 0;
+
+  // res ist jetzt ggf. der Spieler. Aber wenn der ne andere als die gesuchte
+  // UUID hat, ist das Ergebnis trotzdem 0.
+  if (is_uuid && getuuid(res)!=uuid)
+      return 0;
+
+  // endlich gefunden
+  return res;
+}
+
+private int check_match( string str, int players_only )
+{
+    mixed match;
+
+    if ( !(match = name_living_m[str]) )
+       return 0;
+
+    if ( !pointerp(match) )
+       match = ({ match });
+
+    match -= ({0});
+
+    if ( sizeof(match) ){
+       if ( players_only )
+           return sizeof(filter( match, #'query_once_interactive/*'*/ ))
+              > 0;
+       else
+           return 1;
+    }
+
+    m_delete( name_living_m, str );
+    clean_log( sprintf("check_match loescht %s\n", str) );
+    return 0;
+}
+
+//TODO:: string|string* exclude
+varargs mixed match_living( string str, int players_only, mixed exclude )
+{
+    int i, s;
+    mixed match, *user;
+
+    if ( !str || str == "" )
+       return 0;
+
+    if ( !pointerp(exclude) )
+       exclude = ({ exclude });
+
+    if ( member(exclude, str) < 0 && check_match(str, players_only) )
+       return str;
+
+    user = m_indices(name_living_m);
+    s = sizeof(str);
+    match = 0;
+
+    for ( i = sizeof(user); i--; )
+       if ( str == user[i][0..s-1] && member( exclude, user[i] ) < 0 )
+           if ( match )
+              return -1;
+           else
+              if ( check_match(user[i], players_only) )
+                  match = user[i];
+
+    if ( !match )
+       return -2;
+
+    return match;
+}
+
+mixed *dump_livings()
+{
+  return sort_array(m_indices(name_living_m),#'>);
+}
+
+// * regelmaessig Listen von Ballast befreien
+private void clean_name_living_m(string *keys, int left, int num)
+{
+  int i, j;
+  mixed a;
+
+  while (left && num--)
+  {
+    if (pointerp(a = name_living_m[keys[--left]]) && member(a, 0)>=0)
+    {
+      a-=({0});
+      name_living_m[keys[left]] = a;
+    }
+    if (!a || (pointerp(a) && !sizeof(a)))
+    {
+      clean_log("Toasting "+keys[left]+"\n");
+      m_delete(name_living_m, keys[left]);
+    } else clean_log(sprintf("KEEPING %s (%O)\n",keys[left],pointerp(a)?sizeof(a):a));
+  }
+  if (left)
+    efun::call_out(#'clean_name_living_m, 1, keys, left, 80);
+  else
+    clean_log("Clean process finished\n");
+}
+
+private void clean_netdead() {
+  int i;
+  string *s;
+  object ob;
+
+  s=m_indices(netdead);
+  for (i=sizeof(s)-1;i>=0;i--)
+    if (!objectp(ob=netdead[s[i]]) || interactive(ob))
+      m_delete(netdead,s[i]);
+}
+
+private void CleanLivingData() {
+  if (find_call_out(#'clean_name_living_m) < 0) {
+    clean_log("Starting clean process\n");
+    efun::call_out(#'clean_name_living_m,
+                 1,
+                 m_indices(name_living_m),
+                 sizeof(name_living_m),
+                 80
+                 );
+  }
+  efun::call_out(#'clean_netdead,2);
+}
+
+#undef clean_log
+
diff --git a/secure/simul_efun/spare/spare/object_info.c b/secure/simul_efun/spare/spare/object_info.c
new file mode 100644
index 0000000..ed7c009
--- /dev/null
+++ b/secure/simul_efun/spare/spare/object_info.c
@@ -0,0 +1,106 @@
+/* This sefun is to provide the old semantics of the efun object_info().
+ * Feel free to add it to your mudlibs, if you have much code relying on that.
+ */
+
+#if __EFUN_DEFINED__(driver_info) /* Newer version is there */
+
+#include <objectinfo.h>
+#include <object_info.h>
+
+mixed object_info(object ob, int what, varargs int* index)
+{
+    mixed * result;
+
+    if (sizeof(index) > 1)
+        raise_error("Too many arguments to object_info\n");
+
+    switch(what)
+    {
+        default:
+            raise_error(sprintf("Illegal value %d for object_info().\n", what));
+
+        case OINFO_BASIC:
+        {
+            result = allocate(OIB_MAX);
+
+            result[OIB_HEART_BEAT]        = efun::object_info(ob, OC_HEART_BEAT);
+            result[OIB_IS_WIZARD]         = 0;   /* Not supported anymore. */
+            result[OIB_ENABLE_COMMANDS]   = efun::object_info(ob, OC_COMMANDS_ENABLED);
+            result[OIB_CLONE]             = efun::clonep(ob);
+            result[OIB_DESTRUCTED]        = 0;   /* Not possible anymore. */
+            result[OIB_SWAPPED]           = efun::object_info(ob, OI_SWAPPED);
+            result[OIB_ONCE_INTERACTIVE]  = efun::object_info(ob, OI_ONCE_INTERACTIVE);
+            result[OIB_RESET_STATE]       = efun::object_info(ob, OI_RESET_STATE);
+            result[OIB_WILL_CLEAN_UP]     = efun::object_info(ob, OI_WILL_CLEAN_UP);
+            result[OIB_LAMBDA_REFERENCED] = efun::object_info(ob, OI_LAMBDA_REFERENCED);
+            result[OIB_SHADOW]            = efun::object_info(ob, OI_SHADOW_PREV) && 1;
+            result[OIB_REPLACED]          = efun::object_info(ob, OI_REPLACED);
+            result[OIB_NEXT_RESET]        = efun::object_info(ob, OI_NEXT_RESET_TIME);
+            result[OIB_TIME_OF_REF]       = efun::object_info(ob, OI_LAST_REF_TIME);
+            result[OIB_REF]               = efun::object_info(ob, OI_OBJECT_REFS);
+            result[OIB_GIGATICKS]         = efun::object_info(ob, OI_GIGATICKS);
+            result[OIB_TICKS]             = efun::object_info(ob, OI_TICKS);
+            result[OIB_SWAP_NUM]          = efun::object_info(ob, OI_SWAP_NUM);
+            result[OIB_PROG_SWAPPED]      = efun::object_info(ob, OI_PROG_SWAPPED);
+            result[OIB_VAR_SWAPPED]       = efun::object_info(ob, OI_VAR_SWAPPED);
+            result[OIB_NAME]              = efun::object_name(ob);
+            result[OIB_LOAD_NAME]         = efun::load_name(ob);
+            result[OIB_NEXT_ALL]          = efun::object_info(ob, OI_OBJECT_NEXT);
+            result[OIB_PREV_ALL]          = efun::object_info(ob, OI_OBJECT_PREV);
+            result[OIB_NEXT_CLEANUP]      = efun::object_info(ob, OI_NEXT_CLEANUP_TIME);
+            break;
+        }
+
+        case OINFO_POSITION:
+        {
+            result = allocate(OIP_MAX);
+
+            result[OIP_NEXT] = efun::object_info(ob, OI_OBJECT_NEXT);
+            result[OIP_PREV] = efun::object_info(ob, OI_OBJECT_PREV);
+            result[OIP_POS]  = efun::object_info(ob, OI_OBJECT_POS);
+            break;
+        }
+
+        case OINFO_MEMORY:
+        {
+            result = allocate(OIM_MAX);
+
+            result[OIM_REF]                = efun::object_info(ob, OI_PROG_REFS);
+            result[OIM_NAME]               = efun::program_name(ob);
+            result[OIM_PROG_SIZE]          = efun::object_info(ob, OI_PROG_SIZE);
+            result[OIM_NUM_FUNCTIONS]      = efun::object_info(ob, OI_NUM_FUNCTIONS);
+            result[OIM_SIZE_FUNCTIONS]     = efun::object_info(ob, OI_SIZE_FUNCTIONS);
+            result[OIM_NUM_VARIABLES]      = efun::object_info(ob, OI_NUM_VARIABLES);
+            result[OIM_SIZE_VARIABLES]     = efun::object_info(ob, OI_SIZE_VARIABLES);
+            result[OIM_NUM_STRINGS]        = efun::object_info(ob, OI_NUM_STRINGS);
+            result[OIM_SIZE_STRINGS]       = efun::object_info(ob, OI_SIZE_STRINGS);
+            result[OIM_SIZE_STRINGS_DATA]  = efun::object_info(ob, OI_SIZE_STRINGS_DATA);
+            result[OIM_SIZE_STRINGS_TOTAL] = efun::object_info(ob, OI_SIZE_STRINGS_DATA_TOTAL);
+            result[OIM_NUM_INHERITED]      = efun::object_info(ob, OI_NUM_INHERITED);
+            result[OIM_SIZE_INHERITED]     = efun::object_info(ob, OI_SIZE_INHERITED);
+            result[OIM_TOTAL_SIZE]         = efun::object_info(ob, OI_PROG_SIZE_TOTAL);
+            result[OIM_DATA_SIZE]          = efun::object_info(ob, OI_DATA_SIZE);
+            result[OIM_TOTAL_DATA_SIZE]    = efun::object_info(ob, OI_DATA_SIZE_TOTAL);
+            result[OIM_NO_INHERIT]         = efun::object_info(ob, OI_NO_INHERIT);
+            result[OIM_NO_CLONE]           = efun::object_info(ob, OI_NO_CLONE);
+            result[OIM_NO_SHADOW]          = efun::object_info(ob, OI_NO_SHADOW);
+            result[OIM_NUM_INCLUDES]       = efun::object_info(ob, OI_NUM_INCLUDED);
+            result[OIM_SHARE_VARIABLES]    = efun::object_info(ob, OI_SHARE_VARIABLES);
+            break;
+        }
+    }
+
+    if (sizeof(index))
+    {
+        int idx = index[0];
+        if (idx < 0 || idx >= sizeof(result))
+            raise_error(sprintf("Illegal index for object_info(): %d, expected 0..%d\n",
+                idx, sizeof(result)-1));
+
+        return result[idx];
+    }
+    else
+        return result;
+}
+
+#endif
diff --git a/secure/simul_efun/spare/spare/query_editing.c b/secure/simul_efun/spare/spare/query_editing.c
new file mode 100644
index 0000000..5146dcd
--- /dev/null
+++ b/secure/simul_efun/spare/spare/query_editing.c
@@ -0,0 +1,17 @@
+/* This sefun is to provide a replacement for the efun query_editing().
+ * Feel free to add it to your mudlibs, if you have much code relying on that.
+ */
+
+#if ! __EFUN_DEFINED__(query_editing)
+
+#include <interactive_info.h>
+
+int|object query_editing(object ob)
+{
+    if(!efun::interactive(ob))
+        return 0;
+
+    return efun::interactive_info(ob, II_EDITING);
+}
+
+#endif
diff --git a/secure/simul_efun/spare/spare/query_idle.c b/secure/simul_efun/spare/spare/query_idle.c
new file mode 100644
index 0000000..93a32ae
--- /dev/null
+++ b/secure/simul_efun/spare/spare/query_idle.c
@@ -0,0 +1,14 @@
+/* This sefun is to provide a replacement for the efun query_idle().
+ * Feel free to add it to your mudlibs, if you have much code relying on that.
+ */
+
+#if ! __EFUN_DEFINED__(query_idle)
+
+#include <interactive_info.h>
+
+int query_idle(object ob)
+{
+    return efun::interactive_info(ob, II_IDLE);
+}
+
+#endif
diff --git a/secure/simul_efun/spare/spare/query_input_pending.c b/secure/simul_efun/spare/spare/query_input_pending.c
new file mode 100644
index 0000000..dd8c311
--- /dev/null
+++ b/secure/simul_efun/spare/spare/query_input_pending.c
@@ -0,0 +1,17 @@
+/* This sefun is to provide a replacement for the efun query_input_pending().
+ * Feel free to add it to your mudlibs, if you have much code relying on that.
+ */
+
+#if ! __EFUN_DEFINED__(query_input_pending)
+
+#include <interactive_info.h>
+
+object query_input_pending(object ob)
+{
+    if(!efun::interactive(ob))
+        return 0;
+
+    return efun::interactive_info(ob, II_INPUT_PENDING);
+}
+
+#endif
diff --git a/secure/simul_efun/spare/spare/query_ip_name.c b/secure/simul_efun/spare/spare/query_ip_name.c
new file mode 100644
index 0000000..1e74ea0
--- /dev/null
+++ b/secure/simul_efun/spare/spare/query_ip_name.c
@@ -0,0 +1,48 @@
+/* This sefun is to provide a replacement for the efuns query_ip_name() and
+ * query_ip_number().
+ * Feel free to add it to your mudlibs, if you have much code relying on that.
+ */
+
+#if ! __EFUN_DEFINED__(query_ip_name)
+
+#include <interactive_info.h>
+
+private varargs string _query_ip_name(object player)
+{
+    object ob = player;
+    ob ||= efun::this_player();
+
+    player = efun::interactive_info(ob, II_IP_ADDRESS);
+    return efun::interactive_info(ob, II_IP_NAME);
+}
+
+private varargs string _query_ip_number(object player)
+{
+    object ob = player;
+    ob ||= efun::this_player();
+
+    player = efun::interactive_info(ob, II_IP_ADDRESS);
+    return efun::interactive_info(ob, II_IP_NUMBER);
+}
+
+// * Herkunfts-Ermittlung
+string query_ip_number(object ob)
+{
+  ob= ob || this_player();
+  if (!objectp(ob) || !interactive(ob)) return 0;
+  if(ob->query_realip() && (string)ob->query_realip()!="")
+  {
+    return (string)ob->query_realip();
+  }
+  return _query_ip_number(ob);
+}
+
+string query_ip_name(mixed ob)
+{
+  if ( !ob || objectp(ob) )
+      ob=_query_ip_number(ob);
+  return (string)"/p/daemon/iplookup"->host(ob);
+}
+
+#endif
+
diff --git a/secure/simul_efun/spare/spare/query_limits.c b/secure/simul_efun/spare/spare/query_limits.c
new file mode 100644
index 0000000..ecbf390
--- /dev/null
+++ b/secure/simul_efun/spare/spare/query_limits.c
@@ -0,0 +1,14 @@
+/* This sefun is to provide a replacement for the efun query_limits().
+ * Feel free to add it to your mudlibs, if you have much code relying on that.
+ */
+
+#if ! __EFUN_DEFINED__(query_limits)
+
+#include <driver_info.h>
+
+varargs int* query_limits(int def)
+{
+    return efun::driver_info(def ? DC_DEFAULT_RUNTIME_LIMITS : DI_CURRENT_RUNTIME_LIMITS);
+}
+
+#endif
diff --git a/secure/simul_efun/spare/spare/query_load_average.c b/secure/simul_efun/spare/spare/query_load_average.c
new file mode 100644
index 0000000..3b36f0e
--- /dev/null
+++ b/secure/simul_efun/spare/spare/query_load_average.c
@@ -0,0 +1,16 @@
+/* This sefun is to provide a replacement for the efun query_load_average().
+ * Feel free to add it to your mudlibs, if you have much code relying on that.
+ */
+
+#if ! __EFUN_DEFINED__(query_load_average)
+
+#include <driver_info.h>
+
+string query_load_average()
+{
+    return efun::sprintf("%.2f cmds/s, %.2f comp lines/s",
+        efun::driver_info(DI_LOAD_AVERAGE_COMMANDS),
+        efun::driver_info(DI_LOAD_AVERAGE_LINES));
+}
+
+#endif
diff --git a/secure/simul_efun/spare/spare/query_mud_port.c b/secure/simul_efun/spare/spare/query_mud_port.c
new file mode 100644
index 0000000..bff009d
--- /dev/null
+++ b/secure/simul_efun/spare/spare/query_mud_port.c
@@ -0,0 +1,35 @@
+/* This sefun is to provide a replacement for the efun query_mud_port().
+ * Feel free to add it to your mudlibs, if you have much code relying on that.
+ */
+
+#if ! __EFUN_DEFINED__(query_mud_port)
+
+#include <interactive_info.h>
+#include <driver_info.h>
+
+int query_mud_port(varargs int*|object* args)
+{
+    if(!sizeof(args) && efun::this_player() && efun::interactive(this_player()))
+        return efun::interactive_info(this_player(), II_MUD_PORT);
+
+    if(sizeof(args) > 1)
+        raise_error("Too many arguments to query_mud_port\n");
+
+    if(sizeof(args) && efun::objectp(args[0]) && efun::interactive(args[0]))
+        return efun::interactive_info(args[0], II_MUD_PORT);
+
+    if(sizeof(args) && intp(args[0]))
+    {
+        int* ports = efun::driver_info(DI_MUD_PORTS);
+        if(args[0] < -1 || args[0] >= sizeof(ports))
+            raise_error(efun::sprintf("Bad arg 1 to query_mud_port(): value %d out of range.\n", args[0]));
+        else if(args[0] == -1)
+            return sizeof(ports);
+        else
+            return ports[args[0]];
+    }
+
+    return efun::driver_info(DI_MUD_PORTS)[0];
+}
+
+#endif
diff --git a/secure/simul_efun/spare/spare/query_once_interactive.c b/secure/simul_efun/spare/spare/query_once_interactive.c
new file mode 100644
index 0000000..bc7829f
--- /dev/null
+++ b/secure/simul_efun/spare/spare/query_once_interactive.c
@@ -0,0 +1,14 @@
+/* This sefun is to provide a replacement for the efun query_once_interactive().
+ * Feel free to add it to your mudlibs, if you have much code relying on that.
+ */
+
+#if ! __EFUN_DEFINED__(query_once_interactive)
+
+#include <object_info.h>
+
+int query_once_interactive(object ob)
+{
+    return efun::object_info(ob, OI_ONCE_INTERACTIVE);
+}
+
+#endif
diff --git a/secure/simul_efun/spare/spare/query_snoop.c b/secure/simul_efun/spare/spare/query_snoop.c
new file mode 100644
index 0000000..e6a69c0
--- /dev/null
+++ b/secure/simul_efun/spare/spare/query_snoop.c
@@ -0,0 +1,30 @@
+/* This sefun is to provide a replacement for the efun query_snoop().
+ * Feel free to add it to your mudlibs, if you have much code relying on that.
+ */
+
+
+#include <interactive_info.h>
+
+private object _query_snoop(object ob)
+{
+    if(!efun::interactive(ob))
+        return 0;
+
+    object prev = efun::previous_object();
+    efun::set_this_object(prev);
+#if ! __EFUN_DEFINED__(query_snoop)
+    return efun::interactive_info(ob, II_SNOOP_NEXT);
+#else
+    return efun::query_snoop(ob);
+#endif
+}
+
+nomask object query_snoop(object who) {
+  object snooper;
+
+  snooper=_query_snoop(who);
+  if (!snooper) return 0;
+  if (query_wiz_grp(snooper)>query_wiz_grp(getuid(previous_object())) &&
+      IS_ARCH(snooper)) return 0;
+  return snooper;
+}
diff --git a/secure/simul_efun/spare/spare/set_heart_beat.c b/secure/simul_efun/spare/spare/set_heart_beat.c
new file mode 100644
index 0000000..a3995eb
--- /dev/null
+++ b/secure/simul_efun/spare/spare/set_heart_beat.c
@@ -0,0 +1,24 @@
+/* This sefun is to provide a replacement for the efun set_heart_beat().
+ * Feel free to add it to your mudlibs, if you have much code relying on that.
+ */
+
+#if ! __EFUN_DEFINED__(set_heart_beat)
+
+#include <configuration.h>
+
+int set_heart_beat(int flag)
+{
+    object ob = efun::previous_object();
+    int hb = efun::object_info(ob, OC_HEART_BEAT);
+
+    if (!flag == !hb)
+        return 0;
+
+    efun::configure_object(ob, OC_HEART_BEAT, flag);
+
+    return 1;
+}
+
+#endif
+
+
diff --git a/secure/simul_efun/spare/spare/set_is_wizard.c b/secure/simul_efun/spare/spare/set_is_wizard.c
new file mode 100644
index 0000000..001ccdb
--- /dev/null
+++ b/secure/simul_efun/spare/spare/set_is_wizard.c
@@ -0,0 +1,137 @@
+/* These are the special commands from the driver that are activated with
+ * set_is_wizard(). These functions must be added to the player object.
+ * Also set_is_wizard() must be called in the corresponding player object,
+ * not as an (simul-)efun.
+ */
+
+#include <commands.h>
+#include <driver_info.h>
+
+/* is_wizard:
+ *  1: has actions,
+ *  0: never had actions,
+ * -1: had actions once.
+ */
+private nosave int is_wizard;
+
+private int driver_malloc(string str)
+{
+    if(is_wizard <= 0)
+        return 0;
+
+    if(!sizeof(str))
+    {
+        write(efun::driver_info(DI_STATUS_TEXT_MALLOC));
+        return 1;
+    }
+
+    if(str == "extstats")
+    {
+        write(efun::driver_info(DI_STATUS_TEXT_MALLOC_EXTENDED));
+        return 1;
+    }
+
+    return 0;
+}
+
+private int driver_dumpallobj(string str)
+{
+    if(is_wizard <= 0 || sizeof(str))
+        return 0;
+
+    write("Dumping to /OBJ_DUMP ... ");
+    efun::dump_driver_info(DDI_OBJECTS);
+    efun::dump_driver_info(DDI_OBJECTS_DESTRUCTED);
+    write("done\n");
+    return 1;
+}
+
+private int driver_opcdump(string str)
+{
+    if(is_wizard <= 0 || sizeof(str))
+        return 0;
+
+    efun::dump_driver_info(DDI_OPCODES);
+    return 1;
+}
+
+private int driver_status(string str)
+{
+    int opt;
+    if(is_wizard <= 0)
+        return 0;
+
+    switch(str || "")
+    {
+        case "":
+            opt = DI_STATUS_TEXT_MEMORY;
+            break;
+
+        case "tables":
+        case " tables":
+            opt = DI_STATUS_TEXT_TABLES;
+            break;
+
+        case "swap":
+        case " swap":
+            opt = DI_STATUS_TEXT_SWAP;
+            break;
+
+        case "malloc":
+        case " malloc":
+            opt = DI_STATUS_TEXT_MALLOC;
+            break;
+
+        case "malloc extstats":
+        case " malloc extstats":
+            opt = DI_STATUS_TEXT_MALLOC_EXTENDED;
+            break;
+
+        default:
+            return 0;
+    }
+
+    write(efun::driver_info(opt));
+    return 1;
+}
+
+int set_is_wizard(varargs <object|int>* args)
+{
+    int oldval = is_wizard;
+
+    if(!sizeof(args))
+        raise_error("Too few arguments to set_is_wizard\n");
+    if(sizeof(args) > 2)
+        raise_error("Too many arguments to set_is_wizard\n");
+    if(!objectp(args[0]))
+        raise_error("Bad arg 1 to set_is_wizard()\n");
+    if(args[0] != this_object())
+        raise_error("Only set_is_wizard for the current object supported\n");
+    if(this_player() != this_object())
+        raise_error("The current object must be this_player() for set_is_wizard()\n");
+    if(sizeof(args) == 2 && !intp(args[1]))
+        raise_error("Bad arg 2 to set_is_wizard()\n");
+
+    if(sizeof(args) == 2 && !args[1])
+    {
+        if(is_wizard > 0)
+            is_wizard = -1;
+    }
+    else if(sizeof(args) == 2 && args[1]<0)
+    {
+         // Just return the old value.
+    }
+    else
+    {
+        if(!is_wizard)
+        {
+            add_action(#'driver_malloc, "malloc");
+            add_action(#'driver_dumpallobj, "dumpallobj");
+            add_action(#'driver_opcdump, "opcdump");
+            add_action(#'driver_status, "status", AA_NOSPACE);
+        }
+        is_wizard = 1;
+    }
+
+    return oldval > 0;
+}
diff --git a/secure/simul_efun/spare/spare/set_prompt.c b/secure/simul_efun/spare/spare/set_prompt.c
new file mode 100644
index 0000000..0b59692
--- /dev/null
+++ b/secure/simul_efun/spare/spare/set_prompt.c
@@ -0,0 +1,21 @@
+/* This sefun is to provide a replacement for the efun set_prompt().
+ * Feel free to add it to your mudlibs, if you have much code relying on that.
+ */
+
+#if ! __EFUN_DEFINED__(set_prompt)
+
+#include <configuration.h>
+
+varargs string|closure set_prompt(string|closure|int prompt, object ob)
+{
+    ob ||= efun::this_player();
+
+    mixed oldprompt = efun::interactive_info(ob, IC_PROMPT);
+
+    if(!intp(prompt))
+        efun::configure_interactive(ob, IC_PROMPT, prompt);
+
+    return oldprompt;
+}
+
+#endif
diff --git a/secure/simul_efun/spare/spare/shadow.c b/secure/simul_efun/spare/spare/shadow.c
new file mode 100644
index 0000000..7608c73
--- /dev/null
+++ b/secure/simul_efun/spare/spare/shadow.c
@@ -0,0 +1,55 @@
+/* These sefuns are to provide a replacement for the efun query_shadowing()
+ * and the old semantics of the efun shadow().
+ * Feel free to add it to your mudlibs, if you have much code relying on that.
+ */
+
+#if ! __EFUN_DEFINED__(query_shadowing)
+
+#include <object_info.h>
+
+object query_shadowing(object ob)
+{
+    return efun::object_info(ob, OI_SHADOW_PREV);
+}
+
+private object _shadow(object ob, int flag)
+{
+    if(flag)
+    {
+        object shadower = efun::previous_object(1);
+        efun::set_this_object(shadower);
+
+        if (efun::shadow(ob))
+            return efun::object_info(shadower, OI_SHADOW_PREV);
+        else
+            return 0;
+    }
+    else
+        return efun::object_info(ob, OI_SHADOW_NEXT);
+}
+#else
+private object _shadow(object ob, int flag)
+{
+  set_this_object(previous_object(1));
+  return efun::shadow(ob, flag);
+}
+#endif
+
+
+public object shadow(object ob, int flag)
+{
+  object res = funcall(#'_shadow,ob, flag);
+  if (flag)
+    "/secure/shadowmaster"->RegisterShadow(previous_object());
+  return res;
+}
+
+private void _unshadow() {
+  set_this_object(previous_object(1));
+  efun::unshadow();
+}
+
+public void unshadow() {
+  funcall(#'_unshadow);
+  "/secure/shadowmaster"->UnregisterShadow(previous_object());
+}
diff --git a/secure/simul_efun/spare/spare/simul_efun.c b/secure/simul_efun/spare/spare/simul_efun.c
new file mode 100644
index 0000000..5b68789
--- /dev/null
+++ b/secure/simul_efun/spare/spare/simul_efun.c
@@ -0,0 +1,1995 @@
+// MorgenGrauen MUDlib
+//
+// simul_efun.c -- simul efun's
+//
+// $Id: simul_efun.c 7408 2010-02-06 00:27:25Z Zesstra $
+#pragma strict_types,save_types,rtt_checks
+#pragma no_clone,no_shadow,no_inherit
+#pragma pedantic,range_check,warn_deprecated
+#pragma warn_empty_casts,warn_missing_return,warn_function_inconsistent
+
+// Absolute Pfade erforderlich - zum Zeitpunkt, wo der Master geladen
+// wird, sind noch keine Include-Pfade da ...
+
+#define SNOOPLOGFILE "SNOOP"
+#define ASNOOPLOGFILE "ARCH/SNOOP"
+
+#include "/secure/config.h"
+#include "/secure/wizlevels.h"
+#include "/sys/snooping.h"
+#include "/sys/language.h"
+#include "/sys/thing/properties.h"
+#include "/sys/wizlist.h"
+#include "/sys/erq.h"
+#include "/sys/lpctypes.h"
+#include "/sys/daemon.h"
+#include "/sys/player/base.h"
+#include "/sys/thing/description.h"
+#include "/sys/container.h"
+#include "/sys/defines.h"
+#include "/sys/telnet.h"
+#include "/sys/objectinfo.h"
+#include "/sys/files.h"
+#include "/sys/strings.h"
+#include "/sys/time.h"
+#include "/sys/lpctypes.h"
+#include "/sys/notify_fail.h"
+#include "/sys/tls.h"
+#include "/sys/input_to.h"
+#include "/sys/object_info.h"
+
+/* function prototypes
+ */
+string dtime(int wann);
+varargs int log_file(string file, string txt, int size_to_break);
+int query_wiz_level(mixed player);
+nomask varargs int snoop(object me, object you);
+varargs string country(mixed ip, string num);
+int query_wiz_grp(mixed wiz);
+public varargs object deep_present(mixed what, object ob);
+nomask int secure_level();
+nomask string secure_euid();
+public nomask int process_call();
+nomask mixed __create_player_dummy(string name);
+varargs string replace_personal(string str, mixed *obs, int caps);
+
+
+//replacements for dropped efuns in LD
+#if !__EFUN_DEFINED__(extract)
+varargs string extract(string str, int from, int to);
+#endif
+#if !__EFUN_DEFINED__(slice_array)
+varargs mixed slice_array(mixed array, int from, int to);
+#endif
+#if !__EFUN_DEFINED__(member_array)
+int member_array(mixed item, mixed arraystring);
+#endif
+
+// Include the different sub 'modules' of the simul_efun
+#include __DIR__"debug_info.c"
+#include __DIR__"enable_commands.c"
+#include __DIR__"hash.c"
+#include __DIR__"object_info.c"
+#include __DIR__"query_editing.c"
+#include __DIR__"query_idle.c"
+#include __DIR__"query_input_pending.c"
+#include __DIR__"query_ip_name.c"
+#include __DIR__"query_limits.c"
+#include __DIR__"query_load_average.c"
+#include __DIR__"query_mud_port.c"
+#include __DIR__"query_once_interactive.c"
+#include __DIR__"query_snoop.c"
+#include __DIR__"set_heart_beat.c"
+#if __BOOT_TIME__ < 1456261859
+#include __DIR__"set_prompt.c"
+#endif
+#include __DIR__"shadow.c"
+#include __DIR__"livings.c"
+#include __DIR__"comm.c"
+
+#define TO        efun::this_object()
+#define TI        efun::this_interactive()
+#define TP        efun::this_player()
+#define PO        efun::previous_object(0)
+#define LEVEL(x) query_wiz_level(x)
+#define NAME(x)  capitalize(getuid(x))
+
+#define DEBUG(x) if (find_player("zesstra")) \
+  tell_object(find_player("zesstra"),x)
+
+mixed dtime_cache = ({-1,""});
+
+#ifdef IOSTATS
+struct iostat_s {
+  string oname;
+  int time;
+  int size;
+};
+mixed saveo_stat = ({ 0,allocate(200, 0) });
+mixed restoreo_stat = ({ 0,allocate(200,0) });
+//mixed writefile_stat = ({ 0,allocate(100,(<iostat_s>)) });
+//mixed readfile_stat = ({ 0,allocate(100,(<iostat_s>)) });
+//mixed log_stat = ({ 0,allocate(100,(<iostat_s>)) });
+
+mixed ___iostats(int type) {
+  switch(type) {
+    case 1:
+      return saveo_stat;
+    case 2:
+      return restoreo_stat;
+/*    case 3:
+      return writefile_stat;
+    case 4:
+      return readfile_stat;
+    case 5:
+      return log_stat;
+      */
+  }
+  return 0;
+}
+#endif
+
+// Nicht jeder Magier muss die simul_efun entsorgen koennen.
+string NotifyDestruct(object caller) {
+    if( (caller!=this_object() && !ARCH_SECURITY) || process_call() ) {
+      return "Du darfst das simul_efun Objekt nicht zerstoeren!\n";
+    }
+    return 0;
+}
+
+public nomask void remove_interactive( object ob )
+{
+    if ( objectp(ob) && previous_object()
+        && object_name(previous_object())[0..7] != "/secure/"
+        && ((previous_object() != ob
+             && (ob != this_player() || ob != this_interactive()))
+            || (previous_object() == ob
+               && (this_player() && this_player() != ob
+                   || this_interactive() && this_interactive() != ob)) ) )
+
+       log_file( "PLAYERDEST",
+                sprintf( "%s: %O ausgeloggt von PO %O, TI %O, TP %O\n",
+                        dtime(time()), ob, previous_object(),
+                        this_interactive(), this_player() ) );
+
+    efun::remove_interactive(ob);
+}
+
+
+void ___updmaster()
+{
+    object ob;
+
+    //sollte nicht jeder duerfen.
+    if (process_call() || !ARCH_SECURITY)
+      raise_error("Illegal use of ___updmaster()!");
+
+    write("Removing old master ... ");
+    foreach(string file: 
+        get_dir("/secure/master/*.c",GETDIR_NAMES|GETDIR_UNSORTED|GETDIR_PATH)) {
+      if (ob = find_object(file))
+       efun::destruct(ob);
+    }
+    efun::destruct(efun::find_object("/secure/master"));
+    write("done.\nLoading again ... ");
+    load_object("/secure/master");
+
+    write("done.\n");
+}
+
+varargs string country(mixed ip, string num) {
+  mixed ret;
+
+  if(ret = (string)"/p/daemon/iplookup"->country(num || ip)) {
+    return ret;
+  } else return "???";
+}
+
+
+// * Snoopen und was dazugehoert
+static object find_snooped(object who)
+{
+  object *u;
+  int i;
+
+  for (i=sizeof(u=users())-1;i>=0;i--)
+    if (who==efun::interactive_info(u[i], II_SNOOP_NEXT))
+      return u[i];
+  return 0;
+}
+
+private string Lcut(string str) {
+  return str[5..11]+str[18..];
+}
+
+nomask varargs int snoop( object me, object you )
+{
+    int ret;
+    object snooper0, snooper, snooper2, snooper3;
+
+    if( !objectp(me) || me == you || !PO )
+       return 0;
+
+    snooper0 = find_snooped(me);
+
+     if(you){
+        if ( PO != me && query_wiz_grp(me) >= query_wiz_grp(geteuid(PO)) )
+            return 0;
+
+        if ( query_wiz_grp(me) <= query_wiz_grp(you) &&
+             !(you->QueryAllowSnoop(me)) )
+            if ( !IS_DEPUTY(me) || IS_ARCH(you) )
+               return 0;
+
+        if ( (snooper = efun::interactive_info(you, II_SNOOP_NEXT)) &&
+             query_wiz_grp(snooper) >= query_wiz_grp(me) ){
+            if ( (int)snooper->QueryProp(P_SNOOPFLAGS) & SF_LOCKED )
+               return 0;
+
+            tell_object( snooper, sprintf( "%s snooped jetzt %s.\n",
+                                       me->name(WER), you->name(WER) ) );
+
+            snooper2 = me;
+
+            while ( snooper3 = interactive_info(snooper2, II_SNOOP_NEXT) ){
+               tell_object( snooper,
+                           sprintf( "%s wird seinerseits von %s gesnooped.\n"
+                                   ,snooper2->name(WER),
+                                   snooper3->name(WEM) ) );
+               snooper2 = snooper3;
+            }
+
+            efun::snoop( snooper, snooper2 );
+
+            if ( efun::interactive_info(snooper2, II_SNOOP_NEXT) != snooper )
+               tell_object( snooper, sprintf( "Du kannst %s nicht snoopen.\n",
+                                          snooper2->name(WEN) ) );
+            else{
+               tell_object( snooper, sprintf( "Du snoopst jetzt %s.\n",
+                                          snooper2->name(WEN) ) );
+               if ( !IS_DEPUTY(snooper) ){
+                   log_file( SNOOPLOGFILE, sprintf("%s: %O %O %O\n",
+                                               dtime(time()),
+                                               snooper,
+                                               snooper2,
+                                               environment(snooper2) ),
+                            100000 );
+                   if (snooper0)
+                      CHMASTER->send( "Snoop", snooper,
+                                    sprintf( "%s *OFF* %s (%O)",
+                                            capitalize(getuid(snooper)),
+                                            capitalize(getuid(snooper0)),
+                                            environment(snooper0) ) );
+
+                   CHMASTER->send( "Snoop", snooper,
+                                 sprintf("%s -> %s (%O)",
+                                        capitalize(getuid(snooper)),
+                                        capitalize(getuid(snooper2)),
+                                        environment(snooper2)));
+               }
+               else{
+                   log_file( ASNOOPLOGFILE, sprintf( "%s: %O %O %O\n",
+                                                 dtime(time()),
+                                                 snooper,
+                                                 snooper2,
+                                                 environment(snooper2) )
+                            ,100000 );
+               }
+            }
+        }
+        else
+            if (snooper)
+               if ( !me->QueryProp(P_SNOOPFLAGS) & SF_LOCKED ){
+                   printf( "%s wird bereits von %s gesnooped. Benutze das "
+                          "\"f\"-Flag, wenn du dennoch snoopen willst.\n",
+                          you->name(WER), snooper->name(WEM) );
+                   return 0;
+               }
+
+        ret = efun::snoop( me, you );
+
+        if ( !IS_DEPUTY(me) && efun::interactive_info(you, II_SNOOP_NEXT) == me){
+            log_file( SNOOPLOGFILE, sprintf( "%s: %O %O %O\n",
+                                         Lcut(dtime(time())),
+                                         me, you, environment(you) ),
+                     100000 );
+
+            if (snooper0)
+               CHMASTER->send( "Snoop", me,
+                             sprintf( "%s *OFF* %s (%O).",
+                                     capitalize(getuid(me)),
+                                     capitalize(getuid(snooper0)),
+                                     environment(snooper0) ) );
+
+            CHMASTER->send( "Snoop", me, sprintf( "%s -> %s (%O).",
+                                             capitalize(getuid(me)),
+                                             capitalize(getuid(you)),
+                                             environment(you) ) );
+        }
+        else{
+            if ( efun::interactive_info(you, II_SNOOP_NEXT) == me ){
+               log_file( ASNOOPLOGFILE, sprintf( "%s: %O %O %O\n",
+                                             Lcut(dtime(time())),
+                                             me, you, environment(you) ),
+                        100000 );
+            }
+        }
+
+        if ( ret && query_wiz_grp(me) <= query_wiz_grp(you) &&
+             !IS_DEPUTY(me) )
+            tell_object( you, "*** " + NAME(me) + " snoopt Dich!\n" );
+
+        return ret;
+     }
+     else {
+        if ( (me == PO ||
+              query_wiz_grp(geteuid(PO)) > query_wiz_grp(me) ||
+              (query_wiz_grp(geteuid(PO)) == query_wiz_grp(me) &&
+              efun::interactive_info(PO, II_SNOOP_NEXT) == me)) && snooper0 ){
+            if ( !IS_DEPUTY(me) ){
+               log_file( SNOOPLOGFILE, sprintf( "%s: %O %O %O *OFF*\n",
+                                            Lcut(dtime(time())), me,
+                                            snooper0,
+                                            environment(snooper0) ),
+                        100000 );
+
+                CHMASTER->send( "Snoop", me,
+                              sprintf( "%s *OFF* %s (%O).",
+                                      capitalize(getuid(me)),
+                                      capitalize(getuid(snooper0)),
+                                      environment(snooper0) ) );
+            }
+            else{
+               log_file( ASNOOPLOGFILE, sprintf( "%s: %O %O %O *OFF*\n",
+                                             Lcut(dtime(time())), me,
+                                             snooper0,
+                                             environment(snooper0) ),
+                        100000 );
+            }
+
+            return efun::snoop(me);
+        }
+     }
+     return 0;
+}
+
+
+
+// * Emulation des 'alten' explode durch das neue
+string *old_explode(string str, string del) {
+  int s, t;
+  string *strs;
+
+  if (!stringp(str)) {
+      set_this_object(previous_object());
+      raise_error(sprintf(
+         "Invalid argument 1 to old_explode()! Expected <string>, got: "
+         "%.30O\n",str));
+  }
+  if (!stringp(del)) {
+      set_this_object(previous_object());
+      raise_error(sprintf(
+         "Invalid argument 2 to old_explode()! Expected <string>, got: "
+         "%.30O\n",del));
+  }
+  if(del == "")
+    return ({str});
+  strs=efun::explode(str, del);
+  t=sizeof(strs)-1;
+  while(s<=t && strs[s++] == "");s--;
+  while(t>=0 && strs[t--] == "");t++;
+  if(s<=t)
+    return strs[s..t];
+  return ({});
+}
+
+int file_time(string path) {
+  mixed *v;
+
+  set_this_object(previous_object());
+  if (sizeof(v=get_dir(path,GETDIR_DATES))) return v[0];
+  return(0); //sonst
+}
+
+// * Bei 50k Groesse Log-File rotieren
+varargs int log_file(string file, string txt, int size_to_break) {
+  mixed *st;
+
+  file="/log/"+file;
+  file=implode((efun::explode(file,"/")-({".."})),"/");
+//  tell_object(find_player("jof"),sprintf("LOG FILE: %O -> %O\n",previous_object(),file));
+  if (!funcall(bind_lambda(#'efun::call_other,PO),"secure/master",//')
+              "valid_write",file,geteuid(PO),"log_file",PO))
+      return 0;
+  if ( size_to_break >= 0 & (
+      sizeof(st = get_dir(file,2) ) && st[0] >= (size_to_break|MAX_LOG_SIZE)))
+      catch(rename(file, file + ".old");publish); /* No panic if failure */
+  return(write_file(file,txt));
+}
+
+// * Magier-Level abfragen
+int query_wiz_level(mixed player) {
+  return (int)"/secure/master"->query_wiz_level(player);
+}
+
+#ifdef __ALISTS__
+// * Element aus Alist loeschen (by key)
+mixed *remove_alist(mixed key,mixed *alist)
+{
+  int i,j;
+
+  if (!pointerp(alist) || !sizeof(alist))
+    return alist;
+  if (!pointerp(alist[0]))
+  {
+    if ((i=assoc(key,alist))<0)
+      return alist;
+    return alist[0..i-1]+alist[i+1..];
+  }
+  i = assoc(key,alist[0]);
+  if ((i=assoc(key,alist[0]))<0)
+    return alist;
+  alist=alist[0..];
+  for (j=sizeof(alist)-1;j>=0;j--)
+    alist[j]=alist[j][0..i-1]+alist[j][i+1..];
+  return alist;
+}
+
+// * Element aus Alist loeschen (by pos)
+mixed *exclude_alist(int i,mixed *alist)
+{
+  int j;
+  if (!pointerp(alist) || !sizeof(alist) || i<0)
+    return alist;
+  if (!pointerp(alist[0]))
+    return alist[0..i-1]+alist[i+1..];
+  alist=alist[0..]; /* Create PHYSICAL copy of alist */
+  for (j=sizeof(alist)-1;j>=0;j--)
+    alist[j]=alist[j][0..i-1]+alist[j][i+1..];
+  return alist; /* order_alist is NOT necessary - see /doc/LPC/alist */
+}
+#endif // __ALISTS__
+
+// * German version of ctime()
+#define TAGE ({"Son","Mon","Die","Mit","Don","Fre","Sam"})
+#define MONATE ({"Jan","Feb","Mar","Apr","Mai","Jun","Jul","Aug",\
+                 "Sep","Okt","Nov","Dez"})
+string dtime(int wann) {
+  
+  if (wann == dtime_cache[0])
+    return(dtime_cache[1]);
+
+  int *lt = localtime(wann);
+  return sprintf("%s, %2d. %s %d, %02d:%02d:%02d",
+      TAGE[lt[TM_WDAY]], lt[TM_MDAY], MONATE[lt[TM_MON]], 
+      lt[TM_YEAR],lt[TM_HOUR], lt[TM_MIN], lt[TM_SEC]);
+}
+
+// wenn strftime im Driver nicht existiert, ist dies hier ein Alias auf dtime(),
+// zwar stimmt das Format dann nicht, aber die Mudlib buggt nicht und schreibt
+// ein ordentliches Datum/Uhrzeit.
+#if !__EFUN_DEFINED__(strftime)
+varargs string strftime(mixed fmt, int clock, int localized) {
+  if (intp(clock) && clock >= 0)
+    return dtime(clock);
+  else if (intp(fmt) && fmt >= 0)
+    return dtime(fmt);
+  
+  return dtime(time());
+}
+#endif //!__EFUN_DEFINED__(strftime)
+
+// * Shutdown mit zusaetzlichem logging
+nomask int shutdown(string reason)
+{
+  string name;
+  string obname;
+  string output;
+
+  if (!reason)
+    return 0;
+  if ( !ARCH_SECURITY && getuid(previous_object())!=ROOTID &&
+          object_name(previous_object())!="/obj/shut" )
+  {
+    write("You have no permission to shut down the gamedriver!\n");
+    return 0;
+  }
+  if ((this_interactive())&&(name=getuid(this_interactive())))
+  {
+    name=capitalize(name);
+    filter(users(),#'tell_object,//'
+               capitalize(name)+" faehrt das Spiel herunter!\n");
+  }
+  else
+    name="ANONYMOUS";
+  if (previous_object()) obname=capitalize(getuid(previous_object()));
+  output=name;
+  if (obname && name!=obname) output=output+" ("+obname+")";
+  if (previous_object()&&object_name(previous_object())=="/obj/shut"){
+    output+=" faehrt das Spiel via Armageddon herunter.\n";
+    output=dtime(time())+": "+output;
+    log_file("GAME_LOG",output+"\n",-1);
+    efun::shutdown();
+    return 1;
+  }
+  output=ctime(time())+": "+output+" faehrt das Spiel herunter.\n";
+  output+="    Grund: "+reason;
+  log_file("GAME_LOG",output+"\n",-1);
+  efun::shutdown();
+  return 1;
+}
+
+// * lowerchar
+
+int lowerchar(int char) {
+  if (char<'A' || char>'Z') return char;
+  return char+32;
+}
+
+// * upperstring
+
+string upperstring(string s)
+{
+#if __EFUN_DEFINED__(upper_case)
+  return(upper_case(s));
+#else
+  int i;
+  if (!stringp(s)) return 0;
+  for (i=sizeof(s)-1;i>=0;i--) s[i]=((s[i]<'a'||s[i]>'z')?s[i]:s[i]-32);
+  return s;
+#endif
+}
+
+// * lowerstring
+
+string lowerstring(string s)
+{
+  if (!stringp(s)) return 0;
+  return lower_case(s);
+}
+
+
+// * GD version
+string version()
+{
+  return __VERSION__;
+}
+
+// * break_string
+// stretch() -- stretch a line to fill a given width 
+private string stretch(string s, int width) {
+  int len=sizeof(s);
+  if (len==width) return s;
+
+  // reine Leerzeilen, direkt zurueckgeben
+  string trimmed=trim(s,TRIM_LEFT," ");
+  if (trimmed=="") return s; 
+  int start_spaces = len - sizeof(trimmed);
+
+  string* words = explode(trimmed, " ");
+  // der letzte kriegt keine Spaces
+  int word_count=sizeof(words) - 1;
+  // wenn Zeile nur aus einem Wort, wird das Wort zurueckgegeben
+  if (!word_count)
+    return " "*start_spaces + words[0];
+
+  int space_count = width - len;
+
+  int space_per_word=(word_count+space_count) / word_count;
+  // Anz.Woerter mit Zusatz-Space
+  int rest=(word_count+space_count) % word_count; 
+  // Rest-Spaces Verteilen
+  foreach (int pos : rest) words[pos]+=" ";
+  return (" "*start_spaces) + implode( words, " "*space_per_word );
+}
+
+// aus Geschwindigkeitsgruenden hat der Blocksatz fuer break_string eine
+// eigene Funktion bekommen:
+private varargs string block_string(string s, int width, int flags) {
+  // wenn BS_LEAVE_MY_LFS, aber kein BS_NO_PARINDENT, dann werden Zeilen mit
+  // einem Leerzeichen begonnen.
+  // BTW: Wenn !BS_LEAVE_MY_LFS, hat der Aufrufer bereits alle \n durch " "
+  // ersetzt.
+  if ( (flags & BS_LEAVE_MY_LFS)
+      && !(flags & BS_NO_PARINDENT))
+  {
+      s = " "+regreplace(s,"\n","\n ",1);
+  }
+
+  // sprintf fuellt die letzte Zeile auf die Feldbreite (hier also
+  // Zeilenbreite) mit Fuellzeichen auf, wenn sie NICHT mit \n umgebrochen
+  // ist. Es wird an die letzte Zeile aber kein Zeilenumbruch angehaengt.
+  // Eigentlich ist das Auffuellen doof, aber vermutlich ist es unnoetig, es
+  // wieder rueckgaengig zu machen.
+  s = sprintf( "%-*=s", width, s);
+
+  string *tmp=explode(s, "\n");
+  // Nur wenn s mehrzeilig ist, Blocksatz draus machen. Die letzte Zeile wird
+  // natuerlich nicht gedehnt. Sie ist dafuer schon von sprintf() aufgefuellt
+  // worden. BTW: Die letzte Zeile endet u.U. noch nicht mit einem \n (bzw.
+  // nur dann, wenn BS_LEAVE_MY_LFS und der uebergebene Text schon nen \n am
+  // Ende der letzten Zeile hat), das macht der Aufrufer...
+  if (sizeof(tmp) > 1)
+    return implode( map( tmp[0..<2], #'stretch/*'*/, width ), "\n" ) 
+      + "\n" + tmp[<1];
+
+  return s;
+}
+
+public varargs string break_string(string s, int w, mixed indent, int flags)
+{
+    if ( !s || s == "" ) return "";
+
+    if ( !w ) w=78;
+
+    if( intp(indent) )
+       indent = indent ? " "*indent : "";
+
+    int indentlen=stringp(indent) ? sizeof(indent) : 0;
+
+    if (indentlen>w) {
+      set_this_object(previous_object());
+      raise_error(sprintf("break_string: indent longer %d than width %d\n",
+                  indentlen,w));
+      // w=((indentlen/w)+1)*w;
+    }
+
+    if (!(flags & BS_LEAVE_MY_LFS)) 
+      s=regreplace( s, "\n", " ", 1 );
+
+    if ( flags & BS_SINGLE_SPACE )
+       s = regreplace( s, "(^|\n| )  *", "\\1", 1 );
+ 
+    string prefix="";
+    if (indentlen && flags & BS_PREPEND_INDENT) {
+      if (indentlen+sizeof(s) > w || 
+         (flags & BS_LEAVE_MY_LFS) && strstr(s,"\n")>-1) {
+       prefix=indent+"\n";
+       indent=(flags & BS_NO_PARINDENT) ? "" : " ";
+       indentlen=sizeof(indent);
+      }
+    }
+
+    if ( flags & BS_BLOCK ) {
+      /*
+           s = implode( map( explode( s, "\n" ),
+                               #'block_string, w, indentlen, flags),
+                      "" );
+      */
+      s = block_string( s , w - indentlen, flags );
+    }
+    else {
+      s = sprintf("%-1.*=s",w-indentlen,s);
+    }
+    if ( s[<1] != '\n' ) s += "\n";
+
+    if ( !indentlen ) return prefix + s;
+    
+    string indent2 = ( flags & BS_INDENT_ONCE ) ? (" "*indentlen) :indent;
+      
+    return prefix + indent + 
+      regreplace( s[0..<2], "\n", "\n"+indent2, 1 ) + "\n";
+      /*
+       string *buf;
+
+       buf = explode( s, "\n" );
+       return prefix + indent + implode( buf[0..<2], "\n"+indent2 ) + buf[<1] + "\n";
+      */
+}
+
+// * Elemente aus mapping loeschen - mapping vorher kopieren
+
+mapping m_copy_delete(mapping m, mixed key) {
+  return m_delete(copy(m), key);
+}
+
+// * times
+int last_reboot_time()
+{
+  return __BOOT_TIME__;
+}
+
+int first_boot_time()
+{
+  return 701517600;
+}
+
+int exist_days()
+{
+  return (((time()-first_boot_time())/8640)+5)/10;
+}
+
+// * uptime :)
+string uptime()
+{
+  int t;
+  int tmp;
+  string s;
+
+  t=time()-__BOOT_TIME__;
+  s="";
+  if (t>=86400)
+    s+=sprintf("%d Tag%s, ",tmp=t/86400,(tmp==1?"":"e"));
+  if (t>=3600)
+    s+=sprintf("%d Stunde%s, ",tmp=(t=t%86400)/3600,(tmp==1?"":"n"));
+  if (t>60)
+    s+=sprintf("%d Minute%s und ",tmp=(t=t%3600)/60,(tmp==1?"":"n"));
+  return s+sprintf("%d Sekunde%s",t=t%60,(t==1?"":"n"));
+}
+
+// * Was tun bei 'dangling' lfun-closures ?
+void dangling_lfun_closure() {
+  raise_error("dangling lfun closure\n");
+}
+
+// * Sperren ausser fuer master/simul_efun
+
+#if __EFUN_DEFINED__(set_environment)
+nomask void set_environment(object o1, object o2) {
+  raise_error("Available only for root\n");
+}
+#endif
+
+nomask void set_this_player(object pl) {
+  raise_error("Available only for root\n");
+}
+
+#if __EFUN_DEFINED__(export_uid)
+nomask void export_uid(object ob) {
+  raise_error("Available only for root\n");
+}
+#endif
+
+// * Jetzt auch closures
+int process_flag;
+
+public nomask int process_call()
+{
+  if (process_flag>0)
+    return process_flag;
+  else return(0);
+}
+
+private nomask string _process_string(string str,object po) {
+              set_this_object(po);
+              return(efun::process_string(str));
+}
+
+nomask string process_string( mixed str )
+{
+  string tmp, err;
+  int flag; 
+
+  if ( closurep(str) ) {
+      set_this_object( previous_object() );
+      return funcall(str);
+  }
+  else if (str==0)
+      return((string)str);
+  else if ( !stringp(str) ) {
+      return to_string(str);
+  }
+
+  if ( !(flag = process_flag > time() - 60))                     
+      process_flag=time();
+
+  err = catch(tmp = funcall(#'_process_string,str,previous_object()); publish);
+
+  if ( !flag )
+    process_flag=0;
+
+  if (err) {
+    // Verarbeitung abbrechen
+    set_this_object(previous_object());
+    raise_error(err);
+  }
+  return tmp;
+}
+
+// 'mkdir -p' - erzeugt eine komplette Hierarchie von Verzeichnissen.
+// wenn das Verzeichnis angelegt wurde oder schon existiert, wird 1
+// zurueckgeliefert, sonst 0.
+// Wirft einen Fehler, wenn das angebene Verzeichnis nicht absolut ist!
+public int mkdirp(string dir) {
+  // wenn es nur keinen fuehrenden / gibt, ist das ein Fehler.
+  if (strstr(dir, "/") != 0)
+    raise_error("mkdirp(): Pfad ist nicht absolute.\n");
+  // cut off trailing /...
+  if (dir[<1]=='/')
+      dir = dir[0..<2];
+
+  int fstat = file_size(dir);
+  // wenn es schon existiert, tun wir einfach so, als haetten wir es angelegt.
+  if (fstat == FSIZE_DIR)
+    return 1;
+  // wenn schon ne Datei existiert, geht es nicht.
+  if (fstat != FSIZE_NOFILE)
+    return 0;
+  // wenn es nur einen / gibt (den fuehrenden), dann ist es ein
+  // toplevel-verzeichnis, was direkt angelegt wird.
+  if (strrstr(dir,"/")==0) {
+    return funcall(bind_lambda(#'efun::mkdir, previous_object()), dir);
+  }
+
+  // mkdir() nicht direkt rufen, sondern vorher als closure ans aufrufende
+  // Objekt binden. Sonst laeuft die Rechtepruefung in valid_write() im Master
+  // unter der Annahme, dass die simul_efun.c mit ihrer root id was will.
+
+  // jetzt rekursiv die Verzeichnishierarchie anlegen. Wenn das erfolgreich
+  // ist, dann koennen wir jetzt mit mkdir das tiefste Verzeichnis anlegen
+  if (mkdirp(dir[0..strrstr(dir,"/")-1]) == 1)
+    return funcall(bind_lambda(#'efun::mkdir, previous_object()), dir);
+}
+
+
+// * Properties ggfs. mitspeichern
+mixed save_object(mixed name)
+{
+  mapping properties;
+  mapping save;
+  mixed index, res;
+  int i;
+
+  // nur Strings und 0 zulassen
+  if ((!stringp(name) || !sizeof(name)) && 
+      (!intp(name) || name!=0)) {
+      set_this_object(previous_object());
+      raise_error(sprintf(
+         "Only non-empty strings and 0 may be used as filename in "
+         "sefun::save_object()! Argument was %O\n",name));
+  }
+
+  save = m_allocate(0, 2);
+  properties = (mapping)previous_object()->QueryProperties();
+
+  if(mappingp(properties))
+  {
+    // delete all entries in mapping properties without SAVE flag!
+    index = m_indices(properties);
+    for(i = sizeof(index)-1; i>=0;i--)
+    {
+      if(properties[index[i], F_MODE] & SAVE)
+      {
+       save[index[i]] = properties[index[i]];
+       save[index[i], F_MODE] =
+       properties[index[i], F_MODE] &
+                    (~(SETMNOTFOUND|QUERYMNOTFOUND|QUERYCACHED|SETCACHED));
+      }
+    }
+  }
+  else save = ([]);
+
+  // save object!
+  previous_object()->_set_save_data(save);
+  // format: wie definiert in config.h
+  if (stringp(name))
+    res = funcall(bind_lambda(#'efun::save_object, previous_object()), name,
+       __LIB__SAVE_FORMAT_VERSION__);
+  else
+    res = funcall(bind_lambda(#'efun::save_object, previous_object()),
+       __LIB__SAVE_FORMAT_VERSION__);
+  previous_object()->_set_save_data(0);
+
+#ifdef IOSTATS
+  // Stats...
+  struct iostat_s stat = (<iostat_s>);
+  stat->oname = object_name(previous_object());
+  stat->time = time();
+  //stat->size = (int)object_info(previous_object(),OINFO_MEMORY,
+  //    OIM_TOTAL_DATA_SIZE);
+  if (stringp(name))
+      stat->size = file_size(name + ".o");
+  else
+      stat->sizeof(res);
+  //debug_message("saveo: "+saveo_stat[0]+"\n");
+  saveo_stat[1][saveo_stat[0]] = stat;
+  saveo_stat[0] = (saveo_stat[0] + 1) % sizeof(saveo_stat[1]);
+  //debug_message("saveo 2: "+saveo_stat[0]+"\n");
+#endif
+
+  return res;
+}
+
+// * Auch Properties laden
+int restore_object(string name)
+{
+  int result;
+  mixed index;
+  mixed save;
+  mapping properties;
+  int i;
+  closure cl;
+
+  // get actual property settings (by create())
+  properties = (mapping)previous_object()->QueryProperties();
+
+//  DEBUG(sprintf("RESTORE %O\n",name));
+  // restore object
+  result=funcall(bind_lambda(#'efun::restore_object, previous_object()), name);
+  //'))
+  //_get_save_data liefert tatsaechlich mixed zurueck, wenn das auch immer ein 
+  //mapping sein sollte.
+  save = (mixed)previous_object()->_get_save_data();
+  if(mappingp(save))
+  {
+    index = m_indices(save);
+    for(i = sizeof(index)-1; i>=0; i--)
+    {
+      properties[index[i]] = save[index[i]];
+      properties[index[i], F_MODE] = save[index[i], F_MODE]
+                            &~(SETCACHED|QUERYCACHED);
+    }
+  }
+  else properties = ([]);
+
+  // restore properties
+  funcall(
+          bind_lambda(
+                     unbound_lambda(({'arg}), //'})
+                                  ({#'call_other,({#'this_object}),
+                                  "SetProperties",'arg})),//')
+                     previous_object()),properties);
+  previous_object()->_set_save_data(0);
+
+#ifdef IOSTATS
+  // Stats...
+  //debug_message("restoreo: "+restoreo_stat[0]+"\n");
+  struct iostat_s stat = (<iostat_s>);
+  stat->oname = object_name(previous_object());
+  stat->time = time();
+  //stat->size = (int)object_info(previous_object(),OINFO_MEMORY,
+  //    OIM_TOTAL_DATA_SIZE);
+  stat->size = file_size(name + ".o");
+  restoreo_stat[1][restoreo_stat[0]] = stat;
+
+  restoreo_stat[0] = (restoreo_stat[0] + 1) % sizeof(restoreo_stat[1]);
+#endif
+
+  return result;
+}
+
+// * HB eines Objektes ein/ausschalten
+int set_object_heart_beat(object ob, int flag)
+{
+  if (objectp(ob))
+    return funcall(bind_lambda(#'efun::configure_object,ob), ob, OC_HEART_BEAT, flag);
+}
+
+// * Magierlevelgruppen ermitteln
+int query_wiz_grp(mixed wiz)
+{
+  int lev;
+
+  lev=query_wiz_level(wiz);
+  if (lev<SEER_LVL) return 0;
+  if (lev>=GOD_LVL) return lev;
+  if (lev>=ARCH_LVL) return ARCH_GRP;
+  if (lev>=ELDER_LVL) return ELDER_GRP;
+  if (lev>=LORD_LVL) return LORD_GRP;
+  if (lev>=SPECIAL_LVL) return SPECIAL_GRP;
+  if (lev>=DOMAINMEMBER_LVL) return DOMAINMEMBER_GRP;
+  if (lev>=WIZARD_LVL) return WIZARD_GRP;
+  if (lev>=LEARNER_LVL) return LEARNER_GRP;
+  return SEER_GRP;
+}
+
+mixed *wizlist_info()
+{
+  if (ARCH_SECURITY || !extern_call())
+            return efun::wizlist_info();
+  return 0;
+}
+
+// * wizlist ausgeben
+varargs void wizlist(string name, int sortkey ) {
+
+  if (!name)
+  {
+    if (this_player())
+      name = getuid(this_player());
+    if (!name)
+      return;
+  }
+
+  // Schluessel darf nur in einem gueltigen Bereich sein
+  if (sortkey<WL_NAME || sortkey>=WL_SIZE) sortkey=WL_COST;
+
+  mixed** wl = efun::wizlist_info();
+  // nach <sortkey> sortieren
+  wl = sort_array(wl, function int (mixed a, mixed b)
+      {return a[sortkey] < b[sortkey]; } );
+
+  // Summe ueber alle Kommandos ermitteln.
+  int total_cmd, i;
+  int pos=-1;
+  foreach(mixed entry : wl)
+  {
+    total_cmd += entry[WL_COMMANDS];
+    if (entry[WL_NAME] == name)
+      pos = i;
+    ++i;
+  }
+
+  if (pos < 0 && name != "ALL" && name != "TOP100")
+    return;
+
+  if (name == "TOP100")
+  {
+    if (sizeof(wl) > 100)
+      wl = wl[0..100];
+    else
+      wl = wl;
+  }
+  // um name herum schneiden
+  else if (name != "ALL")
+  {
+    if (sizeof(wl) <= 21)
+      wl = wl;
+    else if (pos + 10 < sizeof(wl) && pos - 10 > 0)
+      wl = wl[pos-10..pos+10];
+    else if (pos <=21)
+      wl = wl[0..20];
+    else if (pos >= sizeof(wl) - 21)
+      wl = wl[<21..];
+    else
+      wl = wl;
+  }
+
+  write("\nWizard top score list\n\n");
+  if (total_cmd == 0)
+    total_cmd = 1;
+  printf("%-20s %-6s %-3s %-17s %-6s %-6s %-6s\n",
+         "EUID", "cmds", "%", "Costs", "HB", "Arrays","Mapp.");
+  foreach(mixed e: wl)
+  {
+    printf("%-:20s %:6d %:2d%% [%:6dk,%:6dG] %:6d %:6d %:6d\n",
+          e[WL_NAME], e[WL_COMMANDS], e[WL_COMMANDS] * 100 / total_cmd,
+          e[WL_COST] / 1000, e[WL_TOTAL_GIGACOST],
+          e[WL_HEART_BEATS], e[WL_ARRAY_TOTAL], e[WL_MAPPING_TOTAL]
+          );
+  }
+  printf("\nTotal         %7d         (%d)\n\n", total_cmd, sizeof(wl));
+}
+
+
+// Ab hier folgen Hilfsfunktionen fuer call_out() bzw. fuer deren Begrenzung
+
+// ermittelt das Objekt des Callouts.
+private object _call_out_obj( mixed call_out ) {
+    return pointerp(call_out) ? call_out[0] : 0;
+}
+
+private void _same_object( object ob, mapping m ) {
+  // ist nicht so bloed, wie es aussieht, s. nachfolgede Verwendung von m
+  if ( m[ob] )
+    m[ob] += ({ ob });
+  else
+    m[ob] = ({ ob }); 
+}
+
+// alle Objekte im Mapping m zusammenfassen, die den gleichen Loadname (Name der
+// Blueprint) haben, also alle Clones der BP, die Callouts laufen haben.
+// Objekte werden auch mehrfach erfasst, je nach Anzahl ihrer Callouts.
+private void _same_path( object key, object *obs, mapping m ) {
+  string path;
+  if (!objectp(key) || !pointerp(obs)) return;
+
+  path = load_name(key);
+
+  if ( m[path] )
+    m[path] += obs;
+  else
+    m[path] = obs;
+}
+
+// key kann object oder string sein.
+private int _too_many( mixed key, mapping m, int i ) {
+    return sizeof(m[key]) >= i;
+}
+
+// alle Objekte in obs zerstoeren und Fehlermeldung ausgeben. ACHTUNG: Die
+// Objekte werden idR zu einer BP gehoeren, muessen aber nicht! In dem Fall
+// wird auf der Ebene aber nur ein Objekt in der Fehlermeldung erwaehnt.
+private void _destroy( mixed key, object *obs, string text, int uid ) {
+    if (!pointerp(obs)) return;
+    // Array mit unique Eintraege erzeugen.
+    obs = m_indices( mkmapping(obs) );
+    // Fehlermeldung auf der Ebene ausgeben, im catch() mit publish, damit es
+    // auf der Ebene direkt scrollt, der backtrace verfuegbar ist (im
+    // gegensatz zur Loesung mittels Callout), die Ausfuehrung aber weiter
+    // laeuft.
+    catch( efun::raise_error(           
+         sprintf( text,                   
+           uid ? (string)master()->creator_file(key) : key,                   
+           sizeof(obs), object_name(obs[<1]) ) );publish);
+    // Und weg mit dem Kram...
+    filter( obs, #'efun::destruct/*'*/ );
+}
+
+// Alle Objekt einer UID im Mapping m mit UID als KEys zusammenfassen. Objekt
+// sind dabei nicht unique.
+private void _same_uid( string key, object *obs, mapping m, closure cf ) {
+  string uid;
+
+  if ( !pointerp(obs) || !sizeof(obs) )
+    return;
+
+  uid = funcall( cf, key );
+
+  if ( m[uid] )
+    m[uid] += obs; // obs ist nen Array
+  else
+    m[uid] = obs;
+}
+
+nomask varargs void call_out( varargs mixed *args )
+{
+    mixed tmp, *call_outs;
+
+    // Bei >600 Callouts alle Objekte killen, die mehr als 30 Callouts laufen
+    // haben.
+    if ( efun::driver_info(DI_NUM_CALLOUTS) > 600
+        && geteuid(previous_object()) != ROOTID )
+    {
+       // Log erzeugen...
+      
+       // Objekte aller Callouts ermitteln
+       call_outs = map( efun::call_out_info(), #'_call_out_obj );
+       mapping objectmap = ([]);
+       filter( call_outs, #'_same_object, &objectmap );
+       // Master nicht grillen...
+       efun::m_delete( objectmap, master(1) );
+       // alle Objekte raussuchen, die zuviele haben...
+       mapping res = filter_indices( objectmap, #'_too_many, objectmap, 29 );
+       // und ueber alle Keys gehen, an _destroy() werden Key und Array mit
+       // Objekten uebergeben (in diesem Fall sind Keys und Array mit
+       // Objekten jeweils das gleiche Objekt).
+       if ( sizeof(res) )       
+           walk_mapping(res, #'_destroy, "CALL_OUT overflow by single "             
+              "object [%O]. Destructed %d objects. [%s]\n", 0 );
+
+       // Bei (auch nach dem ersten Aufraeumen noch) >800 Callouts alle
+       // Objekte killen, die mehr als 50 Callouts laufen haben - und
+       // diesmal zaehlen Clones nicht eigenstaendig! D.h. es werden alle
+       // Clones einer BP gekillt, die Callouts laufen haben, falls alle
+       // diese Objekte _zusammen_ mehr als 50 Callouts haben!
+       if ( efun::driver_info(DI_NUM_CALLOUTS) > 800 ) {
+           // zerstoerte Objekte von der letzten Aktion sind in objectmap nicht
+           // mehr drin, da sie dort als Keys verwendet wurden.
+           mapping pathmap=([]);
+           // alle Objekt einer BP in res sortieren, BP-Name als Key, Arrays
+           // von Objekten als Werte.
+           walk_mapping( objectmap, #'_same_path, &pathmap);
+           // alle BPs (und ihre Objekte) raussuchen, die zuviele haben...
+           res = filter_indices( pathmap, #'_too_many/*'*/, pathmap, 50 );
+           // und ueber alle Keys gehen, an _destroy() werden die Clones
+           // uebergeben, die Callouts haben.
+           if ( sizeof(res) )
+              walk_mapping( res, #'_destroy/*'*/, "CALL_OUT overflow by file "
+                           "'%s'. Destructed %d objects. [%s]\n", 0 );
+
+           // Wenn beide Aufraeumarbeiten nichts gebracht haben und immer
+           // noch >1000 Callouts laufen, werden diesmal alle Callouts
+           // einer UID zusammengezaehlt.
+           // Alle Objekte einer UID, die es in Summe aller ihrer Objekt mit
+           // Callouts auf mehr als 100 Callouts bringt, werden geroestet.
+           if (efun::driver_info(DI_NUM_CALLOUTS) > 1000)
+           {
+              // das nach BP-Namen vorgefilterte Mapping jetzt nach UIDs
+              // zusammensortieren. Zerstoerte Clones filter _same_uid()
+              // raus.
+              mapping uidmap=([]);
+              walk_mapping( pathmap, #'_same_uid, &uidmap,
+                           symbol_function( "creator_file",
+                                          "/secure/master" ) );
+              // In res nun UIDs als Keys und Arrays von Objekten als Werte.
+              // Die rausfiltern, die mehr als 100 Objekte (non-unique, d.h.
+              // 100 Callouts!) haben.
+              res = filter_indices( uidmap, #'_too_many, uidmap, 100 );
+              // und erneut ueber die Keys laufen und jeweils die Arrays mit
+              // den Objekten zur Zerstoerung an _destroy()...
+              if ( sizeof(res) )
+                  walk_mapping( res, #'_destroy, "CALL_OUT overflow by "
+                              "UID '%s'. Destructed %d objects. [%s]\n",
+                              1 );
+           }
+       }
+    }
+
+    // Falls das aufrufende Objekt zerstoert wurde beim Aufraeumen
+    if ( !previous_object() )
+       return;
+
+    set_this_object( previous_object() );
+    apply( #'efun::call_out, args );
+    return;
+}
+
+mixed call_out_info() {
+  
+  object po = previous_object();
+  mixed coi = efun::call_out_info();
+
+  // ungefilterten Output nur fuer bestimmte Objekte, Objekte in /std oder
+  // /obj haben die BackboneID.
+  if (query_wiz_level(getuid(po)) >= ARCH_LVL
+       || (string)master()->creator_file(load_name(po)) == BACKBONEID ) {
+      return coi;
+  }
+  else {
+      return filter(coi, function mixed (mixed arr) {
+              if (pointerp(arr) && arr[0]==po)
+                 return 1;
+              else return 0; });
+  }
+}
+
+// * Zu einer closure das Objekt, an das sie gebunden ist, suchen
+mixed query_closure_object(closure c) {
+  return
+    CLOSURE_IS_UNBOUND_LAMBDA(get_type_info(c, 1)) ?
+      0 :
+  (to_object(c) || -1);
+}
+
+// * Wir wollen nur EIN Argument ... ausserdem checks fuer den Netztotenraum
+varargs void move_object(mixed what, mixed where)
+{
+  object po,tmp;
+
+  po=previous_object();
+  if (!where)
+  {
+    where=what;
+    what=po;
+  }
+  if (((stringp(where) && where==NETDEAD_ROOM ) ||
+       (objectp(where) && where==find_object(NETDEAD_ROOM))) &&
+       objectp(what) && object_name(what)!="/obj/sperrer")
+  {
+    if (!query_once_interactive(what))
+    {
+      what->remove();
+      if (what) destruct(what);
+      return;
+    }
+    if (living(what) || interactive(what))
+    {
+      log_file("NDEAD2",sprintf("TRYED TO MOVE TO NETDEAD: %O\n",what));
+      return;
+    }
+    set_object_heart_beat(what,0);
+  }
+  tmp=what;
+  while (tmp=environment(tmp))
+      // Ja. Man ruft die _set_xxx()-Funktionen eigentlich nicht direkt auf.
+      // Aber das Lichtsystem ist schon *so* rechenintensiv und gerade der
+      // P_LAST_CONTENT_CHANGE-Cache wird *so* oft benoetigt, dass es mir
+      // da um jedes bisschen Rechenzeit geht.
+      // Der Zweck heiligt ja bekanntlich die Mittel. ;-)
+      //
+      // Tiamak
+    tmp->_set_last_content_change();
+  funcall(bind_lambda(#'efun::move_object,po),what,where);
+  if (tmp=what)
+    while (tmp=environment(tmp))
+      tmp->_set_last_content_change();
+}
+
+
+void start_simul_efun() {
+  mixed *info;
+
+  // Falls noch nicht getan, extra_wizinfo initialisieren
+  if ( !pointerp(info = get_extra_wizinfo(0)) )
+    set_extra_wizinfo(0, info = allocate(BACKBONE_WIZINFO_SIZE));
+
+  InitLivingData(info);
+
+  set_next_reset(10); // direkt mal aufraeumen
+}
+
+protected void reset() {
+  set_next_reset(7200);
+  CleanLivingData();
+}
+
+#if !__EFUN_DEFINED__(absolute_hb_count)
+int absolute_hb_count() {
+  return efun::driver_info(DI_NUM_HEARTBEAT_TOTAL_CYCLES);
+}
+#endif
+
+void __set_environment(object ob, mixed target)
+{
+  string path;
+  object obj;
+
+  if (!objectp(ob))
+    return;
+  if (!IS_ARCH(geteuid(previous_object())) || !ARCH_SECURITY )
+    return;
+  if (objectp(target))
+  {
+    efun::set_environment(ob,target);
+    return;
+  }
+  path=(string)MASTER->_get_path(target,this_interactive());
+  if (stringp(path) && file_size(path+".c")>=0 &&
+      !catch(load_object(path);publish) )
+  {
+    obj=find_object(path);
+    efun::set_environment(ob,obj);
+    return;
+  }
+}
+
+void _dump_wizlist(string file, int sortby) {
+  int i;
+  mixed *a;
+
+  if (!LORD_SECURITY)
+    return;
+  if (!master()->valid_write(file,geteuid(previous_object()),"write_file"))
+  {
+    write("NO WRITE PERMISSION\n");
+    return;
+  }
+  a = wizlist_info();
+  a = sort_array(a, lambda( ({'a,'b}),
+                        ({#'<,
+                          ({#'[,'a,sortby}),
+                          ({#'[,'b,sortby})
+                         })));
+  rm(file);
+  for (i=sizeof(a)-1;i>=0;i--)
+    write_file(file,sprintf("%-11s: eval=%-8d cmds=%-6d HBs=%-5d array=%-5d mapping=%-7d\n",
+      a[i][WL_NAME],a[i][WL_TOTAL_COST],a[i][WL_COMMANDS],a[i][WL_HEART_BEATS],
+      a[i][WL_ARRAY_TOTAL],a[i][WL_CALL_OUT]));
+}
+
+public varargs object deep_present(mixed what, object ob) {
+
+  if(!objectp(ob))
+    ob=previous_object();
+  // Wenn ein Objekt gesucht wird: Alle Envs dieses Objekts ermitteln und
+  // schauen, ob in diesen ob vorkommt. Dann ist what in ob enthalten.
+  if(objectp(what)) {
+    object *envs=all_environment(what);
+    // wenn ob kein Environment hat, ist es offensichtlich nicht in what
+    // enthalten.
+    if (!pointerp(envs)) return 0;
+    if (member(envs, ob) != -1) return what;
+  }
+  // sonst wirds teurer, ueber alle Objekte im (deep) Inv laufen und per id()
+  // testen. Dabei muss aber die gewuenschte Nr. ("flasche 3") abgeschnitten
+  // werden und selber gezaehlt werden, welche das entsprechende Objekt ist.
+  else if (stringp(what)) {
+      int cnt;
+      string newwhat;
+      if(sscanf(what,"%s %d",newwhat,cnt)!=2)
+       cnt=1;
+      else
+       what=newwhat;
+      foreach(object invob: deep_inventory(ob)) {
+       if (invob->id(what) && !--cnt)
+           return invob;
+      }
+  }
+  else {
+    set_this_object(previous_object());
+    raise_error(sprintf("Wrong argument 1 to deep_present(). "
+         "Expected \"object\" or \"string\", got %.50O.\n",
+         what));
+  }
+  return 0;
+}
+
+mapping dump_ip_mapping()
+{
+  return 0;
+}
+
+nomask void swap(object obj)
+{
+  write("Your are not allowed to swap objects by hand!\n");
+  return;
+}
+
+nomask varargs void garbage_collection(string str)
+{
+  if(previous_object()==0 || !IS_ARCH(geteuid(previous_object())) 
+      || !ARCH_SECURITY)
+  {
+    write("Call GC now and the mud will crash in 5-6 hours. DONT DO IT!\n");
+    return;
+  }
+  else if (stringp(str))
+  {
+    return efun::garbage_collection(str);
+  }
+  else 
+    return efun::garbage_collection();
+}
+
+varargs void notify_fail(mixed nf, int prio) {
+  object po,oldo;
+  int oldprio;
+  
+  if (!PL || !objectp(po=previous_object())) return;
+  if (!stringp(nf) && !closurep(nf)) {
+      set_this_object(po);
+      raise_error(sprintf(
+         "Only strings and closures allowed for notify_fail! "
+         "Argument was: %.50O...\n",nf));
+  }
+
+  // falls ein Objekt bereits nen notify_fail() setzte, Prioritaeten abschaetzen
+  // und vergleichen.
+  if (objectp(oldo=query_notify_fail(1)) && po!=oldo) {
+    if (!prio) {       
+      //Prioritaet dieses notify_fail() 'abschaetzen'
+      if (po==PL) // Spieler-interne (soul-cmds)
+        prio=NF_NL_OWN;
+      else if (living(po))
+        prio=NF_NL_LIVING;
+      else if ((int)po->IsRoom())
+        prio=NF_NL_ROOM;
+      else
+        prio=NF_NL_THING;
+    }
+    //Prioritaet des alten Setzers abschaetzen
+    if (oldo==PL)
+      oldprio=NF_NL_OWN;
+    else if (living(oldo))
+      oldprio=NF_NL_LIVING;
+    else if ((int)oldo->IsRoom())
+      oldprio=NF_NL_ROOM;
+    else
+      oldprio=NF_NL_THING;
+  }
+  else // wenn es noch kein Notify_fail gibt:
+    oldprio=NF_NL_NONE;
+
+  //vergleichen und ggf. setzen
+  if (prio >= oldprio) { 
+    set_this_object(po);
+    efun::notify_fail(nf);
+  }
+
+  return;
+}
+
+void _notify_fail(string str)
+{
+  //query_notify_fail() benutzen, um das Objekt
+  //des letzten notify_fail() zu ermitteln
+  object o;
+  if ((o=query_notify_fail(1)) && o!=previous_object())
+    return;
+  //noch kein notify_fail() fuer dieses Kommando gesetzt, auf gehts.
+  set_this_object(previous_object());
+  efun::notify_fail(str);
+  return;
+}
+
+string time2string( string format, int zeit )
+{
+  int i,ch,maxunit,dummy;
+  string *parts, fmt;
+
+  int secs = zeit;
+  int mins = (zeit/60);
+  int hours = (zeit/3600);
+  int days = (zeit/86400);
+  int weeks =  (zeit/604800);
+  int months = (zeit/2419200);
+  int abbr = 0;
+
+  parts = regexplode( format, "\(%\(-|\)[0-9]*[nwdhmsxNWDHMSX]\)|\(%%\)" );
+
+  for( i=1; i<sizeof(parts); i+=2 )
+  {
+    ch = parts[i][<1];
+    switch( parts[i][<1] )
+    {
+    case 'x': case 'X':
+       abbr = sscanf( parts[i], "%%%d", dummy ) && dummy==0;
+       // NO break !
+    case 'n': case 'N':
+       maxunit |= 31;
+       break;
+    case 'w': case 'W':
+       maxunit |= 15;
+       break;
+    case 'd': case 'D':
+       maxunit |= 7;
+       break;
+    case 'h': case 'H':
+       maxunit |= 3;
+       break;
+    case 'm': case 'M':
+       maxunit |= 1;
+       break;
+    }
+  }
+  if( maxunit & 16 ) weeks %= 4;
+  if( maxunit & 8 ) days %= 7;
+  if( maxunit & 4 ) hours %= 24;
+  if( maxunit & 2 ) mins %= 60;
+  if( maxunit ) secs %= 60;
+
+  for( i=1; i<sizeof(parts); i+=2 )
+  {
+    fmt = parts[i][0..<2];
+    ch = parts[i][<1];
+    if( ch=='x' )
+    {
+      if (months > 0) ch='n';
+      else if( weeks>0 ) ch='w';
+      else if( days>0 ) ch='d';
+      else if( hours>0 ) ch='h'; 
+      else if(mins > 0) ch = 'm';
+      else ch = 's';
+    }
+    else if( ch=='X' )
+    {
+      if (months > 0) ch='N';
+      else if( weeks>0 ) ch='W';
+      else if( days>0 ) ch='D';
+      else if( hours>0 ) ch='H'; 
+      else if(mins > 0) ch = 'M';
+      else ch = 'S';
+    }
+    
+    switch( ch )
+    {
+      case 'n': parts[i] = sprintf( fmt+"d", months ); break;
+      case 'w': parts[i] = sprintf( fmt+"d", weeks ); break;
+      case 'd': parts[i] = sprintf( fmt+"d", days ); break;
+      case 'h': parts[i] = sprintf( fmt+"d", hours ); break;
+      case 'm': parts[i] = sprintf( fmt+"d", mins ); break;
+      case 's': parts[i] = sprintf( fmt+"d", secs ); break;
+      case 'N':
+       if(abbr) parts[i] = "M";
+       else parts[i] = sprintf( fmt+"s", (months==1) ? "Monat" : "Monate" );
+       break;
+      case 'W':
+       if(abbr) parts[i] = "w"; else
+       parts[i] = sprintf( fmt+"s", (weeks==1) ? "Woche" : "Wochen" );
+       break;
+      case 'D':
+       if(abbr) parts[i] = "d"; else
+       parts[i] = sprintf( fmt+"s", (days==1) ? "Tag" : "Tage" );
+       break;
+      case 'H':
+       if(abbr) parts[i] = "h"; else
+       parts[i] = sprintf( fmt+"s", (hours==1) ? "Stunde" : "Stunden" );
+       break;
+      case 'M':
+       if(abbr) parts[i] = "m"; else
+       parts[i] = sprintf( fmt+"s", (mins==1) ? "Minute" : "Minuten" );
+       break;
+      case 'S':
+       if(abbr) parts[i] = "s"; else
+       parts[i] = sprintf( fmt+"s", (secs==1) ? "Sekunde" : "Sekunden" );
+       break;
+      case '%':
+       parts[i] = "%";
+       break;
+      }
+    }
+    return implode( parts, "" );
+}
+
+nomask mixed __create_player_dummy(string name)
+{
+  string err;
+  object ob;
+  mixed m;
+  //hat nen Scherzkeks die Blueprint bewegt?
+  if ((ob=find_object("/secure/login")) && environment(ob))
+      catch(destruct(ob);publish);
+  err = catch(ob = clone_object("secure/login");publish);
+  if (err)
+  {
+    write("Fehler beim Laden von /secure/login.c\n"+err+"\n");
+    return 0;
+  }
+  if (objectp(m=(mixed)ob->new_logon(name))) netdead[name]=m;
+  return m;
+}
+
+nomask int secure_level()
+{
+  int *level;
+  //kette der Caller durchlaufen, den niedrigsten Level in der Kette
+  //zurueckgeben. Zerstoerte Objekte (Selbstzerstoerer) fuehren zur Rueckgabe
+  //von 0.
+  //caller_stack(1) fuegt dem Rueckgabearray this_interactive() hinzu bzw. 0,
+  //wenn es keinen Interactive gibt. Die 0 fuehrt dann wie bei zerstoerten
+  //Objekten zur Rueckgabe von 0, was gewuenscht ist, da es hier einen
+  //INteractive geben muss.
+  level=map(caller_stack(1),function int (object caller)
+      {if (objectp(caller))
+       return(query_wiz_level(geteuid(caller)));
+       return(0); // kein Objekt da, 0.
+      } );
+  return(min(level)); //den kleinsten Wert im Array zurueckgeben (ggf. 0)
+}
+
+nomask string secure_euid()
+{
+  string euid;
+
+  if (!this_interactive()) // Es muss einen interactive geben
+     return 0;
+  euid=geteuid(this_interactive());
+  // ueber alle Caller iterieren. Wenn eines davon eine andere euid hat als
+  // der Interactive und diese nicht die ROOTID ist, wird 0 zurueckgeben.
+  // Ebenso, falls ein Selbstzerstoerer irgendwo in der Kette ist.
+  foreach(object caller: caller_stack()) {
+      if (!objectp(caller) ||
+       (geteuid(caller)!=euid && geteuid(caller)!=ROOTID))
+         return 0;
+  }
+  return euid; // 'sichere' euid zurueckgeben
+}
+
+// INPUT_PROMPT und nen Leerprompt hinzufuegen, wenn keins uebergeben wird.
+// Das soll dazu dienen, dass alle ggf. ein EOR am Promptende kriegen...
+//#if __BOOT_TIME__ < 1360017213
+varargs void input_to( mixed fun, int flags, varargs mixed *args )
+{
+    mixed *arr;
+    int i;
+
+    if ( !this_player() || !previous_object() )
+       return;
+
+    // TODO: input_to(...,INPUT_PROMPT, "", ...), wenn kein INPUT_PROMPT
+    // vorkommt...
+    if ( flags&INPUT_PROMPT ) {    
+        arr = ({ fun, flags }) + args;
+    }
+    else {
+        // ggf. ein INPUT_PROMPT hinzufuegen und nen Leerstring als Prompt.
+        flags |= INPUT_PROMPT;
+        arr = ({ fun, flags, "" }) + args;
+    }
+
+    // Arrays gegen flatten quoten.
+    for ( i = sizeof(arr) - 1; i > 1; i-- )
+       if ( pointerp(arr[i]) )
+           arr[i] = quote(arr[i]);
+
+    apply( bind_lambda( unbound_lambda( ({}),
+                                     ({ #'efun::input_to/*'*/ }) + arr ),
+                       previous_object() ) );
+}
+//#endif
+
+nomask int set_light(int i)
+// erhoeht das Lichtlevel eines Objekts um i
+// result: das Lichtlevel innerhalb des Objekts
+{
+    object ob, *inv;
+    int lall, light, dark, tmp;
+
+    if (!(ob=previous_object())) return 0; // ohne das gehts nicht.
+
+    // aus kompatibilitaetsgruenden kann man auch den Lichtlevel noch setzen
+    if (i!=0) ob->SetProp(P_LIGHT, ob->QueryProp(P_LIGHT)+i);
+
+    // Lichtberechnung findet eigentlich in der Mudlib statt.
+    return (int)ob->QueryProp(P_INT_LIGHT);
+}
+
+
+public string iso2ascii( string str )
+{
+    if ( !stringp(str) || !sizeof(str) )
+       return "";
+
+    str = regreplace( str, "ä", "ae", 1 );
+    str = regreplace( str, "ö", "oe", 1 );
+    str = regreplace( str, "ü", "ue", 1 );
+    str = regreplace( str, "Ä", "Ae", 1 );
+    str = regreplace( str, "Ö", "Oe", 1 );
+    str = regreplace( str, "Ü", "Ue", 1 );
+    str = regreplace( str, "ß", "ss", 1 );
+    str = regreplace( str, "[^ -~]", "?", 1 );
+
+    return str;
+}
+
+
+public varargs string CountUp( string *s, string sep, string lastsep )
+{
+    string ret;
+
+    if ( !pointerp(s) )
+       return "";
+    
+    if (!sep) sep = ", ";
+    if (!lastsep) lastsep = " und ";
+
+    switch (sizeof(s))  {
+       case 0: ret=""; break;
+       case 1: ret=s[0]; break;
+       default:
+              ret = implode(s[0..<2], sep);
+              ret += lastsep + s[<1];
+    }
+    return ret;
+}
+
+nomask varargs int query_next_reset(object ob) {
+
+    // Typpruefung: etwas anderes als Objekte oder 0 sollen Fehler sein.
+    if (ob && !objectp(ob))
+      raise_error(sprintf("Bad arg 1 to query_next_reset(): got %.20O, "
+           "expected object.\n",ob));
+
+    // Defaultobjekt PO, wenn 0 uebergeben.
+    if ( !objectp(ob) )
+      ob = previous_object();
+
+    return efun::object_info(ob, OI_NEXT_RESET_TIME);
+}
+
+
+#if !__EFUN_DEFINED__(copy_file)
+#define MAXLEN 50000
+nomask int copy_file(string source, string dest)
+{
+
+  int ptr;
+  string bytes;
+
+  set_this_object(previous_object());
+  if (!sizeof(source)||!sizeof(dest)||source==dest||(file_size(source)==-1)||
+      (!call_other(master(),"valid_read",source,
+                   getuid(this_interactive()||
+                 previous_object()),"read_file",previous_object()))||
+      (!call_other(master(),"valid_read",source,
+                   getuid(this_interactive()||
+                 previous_object()),"write_file",previous_object())))
+    return 1;
+  switch (file_size(dest))
+  {
+  case -1:
+    break;
+  case -2:
+    if (dest[<1]!='/') dest+="/";
+    dest+=efun::explode(source,"/")[<1];
+    if (file_size(dest)==-2) return 1;
+    if (file_size(dest)!=-1) break;
+  default:
+    if (!rm(dest)) return 1;
+    break;
+  }
+  do
+  {
+    bytes = read_bytes(source, ptr, MAXLEN); ptr += MAXLEN;
+    if (!bytes) bytes="";
+    write_file(dest, bytes);
+  }
+  while(sizeof(bytes) == MAXLEN);
+  return 0;
+}
+#endif //!__EFUN_DEFINED__(copy_file)
+
+
+// ### Ersatzaufloesung in Strings ###
+varargs string replace_personal(string str, mixed *obs, int caps) {
+  int i;
+  string *parts;
+
+  parts = regexplode(str, "@WE[A-SU]*[0-9]");
+  i = sizeof(parts);
+
+  if (i>1) {
+    int j, t;
+    closure *name_cls;
+
+    t = j = sizeof(obs);
+
+    name_cls  =  allocate(j);
+    while (j--)
+      if (objectp(obs[j]))
+        name_cls[j] = symbol_function("name", obs[j]);
+      else if (stringp(obs[j]))
+        name_cls[j] = obs[j];
+
+    while ((i-= 2)>0) {
+      int ob_nr;
+      // zu ersetzendes Token in Fall und Objektindex aufspalten
+      ob_nr = parts[i][<1]-'1';
+      if (ob_nr<0 || ob_nr>=t) {
+        set_this_object(previous_object());
+        raise_error(sprintf("replace_personal: using wrong object index %d\n",
+                    ob_nr));
+        return implode(parts, "");
+      }
+
+      // casus kann man schon hier entscheiden
+      int casus;
+      string part = parts[i];
+      switch (part[3]) {
+        case 'R': casus = WER;    break;
+        case 'S': casus = WESSEN; break;
+        case 'M': casus = WEM;    break;
+        case 'N': casus = WEN;    break;
+        default:  continue; // passt schon jetzt nicht in das Hauptmuster
+      }
+
+      // und jetzt die einzelnen Keywords ohne fuehrendes "@WE", beendende Id
+      mixed tmp;
+      switch (part[3..<2]) {
+        case "R": case "SSEN": case "M": case "N":               // Name
+          parts[i] = funcall(name_cls[ob_nr], casus, 1);  break;
+        case "RU": case "SSENU": case "MU": case "NU":           // unbestimmt
+          parts[i] = funcall(name_cls[ob_nr], casus);     break;
+        case "RQP": case "SSENQP": case "MQP": case "NQP":       // Pronoun
+          if (objectp(tmp = obs[ob_nr]))
+            parts[i] = (string)tmp->QueryPronoun(casus);
+          break;
+        case "RQA": case "SSENQA": case "MQA": case "NQA":       // Article
+          if (objectp(tmp = obs[ob_nr]))
+            tmp = (string)tmp->QueryArticle(casus, 1, 1);
+          if (stringp(tmp) && !(tmp[<1]^' ')) 
+            tmp = tmp[0..<2];                // Extra-Space wieder loeschen
+          break;
+        case "RQPPMS": case "SSENQPPMS": case "MQPPMS": case "NQPPMS":
+          if (objectp(tmp = obs[ob_nr]))
+            parts[i] = (string)tmp->QueryPossPronoun(MALE, casus, SINGULAR);
+          break;
+        case "RQPPFS": case "SSENQPPFS": case "MQPPFS": case "NQPPFS":
+          if (objectp(tmp = obs[ob_nr]))
+            parts[i] = (string)tmp->QueryPossPronoun(FEMALE, casus, SINGULAR);
+          break;
+        case "RQPPNS": case "SSENQPPNS": case "MQPPNS": case "NQPPNS":
+          if (objectp(tmp = obs[ob_nr]))
+            parts[i] = (string)tmp->QueryPossPronoun(NEUTER, casus, SINGULAR);
+          break;
+        case "RQPPMP": case "SSENQPPMP": case "MQPPMP": case "NQPPMP":
+          if (objectp(tmp = obs[ob_nr]))
+            parts[i] = (string)tmp->QueryPossPronoun(MALE, casus, PLURAL);
+          break;
+        case "RQPPFP": case "SSENQPPFP": case "MQPPFP": case "NQPPFP":
+          if (objectp(tmp = obs[ob_nr]))
+            parts[i] = (string)tmp->QueryPossPronoun(FEMALE, casus, PLURAL);
+          break;
+        case "RQPPNP": case "SSENQPPNP": case "MQPPNP": case "NQPPNP":
+          if (objectp(tmp = obs[ob_nr]))
+            parts[i] = (string)tmp->QueryPossPronoun(NEUTER, casus, PLURAL);
+          break;
+        default:
+          continue;
+      }
+      
+      // wenn tmp ein String war, weisen wir es hier pauschal zu
+      if (stringp(tmp))
+        parts[i] = tmp;
+
+      // auf Wunsch wird nach Satzenden gross geschrieben
+      if (caps)
+        switch (parts[i-1][<2..]) {
+          case ". ":  case "! ":  case "? ":
+          case ".":   case "!":   case "?":
+          case ".\n": case "!\n": case "?\n":
+          case "\" ": case "\"\n":
+            parts[i] = capitalize(parts[i]);
+            break;
+        }
+    }
+    return implode(parts, "");
+  }
+  return str;
+}
+
+
+//replacements for dropped efuns in LD
+#if !__EFUN_DEFINED__(extract)
+deprecated varargs string extract(string str, int from, int to) {
+
+  if(!stringp(str)) {
+    set_this_object(previous_object());
+    raise_error(sprintf("Bad argument 1 to extract(): %O",str));
+  }
+  if (intp(from) && intp(to)) {
+    if (from>=0 && to>=0)
+      return(str[from .. to]);
+    else if (from>=0 && to<0)
+      return(str[from .. <abs(to)]);
+    else if (from<0 && to>=0)
+      return(str[<abs(from) .. to]);
+    else
+      return(str[<abs(from) .. <abs(to)]);
+  }
+  else if (intp(from)) {
+    if (from>=0)
+      return(str[from .. ]);
+    else
+      return(str[<abs(from) .. ]);
+  }
+  else {
+    return(str);
+  }
+}
+#endif // !__EFUN_DEFINED__(extract)
+
+#if !__EFUN_DEFINED__(slice_array)
+deprecated varargs mixed slice_array(mixed array, int from, int to) {
+
+  if(!pointerp(array)) {
+    set_this_object(previous_object());
+    raise_error(sprintf("Bad argument 1 to slice_array(): %O",array));
+  }
+  if (intp(from) && intp(to)) {
+    if (from>=0 && to>=0)
+      return(array[from .. to]);
+    else if (from>=0 && to<0)
+      return(array[from .. <abs(to)]);
+    else if (from<0 && to>=0)
+      return(array[<abs(from) .. to]);
+    else
+      return(array[<abs(from) .. <abs(to)]);
+  }
+  else if (intp(from)) {
+    if (from>=0)
+      return(array[from .. ]);
+    else
+      return(array[<abs(from) .. ]);
+  }
+  else {
+    return(array);
+  }
+}
+#endif // !__EFUN_DEFINED__(slice_array)
+
+#if !__EFUN_DEFINED__(member_array)
+deprecated int member_array(mixed item, mixed arraystring) {
+
+  if (pointerp(arraystring)) {
+    return(efun::member(arraystring,item));
+  }
+  else if (stringp(arraystring)) {
+    return(efun::member(arraystring,to_int(item)));
+  }
+  else {
+    set_this_object(previous_object());
+    raise_error(sprintf("Bad argument 1 to member_array(): %O",arraystring));
+  }
+}
+#endif // !__EFUN_DEFINED__(member_array)
+
+// The digit at the i'th position is the number of bits set in 'i'.
+string count_table =
+    "0112122312232334122323342334344512232334233434452334344534454556";
+int broken_count_bits( string s ) {
+    int i, res;
+    if( !stringp(s) || !(i=sizeof(s)) ) return 0;
+    for( ; i-->0; ) {
+        // We are counting 6 bits at a time using a precompiled table.
+        res += count_table[(s[i]-' ')&63]-'0';
+    }
+    return res;
+}
+
+#if !__EFUN_DEFINED__(count_bits)
+int count_bits( string s ) {
+    return(broken_count_bits(s));
+}
+#endif
+
+
+// * Teile aus einem Array entfernen *** OBSOLETE
+deprecated mixed *exclude_array(mixed *arr,int from,int to)
+{
+  if (to<from)
+    to = from;
+  return arr[0..from-1]+arr[to+1..];
+}
+
diff --git a/secure/simul_efun/spare/spare/spare/README b/secure/simul_efun/spare/spare/spare/README
new file mode 100644
index 0000000..303429a
--- /dev/null
+++ b/secure/simul_efun/spare/spare/spare/README
@@ -0,0 +1,20 @@
+simul_efun 
+---------- 
+Das simul_efun Objekt /secure/simul_efun/simul_efun benutzt die Dateien
+in /secure/simul_efun.
+Im Verzeichnis /secure/simul_efun/spare ist eine Sicherheitskopie von allen 
+Dateien in /secure/simul_efun.
+
+Falls /secure/simul_efun/simul_efun.c nicht ladbar ist, wird 
+/secure/simul_efun/spare/simul_efun.c als Ersatz versucht. Wenn auch das
+nicht ladbar ist, erfolgt ein Shutdown des Muds.
+
+Bei Aenderungen sollte _zuerst_ die normale simul efun editiert und
+anschliessend zerstoert/entladen werden, woraufhin sie implizit durch den
+Driver und Master neugeladen wird. Ist dies erfolgreich und die neue
+simul_efun laeuft (erst dann!) kopiert man die Dateien aus 
+/secure/simul_efun nach /secure/simul_efun/spare.
+
+Die Dateien hier sind mit Ausnahme der simul_efun.c selbst _nicht_ dazu 
+gedacht, geladen zu werden.
+
diff --git a/secure/simul_efun/spare/spare/spare/comm.c b/secure/simul_efun/spare/spare/spare/comm.c
new file mode 100644
index 0000000..ffb598c
--- /dev/null
+++ b/secure/simul_efun/spare/spare/spare/comm.c
@@ -0,0 +1,125 @@
+#include <living/comm.h>
+
+// Sendet an alle Objekte in room und room selber durch Aufruf von
+// ReceiveMsg().
+varargs void send_room(object|string room, string msg, int msg_type,
+                       string msg_action, string msg_prefix, object *exclude,
+                       object origin)
+{
+  if (stringp(room))
+    room=load_object(room);
+  
+  origin ||= previous_object();
+  object *dest = exclude ? all_inventory(room) - exclude :
+                           all_inventory(room);
+
+  dest->ReceiveMsg(msg, msg_type, msg_action, msg_prefix, origin);
+  room->ReceiveMsg(msg, msg_type, msg_action, msg_prefix, origin);
+}
+
+static int _shout_filter( object ob, string pat )
+{
+    string *ignore;
+
+    if ( !environment(ob) )
+       return 0;
+
+    return sizeof( regexp( ({ object_name( environment(ob) ) }), pat ) );
+}
+
+varargs void shout( string s, mixed where ){
+    object *u;
+    string *pfade;
+
+    if ( !sizeof( u = users() - ({ this_player() }) ) )
+       return;
+
+    if ( !where )
+       pfade = ({ "/" });
+    else if ( intp(where) )
+       pfade =
+           ({ implode( efun::explode( object_name( environment(this_player()) ),
+                                   "/" )[0..2], "/" ) + "/" });
+    else if ( stringp(where) )
+       pfade = ({ where });
+    else
+       pfade = where;
+
+    u = filter( u, "_shout_filter", ME, implode( pfade, "|" ) );
+    u->ReceiveMsg(s, MT_COMM|MT_FAR|MSG_DONT_WRAP|MSG_DONT_STORE,
+                  MA_SHOUT_SEFUN, 0, previous_object());
+}
+
+
+#if __VERSION__ > "3.3.718"
+// This sefun replaces the deprecated efun cat().
+#define CAT_MAX_LINES 50
+varargs int cat(string file, int start, int num)
+{
+    if (extern_call())
+        set_this_object(previous_object());
+
+    int more;
+
+    if (num < 0 || !this_player())
+        return 0;
+
+    if (!start)
+        start = 1;
+
+    if (!num || num > CAT_MAX_LINES) {
+        num = CAT_MAX_LINES;
+        more = sizeof(read_file(file, start+num, 1));
+    }
+
+    string txt = read_file(file, start, num);
+    if (!txt)
+        return 0;
+
+    efun::tell_object(this_player(), txt);
+
+    if (more)
+        efun::tell_object(this_player(), "*****TRUNCATED****\n");
+
+    return sizeof(txt & "\n");
+}
+#undef CAT_MAX_LINES
+#endif // __VERSION__ > "3.3.718"
+
+#if __VERSION__ > "3.3.719"
+// This sefun replaces the deprecated efun tail().
+#define TAIL_MAX_BYTES 1000
+varargs int tail(string file)
+{
+    if (extern_call())
+        set_this_object(previous_object());
+
+    if (!stringp(file) || !this_player())
+        return 0;
+
+    string txt = read_bytes(file, -(TAIL_MAX_BYTES + 80), (TAIL_MAX_BYTES + 80));
+    // read_bytes() returns 0 if the start of the section given by
+    // parameter #2 lies beyond the beginning of the file.
+    // In this case we simply try and read the entire file.
+    if (!stringp(txt) && file_size(file) < TAIL_MAX_BYTES+80)
+      txt = read_file(file);
+    // Exit if still nothing could be read.
+    if (!stringp(txt))
+      return 0;
+
+    // cut off first (incomplete) line
+    int index = strstr(txt, "\n");
+    if (index > -1) {
+        if (index + 1 < sizeof(txt))
+            txt = txt[index+1..];
+        else
+            txt = "";
+    }
+
+    efun::tell_object(this_player(), txt);
+
+    return 1;
+}
+#undef TAIL_MAX_BYTES
+#endif // __VERSION__ > "3.3.719"
+
diff --git a/secure/simul_efun/spare/spare/spare/debug_info.c b/secure/simul_efun/spare/spare/spare/debug_info.c
new file mode 100644
index 0000000..d607c2f
--- /dev/null
+++ b/secure/simul_efun/spare/spare/spare/debug_info.c
@@ -0,0 +1,434 @@
+/* This sefun is to provide a replacement for the efun debug_info().
+ * Feel free to add it to your mudlibs, if you have much code relying on that.
+ */
+
+#if ! __EFUN_DEFINED__(debug_info)
+
+#include <driver_info.h>
+#include <debug_info.h>
+
+mixed debug_info(int what, varargs mixed* args)
+{
+    if (sizeof(args) > 2)
+        raise_error("Too many arguments to debug_info\n");
+
+    switch(what)
+    {
+        default:
+            raise_error(sprintf("Illegal value %d for debug_info().\n", what));
+
+        case DINFO_OBJECT:
+        {
+            object ob;
+
+            if (sizeof(args) != 1)
+                raise_error("bad number of arguments to debug_info\n");
+            if (!objectp(args[0]))
+                raise_error("bag arg 2 to debug_info().\n");
+
+            ob = args[0];
+            printf("O_HEART_BEAT      : %s\n", efun::object_info(ob, OC_HEART_BEAT)       ? "TRUE" : "FALSE");
+            printf("O_ENABLE_COMMANDS : %s\n", efun::object_info(ob, OC_COMMANDS_ENABLED) ? "TRUE" : "FALSE");
+            printf("O_CLONE           : %s\n", efun::clonep(ob)                           ? "TRUE" : "FALSE");
+            printf("O_DESTRUCTED      : FALSE\n");
+            printf("O_SWAPPED         : %s\n", efun::object_info(ob, OI_SWAPPED)          ? "TRUE" : "FALSE");
+            printf("O_ONCE_INTERACTIVE: %s\n", efun::object_info(ob, OI_ONCE_INTERACTIVE) ? "TRUE" : "FALSE");
+            printf("O_RESET_STATE     : %s\n", efun::object_info(ob, OI_RESET_STATE)      ? "TRUE" : "FALSE");
+            printf("O_WILL_CLEAN_UP   : %s\n", efun::object_info(ob, OI_WILL_CLEAN_UP)    ? "TRUE" : "FALSE");
+            printf("O_REPLACED        : %s\n", efun::object_info(ob, OI_REPLACED)         ? "TRUE" : "FALSE");
+            printf("time_reset  : %d\n",       efun::object_info(ob, OI_NEXT_RESET_TIME));
+            printf("time_of_ref : %d\n",       efun::object_info(ob, OI_LAST_REF_TIME));
+            printf("ref         : %d\n",       efun::object_info(ob, OI_OBJECT_REFS));
+
+            int gticks = efun::object_info(ob, OI_GIGATICKS);
+            if(gticks)
+                printf("evalcost   :  %d%09d\n", gticks, efun::object_info(ob, OI_TICKS));
+            else
+                printf("evalcost   :  %d\n",   efun::object_info(ob, OI_TICKS));
+
+            printf("swap_num    : %d\n",       efun::object_info(ob, OI_SWAP_NUM));
+            printf("name        : '%s'\n",     efun::object_name(ob));
+            printf("load_name   : '%s'\n",     efun::load_name(ob));
+
+            object next_ob = efun::object_info(ob, OI_OBJECT_NEXT);
+            if (next_ob)
+                printf("next_all    : OBJ(%s)\n", efun::object_name(next_ob));
+
+            object prev_ob = efun::object_info(ob, OI_OBJECT_PREV);
+            if (prev_ob)
+                printf("Previous object in object list: OBJ(%s)\n", efun::object_name(prev_ob));
+            else
+                printf("This object is the head of the object list.\n");
+            break;
+        }
+
+        case DINFO_MEMORY:
+        {
+            object ob;
+
+            if (sizeof(args) != 1)
+                raise_error("bad number of arguments to debug_info\n");
+            if (!objectp(args[0]))
+                raise_error("bag arg 2 to debug_info().\n");
+
+            ob = args[0];
+
+            printf("program ref's %3d\n",   efun::object_info(ob, OI_PROG_REFS));
+            printf("Name: '%s'\n",          efun::program_name(ob));
+            printf("program size    %6d\n", efun::object_info(ob, OI_PROG_SIZE));
+            printf("num func's:  %3d (%4d)\n",
+                                            efun::object_info(ob, OI_NUM_FUNCTIONS),
+                                            efun::object_info(ob, OI_SIZE_FUNCTIONS));
+            printf("num vars:    %3d (%4d)\n",
+                                            efun::object_info(ob, OI_NUM_VARIABLES),
+                                            efun::object_info(ob, OI_SIZE_VARIABLES));
+
+            printf("num strings: %3d (%4d) : overhead %d + data %d (%d)\n",
+                                            efun::object_info(ob, OI_NUM_STRINGS),
+                                            efun::object_info(ob, OI_SIZE_STRINGS) + efun::object_info(ob, OI_SIZE_STRINGS_DATA),
+                                            efun::object_info(ob, OI_SIZE_STRINGS),
+                                            efun::object_info(ob, OI_SIZE_STRINGS_DATA),
+                                            efun::object_info(ob, OI_SIZE_STRINGS_DATA_TOTAL));
+
+            printf("num inherits %3d (%4d)\n",
+                                            efun::object_info(ob, OI_NUM_INHERITED),
+                                            efun::object_info(ob, OI_SIZE_INHERITED));
+
+            printf("total size      %6d\n", efun::object_info(ob, OI_PROG_SIZE_TOTAL));
+
+            printf("data size       %6d (%6d\n",
+                                            efun::object_info(ob, OI_DATA_SIZE),
+                                            efun::object_info(ob, OI_DATA_SIZE_TOTAL));
+            break;
+        }
+
+        case DINFO_OBJLIST:
+        {
+            if (sizeof(args) == 0)
+                raise_error("bad number of arguments to debug_info\n");
+
+            if (sizeof(args) == 1)
+            {
+                object * obs = efun::objects(args[0], 1);
+                return obs[0];
+            }
+            else
+                return efun::objects(args[0], args[1]);
+            break;
+        }
+
+        case DINFO_MALLOC:
+            write(debug_info(DINFO_STATUS, "malloc"));
+            break;
+
+        case DINFO_STATUS:
+        {
+            string which;
+            int opt;
+
+            if (sizeof(args) > 1)
+                raise_error("bad number of arguments to debug_info\n");
+
+            if (!sizeof(args) || !args[0])
+                which = "";
+            else if(stringp(args[0]))
+                which = args[0];
+            else
+                raise_error("bag arg 2 to debug_info().\n");
+
+            switch(which)
+            {
+                case "":
+                    opt = DI_STATUS_TEXT_MEMORY;
+                    break;
+
+                case "tables":
+                    opt = DI_STATUS_TEXT_TABLES;
+                    break;
+
+                case "swap":
+                    opt = DI_STATUS_TEXT_SWAP;
+                    break;
+
+                case "malloc":
+                    opt = DI_STATUS_TEXT_MALLOC;
+                    break;
+
+                case "malloc extstats":
+                    opt = DI_STATUS_TEXT_MALLOC_EXTENDED;
+                    break;
+
+                default:
+                    return 0;
+            }
+
+            return efun::driver_info(opt);
+        }
+
+        case DINFO_DUMP:
+        {
+            int opt;
+
+            if (!sizeof(args))
+                raise_error("bad number of arguments to debug_info\n");
+
+            if(!stringp(args[0]))
+                raise_error("bag arg 2 to debug_info().\n");
+
+            switch(args[0])
+            {
+                case "objects":
+                    opt = DDI_OBJECTS;
+                    break;
+
+                case "destructed":
+                    opt = DDI_OBJECTS_DESTRUCTED;
+                    break;
+
+                case "opcodes":
+                    opt = DDI_OPCODES;
+                    break;
+
+                case "memory":
+                    opt = DDI_MEMORY;
+                    break;
+
+                default:
+                    raise_error(sprintf("Bad argument '%s' to debug_info(DINFO_DUMP).\n", args[0]));
+                    return 0;
+            }
+
+            return efun::dump_driver_info(opt, args[1..1]...);
+        }
+
+        case DINFO_DATA:
+        {
+            mixed * result;
+
+            if (!sizeof(args))
+                raise_error("bad number of arguments to debug_info\n");
+
+            if (!intp(args[0]))
+                raise_error("bag arg 2 to debug_info().\n");
+
+            if (sizeof(args) == 2 && !intp(args[1]))
+                raise_error("bag arg 3 to debug_info().\n");
+
+            switch(args[0])
+            {
+                case DID_STATUS:
+                    result = allocate(DID_STATUS_MAX);
+
+                    result[DID_ST_ACTIONS]               = efun::driver_info(DI_NUM_ACTIONS);
+                    result[DID_ST_ACTIONS_SIZE]          = efun::driver_info(DI_SIZE_ACTIONS);
+                    result[DID_ST_SHADOWS]               = efun::driver_info(DI_NUM_SHADOWS);
+                    result[DID_ST_SHADOWS_SIZE]          = efun::driver_info(DI_SIZE_SHADOWS);
+
+                    result[DID_ST_OBJECTS]               = efun::driver_info(DI_NUM_OBJECTS);
+                    result[DID_ST_OBJECTS_SIZE]          = efun::driver_info(DI_SIZE_OBJECTS);
+                    result[DID_ST_OBJECTS_SWAPPED]       = efun::driver_info(DI_NUM_OBJECTS_SWAPPED);
+                    result[DID_ST_OBJECTS_SWAP_SIZE]     = efun::driver_info(DI_SIZE_OBJECTS_SWAPPED);
+                    result[DID_ST_OBJECTS_LIST]          = efun::driver_info(DI_NUM_OBJECTS_IN_LIST);
+                    result[DID_ST_OBJECTS_NEWLY_DEST]    = efun::driver_info(DI_NUM_OBJECTS_NEWLY_DESTRUCTED);
+                    result[DID_ST_OBJECTS_DESTRUCTED]    = efun::driver_info(DI_NUM_OBJECTS_DESTRUCTED);
+                    result[DID_ST_OBJECTS_PROCESSED]     = efun::driver_info(DI_NUM_OBJECTS_LAST_PROCESSED);
+                    result[DID_ST_OBJECTS_AVG_PROC]      = efun::driver_info(DI_LOAD_AVERAGE_PROCESSED_OBJECTS_RELATIVE);
+
+                    result[DID_ST_OTABLE]                = efun::driver_info(DI_NUM_OBJECTS_IN_TABLE);
+                    result[DID_ST_OTABLE_SLOTS]          = efun::driver_info(DI_NUM_OBJECT_TABLE_SLOTS);
+                    result[DID_ST_OTABLE_SIZE]           = efun::driver_info(DI_SIZE_OBJECT_TABLE);
+
+                    result[DID_ST_HBEAT_OBJS]            = efun::driver_info(DI_NUM_HEARTBEATS);
+                    result[DID_ST_HBEAT_CALLS]           = efun::driver_info(DI_NUM_HEARTBEAT_ACTIVE_CYCLES);
+                    result[DID_ST_HBEAT_CALLS_TOTAL]     = efun::driver_info(DI_NUM_HEARTBEAT_TOTAL_CYCLES);
+                    result[DID_ST_HBEAT_SLOTS]           = efun::driver_info(DI_NUM_HEARTBEATS);
+                    result[DID_ST_HBEAT_SIZE]            = efun::driver_info(DI_SIZE_HEARTBEATS);
+                    result[DID_ST_HBEAT_PROCESSED]       = efun::driver_info(DI_NUM_HEARTBEATS_LAST_PROCESSED);
+                    result[DID_ST_HBEAT_AVG_PROC]        = efun::driver_info(DI_LOAD_AVERAGE_PROCESSED_HEARTBEATS_RELATIVE);
+
+                    result[DID_ST_CALLOUTS]              = efun::driver_info(DI_NUM_CALLOUTS);
+                    result[DID_ST_CALLOUT_SIZE]          = efun::driver_info(DI_SIZE_CALLOUTS);
+
+                    result[DID_ST_ARRAYS]                = efun::driver_info(DI_NUM_ARRAYS);
+                    result[DID_ST_ARRAYS_SIZE]           = efun::driver_info(DI_SIZE_ARRAYS);
+
+                    result[DID_ST_MAPPINGS]              = efun::driver_info(DI_NUM_MAPPINGS);
+                    result[DID_ST_MAPPINGS_SIZE]         = efun::driver_info(DI_SIZE_MAPPINGS);
+                    result[DID_ST_HYBRID_MAPPINGS]       = efun::driver_info(DI_NUM_MAPPINGS_HYBRID);
+                    result[DID_ST_HASH_MAPPINGS]         = efun::driver_info(DI_NUM_MAPPINGS_HASH);
+
+                    result[DID_ST_STRUCTS]               = efun::driver_info(DI_NUM_STRUCTS);
+                    result[DID_ST_STRUCTS_SIZE]          = efun::driver_info(DI_SIZE_STRUCTS);
+                    result[DID_ST_STRUCT_TYPES]          = efun::driver_info(DI_NUM_STRUCT_TYPES);
+                    result[DID_ST_STRUCT_TYPES_SIZE]     = efun::driver_info(DI_SIZE_STRUCT_TYPES);
+
+                    result[DID_ST_PROGS]                 = efun::driver_info(DI_NUM_PROGS);
+                    result[DID_ST_PROGS_SIZE]            = efun::driver_info(DI_SIZE_PROGS);
+
+                    result[DID_ST_PROGS_SWAPPED]         = efun::driver_info(DI_NUM_PROGS_SWAPPED);
+                    result[DID_ST_PROGS_SWAP_SIZE]       = efun::driver_info(DI_SIZE_PROGS_SWAPPED);
+
+                    result[DID_ST_USER_RESERVE]          = efun::driver_info(DI_MEMORY_RESERVE_USER);
+                    result[DID_ST_MASTER_RESERVE]        = efun::driver_info(DI_MEMORY_RESERVE_MASTER);
+                    result[DID_ST_SYSTEM_RESERVE]        = efun::driver_info(DI_MEMORY_RESERVE_SYSTEM);
+
+                    result[DID_ST_ADD_MESSAGE]           = efun::driver_info(DI_NUM_MESSAGES_OUT);
+                    result[DID_ST_PACKETS]               = efun::driver_info(DI_NUM_PACKETS_OUT);
+                    result[DID_ST_PACKET_SIZE]           = efun::driver_info(DI_SIZE_PACKETS_OUT);
+                    result[DID_ST_PACKETS_IN]            = efun::driver_info(DI_NUM_PACKETS_IN);
+                    result[DID_ST_PACKET_SIZE_IN]        = efun::driver_info(DI_SIZE_PACKETS_IN);
+
+                    result[DID_ST_APPLY]                 = efun::driver_info(DI_NUM_FUNCTION_NAME_CALLS);
+                    result[DID_ST_APPLY_HITS]            = efun::driver_info(DI_NUM_FUNCTION_NAME_CALL_HITS);
+
+                    result[DID_ST_STRINGS]               = efun::driver_info(DI_NUM_VIRTUAL_STRINGS);
+                    result[DID_ST_STRING_SIZE]           = efun::driver_info(DI_SIZE_STRINGS);
+                    result[DID_ST_STR_TABLE_SIZE]        = efun::driver_info(DI_SIZE_STRING_TABLE);
+                    result[DID_ST_STR_OVERHEAD]          = efun::driver_info(DI_SIZE_STRING_OVERHEAD);
+                    result[DID_ST_UNTABLED]              = efun::driver_info(DI_NUM_STRINGS_UNTABLED);
+                    result[DID_ST_UNTABLED_SIZE]         = efun::driver_info(DI_SIZE_STRINGS_UNTABLED);
+                    result[DID_ST_TABLED]                = efun::driver_info(DI_NUM_STRINGS_TABLED);
+                    result[DID_ST_TABLED_SIZE]           = efun::driver_info(DI_SIZE_STRINGS_TABLED);
+                    result[DID_ST_STR_SEARCHES]          = efun::driver_info(DI_NUM_STRING_TABLE_LOOKUPS_BY_INDEX);
+                    result[DID_ST_STR_SEARCHLEN]         = efun::driver_info(DI_NUM_STRING_TABLE_LOOKUP_STEPS_BY_INDEX);
+                    result[DID_ST_STR_SEARCHES_BYVALUE]  = efun::driver_info(DI_NUM_STRING_TABLE_LOOKUPS_BY_VALUE);
+                    result[DID_ST_STR_SEARCHLEN_BYVALUE] = efun::driver_info(DI_NUM_STRING_TABLE_LOOKUP_STEPS_BY_VALUE);
+                    result[DID_ST_STR_CHAINS]            = efun::driver_info(DI_NUM_STRING_TABLE_SLOTS_USED);
+                    result[DID_ST_STR_ADDED]             = efun::driver_info(DI_NUM_STRING_TABLE_STRINGS_ADDED);
+                    result[DID_ST_STR_DELETED]           = efun::driver_info(DI_NUM_STRING_TABLE_STRINGS_REMOVED);
+                    result[DID_ST_STR_COLLISIONS]        = efun::driver_info(DI_NUM_STRING_TABLE_COLLISIONS);
+                    result[DID_ST_STR_FOUND]             = efun::driver_info(DI_NUM_STRING_TABLE_HITS_BY_INDEX);
+                    result[DID_ST_STR_FOUND_BYVALUE]     = efun::driver_info(DI_NUM_STRING_TABLE_HITS_BY_VALUE);
+
+                    result[DID_ST_RX_CACHED]             = efun::driver_info(DI_NUM_REGEX);
+                    result[DID_ST_RX_TABLE]              = efun::driver_info(DI_NUM_REGEX_TABLE_SLOTS);
+                    result[DID_ST_RX_TABLE_SIZE]         = efun::driver_info(DI_SIZE_REGEX);
+                    result[DID_ST_RX_REQUESTS]           = efun::driver_info(DI_NUM_REGEX_LOOKUPS);
+                    result[DID_ST_RX_REQ_FOUND]          = efun::driver_info(DI_NUM_REGEX_LOOKUP_HITS);
+                    result[DID_ST_RX_REQ_COLL]           = efun::driver_info(DI_NUM_REGEX_LOOKUP_COLLISIONS);
+
+                    result[DID_ST_MB_FILE]               = efun::driver_info(DI_SIZE_BUFFER_FILE);
+                    result[DID_ST_MB_SWAP]               = efun::driver_info(DI_SIZE_BUFFER_SWAP);
+
+                    result[DID_ST_BOOT_TIME]             = efun::driver_info(DI_BOOT_TIME);
+                    break;
+
+                case DID_SWAP:
+                    result = allocate(DID_SWAP_MAX);
+
+                    result[DID_SW_PROGS]                 = efun::driver_info(DI_NUM_PROGS_SWAPPED);
+                    result[DID_SW_PROG_SIZE]             = efun::driver_info(DI_SIZE_PROGS_SWAPPED);
+                    result[DID_SW_PROG_UNSWAPPED]        = efun::driver_info(DI_NUM_PROGS_UNSWAPPED);
+                    result[DID_SW_PROG_U_SIZE]           = efun::driver_info(DI_SIZE_PROGS_UNSWAPPED);
+                    result[DID_SW_VARS]                  = efun::driver_info(DI_NUM_OBJECTS_SWAPPED);
+                    result[DID_SW_VAR_SIZE]              = efun::driver_info(DI_SIZE_OBJECTS_SWAPPED);
+                    result[DID_SW_FREE]                  = efun::driver_info(DI_NUM_SWAP_BLOCKS_FREE);
+                    result[DID_SW_FREE_SIZE]             = efun::driver_info(DI_SIZE_SWAP_BLOCKS_FREE);
+                    result[DID_SW_FILE_SIZE]             = efun::driver_info(DI_SIZE_SWAP_BLOCKS);
+                    result[DID_SW_REUSED]                = efun::driver_info(DI_SIZE_SWAP_BLOCKS_REUSED);
+                    result[DID_SW_SEARCHES]              = efun::driver_info(DI_NUM_SWAP_BLOCKS_REUSE_LOOKUPS);
+                    result[DID_SW_SEARCH_LEN]            = efun::driver_info(DI_NUM_SWAP_BLOCKS_REUSE_LOOKUP_STEPS);
+                    result[DID_SW_F_SEARCHES]            = efun::driver_info(DI_NUM_SWAP_BLOCKS_FREE_LOOKUPS);
+                    result[DID_SW_F_SEARCH_LEN]          = efun::driver_info(DI_NUM_SWAP_BLOCKS_FREE_LOOKUP_STEPS);
+                    result[DID_SW_COMPACT]               = efun::driver_info(DC_SWAP_COMPACT_MODE);
+                    result[DID_SW_RECYCLE_FREE]          = efun::driver_info(DI_SWAP_RECYCLE_PHASE);
+                    break;
+
+                case DID_MEMORY:
+                    result = allocate(DID_MEMORY_MAX);
+
+                    result[DID_MEM_NAME]                 = efun::driver_info(DI_MEMORY_ALLOCATOR_NAME);
+                    result[DID_MEM_SBRK]                 = efun::driver_info(DI_NUM_SYS_ALLOCATED_BLOCKS);
+                    result[DID_MEM_SBRK_SIZE]            = efun::driver_info(DI_SIZE_SYS_ALLOCATED_BLOCKS);
+                    result[DID_MEM_LARGE]                = efun::driver_info(DI_NUM_LARGE_BLOCKS_ALLOCATED);
+                    result[DID_MEM_LARGE_SIZE]           = efun::driver_info(DI_SIZE_LARGE_BLOCKS_ALLOCATED);
+                    result[DID_MEM_LFREE]                = efun::driver_info(DI_NUM_LARGE_BLOCKS_FREE);
+                    result[DID_MEM_LFREE_SIZE]           = efun::driver_info(DI_SIZE_LARGE_BLOCKS_FREE);
+                    result[DID_MEM_LWASTED]              = efun::driver_info(DI_NUM_LARGE_BLOCKS_WASTE);
+                    result[DID_MEM_LWASTED_SIZE]         = efun::driver_info(DI_SIZE_LARGE_BLOCKS_WASTE);
+                    result[DID_MEM_CHUNK]                = efun::driver_info(DI_NUM_SMALL_BLOCK_CHUNKS);
+                    result[DID_MEM_CHUNK_SIZE]           = efun::driver_info(DI_SIZE_SMALL_BLOCK_CHUNKS);
+                    result[DID_MEM_SMALL]                = efun::driver_info(DI_NUM_SMALL_BLOCKS_ALLOCATED);
+                    result[DID_MEM_SMALL_SIZE]           = efun::driver_info(DI_SIZE_SMALL_BLOCKS_ALLOCATED);
+                    result[DID_MEM_SFREE]                = efun::driver_info(DI_NUM_SMALL_BLOCKS_FREE);
+                    result[DID_MEM_SFREE_SIZE]           = efun::driver_info(DI_SIZE_SMALL_BLOCKS_FREE);
+                    result[DID_MEM_SWASTED]              = efun::driver_info(DI_NUM_SMALL_BLOCKS_WASTE);
+                    result[DID_MEM_SWASTED_SIZE]         = efun::driver_info(DI_SIZE_SMALL_BLOCKS_WASTE);
+                    result[DID_MEM_MINC_CALLS]           = efun::driver_info(DI_NUM_INCREMENT_SIZE_CALLS);
+                    result[DID_MEM_MINC_SUCCESS]         = efun::driver_info(DI_NUM_INCREMENT_SIZE_CALL_SUCCESSES);
+                    result[DID_MEM_MINC_SIZE]            = efun::driver_info(DI_SIZE_INCREMENT_SIZE_CALL_DIFFS);
+                    result[DID_MEM_PERM]                 = efun::driver_info(DI_NUM_UNMANAGED_BLOCKS);
+                    result[DID_MEM_PERM_SIZE]            = efun::driver_info(DI_SIZE_UNMANAGED_BLOCKS);
+                    result[DID_MEM_CLIB]                 = efun::driver_info(DI_NUM_REPLACEMENT_MALLOC_CALLS);
+                    result[DID_MEM_CLIB_SIZE]            = efun::driver_info(DI_SIZE_REPLACEMENT_MALLOC_CALLS);
+                    result[DID_MEM_OVERHEAD]             = efun::driver_info(DI_SIZE_SMALL_BLOCK_OVERHEAD);
+                    result[DID_MEM_ALLOCATED]            = efun::driver_info(DI_SIZE_MEMORY_USED) + efun::driver_info(DI_SIZE_MEMORY_OVERHEAD);
+                    result[DID_MEM_USED]                 = efun::driver_info(DI_SIZE_MEMORY_USED);
+                    result[DID_MEM_TOTAL_UNUSED]         = efun::driver_info(DI_SIZE_MEMORY_UNUSED);
+                    result[DID_MEM_DEFRAG_CALLS]         = efun::driver_info(DI_NUM_MEMORY_DEFRAGMENTATION_CALLS_FULL) + efun::driver_info(DI_NUM_MEMORY_DEFRAGMENTATION_CALLS_TARGETED);
+                    result[DID_MEM_DEFRAG_CALLS_REQ]     = efun::driver_info(DI_NUM_MEMORY_DEFRAGMENTATION_CALLS_TARGETED);
+                    result[DID_MEM_DEFRAG_REQ_SUCCESS]   = efun::driver_info(DI_NUM_MEMORY_DEFRAGMENTATION_CALL_TARGET_HITS);
+                    result[DID_MEM_DEFRAG_BLOCKS_INSPECTED] = efun::driver_info(DI_NUM_MEMORY_DEFRAGMENTATION_BLOCKS_INSPECTED);
+                    result[DID_MEM_DEFRAG_BLOCKS_MERGED] = efun::driver_info(DI_NUM_MEMORY_DEFRAGMENTATION_BLOCKS_MERGED);
+                    result[DID_MEM_DEFRAG_BLOCKS_RESULT] = efun::driver_info(DI_NUM_MEMORY_DEFRAGMENTATION_BLOCKS_RESULTING);
+                    result[DID_MEM_AVL_NODES]            = efun::driver_info(DI_NUM_FREE_BLOCKS_AVL_NODES);
+                    result[DID_MEM_EXT_STATISTICS]       = efun::driver_info(DI_MEMORY_EXTENDED_STATISTICS);
+                    break;
+            }
+
+            if (sizeof(args) == 2)
+            {
+                int idx = args[0];
+                if (idx < 0 || idx >= sizeof(result))
+                    raise_error(sprintf("Illegal index for debug_info(): %d, expected 0..%d\n",
+                        idx, sizeof(result)-1));
+
+                return result[idx];
+            }
+            else
+                return result;
+        }
+
+        case DINFO_TRACE:
+        {
+            int which = DIT_CURRENT;
+
+            if (sizeof(args) > 1)
+                raise_error("bad number of arguments to debug_info\n");
+            if (sizeof(args))
+            {
+                if (!intp(args[0]))
+                    raise_error("bag arg 2 to debug_info().\n");
+                which = args[0];
+            }
+
+            switch (which)
+            {
+                case DIT_CURRENT:
+                    return efun::driver_info(DI_TRACE_CURRENT);
+
+                case DIT_ERROR:
+                    return efun::driver_info(DI_TRACE_LAST_ERROR) || ({ "No trace." });
+
+                case DIT_UNCAUGHT_ERROR:
+                    return efun::driver_info(DI_TRACE_LAST_UNCAUGHT_ERROR) || ({ "No trace." });
+
+                case DIT_STR_CURRENT:
+                    return efun::driver_info(DI_TRACE_CURRENT_AS_STRING);
+
+                case DIT_CURRENT_DEPTH:
+                    return efun::driver_info(DI_TRACE_CURRENT_DEPTH);
+
+                default:
+                    raise_error("bad arg 2 to debug_info().\n");
+            }
+
+        }
+
+        case DINFO_EVAL_NUMBER:
+            return efun::driver_info(DI_EVAL_NUMBER);
+    }
+    return 0;
+}
+
+#endif
diff --git a/secure/simul_efun/spare/spare/spare/enable_commands.c b/secure/simul_efun/spare/spare/spare/enable_commands.c
new file mode 100644
index 0000000..9d1c963
--- /dev/null
+++ b/secure/simul_efun/spare/spare/spare/enable_commands.c
@@ -0,0 +1,30 @@
+/* These sefuns are to provide a replacement for the efuns enable_commands()
+ * and disable_commands().
+ * Feel free to add it to your mudlibs, if you have much code relying on that.
+ */
+
+#include <configuration.h>
+
+#if ! __EFUN_DEFINED__(enable_commands)
+
+void enable_commands()
+{
+    object ob = efun::previous_object();
+
+    efun::configure_object(ob, OC_COMMANDS_ENABLED, 1);
+    efun::set_this_player(ob);
+}
+
+#endif
+
+#if ! __EFUN_DEFINED__(disable_commands)
+
+void disable_commands()
+{
+    object ob = efun::previous_object();
+
+    efun::configure_object(ob, OC_COMMANDS_ENABLED, 0);
+    efun::set_this_player(0);
+}
+
+#endif
diff --git a/secure/simul_efun/spare/spare/spare/hash.c b/secure/simul_efun/spare/spare/spare/hash.c
new file mode 100644
index 0000000..51bbf60
--- /dev/null
+++ b/secure/simul_efun/spare/spare/spare/hash.c
@@ -0,0 +1,17 @@
+#include "/sys/tls.h"
+
+deprecated string md5(mixed arg, varargs mixed* iterations)
+{
+    if (extern_call())
+         set_this_object(previous_object());
+
+    return hash(TLS_HASH_MD5, arg, iterations...);
+}
+
+deprecated string sha1(mixed arg, varargs mixed* iterations)
+{
+    if (extern_call())
+         set_this_object(previous_object());
+
+    return hash(TLS_HASH_SHA1, arg, iterations...);
+}
diff --git a/secure/simul_efun/spare/spare/spare/livings.c b/secure/simul_efun/spare/spare/spare/livings.c
new file mode 100644
index 0000000..160fcee
--- /dev/null
+++ b/secure/simul_efun/spare/spare/spare/livings.c
@@ -0,0 +1,348 @@
+// * living_name-Behandlung
+
+#define clean_log(s)
+//#define clean_log(s) log_file("CLEAN_SIM",ctime(time())[4..18]+": "+(s));
+
+private nosave mapping living_name_m, name_living_m, netdead;
+
+private void InitLivingData(mixed wizinfo) {
+  // living_name ist ein Pointer der auch in der extra_wizinfo gehalten 
+  // wird, so kann die simul_efun neu geladen werden, ohne dass dieses
+  // Mapping verloren geht
+  if (!mappingp(living_name_m = wizinfo[LIVING_NAME]))
+    living_name_m = wizinfo[LIVING_NAME] = m_allocate(0, 1);
+
+  // Gleiches gilt fuer das Mapping Name-Living
+  if (!mappingp(name_living_m = wizinfo[NAME_LIVING]))
+    name_living_m = wizinfo[NAME_LIVING] = m_allocate(0, 1);
+
+  // Netztote sind ebenfalls in der extra_wizinfo
+  if (!mappingp(netdead = wizinfo[NETDEAD_MAP]))
+    netdead = wizinfo[NETDEAD_MAP] = ([]);
+}
+
+public varargs string getuuid( object ob )
+{
+    mixed *ret;
+
+    if ( !objectp(ob) )
+       ob = previous_object();
+
+    if ( !query_once_interactive(ob) )
+       return getuid(ob);
+
+    ret = (mixed)master()->get_userinfo( getuid(ob) );
+
+    if ( !pointerp(ret) || sizeof(ret) < 5 )
+       return getuid(ob);
+
+    // Username + "_" + CreationDate
+    return ret[0] + "_" + ret[5];
+}
+
+void set_object_living_name(string livname, object obj)
+{
+  string old;
+  mixed a;
+  int i;
+
+  if (previous_object()==obj || previous_object()==this_object() ||
+      previous_object()==master())
+  {
+    if(!livname || !stringp(livname)) {
+      set_this_object(previous_object());
+      raise_error(sprintf("%O: illegal living name: %O\n", obj, livname));
+    }
+    if (old = living_name_m[obj]) {
+      if (pointerp(a = name_living_m[old])) {
+       a[member(a, obj)] = 0;
+      } else {
+       m_delete(name_living_m, old);
+      }
+    }
+    living_name_m[obj] = livname;
+    if (a = name_living_m[livname]) {
+      if (!pointerp(a)) {
+       name_living_m[livname] = ({a, obj});
+       return;
+      }
+      /* Try to reallocate entry from destructed object */
+      if ((i = member(a, 0)) >= 0) {
+       a[i] = obj;
+       return;
+      }
+      name_living_m[livname] = a + ({obj});
+      return;
+    }
+    name_living_m[livname] = obj;
+  }
+}
+
+void set_living_name(string livname)
+{
+  set_object_living_name(livname,previous_object());
+}
+
+void remove_living_name()
+{
+  string livname;
+
+  if (!previous_object())
+    return;
+  if (livname=living_name_m[previous_object()])
+  {
+    m_delete(living_name_m,previous_object());
+    if (objectp(name_living_m[livname]))
+    {
+      if (name_living_m[livname]==previous_object())
+       m_delete(name_living_m,livname);
+      return;
+    }
+    if (pointerp(name_living_m[livname]))
+    {
+      name_living_m[livname]-=({previous_object()});
+      if (!sizeof(name_living_m[livname]))
+       m_delete(name_living_m,livname);
+    }
+  }
+}
+
+void _set_netdead()
+{
+  if (query_once_interactive(previous_object()))
+    netdead[getuid(previous_object())]=previous_object();
+}
+
+void _remove_netdead()
+{
+  m_delete(netdead,getuid(previous_object()));
+}
+
+object find_netdead(string uuid)
+{
+  int i;
+  string uid;
+  // Wenn sscanf() 2 liefert, ist uuid auch ne uuid.
+  int is_uuid = sscanf(uuid, "%s_%d", uid, i) == 2;
+  if (!is_uuid)
+    uid = uuid;
+ 
+  if (is_uuid && getuuid(netdead[uid]) != uuid)
+      return 0;
+
+  return netdead[uid];
+}
+
+string *dump_netdead()
+{
+  return m_indices(netdead);
+}
+
+object find_living(string livname) {
+  mixed *a, r;
+  int i;
+
+  if (pointerp(r = name_living_m[livname])) {
+    if (member(r,0)>=0)
+      r-=({0});
+    if (!sizeof(r)){
+      m_delete(name_living_m,livname);
+      clean_log(sprintf("find_living loescht %s\n",livname));
+      return 0;
+    }
+    if ( !living(r = (a = r)[0])) {
+      for (i = sizeof(a); --i;) {
+       if (living(a[<i])) {
+         r = a[<i];
+         a[<i] = a[0];
+         return a[0] = r;
+       }
+      }
+    }
+    return r;
+  }
+  return living(r) && r;
+}
+
+object *find_livings(string livname)
+{
+  mixed r;
+
+  if (pointerp(r=name_living_m[livname]))
+    r-=({0});
+  else
+    if (objectp(r))
+      r=({r});
+  if (!pointerp(r)||!sizeof(r))
+    return 0;
+  return r;
+}
+
+object find_player(string uuid) {
+  object *objs;
+  mixed res;
+  int i;
+  string uid;
+
+  if (!stringp(uuid))
+    return 0;
+  // Wenn sscanf() 2 liefert, ist uuid auch ne uuid.
+  int is_uuid = sscanf(uuid, "%s_%d", uid, i) == 2;
+  if (!is_uuid)
+    uid = uuid;
+
+  if (pointerp(res = name_living_m[uid])) {
+    // zerstoerte Objekte ggf. entfernen
+    if (member(res,0)>=0) {
+      res-=({0});
+      name_living_m[uid] = res;
+    }
+    // ggf. Namen ohne Objekte entfernen.
+    if (!sizeof(res)){
+      m_delete(name_living_m,uid);
+      clean_log(sprintf("find_player loescht %s\n",uid));
+      return 0;
+    }
+    objs = res;
+    res = objs[0];
+    // Wenn das 0. Element der Spieler ist, sind wir fertig. Ansonsten suchen
+    // wir den Spieler und schieben ihn an die 0. Stelle im Array.
+    if ( !query_once_interactive(res)) {
+      for (i = sizeof(objs); --i;) {
+       if (objs[<i] && query_once_interactive(objs[<i])) {
+         res = objs[<i];
+         objs[<i] = objs[0];
+         objs[0] = res;
+         break;
+       }
+      }
+      res = (objectp(res) && query_once_interactive(res)) ? res : 0;
+    }
+    // else: in res steht der Spieler schon.
+  }
+  else if (objectp(res)) // r ist nen Einzelobjekt
+    res = query_once_interactive(res) ? res : 0;
+
+  // res ist jetzt ggf. der Spieler. Aber wenn der ne andere als die gesuchte
+  // UUID hat, ist das Ergebnis trotzdem 0.
+  if (is_uuid && getuuid(res)!=uuid)
+      return 0;
+
+  // endlich gefunden
+  return res;
+}
+
+private int check_match( string str, int players_only )
+{
+    mixed match;
+
+    if ( !(match = name_living_m[str]) )
+       return 0;
+
+    if ( !pointerp(match) )
+       match = ({ match });
+
+    match -= ({0});
+
+    if ( sizeof(match) ){
+       if ( players_only )
+           return sizeof(filter( match, #'query_once_interactive/*'*/ ))
+              > 0;
+       else
+           return 1;
+    }
+
+    m_delete( name_living_m, str );
+    clean_log( sprintf("check_match loescht %s\n", str) );
+    return 0;
+}
+
+//TODO:: string|string* exclude
+varargs mixed match_living( string str, int players_only, mixed exclude )
+{
+    int i, s;
+    mixed match, *user;
+
+    if ( !str || str == "" )
+       return 0;
+
+    if ( !pointerp(exclude) )
+       exclude = ({ exclude });
+
+    if ( member(exclude, str) < 0 && check_match(str, players_only) )
+       return str;
+
+    user = m_indices(name_living_m);
+    s = sizeof(str);
+    match = 0;
+
+    for ( i = sizeof(user); i--; )
+       if ( str == user[i][0..s-1] && member( exclude, user[i] ) < 0 )
+           if ( match )
+              return -1;
+           else
+              if ( check_match(user[i], players_only) )
+                  match = user[i];
+
+    if ( !match )
+       return -2;
+
+    return match;
+}
+
+mixed *dump_livings()
+{
+  return sort_array(m_indices(name_living_m),#'>);
+}
+
+// * regelmaessig Listen von Ballast befreien
+private void clean_name_living_m(string *keys, int left, int num)
+{
+  int i, j;
+  mixed a;
+
+  while (left && num--)
+  {
+    if (pointerp(a = name_living_m[keys[--left]]) && member(a, 0)>=0)
+    {
+      a-=({0});
+      name_living_m[keys[left]] = a;
+    }
+    if (!a || (pointerp(a) && !sizeof(a)))
+    {
+      clean_log("Toasting "+keys[left]+"\n");
+      m_delete(name_living_m, keys[left]);
+    } else clean_log(sprintf("KEEPING %s (%O)\n",keys[left],pointerp(a)?sizeof(a):a));
+  }
+  if (left)
+    efun::call_out(#'clean_name_living_m, 1, keys, left, 80);
+  else
+    clean_log("Clean process finished\n");
+}
+
+private void clean_netdead() {
+  int i;
+  string *s;
+  object ob;
+
+  s=m_indices(netdead);
+  for (i=sizeof(s)-1;i>=0;i--)
+    if (!objectp(ob=netdead[s[i]]) || interactive(ob))
+      m_delete(netdead,s[i]);
+}
+
+private void CleanLivingData() {
+  if (find_call_out(#'clean_name_living_m) < 0) {
+    clean_log("Starting clean process\n");
+    efun::call_out(#'clean_name_living_m,
+                 1,
+                 m_indices(name_living_m),
+                 sizeof(name_living_m),
+                 80
+                 );
+  }
+  efun::call_out(#'clean_netdead,2);
+}
+
+#undef clean_log
+
diff --git a/secure/simul_efun/spare/spare/spare/object_info.c b/secure/simul_efun/spare/spare/spare/object_info.c
new file mode 100644
index 0000000..ed7c009
--- /dev/null
+++ b/secure/simul_efun/spare/spare/spare/object_info.c
@@ -0,0 +1,106 @@
+/* This sefun is to provide the old semantics of the efun object_info().
+ * Feel free to add it to your mudlibs, if you have much code relying on that.
+ */
+
+#if __EFUN_DEFINED__(driver_info) /* Newer version is there */
+
+#include <objectinfo.h>
+#include <object_info.h>
+
+mixed object_info(object ob, int what, varargs int* index)
+{
+    mixed * result;
+
+    if (sizeof(index) > 1)
+        raise_error("Too many arguments to object_info\n");
+
+    switch(what)
+    {
+        default:
+            raise_error(sprintf("Illegal value %d for object_info().\n", what));
+
+        case OINFO_BASIC:
+        {
+            result = allocate(OIB_MAX);
+
+            result[OIB_HEART_BEAT]        = efun::object_info(ob, OC_HEART_BEAT);
+            result[OIB_IS_WIZARD]         = 0;   /* Not supported anymore. */
+            result[OIB_ENABLE_COMMANDS]   = efun::object_info(ob, OC_COMMANDS_ENABLED);
+            result[OIB_CLONE]             = efun::clonep(ob);
+            result[OIB_DESTRUCTED]        = 0;   /* Not possible anymore. */
+            result[OIB_SWAPPED]           = efun::object_info(ob, OI_SWAPPED);
+            result[OIB_ONCE_INTERACTIVE]  = efun::object_info(ob, OI_ONCE_INTERACTIVE);
+            result[OIB_RESET_STATE]       = efun::object_info(ob, OI_RESET_STATE);
+            result[OIB_WILL_CLEAN_UP]     = efun::object_info(ob, OI_WILL_CLEAN_UP);
+            result[OIB_LAMBDA_REFERENCED] = efun::object_info(ob, OI_LAMBDA_REFERENCED);
+            result[OIB_SHADOW]            = efun::object_info(ob, OI_SHADOW_PREV) && 1;
+            result[OIB_REPLACED]          = efun::object_info(ob, OI_REPLACED);
+            result[OIB_NEXT_RESET]        = efun::object_info(ob, OI_NEXT_RESET_TIME);
+            result[OIB_TIME_OF_REF]       = efun::object_info(ob, OI_LAST_REF_TIME);
+            result[OIB_REF]               = efun::object_info(ob, OI_OBJECT_REFS);
+            result[OIB_GIGATICKS]         = efun::object_info(ob, OI_GIGATICKS);
+            result[OIB_TICKS]             = efun::object_info(ob, OI_TICKS);
+            result[OIB_SWAP_NUM]          = efun::object_info(ob, OI_SWAP_NUM);
+            result[OIB_PROG_SWAPPED]      = efun::object_info(ob, OI_PROG_SWAPPED);
+            result[OIB_VAR_SWAPPED]       = efun::object_info(ob, OI_VAR_SWAPPED);
+            result[OIB_NAME]              = efun::object_name(ob);
+            result[OIB_LOAD_NAME]         = efun::load_name(ob);
+            result[OIB_NEXT_ALL]          = efun::object_info(ob, OI_OBJECT_NEXT);
+            result[OIB_PREV_ALL]          = efun::object_info(ob, OI_OBJECT_PREV);
+            result[OIB_NEXT_CLEANUP]      = efun::object_info(ob, OI_NEXT_CLEANUP_TIME);
+            break;
+        }
+
+        case OINFO_POSITION:
+        {
+            result = allocate(OIP_MAX);
+
+            result[OIP_NEXT] = efun::object_info(ob, OI_OBJECT_NEXT);
+            result[OIP_PREV] = efun::object_info(ob, OI_OBJECT_PREV);
+            result[OIP_POS]  = efun::object_info(ob, OI_OBJECT_POS);
+            break;
+        }
+
+        case OINFO_MEMORY:
+        {
+            result = allocate(OIM_MAX);
+
+            result[OIM_REF]                = efun::object_info(ob, OI_PROG_REFS);
+            result[OIM_NAME]               = efun::program_name(ob);
+            result[OIM_PROG_SIZE]          = efun::object_info(ob, OI_PROG_SIZE);
+            result[OIM_NUM_FUNCTIONS]      = efun::object_info(ob, OI_NUM_FUNCTIONS);
+            result[OIM_SIZE_FUNCTIONS]     = efun::object_info(ob, OI_SIZE_FUNCTIONS);
+            result[OIM_NUM_VARIABLES]      = efun::object_info(ob, OI_NUM_VARIABLES);
+            result[OIM_SIZE_VARIABLES]     = efun::object_info(ob, OI_SIZE_VARIABLES);
+            result[OIM_NUM_STRINGS]        = efun::object_info(ob, OI_NUM_STRINGS);
+            result[OIM_SIZE_STRINGS]       = efun::object_info(ob, OI_SIZE_STRINGS);
+            result[OIM_SIZE_STRINGS_DATA]  = efun::object_info(ob, OI_SIZE_STRINGS_DATA);
+            result[OIM_SIZE_STRINGS_TOTAL] = efun::object_info(ob, OI_SIZE_STRINGS_DATA_TOTAL);
+            result[OIM_NUM_INHERITED]      = efun::object_info(ob, OI_NUM_INHERITED);
+            result[OIM_SIZE_INHERITED]     = efun::object_info(ob, OI_SIZE_INHERITED);
+            result[OIM_TOTAL_SIZE]         = efun::object_info(ob, OI_PROG_SIZE_TOTAL);
+            result[OIM_DATA_SIZE]          = efun::object_info(ob, OI_DATA_SIZE);
+            result[OIM_TOTAL_DATA_SIZE]    = efun::object_info(ob, OI_DATA_SIZE_TOTAL);
+            result[OIM_NO_INHERIT]         = efun::object_info(ob, OI_NO_INHERIT);
+            result[OIM_NO_CLONE]           = efun::object_info(ob, OI_NO_CLONE);
+            result[OIM_NO_SHADOW]          = efun::object_info(ob, OI_NO_SHADOW);
+            result[OIM_NUM_INCLUDES]       = efun::object_info(ob, OI_NUM_INCLUDED);
+            result[OIM_SHARE_VARIABLES]    = efun::object_info(ob, OI_SHARE_VARIABLES);
+            break;
+        }
+    }
+
+    if (sizeof(index))
+    {
+        int idx = index[0];
+        if (idx < 0 || idx >= sizeof(result))
+            raise_error(sprintf("Illegal index for object_info(): %d, expected 0..%d\n",
+                idx, sizeof(result)-1));
+
+        return result[idx];
+    }
+    else
+        return result;
+}
+
+#endif
diff --git a/secure/simul_efun/spare/spare/spare/query_editing.c b/secure/simul_efun/spare/spare/spare/query_editing.c
new file mode 100644
index 0000000..5146dcd
--- /dev/null
+++ b/secure/simul_efun/spare/spare/spare/query_editing.c
@@ -0,0 +1,17 @@
+/* This sefun is to provide a replacement for the efun query_editing().
+ * Feel free to add it to your mudlibs, if you have much code relying on that.
+ */
+
+#if ! __EFUN_DEFINED__(query_editing)
+
+#include <interactive_info.h>
+
+int|object query_editing(object ob)
+{
+    if(!efun::interactive(ob))
+        return 0;
+
+    return efun::interactive_info(ob, II_EDITING);
+}
+
+#endif
diff --git a/secure/simul_efun/spare/spare/spare/query_idle.c b/secure/simul_efun/spare/spare/spare/query_idle.c
new file mode 100644
index 0000000..93a32ae
--- /dev/null
+++ b/secure/simul_efun/spare/spare/spare/query_idle.c
@@ -0,0 +1,14 @@
+/* This sefun is to provide a replacement for the efun query_idle().
+ * Feel free to add it to your mudlibs, if you have much code relying on that.
+ */
+
+#if ! __EFUN_DEFINED__(query_idle)
+
+#include <interactive_info.h>
+
+int query_idle(object ob)
+{
+    return efun::interactive_info(ob, II_IDLE);
+}
+
+#endif
diff --git a/secure/simul_efun/spare/spare/spare/query_input_pending.c b/secure/simul_efun/spare/spare/spare/query_input_pending.c
new file mode 100644
index 0000000..dd8c311
--- /dev/null
+++ b/secure/simul_efun/spare/spare/spare/query_input_pending.c
@@ -0,0 +1,17 @@
+/* This sefun is to provide a replacement for the efun query_input_pending().
+ * Feel free to add it to your mudlibs, if you have much code relying on that.
+ */
+
+#if ! __EFUN_DEFINED__(query_input_pending)
+
+#include <interactive_info.h>
+
+object query_input_pending(object ob)
+{
+    if(!efun::interactive(ob))
+        return 0;
+
+    return efun::interactive_info(ob, II_INPUT_PENDING);
+}
+
+#endif
diff --git a/secure/simul_efun/spare/spare/spare/query_ip_name.c b/secure/simul_efun/spare/spare/spare/query_ip_name.c
new file mode 100644
index 0000000..1e74ea0
--- /dev/null
+++ b/secure/simul_efun/spare/spare/spare/query_ip_name.c
@@ -0,0 +1,48 @@
+/* This sefun is to provide a replacement for the efuns query_ip_name() and
+ * query_ip_number().
+ * Feel free to add it to your mudlibs, if you have much code relying on that.
+ */
+
+#if ! __EFUN_DEFINED__(query_ip_name)
+
+#include <interactive_info.h>
+
+private varargs string _query_ip_name(object player)
+{
+    object ob = player;
+    ob ||= efun::this_player();
+
+    player = efun::interactive_info(ob, II_IP_ADDRESS);
+    return efun::interactive_info(ob, II_IP_NAME);
+}
+
+private varargs string _query_ip_number(object player)
+{
+    object ob = player;
+    ob ||= efun::this_player();
+
+    player = efun::interactive_info(ob, II_IP_ADDRESS);
+    return efun::interactive_info(ob, II_IP_NUMBER);
+}
+
+// * Herkunfts-Ermittlung
+string query_ip_number(object ob)
+{
+  ob= ob || this_player();
+  if (!objectp(ob) || !interactive(ob)) return 0;
+  if(ob->query_realip() && (string)ob->query_realip()!="")
+  {
+    return (string)ob->query_realip();
+  }
+  return _query_ip_number(ob);
+}
+
+string query_ip_name(mixed ob)
+{
+  if ( !ob || objectp(ob) )
+      ob=_query_ip_number(ob);
+  return (string)"/p/daemon/iplookup"->host(ob);
+}
+
+#endif
+
diff --git a/secure/simul_efun/spare/spare/spare/query_limits.c b/secure/simul_efun/spare/spare/spare/query_limits.c
new file mode 100644
index 0000000..ecbf390
--- /dev/null
+++ b/secure/simul_efun/spare/spare/spare/query_limits.c
@@ -0,0 +1,14 @@
+/* This sefun is to provide a replacement for the efun query_limits().
+ * Feel free to add it to your mudlibs, if you have much code relying on that.
+ */
+
+#if ! __EFUN_DEFINED__(query_limits)
+
+#include <driver_info.h>
+
+varargs int* query_limits(int def)
+{
+    return efun::driver_info(def ? DC_DEFAULT_RUNTIME_LIMITS : DI_CURRENT_RUNTIME_LIMITS);
+}
+
+#endif
diff --git a/secure/simul_efun/spare/spare/spare/query_load_average.c b/secure/simul_efun/spare/spare/spare/query_load_average.c
new file mode 100644
index 0000000..3b36f0e
--- /dev/null
+++ b/secure/simul_efun/spare/spare/spare/query_load_average.c
@@ -0,0 +1,16 @@
+/* This sefun is to provide a replacement for the efun query_load_average().
+ * Feel free to add it to your mudlibs, if you have much code relying on that.
+ */
+
+#if ! __EFUN_DEFINED__(query_load_average)
+
+#include <driver_info.h>
+
+string query_load_average()
+{
+    return efun::sprintf("%.2f cmds/s, %.2f comp lines/s",
+        efun::driver_info(DI_LOAD_AVERAGE_COMMANDS),
+        efun::driver_info(DI_LOAD_AVERAGE_LINES));
+}
+
+#endif
diff --git a/secure/simul_efun/spare/spare/spare/query_mud_port.c b/secure/simul_efun/spare/spare/spare/query_mud_port.c
new file mode 100644
index 0000000..bff009d
--- /dev/null
+++ b/secure/simul_efun/spare/spare/spare/query_mud_port.c
@@ -0,0 +1,35 @@
+/* This sefun is to provide a replacement for the efun query_mud_port().
+ * Feel free to add it to your mudlibs, if you have much code relying on that.
+ */
+
+#if ! __EFUN_DEFINED__(query_mud_port)
+
+#include <interactive_info.h>
+#include <driver_info.h>
+
+int query_mud_port(varargs int*|object* args)
+{
+    if(!sizeof(args) && efun::this_player() && efun::interactive(this_player()))
+        return efun::interactive_info(this_player(), II_MUD_PORT);
+
+    if(sizeof(args) > 1)
+        raise_error("Too many arguments to query_mud_port\n");
+
+    if(sizeof(args) && efun::objectp(args[0]) && efun::interactive(args[0]))
+        return efun::interactive_info(args[0], II_MUD_PORT);
+
+    if(sizeof(args) && intp(args[0]))
+    {
+        int* ports = efun::driver_info(DI_MUD_PORTS);
+        if(args[0] < -1 || args[0] >= sizeof(ports))
+            raise_error(efun::sprintf("Bad arg 1 to query_mud_port(): value %d out of range.\n", args[0]));
+        else if(args[0] == -1)
+            return sizeof(ports);
+        else
+            return ports[args[0]];
+    }
+
+    return efun::driver_info(DI_MUD_PORTS)[0];
+}
+
+#endif
diff --git a/secure/simul_efun/spare/spare/spare/query_once_interactive.c b/secure/simul_efun/spare/spare/spare/query_once_interactive.c
new file mode 100644
index 0000000..bc7829f
--- /dev/null
+++ b/secure/simul_efun/spare/spare/spare/query_once_interactive.c
@@ -0,0 +1,14 @@
+/* This sefun is to provide a replacement for the efun query_once_interactive().
+ * Feel free to add it to your mudlibs, if you have much code relying on that.
+ */
+
+#if ! __EFUN_DEFINED__(query_once_interactive)
+
+#include <object_info.h>
+
+int query_once_interactive(object ob)
+{
+    return efun::object_info(ob, OI_ONCE_INTERACTIVE);
+}
+
+#endif
diff --git a/secure/simul_efun/spare/spare/spare/query_snoop.c b/secure/simul_efun/spare/spare/spare/query_snoop.c
new file mode 100644
index 0000000..e6a69c0
--- /dev/null
+++ b/secure/simul_efun/spare/spare/spare/query_snoop.c
@@ -0,0 +1,30 @@
+/* This sefun is to provide a replacement for the efun query_snoop().
+ * Feel free to add it to your mudlibs, if you have much code relying on that.
+ */
+
+
+#include <interactive_info.h>
+
+private object _query_snoop(object ob)
+{
+    if(!efun::interactive(ob))
+        return 0;
+
+    object prev = efun::previous_object();
+    efun::set_this_object(prev);
+#if ! __EFUN_DEFINED__(query_snoop)
+    return efun::interactive_info(ob, II_SNOOP_NEXT);
+#else
+    return efun::query_snoop(ob);
+#endif
+}
+
+nomask object query_snoop(object who) {
+  object snooper;
+
+  snooper=_query_snoop(who);
+  if (!snooper) return 0;
+  if (query_wiz_grp(snooper)>query_wiz_grp(getuid(previous_object())) &&
+      IS_ARCH(snooper)) return 0;
+  return snooper;
+}
diff --git a/secure/simul_efun/spare/spare/spare/set_heart_beat.c b/secure/simul_efun/spare/spare/spare/set_heart_beat.c
new file mode 100644
index 0000000..a3995eb
--- /dev/null
+++ b/secure/simul_efun/spare/spare/spare/set_heart_beat.c
@@ -0,0 +1,24 @@
+/* This sefun is to provide a replacement for the efun set_heart_beat().
+ * Feel free to add it to your mudlibs, if you have much code relying on that.
+ */
+
+#if ! __EFUN_DEFINED__(set_heart_beat)
+
+#include <configuration.h>
+
+int set_heart_beat(int flag)
+{
+    object ob = efun::previous_object();
+    int hb = efun::object_info(ob, OC_HEART_BEAT);
+
+    if (!flag == !hb)
+        return 0;
+
+    efun::configure_object(ob, OC_HEART_BEAT, flag);
+
+    return 1;
+}
+
+#endif
+
+
diff --git a/secure/simul_efun/spare/spare/spare/set_is_wizard.c b/secure/simul_efun/spare/spare/spare/set_is_wizard.c
new file mode 100644
index 0000000..001ccdb
--- /dev/null
+++ b/secure/simul_efun/spare/spare/spare/set_is_wizard.c
@@ -0,0 +1,137 @@
+/* These are the special commands from the driver that are activated with
+ * set_is_wizard(). These functions must be added to the player object.
+ * Also set_is_wizard() must be called in the corresponding player object,
+ * not as an (simul-)efun.
+ */
+
+#include <commands.h>
+#include <driver_info.h>
+
+/* is_wizard:
+ *  1: has actions,
+ *  0: never had actions,
+ * -1: had actions once.
+ */
+private nosave int is_wizard;
+
+private int driver_malloc(string str)
+{
+    if(is_wizard <= 0)
+        return 0;
+
+    if(!sizeof(str))
+    {
+        write(efun::driver_info(DI_STATUS_TEXT_MALLOC));
+        return 1;
+    }
+
+    if(str == "extstats")
+    {
+        write(efun::driver_info(DI_STATUS_TEXT_MALLOC_EXTENDED));
+        return 1;
+    }
+
+    return 0;
+}
+
+private int driver_dumpallobj(string str)
+{
+    if(is_wizard <= 0 || sizeof(str))
+        return 0;
+
+    write("Dumping to /OBJ_DUMP ... ");
+    efun::dump_driver_info(DDI_OBJECTS);
+    efun::dump_driver_info(DDI_OBJECTS_DESTRUCTED);
+    write("done\n");
+    return 1;
+}
+
+private int driver_opcdump(string str)
+{
+    if(is_wizard <= 0 || sizeof(str))
+        return 0;
+
+    efun::dump_driver_info(DDI_OPCODES);
+    return 1;
+}
+
+private int driver_status(string str)
+{
+    int opt;
+    if(is_wizard <= 0)
+        return 0;
+
+    switch(str || "")
+    {
+        case "":
+            opt = DI_STATUS_TEXT_MEMORY;
+            break;
+
+        case "tables":
+        case " tables":
+            opt = DI_STATUS_TEXT_TABLES;
+            break;
+
+        case "swap":
+        case " swap":
+            opt = DI_STATUS_TEXT_SWAP;
+            break;
+
+        case "malloc":
+        case " malloc":
+            opt = DI_STATUS_TEXT_MALLOC;
+            break;
+
+        case "malloc extstats":
+        case " malloc extstats":
+            opt = DI_STATUS_TEXT_MALLOC_EXTENDED;
+            break;
+
+        default:
+            return 0;
+    }
+
+    write(efun::driver_info(opt));
+    return 1;
+}
+
+int set_is_wizard(varargs <object|int>* args)
+{
+    int oldval = is_wizard;
+
+    if(!sizeof(args))
+        raise_error("Too few arguments to set_is_wizard\n");
+    if(sizeof(args) > 2)
+        raise_error("Too many arguments to set_is_wizard\n");
+    if(!objectp(args[0]))
+        raise_error("Bad arg 1 to set_is_wizard()\n");
+    if(args[0] != this_object())
+        raise_error("Only set_is_wizard for the current object supported\n");
+    if(this_player() != this_object())
+        raise_error("The current object must be this_player() for set_is_wizard()\n");
+    if(sizeof(args) == 2 && !intp(args[1]))
+        raise_error("Bad arg 2 to set_is_wizard()\n");
+
+    if(sizeof(args) == 2 && !args[1])
+    {
+        if(is_wizard > 0)
+            is_wizard = -1;
+    }
+    else if(sizeof(args) == 2 && args[1]<0)
+    {
+         // Just return the old value.
+    }
+    else
+    {
+        if(!is_wizard)
+        {
+            add_action(#'driver_malloc, "malloc");
+            add_action(#'driver_dumpallobj, "dumpallobj");
+            add_action(#'driver_opcdump, "opcdump");
+            add_action(#'driver_status, "status", AA_NOSPACE);
+        }
+        is_wizard = 1;
+    }
+
+    return oldval > 0;
+}
diff --git a/secure/simul_efun/spare/spare/spare/set_prompt.c b/secure/simul_efun/spare/spare/spare/set_prompt.c
new file mode 100644
index 0000000..0b59692
--- /dev/null
+++ b/secure/simul_efun/spare/spare/spare/set_prompt.c
@@ -0,0 +1,21 @@
+/* This sefun is to provide a replacement for the efun set_prompt().
+ * Feel free to add it to your mudlibs, if you have much code relying on that.
+ */
+
+#if ! __EFUN_DEFINED__(set_prompt)
+
+#include <configuration.h>
+
+varargs string|closure set_prompt(string|closure|int prompt, object ob)
+{
+    ob ||= efun::this_player();
+
+    mixed oldprompt = efun::interactive_info(ob, IC_PROMPT);
+
+    if(!intp(prompt))
+        efun::configure_interactive(ob, IC_PROMPT, prompt);
+
+    return oldprompt;
+}
+
+#endif
diff --git a/secure/simul_efun/spare/spare/spare/shadow.c b/secure/simul_efun/spare/spare/spare/shadow.c
new file mode 100644
index 0000000..7608c73
--- /dev/null
+++ b/secure/simul_efun/spare/spare/spare/shadow.c
@@ -0,0 +1,55 @@
+/* These sefuns are to provide a replacement for the efun query_shadowing()
+ * and the old semantics of the efun shadow().
+ * Feel free to add it to your mudlibs, if you have much code relying on that.
+ */
+
+#if ! __EFUN_DEFINED__(query_shadowing)
+
+#include <object_info.h>
+
+object query_shadowing(object ob)
+{
+    return efun::object_info(ob, OI_SHADOW_PREV);
+}
+
+private object _shadow(object ob, int flag)
+{
+    if(flag)
+    {
+        object shadower = efun::previous_object(1);
+        efun::set_this_object(shadower);
+
+        if (efun::shadow(ob))
+            return efun::object_info(shadower, OI_SHADOW_PREV);
+        else
+            return 0;
+    }
+    else
+        return efun::object_info(ob, OI_SHADOW_NEXT);
+}
+#else
+private object _shadow(object ob, int flag)
+{
+  set_this_object(previous_object(1));
+  return efun::shadow(ob, flag);
+}
+#endif
+
+
+public object shadow(object ob, int flag)
+{
+  object res = funcall(#'_shadow,ob, flag);
+  if (flag)
+    "/secure/shadowmaster"->RegisterShadow(previous_object());
+  return res;
+}
+
+private void _unshadow() {
+  set_this_object(previous_object(1));
+  efun::unshadow();
+}
+
+public void unshadow() {
+  funcall(#'_unshadow);
+  "/secure/shadowmaster"->UnregisterShadow(previous_object());
+}
diff --git a/secure/simul_efun/spare/spare/spare/simul_efun.c b/secure/simul_efun/spare/spare/spare/simul_efun.c
new file mode 100644
index 0000000..5b68789
--- /dev/null
+++ b/secure/simul_efun/spare/spare/spare/simul_efun.c
@@ -0,0 +1,1995 @@
+// MorgenGrauen MUDlib
+//
+// simul_efun.c -- simul efun's
+//
+// $Id: simul_efun.c 7408 2010-02-06 00:27:25Z Zesstra $
+#pragma strict_types,save_types,rtt_checks
+#pragma no_clone,no_shadow,no_inherit
+#pragma pedantic,range_check,warn_deprecated
+#pragma warn_empty_casts,warn_missing_return,warn_function_inconsistent
+
+// Absolute Pfade erforderlich - zum Zeitpunkt, wo der Master geladen
+// wird, sind noch keine Include-Pfade da ...
+
+#define SNOOPLOGFILE "SNOOP"
+#define ASNOOPLOGFILE "ARCH/SNOOP"
+
+#include "/secure/config.h"
+#include "/secure/wizlevels.h"
+#include "/sys/snooping.h"
+#include "/sys/language.h"
+#include "/sys/thing/properties.h"
+#include "/sys/wizlist.h"
+#include "/sys/erq.h"
+#include "/sys/lpctypes.h"
+#include "/sys/daemon.h"
+#include "/sys/player/base.h"
+#include "/sys/thing/description.h"
+#include "/sys/container.h"
+#include "/sys/defines.h"
+#include "/sys/telnet.h"
+#include "/sys/objectinfo.h"
+#include "/sys/files.h"
+#include "/sys/strings.h"
+#include "/sys/time.h"
+#include "/sys/lpctypes.h"
+#include "/sys/notify_fail.h"
+#include "/sys/tls.h"
+#include "/sys/input_to.h"
+#include "/sys/object_info.h"
+
+/* function prototypes
+ */
+string dtime(int wann);
+varargs int log_file(string file, string txt, int size_to_break);
+int query_wiz_level(mixed player);
+nomask varargs int snoop(object me, object you);
+varargs string country(mixed ip, string num);
+int query_wiz_grp(mixed wiz);
+public varargs object deep_present(mixed what, object ob);
+nomask int secure_level();
+nomask string secure_euid();
+public nomask int process_call();
+nomask mixed __create_player_dummy(string name);
+varargs string replace_personal(string str, mixed *obs, int caps);
+
+
+//replacements for dropped efuns in LD
+#if !__EFUN_DEFINED__(extract)
+varargs string extract(string str, int from, int to);
+#endif
+#if !__EFUN_DEFINED__(slice_array)
+varargs mixed slice_array(mixed array, int from, int to);
+#endif
+#if !__EFUN_DEFINED__(member_array)
+int member_array(mixed item, mixed arraystring);
+#endif
+
+// Include the different sub 'modules' of the simul_efun
+#include __DIR__"debug_info.c"
+#include __DIR__"enable_commands.c"
+#include __DIR__"hash.c"
+#include __DIR__"object_info.c"
+#include __DIR__"query_editing.c"
+#include __DIR__"query_idle.c"
+#include __DIR__"query_input_pending.c"
+#include __DIR__"query_ip_name.c"
+#include __DIR__"query_limits.c"
+#include __DIR__"query_load_average.c"
+#include __DIR__"query_mud_port.c"
+#include __DIR__"query_once_interactive.c"
+#include __DIR__"query_snoop.c"
+#include __DIR__"set_heart_beat.c"
+#if __BOOT_TIME__ < 1456261859
+#include __DIR__"set_prompt.c"
+#endif
+#include __DIR__"shadow.c"
+#include __DIR__"livings.c"
+#include __DIR__"comm.c"
+
+#define TO        efun::this_object()
+#define TI        efun::this_interactive()
+#define TP        efun::this_player()
+#define PO        efun::previous_object(0)
+#define LEVEL(x) query_wiz_level(x)
+#define NAME(x)  capitalize(getuid(x))
+
+#define DEBUG(x) if (find_player("zesstra")) \
+  tell_object(find_player("zesstra"),x)
+
+mixed dtime_cache = ({-1,""});
+
+#ifdef IOSTATS
+struct iostat_s {
+  string oname;
+  int time;
+  int size;
+};
+mixed saveo_stat = ({ 0,allocate(200, 0) });
+mixed restoreo_stat = ({ 0,allocate(200,0) });
+//mixed writefile_stat = ({ 0,allocate(100,(<iostat_s>)) });
+//mixed readfile_stat = ({ 0,allocate(100,(<iostat_s>)) });
+//mixed log_stat = ({ 0,allocate(100,(<iostat_s>)) });
+
+mixed ___iostats(int type) {
+  switch(type) {
+    case 1:
+      return saveo_stat;
+    case 2:
+      return restoreo_stat;
+/*    case 3:
+      return writefile_stat;
+    case 4:
+      return readfile_stat;
+    case 5:
+      return log_stat;
+      */
+  }
+  return 0;
+}
+#endif
+
+// Nicht jeder Magier muss die simul_efun entsorgen koennen.
+string NotifyDestruct(object caller) {
+    if( (caller!=this_object() && !ARCH_SECURITY) || process_call() ) {
+      return "Du darfst das simul_efun Objekt nicht zerstoeren!\n";
+    }
+    return 0;
+}
+
+public nomask void remove_interactive( object ob )
+{
+    if ( objectp(ob) && previous_object()
+        && object_name(previous_object())[0..7] != "/secure/"
+        && ((previous_object() != ob
+             && (ob != this_player() || ob != this_interactive()))
+            || (previous_object() == ob
+               && (this_player() && this_player() != ob
+                   || this_interactive() && this_interactive() != ob)) ) )
+
+       log_file( "PLAYERDEST",
+                sprintf( "%s: %O ausgeloggt von PO %O, TI %O, TP %O\n",
+                        dtime(time()), ob, previous_object(),
+                        this_interactive(), this_player() ) );
+
+    efun::remove_interactive(ob);
+}
+
+
+void ___updmaster()
+{
+    object ob;
+
+    //sollte nicht jeder duerfen.
+    if (process_call() || !ARCH_SECURITY)
+      raise_error("Illegal use of ___updmaster()!");
+
+    write("Removing old master ... ");
+    foreach(string file: 
+        get_dir("/secure/master/*.c",GETDIR_NAMES|GETDIR_UNSORTED|GETDIR_PATH)) {
+      if (ob = find_object(file))
+       efun::destruct(ob);
+    }
+    efun::destruct(efun::find_object("/secure/master"));
+    write("done.\nLoading again ... ");
+    load_object("/secure/master");
+
+    write("done.\n");
+}
+
+varargs string country(mixed ip, string num) {
+  mixed ret;
+
+  if(ret = (string)"/p/daemon/iplookup"->country(num || ip)) {
+    return ret;
+  } else return "???";
+}
+
+
+// * Snoopen und was dazugehoert
+static object find_snooped(object who)
+{
+  object *u;
+  int i;
+
+  for (i=sizeof(u=users())-1;i>=0;i--)
+    if (who==efun::interactive_info(u[i], II_SNOOP_NEXT))
+      return u[i];
+  return 0;
+}
+
+private string Lcut(string str) {
+  return str[5..11]+str[18..];
+}
+
+nomask varargs int snoop( object me, object you )
+{
+    int ret;
+    object snooper0, snooper, snooper2, snooper3;
+
+    if( !objectp(me) || me == you || !PO )
+       return 0;
+
+    snooper0 = find_snooped(me);
+
+     if(you){
+        if ( PO != me && query_wiz_grp(me) >= query_wiz_grp(geteuid(PO)) )
+            return 0;
+
+        if ( query_wiz_grp(me) <= query_wiz_grp(you) &&
+             !(you->QueryAllowSnoop(me)) )
+            if ( !IS_DEPUTY(me) || IS_ARCH(you) )
+               return 0;
+
+        if ( (snooper = efun::interactive_info(you, II_SNOOP_NEXT)) &&
+             query_wiz_grp(snooper) >= query_wiz_grp(me) ){
+            if ( (int)snooper->QueryProp(P_SNOOPFLAGS) & SF_LOCKED )
+               return 0;
+
+            tell_object( snooper, sprintf( "%s snooped jetzt %s.\n",
+                                       me->name(WER), you->name(WER) ) );
+
+            snooper2 = me;
+
+            while ( snooper3 = interactive_info(snooper2, II_SNOOP_NEXT) ){
+               tell_object( snooper,
+                           sprintf( "%s wird seinerseits von %s gesnooped.\n"
+                                   ,snooper2->name(WER),
+                                   snooper3->name(WEM) ) );
+               snooper2 = snooper3;
+            }
+
+            efun::snoop( snooper, snooper2 );
+
+            if ( efun::interactive_info(snooper2, II_SNOOP_NEXT) != snooper )
+               tell_object( snooper, sprintf( "Du kannst %s nicht snoopen.\n",
+                                          snooper2->name(WEN) ) );
+            else{
+               tell_object( snooper, sprintf( "Du snoopst jetzt %s.\n",
+                                          snooper2->name(WEN) ) );
+               if ( !IS_DEPUTY(snooper) ){
+                   log_file( SNOOPLOGFILE, sprintf("%s: %O %O %O\n",
+                                               dtime(time()),
+                                               snooper,
+                                               snooper2,
+                                               environment(snooper2) ),
+                            100000 );
+                   if (snooper0)
+                      CHMASTER->send( "Snoop", snooper,
+                                    sprintf( "%s *OFF* %s (%O)",
+                                            capitalize(getuid(snooper)),
+                                            capitalize(getuid(snooper0)),
+                                            environment(snooper0) ) );
+
+                   CHMASTER->send( "Snoop", snooper,
+                                 sprintf("%s -> %s (%O)",
+                                        capitalize(getuid(snooper)),
+                                        capitalize(getuid(snooper2)),
+                                        environment(snooper2)));
+               }
+               else{
+                   log_file( ASNOOPLOGFILE, sprintf( "%s: %O %O %O\n",
+                                                 dtime(time()),
+                                                 snooper,
+                                                 snooper2,
+                                                 environment(snooper2) )
+                            ,100000 );
+               }
+            }
+        }
+        else
+            if (snooper)
+               if ( !me->QueryProp(P_SNOOPFLAGS) & SF_LOCKED ){
+                   printf( "%s wird bereits von %s gesnooped. Benutze das "
+                          "\"f\"-Flag, wenn du dennoch snoopen willst.\n",
+                          you->name(WER), snooper->name(WEM) );
+                   return 0;
+               }
+
+        ret = efun::snoop( me, you );
+
+        if ( !IS_DEPUTY(me) && efun::interactive_info(you, II_SNOOP_NEXT) == me){
+            log_file( SNOOPLOGFILE, sprintf( "%s: %O %O %O\n",
+                                         Lcut(dtime(time())),
+                                         me, you, environment(you) ),
+                     100000 );
+
+            if (snooper0)
+               CHMASTER->send( "Snoop", me,
+                             sprintf( "%s *OFF* %s (%O).",
+                                     capitalize(getuid(me)),
+                                     capitalize(getuid(snooper0)),
+                                     environment(snooper0) ) );
+
+            CHMASTER->send( "Snoop", me, sprintf( "%s -> %s (%O).",
+                                             capitalize(getuid(me)),
+                                             capitalize(getuid(you)),
+                                             environment(you) ) );
+        }
+        else{
+            if ( efun::interactive_info(you, II_SNOOP_NEXT) == me ){
+               log_file( ASNOOPLOGFILE, sprintf( "%s: %O %O %O\n",
+                                             Lcut(dtime(time())),
+                                             me, you, environment(you) ),
+                        100000 );
+            }
+        }
+
+        if ( ret && query_wiz_grp(me) <= query_wiz_grp(you) &&
+             !IS_DEPUTY(me) )
+            tell_object( you, "*** " + NAME(me) + " snoopt Dich!\n" );
+
+        return ret;
+     }
+     else {
+        if ( (me == PO ||
+              query_wiz_grp(geteuid(PO)) > query_wiz_grp(me) ||
+              (query_wiz_grp(geteuid(PO)) == query_wiz_grp(me) &&
+              efun::interactive_info(PO, II_SNOOP_NEXT) == me)) && snooper0 ){
+            if ( !IS_DEPUTY(me) ){
+               log_file( SNOOPLOGFILE, sprintf( "%s: %O %O %O *OFF*\n",
+                                            Lcut(dtime(time())), me,
+                                            snooper0,
+                                            environment(snooper0) ),
+                        100000 );
+
+                CHMASTER->send( "Snoop", me,
+                              sprintf( "%s *OFF* %s (%O).",
+                                      capitalize(getuid(me)),
+                                      capitalize(getuid(snooper0)),
+                                      environment(snooper0) ) );
+            }
+            else{
+               log_file( ASNOOPLOGFILE, sprintf( "%s: %O %O %O *OFF*\n",
+                                             Lcut(dtime(time())), me,
+                                             snooper0,
+                                             environment(snooper0) ),
+                        100000 );
+            }
+
+            return efun::snoop(me);
+        }
+     }
+     return 0;
+}
+
+
+
+// * Emulation des 'alten' explode durch das neue
+string *old_explode(string str, string del) {
+  int s, t;
+  string *strs;
+
+  if (!stringp(str)) {
+      set_this_object(previous_object());
+      raise_error(sprintf(
+         "Invalid argument 1 to old_explode()! Expected <string>, got: "
+         "%.30O\n",str));
+  }
+  if (!stringp(del)) {
+      set_this_object(previous_object());
+      raise_error(sprintf(
+         "Invalid argument 2 to old_explode()! Expected <string>, got: "
+         "%.30O\n",del));
+  }
+  if(del == "")
+    return ({str});
+  strs=efun::explode(str, del);
+  t=sizeof(strs)-1;
+  while(s<=t && strs[s++] == "");s--;
+  while(t>=0 && strs[t--] == "");t++;
+  if(s<=t)
+    return strs[s..t];
+  return ({});
+}
+
+int file_time(string path) {
+  mixed *v;
+
+  set_this_object(previous_object());
+  if (sizeof(v=get_dir(path,GETDIR_DATES))) return v[0];
+  return(0); //sonst
+}
+
+// * Bei 50k Groesse Log-File rotieren
+varargs int log_file(string file, string txt, int size_to_break) {
+  mixed *st;
+
+  file="/log/"+file;
+  file=implode((efun::explode(file,"/")-({".."})),"/");
+//  tell_object(find_player("jof"),sprintf("LOG FILE: %O -> %O\n",previous_object(),file));
+  if (!funcall(bind_lambda(#'efun::call_other,PO),"secure/master",//')
+              "valid_write",file,geteuid(PO),"log_file",PO))
+      return 0;
+  if ( size_to_break >= 0 & (
+      sizeof(st = get_dir(file,2) ) && st[0] >= (size_to_break|MAX_LOG_SIZE)))
+      catch(rename(file, file + ".old");publish); /* No panic if failure */
+  return(write_file(file,txt));
+}
+
+// * Magier-Level abfragen
+int query_wiz_level(mixed player) {
+  return (int)"/secure/master"->query_wiz_level(player);
+}
+
+#ifdef __ALISTS__
+// * Element aus Alist loeschen (by key)
+mixed *remove_alist(mixed key,mixed *alist)
+{
+  int i,j;
+
+  if (!pointerp(alist) || !sizeof(alist))
+    return alist;
+  if (!pointerp(alist[0]))
+  {
+    if ((i=assoc(key,alist))<0)
+      return alist;
+    return alist[0..i-1]+alist[i+1..];
+  }
+  i = assoc(key,alist[0]);
+  if ((i=assoc(key,alist[0]))<0)
+    return alist;
+  alist=alist[0..];
+  for (j=sizeof(alist)-1;j>=0;j--)
+    alist[j]=alist[j][0..i-1]+alist[j][i+1..];
+  return alist;
+}
+
+// * Element aus Alist loeschen (by pos)
+mixed *exclude_alist(int i,mixed *alist)
+{
+  int j;
+  if (!pointerp(alist) || !sizeof(alist) || i<0)
+    return alist;
+  if (!pointerp(alist[0]))
+    return alist[0..i-1]+alist[i+1..];
+  alist=alist[0..]; /* Create PHYSICAL copy of alist */
+  for (j=sizeof(alist)-1;j>=0;j--)
+    alist[j]=alist[j][0..i-1]+alist[j][i+1..];
+  return alist; /* order_alist is NOT necessary - see /doc/LPC/alist */
+}
+#endif // __ALISTS__
+
+// * German version of ctime()
+#define TAGE ({"Son","Mon","Die","Mit","Don","Fre","Sam"})
+#define MONATE ({"Jan","Feb","Mar","Apr","Mai","Jun","Jul","Aug",\
+                 "Sep","Okt","Nov","Dez"})
+string dtime(int wann) {
+  
+  if (wann == dtime_cache[0])
+    return(dtime_cache[1]);
+
+  int *lt = localtime(wann);
+  return sprintf("%s, %2d. %s %d, %02d:%02d:%02d",
+      TAGE[lt[TM_WDAY]], lt[TM_MDAY], MONATE[lt[TM_MON]], 
+      lt[TM_YEAR],lt[TM_HOUR], lt[TM_MIN], lt[TM_SEC]);
+}
+
+// wenn strftime im Driver nicht existiert, ist dies hier ein Alias auf dtime(),
+// zwar stimmt das Format dann nicht, aber die Mudlib buggt nicht und schreibt
+// ein ordentliches Datum/Uhrzeit.
+#if !__EFUN_DEFINED__(strftime)
+varargs string strftime(mixed fmt, int clock, int localized) {
+  if (intp(clock) && clock >= 0)
+    return dtime(clock);
+  else if (intp(fmt) && fmt >= 0)
+    return dtime(fmt);
+  
+  return dtime(time());
+}
+#endif //!__EFUN_DEFINED__(strftime)
+
+// * Shutdown mit zusaetzlichem logging
+nomask int shutdown(string reason)
+{
+  string name;
+  string obname;
+  string output;
+
+  if (!reason)
+    return 0;
+  if ( !ARCH_SECURITY && getuid(previous_object())!=ROOTID &&
+          object_name(previous_object())!="/obj/shut" )
+  {
+    write("You have no permission to shut down the gamedriver!\n");
+    return 0;
+  }
+  if ((this_interactive())&&(name=getuid(this_interactive())))
+  {
+    name=capitalize(name);
+    filter(users(),#'tell_object,//'
+               capitalize(name)+" faehrt das Spiel herunter!\n");
+  }
+  else
+    name="ANONYMOUS";
+  if (previous_object()) obname=capitalize(getuid(previous_object()));
+  output=name;
+  if (obname && name!=obname) output=output+" ("+obname+")";
+  if (previous_object()&&object_name(previous_object())=="/obj/shut"){
+    output+=" faehrt das Spiel via Armageddon herunter.\n";
+    output=dtime(time())+": "+output;
+    log_file("GAME_LOG",output+"\n",-1);
+    efun::shutdown();
+    return 1;
+  }
+  output=ctime(time())+": "+output+" faehrt das Spiel herunter.\n";
+  output+="    Grund: "+reason;
+  log_file("GAME_LOG",output+"\n",-1);
+  efun::shutdown();
+  return 1;
+}
+
+// * lowerchar
+
+int lowerchar(int char) {
+  if (char<'A' || char>'Z') return char;
+  return char+32;
+}
+
+// * upperstring
+
+string upperstring(string s)
+{
+#if __EFUN_DEFINED__(upper_case)
+  return(upper_case(s));
+#else
+  int i;
+  if (!stringp(s)) return 0;
+  for (i=sizeof(s)-1;i>=0;i--) s[i]=((s[i]<'a'||s[i]>'z')?s[i]:s[i]-32);
+  return s;
+#endif
+}
+
+// * lowerstring
+
+string lowerstring(string s)
+{
+  if (!stringp(s)) return 0;
+  return lower_case(s);
+}
+
+
+// * GD version
+string version()
+{
+  return __VERSION__;
+}
+
+// * break_string
+// stretch() -- stretch a line to fill a given width 
+private string stretch(string s, int width) {
+  int len=sizeof(s);
+  if (len==width) return s;
+
+  // reine Leerzeilen, direkt zurueckgeben
+  string trimmed=trim(s,TRIM_LEFT," ");
+  if (trimmed=="") return s; 
+  int start_spaces = len - sizeof(trimmed);
+
+  string* words = explode(trimmed, " ");
+  // der letzte kriegt keine Spaces
+  int word_count=sizeof(words) - 1;
+  // wenn Zeile nur aus einem Wort, wird das Wort zurueckgegeben
+  if (!word_count)
+    return " "*start_spaces + words[0];
+
+  int space_count = width - len;
+
+  int space_per_word=(word_count+space_count) / word_count;
+  // Anz.Woerter mit Zusatz-Space
+  int rest=(word_count+space_count) % word_count; 
+  // Rest-Spaces Verteilen
+  foreach (int pos : rest) words[pos]+=" ";
+  return (" "*start_spaces) + implode( words, " "*space_per_word );
+}
+
+// aus Geschwindigkeitsgruenden hat der Blocksatz fuer break_string eine
+// eigene Funktion bekommen:
+private varargs string block_string(string s, int width, int flags) {
+  // wenn BS_LEAVE_MY_LFS, aber kein BS_NO_PARINDENT, dann werden Zeilen mit
+  // einem Leerzeichen begonnen.
+  // BTW: Wenn !BS_LEAVE_MY_LFS, hat der Aufrufer bereits alle \n durch " "
+  // ersetzt.
+  if ( (flags & BS_LEAVE_MY_LFS)
+      && !(flags & BS_NO_PARINDENT))
+  {
+      s = " "+regreplace(s,"\n","\n ",1);
+  }
+
+  // sprintf fuellt die letzte Zeile auf die Feldbreite (hier also
+  // Zeilenbreite) mit Fuellzeichen auf, wenn sie NICHT mit \n umgebrochen
+  // ist. Es wird an die letzte Zeile aber kein Zeilenumbruch angehaengt.
+  // Eigentlich ist das Auffuellen doof, aber vermutlich ist es unnoetig, es
+  // wieder rueckgaengig zu machen.
+  s = sprintf( "%-*=s", width, s);
+
+  string *tmp=explode(s, "\n");
+  // Nur wenn s mehrzeilig ist, Blocksatz draus machen. Die letzte Zeile wird
+  // natuerlich nicht gedehnt. Sie ist dafuer schon von sprintf() aufgefuellt
+  // worden. BTW: Die letzte Zeile endet u.U. noch nicht mit einem \n (bzw.
+  // nur dann, wenn BS_LEAVE_MY_LFS und der uebergebene Text schon nen \n am
+  // Ende der letzten Zeile hat), das macht der Aufrufer...
+  if (sizeof(tmp) > 1)
+    return implode( map( tmp[0..<2], #'stretch/*'*/, width ), "\n" ) 
+      + "\n" + tmp[<1];
+
+  return s;
+}
+
+public varargs string break_string(string s, int w, mixed indent, int flags)
+{
+    if ( !s || s == "" ) return "";
+
+    if ( !w ) w=78;
+
+    if( intp(indent) )
+       indent = indent ? " "*indent : "";
+
+    int indentlen=stringp(indent) ? sizeof(indent) : 0;
+
+    if (indentlen>w) {
+      set_this_object(previous_object());
+      raise_error(sprintf("break_string: indent longer %d than width %d\n",
+                  indentlen,w));
+      // w=((indentlen/w)+1)*w;
+    }
+
+    if (!(flags & BS_LEAVE_MY_LFS)) 
+      s=regreplace( s, "\n", " ", 1 );
+
+    if ( flags & BS_SINGLE_SPACE )
+       s = regreplace( s, "(^|\n| )  *", "\\1", 1 );
+ 
+    string prefix="";
+    if (indentlen && flags & BS_PREPEND_INDENT) {
+      if (indentlen+sizeof(s) > w || 
+         (flags & BS_LEAVE_MY_LFS) && strstr(s,"\n")>-1) {
+       prefix=indent+"\n";
+       indent=(flags & BS_NO_PARINDENT) ? "" : " ";
+       indentlen=sizeof(indent);
+      }
+    }
+
+    if ( flags & BS_BLOCK ) {
+      /*
+           s = implode( map( explode( s, "\n" ),
+                               #'block_string, w, indentlen, flags),
+                      "" );
+      */
+      s = block_string( s , w - indentlen, flags );
+    }
+    else {
+      s = sprintf("%-1.*=s",w-indentlen,s);
+    }
+    if ( s[<1] != '\n' ) s += "\n";
+
+    if ( !indentlen ) return prefix + s;
+    
+    string indent2 = ( flags & BS_INDENT_ONCE ) ? (" "*indentlen) :indent;
+      
+    return prefix + indent + 
+      regreplace( s[0..<2], "\n", "\n"+indent2, 1 ) + "\n";
+      /*
+       string *buf;
+
+       buf = explode( s, "\n" );
+       return prefix + indent + implode( buf[0..<2], "\n"+indent2 ) + buf[<1] + "\n";
+      */
+}
+
+// * Elemente aus mapping loeschen - mapping vorher kopieren
+
+mapping m_copy_delete(mapping m, mixed key) {
+  return m_delete(copy(m), key);
+}
+
+// * times
+int last_reboot_time()
+{
+  return __BOOT_TIME__;
+}
+
+int first_boot_time()
+{
+  return 701517600;
+}
+
+int exist_days()
+{
+  return (((time()-first_boot_time())/8640)+5)/10;
+}
+
+// * uptime :)
+string uptime()
+{
+  int t;
+  int tmp;
+  string s;
+
+  t=time()-__BOOT_TIME__;
+  s="";
+  if (t>=86400)
+    s+=sprintf("%d Tag%s, ",tmp=t/86400,(tmp==1?"":"e"));
+  if (t>=3600)
+    s+=sprintf("%d Stunde%s, ",tmp=(t=t%86400)/3600,(tmp==1?"":"n"));
+  if (t>60)
+    s+=sprintf("%d Minute%s und ",tmp=(t=t%3600)/60,(tmp==1?"":"n"));
+  return s+sprintf("%d Sekunde%s",t=t%60,(t==1?"":"n"));
+}
+
+// * Was tun bei 'dangling' lfun-closures ?
+void dangling_lfun_closure() {
+  raise_error("dangling lfun closure\n");
+}
+
+// * Sperren ausser fuer master/simul_efun
+
+#if __EFUN_DEFINED__(set_environment)
+nomask void set_environment(object o1, object o2) {
+  raise_error("Available only for root\n");
+}
+#endif
+
+nomask void set_this_player(object pl) {
+  raise_error("Available only for root\n");
+}
+
+#if __EFUN_DEFINED__(export_uid)
+nomask void export_uid(object ob) {
+  raise_error("Available only for root\n");
+}
+#endif
+
+// * Jetzt auch closures
+int process_flag;
+
+public nomask int process_call()
+{
+  if (process_flag>0)
+    return process_flag;
+  else return(0);
+}
+
+private nomask string _process_string(string str,object po) {
+              set_this_object(po);
+              return(efun::process_string(str));
+}
+
+nomask string process_string( mixed str )
+{
+  string tmp, err;
+  int flag; 
+
+  if ( closurep(str) ) {
+      set_this_object( previous_object() );
+      return funcall(str);
+  }
+  else if (str==0)
+      return((string)str);
+  else if ( !stringp(str) ) {
+      return to_string(str);
+  }
+
+  if ( !(flag = process_flag > time() - 60))                     
+      process_flag=time();
+
+  err = catch(tmp = funcall(#'_process_string,str,previous_object()); publish);
+
+  if ( !flag )
+    process_flag=0;
+
+  if (err) {
+    // Verarbeitung abbrechen
+    set_this_object(previous_object());
+    raise_error(err);
+  }
+  return tmp;
+}
+
+// 'mkdir -p' - erzeugt eine komplette Hierarchie von Verzeichnissen.
+// wenn das Verzeichnis angelegt wurde oder schon existiert, wird 1
+// zurueckgeliefert, sonst 0.
+// Wirft einen Fehler, wenn das angebene Verzeichnis nicht absolut ist!
+public int mkdirp(string dir) {
+  // wenn es nur keinen fuehrenden / gibt, ist das ein Fehler.
+  if (strstr(dir, "/") != 0)
+    raise_error("mkdirp(): Pfad ist nicht absolute.\n");
+  // cut off trailing /...
+  if (dir[<1]=='/')
+      dir = dir[0..<2];
+
+  int fstat = file_size(dir);
+  // wenn es schon existiert, tun wir einfach so, als haetten wir es angelegt.
+  if (fstat == FSIZE_DIR)
+    return 1;
+  // wenn schon ne Datei existiert, geht es nicht.
+  if (fstat != FSIZE_NOFILE)
+    return 0;
+  // wenn es nur einen / gibt (den fuehrenden), dann ist es ein
+  // toplevel-verzeichnis, was direkt angelegt wird.
+  if (strrstr(dir,"/")==0) {
+    return funcall(bind_lambda(#'efun::mkdir, previous_object()), dir);
+  }
+
+  // mkdir() nicht direkt rufen, sondern vorher als closure ans aufrufende
+  // Objekt binden. Sonst laeuft die Rechtepruefung in valid_write() im Master
+  // unter der Annahme, dass die simul_efun.c mit ihrer root id was will.
+
+  // jetzt rekursiv die Verzeichnishierarchie anlegen. Wenn das erfolgreich
+  // ist, dann koennen wir jetzt mit mkdir das tiefste Verzeichnis anlegen
+  if (mkdirp(dir[0..strrstr(dir,"/")-1]) == 1)
+    return funcall(bind_lambda(#'efun::mkdir, previous_object()), dir);
+}
+
+
+// * Properties ggfs. mitspeichern
+mixed save_object(mixed name)
+{
+  mapping properties;
+  mapping save;
+  mixed index, res;
+  int i;
+
+  // nur Strings und 0 zulassen
+  if ((!stringp(name) || !sizeof(name)) && 
+      (!intp(name) || name!=0)) {
+      set_this_object(previous_object());
+      raise_error(sprintf(
+         "Only non-empty strings and 0 may be used as filename in "
+         "sefun::save_object()! Argument was %O\n",name));
+  }
+
+  save = m_allocate(0, 2);
+  properties = (mapping)previous_object()->QueryProperties();
+
+  if(mappingp(properties))
+  {
+    // delete all entries in mapping properties without SAVE flag!
+    index = m_indices(properties);
+    for(i = sizeof(index)-1; i>=0;i--)
+    {
+      if(properties[index[i], F_MODE] & SAVE)
+      {
+       save[index[i]] = properties[index[i]];
+       save[index[i], F_MODE] =
+       properties[index[i], F_MODE] &
+                    (~(SETMNOTFOUND|QUERYMNOTFOUND|QUERYCACHED|SETCACHED));
+      }
+    }
+  }
+  else save = ([]);
+
+  // save object!
+  previous_object()->_set_save_data(save);
+  // format: wie definiert in config.h
+  if (stringp(name))
+    res = funcall(bind_lambda(#'efun::save_object, previous_object()), name,
+       __LIB__SAVE_FORMAT_VERSION__);
+  else
+    res = funcall(bind_lambda(#'efun::save_object, previous_object()),
+       __LIB__SAVE_FORMAT_VERSION__);
+  previous_object()->_set_save_data(0);
+
+#ifdef IOSTATS
+  // Stats...
+  struct iostat_s stat = (<iostat_s>);
+  stat->oname = object_name(previous_object());
+  stat->time = time();
+  //stat->size = (int)object_info(previous_object(),OINFO_MEMORY,
+  //    OIM_TOTAL_DATA_SIZE);
+  if (stringp(name))
+      stat->size = file_size(name + ".o");
+  else
+      stat->sizeof(res);
+  //debug_message("saveo: "+saveo_stat[0]+"\n");
+  saveo_stat[1][saveo_stat[0]] = stat;
+  saveo_stat[0] = (saveo_stat[0] + 1) % sizeof(saveo_stat[1]);
+  //debug_message("saveo 2: "+saveo_stat[0]+"\n");
+#endif
+
+  return res;
+}
+
+// * Auch Properties laden
+int restore_object(string name)
+{
+  int result;
+  mixed index;
+  mixed save;
+  mapping properties;
+  int i;
+  closure cl;
+
+  // get actual property settings (by create())
+  properties = (mapping)previous_object()->QueryProperties();
+
+//  DEBUG(sprintf("RESTORE %O\n",name));
+  // restore object
+  result=funcall(bind_lambda(#'efun::restore_object, previous_object()), name);
+  //'))
+  //_get_save_data liefert tatsaechlich mixed zurueck, wenn das auch immer ein 
+  //mapping sein sollte.
+  save = (mixed)previous_object()->_get_save_data();
+  if(mappingp(save))
+  {
+    index = m_indices(save);
+    for(i = sizeof(index)-1; i>=0; i--)
+    {
+      properties[index[i]] = save[index[i]];
+      properties[index[i], F_MODE] = save[index[i], F_MODE]
+                            &~(SETCACHED|QUERYCACHED);
+    }
+  }
+  else properties = ([]);
+
+  // restore properties
+  funcall(
+          bind_lambda(
+                     unbound_lambda(({'arg}), //'})
+                                  ({#'call_other,({#'this_object}),
+                                  "SetProperties",'arg})),//')
+                     previous_object()),properties);
+  previous_object()->_set_save_data(0);
+
+#ifdef IOSTATS
+  // Stats...
+  //debug_message("restoreo: "+restoreo_stat[0]+"\n");
+  struct iostat_s stat = (<iostat_s>);
+  stat->oname = object_name(previous_object());
+  stat->time = time();
+  //stat->size = (int)object_info(previous_object(),OINFO_MEMORY,
+  //    OIM_TOTAL_DATA_SIZE);
+  stat->size = file_size(name + ".o");
+  restoreo_stat[1][restoreo_stat[0]] = stat;
+
+  restoreo_stat[0] = (restoreo_stat[0] + 1) % sizeof(restoreo_stat[1]);
+#endif
+
+  return result;
+}
+
+// * HB eines Objektes ein/ausschalten
+int set_object_heart_beat(object ob, int flag)
+{
+  if (objectp(ob))
+    return funcall(bind_lambda(#'efun::configure_object,ob), ob, OC_HEART_BEAT, flag);
+}
+
+// * Magierlevelgruppen ermitteln
+int query_wiz_grp(mixed wiz)
+{
+  int lev;
+
+  lev=query_wiz_level(wiz);
+  if (lev<SEER_LVL) return 0;
+  if (lev>=GOD_LVL) return lev;
+  if (lev>=ARCH_LVL) return ARCH_GRP;
+  if (lev>=ELDER_LVL) return ELDER_GRP;
+  if (lev>=LORD_LVL) return LORD_GRP;
+  if (lev>=SPECIAL_LVL) return SPECIAL_GRP;
+  if (lev>=DOMAINMEMBER_LVL) return DOMAINMEMBER_GRP;
+  if (lev>=WIZARD_LVL) return WIZARD_GRP;
+  if (lev>=LEARNER_LVL) return LEARNER_GRP;
+  return SEER_GRP;
+}
+
+mixed *wizlist_info()
+{
+  if (ARCH_SECURITY || !extern_call())
+            return efun::wizlist_info();
+  return 0;
+}
+
+// * wizlist ausgeben
+varargs void wizlist(string name, int sortkey ) {
+
+  if (!name)
+  {
+    if (this_player())
+      name = getuid(this_player());
+    if (!name)
+      return;
+  }
+
+  // Schluessel darf nur in einem gueltigen Bereich sein
+  if (sortkey<WL_NAME || sortkey>=WL_SIZE) sortkey=WL_COST;
+
+  mixed** wl = efun::wizlist_info();
+  // nach <sortkey> sortieren
+  wl = sort_array(wl, function int (mixed a, mixed b)
+      {return a[sortkey] < b[sortkey]; } );
+
+  // Summe ueber alle Kommandos ermitteln.
+  int total_cmd, i;
+  int pos=-1;
+  foreach(mixed entry : wl)
+  {
+    total_cmd += entry[WL_COMMANDS];
+    if (entry[WL_NAME] == name)
+      pos = i;
+    ++i;
+  }
+
+  if (pos < 0 && name != "ALL" && name != "TOP100")
+    return;
+
+  if (name == "TOP100")
+  {
+    if (sizeof(wl) > 100)
+      wl = wl[0..100];
+    else
+      wl = wl;
+  }
+  // um name herum schneiden
+  else if (name != "ALL")
+  {
+    if (sizeof(wl) <= 21)
+      wl = wl;
+    else if (pos + 10 < sizeof(wl) && pos - 10 > 0)
+      wl = wl[pos-10..pos+10];
+    else if (pos <=21)
+      wl = wl[0..20];
+    else if (pos >= sizeof(wl) - 21)
+      wl = wl[<21..];
+    else
+      wl = wl;
+  }
+
+  write("\nWizard top score list\n\n");
+  if (total_cmd == 0)
+    total_cmd = 1;
+  printf("%-20s %-6s %-3s %-17s %-6s %-6s %-6s\n",
+         "EUID", "cmds", "%", "Costs", "HB", "Arrays","Mapp.");
+  foreach(mixed e: wl)
+  {
+    printf("%-:20s %:6d %:2d%% [%:6dk,%:6dG] %:6d %:6d %:6d\n",
+          e[WL_NAME], e[WL_COMMANDS], e[WL_COMMANDS] * 100 / total_cmd,
+          e[WL_COST] / 1000, e[WL_TOTAL_GIGACOST],
+          e[WL_HEART_BEATS], e[WL_ARRAY_TOTAL], e[WL_MAPPING_TOTAL]
+          );
+  }
+  printf("\nTotal         %7d         (%d)\n\n", total_cmd, sizeof(wl));
+}
+
+
+// Ab hier folgen Hilfsfunktionen fuer call_out() bzw. fuer deren Begrenzung
+
+// ermittelt das Objekt des Callouts.
+private object _call_out_obj( mixed call_out ) {
+    return pointerp(call_out) ? call_out[0] : 0;
+}
+
+private void _same_object( object ob, mapping m ) {
+  // ist nicht so bloed, wie es aussieht, s. nachfolgede Verwendung von m
+  if ( m[ob] )
+    m[ob] += ({ ob });
+  else
+    m[ob] = ({ ob }); 
+}
+
+// alle Objekte im Mapping m zusammenfassen, die den gleichen Loadname (Name der
+// Blueprint) haben, also alle Clones der BP, die Callouts laufen haben.
+// Objekte werden auch mehrfach erfasst, je nach Anzahl ihrer Callouts.
+private void _same_path( object key, object *obs, mapping m ) {
+  string path;
+  if (!objectp(key) || !pointerp(obs)) return;
+
+  path = load_name(key);
+
+  if ( m[path] )
+    m[path] += obs;
+  else
+    m[path] = obs;
+}
+
+// key kann object oder string sein.
+private int _too_many( mixed key, mapping m, int i ) {
+    return sizeof(m[key]) >= i;
+}
+
+// alle Objekte in obs zerstoeren und Fehlermeldung ausgeben. ACHTUNG: Die
+// Objekte werden idR zu einer BP gehoeren, muessen aber nicht! In dem Fall
+// wird auf der Ebene aber nur ein Objekt in der Fehlermeldung erwaehnt.
+private void _destroy( mixed key, object *obs, string text, int uid ) {
+    if (!pointerp(obs)) return;
+    // Array mit unique Eintraege erzeugen.
+    obs = m_indices( mkmapping(obs) );
+    // Fehlermeldung auf der Ebene ausgeben, im catch() mit publish, damit es
+    // auf der Ebene direkt scrollt, der backtrace verfuegbar ist (im
+    // gegensatz zur Loesung mittels Callout), die Ausfuehrung aber weiter
+    // laeuft.
+    catch( efun::raise_error(           
+         sprintf( text,                   
+           uid ? (string)master()->creator_file(key) : key,                   
+           sizeof(obs), object_name(obs[<1]) ) );publish);
+    // Und weg mit dem Kram...
+    filter( obs, #'efun::destruct/*'*/ );
+}
+
+// Alle Objekt einer UID im Mapping m mit UID als KEys zusammenfassen. Objekt
+// sind dabei nicht unique.
+private void _same_uid( string key, object *obs, mapping m, closure cf ) {
+  string uid;
+
+  if ( !pointerp(obs) || !sizeof(obs) )
+    return;
+
+  uid = funcall( cf, key );
+
+  if ( m[uid] )
+    m[uid] += obs; // obs ist nen Array
+  else
+    m[uid] = obs;
+}
+
+nomask varargs void call_out( varargs mixed *args )
+{
+    mixed tmp, *call_outs;
+
+    // Bei >600 Callouts alle Objekte killen, die mehr als 30 Callouts laufen
+    // haben.
+    if ( efun::driver_info(DI_NUM_CALLOUTS) > 600
+        && geteuid(previous_object()) != ROOTID )
+    {
+       // Log erzeugen...
+      
+       // Objekte aller Callouts ermitteln
+       call_outs = map( efun::call_out_info(), #'_call_out_obj );
+       mapping objectmap = ([]);
+       filter( call_outs, #'_same_object, &objectmap );
+       // Master nicht grillen...
+       efun::m_delete( objectmap, master(1) );
+       // alle Objekte raussuchen, die zuviele haben...
+       mapping res = filter_indices( objectmap, #'_too_many, objectmap, 29 );
+       // und ueber alle Keys gehen, an _destroy() werden Key und Array mit
+       // Objekten uebergeben (in diesem Fall sind Keys und Array mit
+       // Objekten jeweils das gleiche Objekt).
+       if ( sizeof(res) )       
+           walk_mapping(res, #'_destroy, "CALL_OUT overflow by single "             
+              "object [%O]. Destructed %d objects. [%s]\n", 0 );
+
+       // Bei (auch nach dem ersten Aufraeumen noch) >800 Callouts alle
+       // Objekte killen, die mehr als 50 Callouts laufen haben - und
+       // diesmal zaehlen Clones nicht eigenstaendig! D.h. es werden alle
+       // Clones einer BP gekillt, die Callouts laufen haben, falls alle
+       // diese Objekte _zusammen_ mehr als 50 Callouts haben!
+       if ( efun::driver_info(DI_NUM_CALLOUTS) > 800 ) {
+           // zerstoerte Objekte von der letzten Aktion sind in objectmap nicht
+           // mehr drin, da sie dort als Keys verwendet wurden.
+           mapping pathmap=([]);
+           // alle Objekt einer BP in res sortieren, BP-Name als Key, Arrays
+           // von Objekten als Werte.
+           walk_mapping( objectmap, #'_same_path, &pathmap);
+           // alle BPs (und ihre Objekte) raussuchen, die zuviele haben...
+           res = filter_indices( pathmap, #'_too_many/*'*/, pathmap, 50 );
+           // und ueber alle Keys gehen, an _destroy() werden die Clones
+           // uebergeben, die Callouts haben.
+           if ( sizeof(res) )
+              walk_mapping( res, #'_destroy/*'*/, "CALL_OUT overflow by file "
+                           "'%s'. Destructed %d objects. [%s]\n", 0 );
+
+           // Wenn beide Aufraeumarbeiten nichts gebracht haben und immer
+           // noch >1000 Callouts laufen, werden diesmal alle Callouts
+           // einer UID zusammengezaehlt.
+           // Alle Objekte einer UID, die es in Summe aller ihrer Objekt mit
+           // Callouts auf mehr als 100 Callouts bringt, werden geroestet.
+           if (efun::driver_info(DI_NUM_CALLOUTS) > 1000)
+           {
+              // das nach BP-Namen vorgefilterte Mapping jetzt nach UIDs
+              // zusammensortieren. Zerstoerte Clones filter _same_uid()
+              // raus.
+              mapping uidmap=([]);
+              walk_mapping( pathmap, #'_same_uid, &uidmap,
+                           symbol_function( "creator_file",
+                                          "/secure/master" ) );
+              // In res nun UIDs als Keys und Arrays von Objekten als Werte.
+              // Die rausfiltern, die mehr als 100 Objekte (non-unique, d.h.
+              // 100 Callouts!) haben.
+              res = filter_indices( uidmap, #'_too_many, uidmap, 100 );
+              // und erneut ueber die Keys laufen und jeweils die Arrays mit
+              // den Objekten zur Zerstoerung an _destroy()...
+              if ( sizeof(res) )
+                  walk_mapping( res, #'_destroy, "CALL_OUT overflow by "
+                              "UID '%s'. Destructed %d objects. [%s]\n",
+                              1 );
+           }
+       }
+    }
+
+    // Falls das aufrufende Objekt zerstoert wurde beim Aufraeumen
+    if ( !previous_object() )
+       return;
+
+    set_this_object( previous_object() );
+    apply( #'efun::call_out, args );
+    return;
+}
+
+mixed call_out_info() {
+  
+  object po = previous_object();
+  mixed coi = efun::call_out_info();
+
+  // ungefilterten Output nur fuer bestimmte Objekte, Objekte in /std oder
+  // /obj haben die BackboneID.
+  if (query_wiz_level(getuid(po)) >= ARCH_LVL
+       || (string)master()->creator_file(load_name(po)) == BACKBONEID ) {
+      return coi;
+  }
+  else {
+      return filter(coi, function mixed (mixed arr) {
+              if (pointerp(arr) && arr[0]==po)
+                 return 1;
+              else return 0; });
+  }
+}
+
+// * Zu einer closure das Objekt, an das sie gebunden ist, suchen
+mixed query_closure_object(closure c) {
+  return
+    CLOSURE_IS_UNBOUND_LAMBDA(get_type_info(c, 1)) ?
+      0 :
+  (to_object(c) || -1);
+}
+
+// * Wir wollen nur EIN Argument ... ausserdem checks fuer den Netztotenraum
+varargs void move_object(mixed what, mixed where)
+{
+  object po,tmp;
+
+  po=previous_object();
+  if (!where)
+  {
+    where=what;
+    what=po;
+  }
+  if (((stringp(where) && where==NETDEAD_ROOM ) ||
+       (objectp(where) && where==find_object(NETDEAD_ROOM))) &&
+       objectp(what) && object_name(what)!="/obj/sperrer")
+  {
+    if (!query_once_interactive(what))
+    {
+      what->remove();
+      if (what) destruct(what);
+      return;
+    }
+    if (living(what) || interactive(what))
+    {
+      log_file("NDEAD2",sprintf("TRYED TO MOVE TO NETDEAD: %O\n",what));
+      return;
+    }
+    set_object_heart_beat(what,0);
+  }
+  tmp=what;
+  while (tmp=environment(tmp))
+      // Ja. Man ruft die _set_xxx()-Funktionen eigentlich nicht direkt auf.
+      // Aber das Lichtsystem ist schon *so* rechenintensiv und gerade der
+      // P_LAST_CONTENT_CHANGE-Cache wird *so* oft benoetigt, dass es mir
+      // da um jedes bisschen Rechenzeit geht.
+      // Der Zweck heiligt ja bekanntlich die Mittel. ;-)
+      //
+      // Tiamak
+    tmp->_set_last_content_change();
+  funcall(bind_lambda(#'efun::move_object,po),what,where);
+  if (tmp=what)
+    while (tmp=environment(tmp))
+      tmp->_set_last_content_change();
+}
+
+
+void start_simul_efun() {
+  mixed *info;
+
+  // Falls noch nicht getan, extra_wizinfo initialisieren
+  if ( !pointerp(info = get_extra_wizinfo(0)) )
+    set_extra_wizinfo(0, info = allocate(BACKBONE_WIZINFO_SIZE));
+
+  InitLivingData(info);
+
+  set_next_reset(10); // direkt mal aufraeumen
+}
+
+protected void reset() {
+  set_next_reset(7200);
+  CleanLivingData();
+}
+
+#if !__EFUN_DEFINED__(absolute_hb_count)
+int absolute_hb_count() {
+  return efun::driver_info(DI_NUM_HEARTBEAT_TOTAL_CYCLES);
+}
+#endif
+
+void __set_environment(object ob, mixed target)
+{
+  string path;
+  object obj;
+
+  if (!objectp(ob))
+    return;
+  if (!IS_ARCH(geteuid(previous_object())) || !ARCH_SECURITY )
+    return;
+  if (objectp(target))
+  {
+    efun::set_environment(ob,target);
+    return;
+  }
+  path=(string)MASTER->_get_path(target,this_interactive());
+  if (stringp(path) && file_size(path+".c")>=0 &&
+      !catch(load_object(path);publish) )
+  {
+    obj=find_object(path);
+    efun::set_environment(ob,obj);
+    return;
+  }
+}
+
+void _dump_wizlist(string file, int sortby) {
+  int i;
+  mixed *a;
+
+  if (!LORD_SECURITY)
+    return;
+  if (!master()->valid_write(file,geteuid(previous_object()),"write_file"))
+  {
+    write("NO WRITE PERMISSION\n");
+    return;
+  }
+  a = wizlist_info();
+  a = sort_array(a, lambda( ({'a,'b}),
+                        ({#'<,
+                          ({#'[,'a,sortby}),
+                          ({#'[,'b,sortby})
+                         })));
+  rm(file);
+  for (i=sizeof(a)-1;i>=0;i--)
+    write_file(file,sprintf("%-11s: eval=%-8d cmds=%-6d HBs=%-5d array=%-5d mapping=%-7d\n",
+      a[i][WL_NAME],a[i][WL_TOTAL_COST],a[i][WL_COMMANDS],a[i][WL_HEART_BEATS],
+      a[i][WL_ARRAY_TOTAL],a[i][WL_CALL_OUT]));
+}
+
+public varargs object deep_present(mixed what, object ob) {
+
+  if(!objectp(ob))
+    ob=previous_object();
+  // Wenn ein Objekt gesucht wird: Alle Envs dieses Objekts ermitteln und
+  // schauen, ob in diesen ob vorkommt. Dann ist what in ob enthalten.
+  if(objectp(what)) {
+    object *envs=all_environment(what);
+    // wenn ob kein Environment hat, ist es offensichtlich nicht in what
+    // enthalten.
+    if (!pointerp(envs)) return 0;
+    if (member(envs, ob) != -1) return what;
+  }
+  // sonst wirds teurer, ueber alle Objekte im (deep) Inv laufen und per id()
+  // testen. Dabei muss aber die gewuenschte Nr. ("flasche 3") abgeschnitten
+  // werden und selber gezaehlt werden, welche das entsprechende Objekt ist.
+  else if (stringp(what)) {
+      int cnt;
+      string newwhat;
+      if(sscanf(what,"%s %d",newwhat,cnt)!=2)
+       cnt=1;
+      else
+       what=newwhat;
+      foreach(object invob: deep_inventory(ob)) {
+       if (invob->id(what) && !--cnt)
+           return invob;
+      }
+  }
+  else {
+    set_this_object(previous_object());
+    raise_error(sprintf("Wrong argument 1 to deep_present(). "
+         "Expected \"object\" or \"string\", got %.50O.\n",
+         what));
+  }
+  return 0;
+}
+
+mapping dump_ip_mapping()
+{
+  return 0;
+}
+
+nomask void swap(object obj)
+{
+  write("Your are not allowed to swap objects by hand!\n");
+  return;
+}
+
+nomask varargs void garbage_collection(string str)
+{
+  if(previous_object()==0 || !IS_ARCH(geteuid(previous_object())) 
+      || !ARCH_SECURITY)
+  {
+    write("Call GC now and the mud will crash in 5-6 hours. DONT DO IT!\n");
+    return;
+  }
+  else if (stringp(str))
+  {
+    return efun::garbage_collection(str);
+  }
+  else 
+    return efun::garbage_collection();
+}
+
+varargs void notify_fail(mixed nf, int prio) {
+  object po,oldo;
+  int oldprio;
+  
+  if (!PL || !objectp(po=previous_object())) return;
+  if (!stringp(nf) && !closurep(nf)) {
+      set_this_object(po);
+      raise_error(sprintf(
+         "Only strings and closures allowed for notify_fail! "
+         "Argument was: %.50O...\n",nf));
+  }
+
+  // falls ein Objekt bereits nen notify_fail() setzte, Prioritaeten abschaetzen
+  // und vergleichen.
+  if (objectp(oldo=query_notify_fail(1)) && po!=oldo) {
+    if (!prio) {       
+      //Prioritaet dieses notify_fail() 'abschaetzen'
+      if (po==PL) // Spieler-interne (soul-cmds)
+        prio=NF_NL_OWN;
+      else if (living(po))
+        prio=NF_NL_LIVING;
+      else if ((int)po->IsRoom())
+        prio=NF_NL_ROOM;
+      else
+        prio=NF_NL_THING;
+    }
+    //Prioritaet des alten Setzers abschaetzen
+    if (oldo==PL)
+      oldprio=NF_NL_OWN;
+    else if (living(oldo))
+      oldprio=NF_NL_LIVING;
+    else if ((int)oldo->IsRoom())
+      oldprio=NF_NL_ROOM;
+    else
+      oldprio=NF_NL_THING;
+  }
+  else // wenn es noch kein Notify_fail gibt:
+    oldprio=NF_NL_NONE;
+
+  //vergleichen und ggf. setzen
+  if (prio >= oldprio) { 
+    set_this_object(po);
+    efun::notify_fail(nf);
+  }
+
+  return;
+}
+
+void _notify_fail(string str)
+{
+  //query_notify_fail() benutzen, um das Objekt
+  //des letzten notify_fail() zu ermitteln
+  object o;
+  if ((o=query_notify_fail(1)) && o!=previous_object())
+    return;
+  //noch kein notify_fail() fuer dieses Kommando gesetzt, auf gehts.
+  set_this_object(previous_object());
+  efun::notify_fail(str);
+  return;
+}
+
+string time2string( string format, int zeit )
+{
+  int i,ch,maxunit,dummy;
+  string *parts, fmt;
+
+  int secs = zeit;
+  int mins = (zeit/60);
+  int hours = (zeit/3600);
+  int days = (zeit/86400);
+  int weeks =  (zeit/604800);
+  int months = (zeit/2419200);
+  int abbr = 0;
+
+  parts = regexplode( format, "\(%\(-|\)[0-9]*[nwdhmsxNWDHMSX]\)|\(%%\)" );
+
+  for( i=1; i<sizeof(parts); i+=2 )
+  {
+    ch = parts[i][<1];
+    switch( parts[i][<1] )
+    {
+    case 'x': case 'X':
+       abbr = sscanf( parts[i], "%%%d", dummy ) && dummy==0;
+       // NO break !
+    case 'n': case 'N':
+       maxunit |= 31;
+       break;
+    case 'w': case 'W':
+       maxunit |= 15;
+       break;
+    case 'd': case 'D':
+       maxunit |= 7;
+       break;
+    case 'h': case 'H':
+       maxunit |= 3;
+       break;
+    case 'm': case 'M':
+       maxunit |= 1;
+       break;
+    }
+  }
+  if( maxunit & 16 ) weeks %= 4;
+  if( maxunit & 8 ) days %= 7;
+  if( maxunit & 4 ) hours %= 24;
+  if( maxunit & 2 ) mins %= 60;
+  if( maxunit ) secs %= 60;
+
+  for( i=1; i<sizeof(parts); i+=2 )
+  {
+    fmt = parts[i][0..<2];
+    ch = parts[i][<1];
+    if( ch=='x' )
+    {
+      if (months > 0) ch='n';
+      else if( weeks>0 ) ch='w';
+      else if( days>0 ) ch='d';
+      else if( hours>0 ) ch='h'; 
+      else if(mins > 0) ch = 'm';
+      else ch = 's';
+    }
+    else if( ch=='X' )
+    {
+      if (months > 0) ch='N';
+      else if( weeks>0 ) ch='W';
+      else if( days>0 ) ch='D';
+      else if( hours>0 ) ch='H'; 
+      else if(mins > 0) ch = 'M';
+      else ch = 'S';
+    }
+    
+    switch( ch )
+    {
+      case 'n': parts[i] = sprintf( fmt+"d", months ); break;
+      case 'w': parts[i] = sprintf( fmt+"d", weeks ); break;
+      case 'd': parts[i] = sprintf( fmt+"d", days ); break;
+      case 'h': parts[i] = sprintf( fmt+"d", hours ); break;
+      case 'm': parts[i] = sprintf( fmt+"d", mins ); break;
+      case 's': parts[i] = sprintf( fmt+"d", secs ); break;
+      case 'N':
+       if(abbr) parts[i] = "M";
+       else parts[i] = sprintf( fmt+"s", (months==1) ? "Monat" : "Monate" );
+       break;
+      case 'W':
+       if(abbr) parts[i] = "w"; else
+       parts[i] = sprintf( fmt+"s", (weeks==1) ? "Woche" : "Wochen" );
+       break;
+      case 'D':
+       if(abbr) parts[i] = "d"; else
+       parts[i] = sprintf( fmt+"s", (days==1) ? "Tag" : "Tage" );
+       break;
+      case 'H':
+       if(abbr) parts[i] = "h"; else
+       parts[i] = sprintf( fmt+"s", (hours==1) ? "Stunde" : "Stunden" );
+       break;
+      case 'M':
+       if(abbr) parts[i] = "m"; else
+       parts[i] = sprintf( fmt+"s", (mins==1) ? "Minute" : "Minuten" );
+       break;
+      case 'S':
+       if(abbr) parts[i] = "s"; else
+       parts[i] = sprintf( fmt+"s", (secs==1) ? "Sekunde" : "Sekunden" );
+       break;
+      case '%':
+       parts[i] = "%";
+       break;
+      }
+    }
+    return implode( parts, "" );
+}
+
+nomask mixed __create_player_dummy(string name)
+{
+  string err;
+  object ob;
+  mixed m;
+  //hat nen Scherzkeks die Blueprint bewegt?
+  if ((ob=find_object("/secure/login")) && environment(ob))
+      catch(destruct(ob);publish);
+  err = catch(ob = clone_object("secure/login");publish);
+  if (err)
+  {
+    write("Fehler beim Laden von /secure/login.c\n"+err+"\n");
+    return 0;
+  }
+  if (objectp(m=(mixed)ob->new_logon(name))) netdead[name]=m;
+  return m;
+}
+
+nomask int secure_level()
+{
+  int *level;
+  //kette der Caller durchlaufen, den niedrigsten Level in der Kette
+  //zurueckgeben. Zerstoerte Objekte (Selbstzerstoerer) fuehren zur Rueckgabe
+  //von 0.
+  //caller_stack(1) fuegt dem Rueckgabearray this_interactive() hinzu bzw. 0,
+  //wenn es keinen Interactive gibt. Die 0 fuehrt dann wie bei zerstoerten
+  //Objekten zur Rueckgabe von 0, was gewuenscht ist, da es hier einen
+  //INteractive geben muss.
+  level=map(caller_stack(1),function int (object caller)
+      {if (objectp(caller))
+       return(query_wiz_level(geteuid(caller)));
+       return(0); // kein Objekt da, 0.
+      } );
+  return(min(level)); //den kleinsten Wert im Array zurueckgeben (ggf. 0)
+}
+
+nomask string secure_euid()
+{
+  string euid;
+
+  if (!this_interactive()) // Es muss einen interactive geben
+     return 0;
+  euid=geteuid(this_interactive());
+  // ueber alle Caller iterieren. Wenn eines davon eine andere euid hat als
+  // der Interactive und diese nicht die ROOTID ist, wird 0 zurueckgeben.
+  // Ebenso, falls ein Selbstzerstoerer irgendwo in der Kette ist.
+  foreach(object caller: caller_stack()) {
+      if (!objectp(caller) ||
+       (geteuid(caller)!=euid && geteuid(caller)!=ROOTID))
+         return 0;
+  }
+  return euid; // 'sichere' euid zurueckgeben
+}
+
+// INPUT_PROMPT und nen Leerprompt hinzufuegen, wenn keins uebergeben wird.
+// Das soll dazu dienen, dass alle ggf. ein EOR am Promptende kriegen...
+//#if __BOOT_TIME__ < 1360017213
+varargs void input_to( mixed fun, int flags, varargs mixed *args )
+{
+    mixed *arr;
+    int i;
+
+    if ( !this_player() || !previous_object() )
+       return;
+
+    // TODO: input_to(...,INPUT_PROMPT, "", ...), wenn kein INPUT_PROMPT
+    // vorkommt...
+    if ( flags&INPUT_PROMPT ) {    
+        arr = ({ fun, flags }) + args;
+    }
+    else {
+        // ggf. ein INPUT_PROMPT hinzufuegen und nen Leerstring als Prompt.
+        flags |= INPUT_PROMPT;
+        arr = ({ fun, flags, "" }) + args;
+    }
+
+    // Arrays gegen flatten quoten.
+    for ( i = sizeof(arr) - 1; i > 1; i-- )
+       if ( pointerp(arr[i]) )
+           arr[i] = quote(arr[i]);
+
+    apply( bind_lambda( unbound_lambda( ({}),
+                                     ({ #'efun::input_to/*'*/ }) + arr ),
+                       previous_object() ) );
+}
+//#endif
+
+nomask int set_light(int i)
+// erhoeht das Lichtlevel eines Objekts um i
+// result: das Lichtlevel innerhalb des Objekts
+{
+    object ob, *inv;
+    int lall, light, dark, tmp;
+
+    if (!(ob=previous_object())) return 0; // ohne das gehts nicht.
+
+    // aus kompatibilitaetsgruenden kann man auch den Lichtlevel noch setzen
+    if (i!=0) ob->SetProp(P_LIGHT, ob->QueryProp(P_LIGHT)+i);
+
+    // Lichtberechnung findet eigentlich in der Mudlib statt.
+    return (int)ob->QueryProp(P_INT_LIGHT);
+}
+
+
+public string iso2ascii( string str )
+{
+    if ( !stringp(str) || !sizeof(str) )
+       return "";
+
+    str = regreplace( str, "ä", "ae", 1 );
+    str = regreplace( str, "ö", "oe", 1 );
+    str = regreplace( str, "ü", "ue", 1 );
+    str = regreplace( str, "Ä", "Ae", 1 );
+    str = regreplace( str, "Ö", "Oe", 1 );
+    str = regreplace( str, "Ü", "Ue", 1 );
+    str = regreplace( str, "ß", "ss", 1 );
+    str = regreplace( str, "[^ -~]", "?", 1 );
+
+    return str;
+}
+
+
+public varargs string CountUp( string *s, string sep, string lastsep )
+{
+    string ret;
+
+    if ( !pointerp(s) )
+       return "";
+    
+    if (!sep) sep = ", ";
+    if (!lastsep) lastsep = " und ";
+
+    switch (sizeof(s))  {
+       case 0: ret=""; break;
+       case 1: ret=s[0]; break;
+       default:
+              ret = implode(s[0..<2], sep);
+              ret += lastsep + s[<1];
+    }
+    return ret;
+}
+
+nomask varargs int query_next_reset(object ob) {
+
+    // Typpruefung: etwas anderes als Objekte oder 0 sollen Fehler sein.
+    if (ob && !objectp(ob))
+      raise_error(sprintf("Bad arg 1 to query_next_reset(): got %.20O, "
+           "expected object.\n",ob));
+
+    // Defaultobjekt PO, wenn 0 uebergeben.
+    if ( !objectp(ob) )
+      ob = previous_object();
+
+    return efun::object_info(ob, OI_NEXT_RESET_TIME);
+}
+
+
+#if !__EFUN_DEFINED__(copy_file)
+#define MAXLEN 50000
+nomask int copy_file(string source, string dest)
+{
+
+  int ptr;
+  string bytes;
+
+  set_this_object(previous_object());
+  if (!sizeof(source)||!sizeof(dest)||source==dest||(file_size(source)==-1)||
+      (!call_other(master(),"valid_read",source,
+                   getuid(this_interactive()||
+                 previous_object()),"read_file",previous_object()))||
+      (!call_other(master(),"valid_read",source,
+                   getuid(this_interactive()||
+                 previous_object()),"write_file",previous_object())))
+    return 1;
+  switch (file_size(dest))
+  {
+  case -1:
+    break;
+  case -2:
+    if (dest[<1]!='/') dest+="/";
+    dest+=efun::explode(source,"/")[<1];
+    if (file_size(dest)==-2) return 1;
+    if (file_size(dest)!=-1) break;
+  default:
+    if (!rm(dest)) return 1;
+    break;
+  }
+  do
+  {
+    bytes = read_bytes(source, ptr, MAXLEN); ptr += MAXLEN;
+    if (!bytes) bytes="";
+    write_file(dest, bytes);
+  }
+  while(sizeof(bytes) == MAXLEN);
+  return 0;
+}
+#endif //!__EFUN_DEFINED__(copy_file)
+
+
+// ### Ersatzaufloesung in Strings ###
+varargs string replace_personal(string str, mixed *obs, int caps) {
+  int i;
+  string *parts;
+
+  parts = regexplode(str, "@WE[A-SU]*[0-9]");
+  i = sizeof(parts);
+
+  if (i>1) {
+    int j, t;
+    closure *name_cls;
+
+    t = j = sizeof(obs);
+
+    name_cls  =  allocate(j);
+    while (j--)
+      if (objectp(obs[j]))
+        name_cls[j] = symbol_function("name", obs[j]);
+      else if (stringp(obs[j]))
+        name_cls[j] = obs[j];
+
+    while ((i-= 2)>0) {
+      int ob_nr;
+      // zu ersetzendes Token in Fall und Objektindex aufspalten
+      ob_nr = parts[i][<1]-'1';
+      if (ob_nr<0 || ob_nr>=t) {
+        set_this_object(previous_object());
+        raise_error(sprintf("replace_personal: using wrong object index %d\n",
+                    ob_nr));
+        return implode(parts, "");
+      }
+
+      // casus kann man schon hier entscheiden
+      int casus;
+      string part = parts[i];
+      switch (part[3]) {
+        case 'R': casus = WER;    break;
+        case 'S': casus = WESSEN; break;
+        case 'M': casus = WEM;    break;
+        case 'N': casus = WEN;    break;
+        default:  continue; // passt schon jetzt nicht in das Hauptmuster
+      }
+
+      // und jetzt die einzelnen Keywords ohne fuehrendes "@WE", beendende Id
+      mixed tmp;
+      switch (part[3..<2]) {
+        case "R": case "SSEN": case "M": case "N":               // Name
+          parts[i] = funcall(name_cls[ob_nr], casus, 1);  break;
+        case "RU": case "SSENU": case "MU": case "NU":           // unbestimmt
+          parts[i] = funcall(name_cls[ob_nr], casus);     break;
+        case "RQP": case "SSENQP": case "MQP": case "NQP":       // Pronoun
+          if (objectp(tmp = obs[ob_nr]))
+            parts[i] = (string)tmp->QueryPronoun(casus);
+          break;
+        case "RQA": case "SSENQA": case "MQA": case "NQA":       // Article
+          if (objectp(tmp = obs[ob_nr]))
+            tmp = (string)tmp->QueryArticle(casus, 1, 1);
+          if (stringp(tmp) && !(tmp[<1]^' ')) 
+            tmp = tmp[0..<2];                // Extra-Space wieder loeschen
+          break;
+        case "RQPPMS": case "SSENQPPMS": case "MQPPMS": case "NQPPMS":
+          if (objectp(tmp = obs[ob_nr]))
+            parts[i] = (string)tmp->QueryPossPronoun(MALE, casus, SINGULAR);
+          break;
+        case "RQPPFS": case "SSENQPPFS": case "MQPPFS": case "NQPPFS":
+          if (objectp(tmp = obs[ob_nr]))
+            parts[i] = (string)tmp->QueryPossPronoun(FEMALE, casus, SINGULAR);
+          break;
+        case "RQPPNS": case "SSENQPPNS": case "MQPPNS": case "NQPPNS":
+          if (objectp(tmp = obs[ob_nr]))
+            parts[i] = (string)tmp->QueryPossPronoun(NEUTER, casus, SINGULAR);
+          break;
+        case "RQPPMP": case "SSENQPPMP": case "MQPPMP": case "NQPPMP":
+          if (objectp(tmp = obs[ob_nr]))
+            parts[i] = (string)tmp->QueryPossPronoun(MALE, casus, PLURAL);
+          break;
+        case "RQPPFP": case "SSENQPPFP": case "MQPPFP": case "NQPPFP":
+          if (objectp(tmp = obs[ob_nr]))
+            parts[i] = (string)tmp->QueryPossPronoun(FEMALE, casus, PLURAL);
+          break;
+        case "RQPPNP": case "SSENQPPNP": case "MQPPNP": case "NQPPNP":
+          if (objectp(tmp = obs[ob_nr]))
+            parts[i] = (string)tmp->QueryPossPronoun(NEUTER, casus, PLURAL);
+          break;
+        default:
+          continue;
+      }
+      
+      // wenn tmp ein String war, weisen wir es hier pauschal zu
+      if (stringp(tmp))
+        parts[i] = tmp;
+
+      // auf Wunsch wird nach Satzenden gross geschrieben
+      if (caps)
+        switch (parts[i-1][<2..]) {
+          case ". ":  case "! ":  case "? ":
+          case ".":   case "!":   case "?":
+          case ".\n": case "!\n": case "?\n":
+          case "\" ": case "\"\n":
+            parts[i] = capitalize(parts[i]);
+            break;
+        }
+    }
+    return implode(parts, "");
+  }
+  return str;
+}
+
+
+//replacements for dropped efuns in LD
+#if !__EFUN_DEFINED__(extract)
+deprecated varargs string extract(string str, int from, int to) {
+
+  if(!stringp(str)) {
+    set_this_object(previous_object());
+    raise_error(sprintf("Bad argument 1 to extract(): %O",str));
+  }
+  if (intp(from) && intp(to)) {
+    if (from>=0 && to>=0)
+      return(str[from .. to]);
+    else if (from>=0 && to<0)
+      return(str[from .. <abs(to)]);
+    else if (from<0 && to>=0)
+      return(str[<abs(from) .. to]);
+    else
+      return(str[<abs(from) .. <abs(to)]);
+  }
+  else if (intp(from)) {
+    if (from>=0)
+      return(str[from .. ]);
+    else
+      return(str[<abs(from) .. ]);
+  }
+  else {
+    return(str);
+  }
+}
+#endif // !__EFUN_DEFINED__(extract)
+
+#if !__EFUN_DEFINED__(slice_array)
+deprecated varargs mixed slice_array(mixed array, int from, int to) {
+
+  if(!pointerp(array)) {
+    set_this_object(previous_object());
+    raise_error(sprintf("Bad argument 1 to slice_array(): %O",array));
+  }
+  if (intp(from) && intp(to)) {
+    if (from>=0 && to>=0)
+      return(array[from .. to]);
+    else if (from>=0 && to<0)
+      return(array[from .. <abs(to)]);
+    else if (from<0 && to>=0)
+      return(array[<abs(from) .. to]);
+    else
+      return(array[<abs(from) .. <abs(to)]);
+  }
+  else if (intp(from)) {
+    if (from>=0)
+      return(array[from .. ]);
+    else
+      return(array[<abs(from) .. ]);
+  }
+  else {
+    return(array);
+  }
+}
+#endif // !__EFUN_DEFINED__(slice_array)
+
+#if !__EFUN_DEFINED__(member_array)
+deprecated int member_array(mixed item, mixed arraystring) {
+
+  if (pointerp(arraystring)) {
+    return(efun::member(arraystring,item));
+  }
+  else if (stringp(arraystring)) {
+    return(efun::member(arraystring,to_int(item)));
+  }
+  else {
+    set_this_object(previous_object());
+    raise_error(sprintf("Bad argument 1 to member_array(): %O",arraystring));
+  }
+}
+#endif // !__EFUN_DEFINED__(member_array)
+
+// The digit at the i'th position is the number of bits set in 'i'.
+string count_table =
+    "0112122312232334122323342334344512232334233434452334344534454556";
+int broken_count_bits( string s ) {
+    int i, res;
+    if( !stringp(s) || !(i=sizeof(s)) ) return 0;
+    for( ; i-->0; ) {
+        // We are counting 6 bits at a time using a precompiled table.
+        res += count_table[(s[i]-' ')&63]-'0';
+    }
+    return res;
+}
+
+#if !__EFUN_DEFINED__(count_bits)
+int count_bits( string s ) {
+    return(broken_count_bits(s));
+}
+#endif
+
+
+// * Teile aus einem Array entfernen *** OBSOLETE
+deprecated mixed *exclude_array(mixed *arr,int from,int to)
+{
+  if (to<from)
+    to = from;
+  return arr[0..from-1]+arr[to+1..];
+}
+
diff --git a/secure/sinmaster.c b/secure/sinmaster.c
new file mode 100644
index 0000000..14b8f94
--- /dev/null
+++ b/secure/sinmaster.c
@@ -0,0 +1,213 @@
+// MorgenGrauen MUDlib
+//
+// secure/sinmaster.c -- Das Strafregister
+//
+// $Id: sinmaster.c 9142 2015-02-04 22:17:29Z Zesstra $
+
+#define SIN_SAVE "/secure/ARCH/sins"
+#define SIN_LOG  "ARCH/sins"
+#define SIN_DUMP "/secure/ARCH/sins.dump"
+
+#include <defines.h>
+#include <properties.h>
+#include <wizlevels.h>
+
+#pragma strict_types
+
+mapping sins;
+
+public void create()
+{
+    seteuid(getuid(ME));
+    if ( !restore_object(SIN_SAVE) )
+        sins = ([]);
+}
+
+static void save_me()
+{
+    save_object(SIN_SAVE);
+}
+
+private varargs int is_allowed(int archonly)
+{
+  if (previous_object() && geteuid(previous_object())==ROOTID)
+    return 1;
+  if (!process_call() && previous_object() && this_interactive() && (ARCH_SECURITY || (!archonly && IS_DEPUTY(secure_euid())) ) )
+    return 1;
+  return 0;
+}
+
+public nomask int query_prevent_shadow()
+{
+    return 1;
+}
+
+public string ListSinners()
+{   string *names;
+
+    if ( !is_allowed() )
+      return "ACCESS DENIED\n";
+
+    if ( sizeof(names=m_indices(sins))<1 )
+      return "Es sind keine Eintraege vorhanden.\n";
+
+    names = sort_array( map( names, #'capitalize ), #'> );
+
+    return sprintf("Liste der eingetragenen Suender:\n%#78.6s\n",
+                   implode(names,"\n"));
+}
+
+public string ListSins(string who)
+{   string re;
+    int    i,j;
+
+    if ( !is_allowed() )
+      return "ACCESS DENIED\n";
+
+    if ( !stringp(who) || (sizeof(who)<1) )
+      return "SYNTAX ERROR.\n";
+
+    if ( !member(sins,who) || !pointerp(sins[who]) || ((j=sizeof(sins[who]))<1) )
+      return sprintf("Es liegen keine Eintraege fuer '%s' vor.\n",CAP(who));
+
+    for ( i=1, re = ((string)sins[who][0]+"\n") ; i<j ; i++ )
+      re += sprintf("%3d: %s\n",i,(string)sins[who][i]);
+
+    return re;
+}
+
+static void _add_entry(string who, string entry, object pl)
+{   string *add;
+
+    if ( member(sins,who) && pointerp(sins[who]) && (sizeof(sins[who])>0) )
+        add = (string*)sins[who];
+    else
+        add = ({ sprintf("Eintraege fuer '%s':",CAP(who)) });
+
+    add += ({ entry });
+
+    sins[who] = add;
+
+    save_me();
+
+    log_file(SIN_LOG,
+        sprintf("%s Eintrag fuer %s von %s\n",
+            dtime(time()),CAP(who),CAP(getuid(pl)) ) );
+}
+
+public string AddSin(string who, string text)
+{   object pl;
+    string ersti;
+
+    if ( !is_allowed() )
+      return "ACCESS DENIED\n";
+
+    if ( !stringp(who) || (sizeof(who)<1)
+        || !stringp(text) || (sizeof(text)<1) )
+      return "SYNTAX ERROR.\n";
+
+    if ( text[0..2]=="-f " )
+      text=text[3..];
+    else if ( file_size(sprintf("/save/%s/%s.o",who[0..0],who))<1)
+      return sprintf("Es gibt keinen Spieler namens '%s'\n",who);
+
+    text = dtime(time()) + " ("+CAP(getuid(RPL))+")\n" 
+         + break_string(text,78,"     " );
+
+    _add_entry( who, text, RPL );
+
+    if ( objectp(pl=(find_player(who)||find_netdead(who)))
+        && !IS_WIZARD(pl) // Magier haben manchmal komische Ersties ...
+        && stringp(ersti=(string)pl->QueryProp(P_SECOND)) )
+    {
+        return ( sprintf("Ok.\nFuege Eintrag bei Ersti '%s' hinzu.\n",ersti)
+                + AddSin( lower_case(ersti), ("-f siehe "+who) ) );
+    }
+
+    return "OK.\n";
+}
+
+public string RemoveSin(string who, int nr)
+{   string *rem;
+
+    if ( !is_allowed(1) )
+      return "ACCESS DENIED\n";
+
+    if ( !intp(nr) || (nr<1) || !stringp(who) || (sizeof(who)<1) )
+      return "SYNTAX ERROR.\n";
+
+    if ( !member(sins,who) || !pointerp(sins[who]) || (sizeof(sins[who])<1) )
+      return sprintf("FEHLER: Keine Eintraege fuer '%s' vorhanden.\n",
+                     CAP(who));
+
+    rem = (string*)sins[who];
+
+    if ( sizeof(rem)<=nr )
+      return "FEHLER: Diesen Eintrag gibt es nicht.\n";
+
+    rem[nr] = 0;
+
+    rem -= ({ 0 });
+
+    log_file(SIN_LOG,
+        sprintf("%s Loeschung bei %s von %s\n",
+            dtime(time()),CAP(who),CAP(getuid(RPL)) ) );
+
+
+    if ( sizeof(rem)<2 )
+    {
+        m_delete(sins,who);
+        save_me();
+        return sprintf("Letzten Eintrag von '%s' geloescht.\n",CAP(who));
+    }
+
+    sins[who] = rem;
+    save_me();
+
+    return sprintf("Eintrag %d von '%s' geloescht.\n",nr,CAP(who));
+}
+
+public varargs string Dump(int flag)
+{   string *snr,*sns,dump;
+    int    i,j,k,s,t;
+
+    if ( !is_allowed(0) )
+      return "ACCESS DENIED\n";
+
+    if ( !flag && file_size(SIN_DUMP)>1 )
+      rm( SIN_DUMP );
+
+    s=i=sizeof(snr=sort_array(m_indices(sins),#'<));
+
+    if ( i<1 )
+      return "Keine Suender da.\n";
+
+    dump = sprintf("\n%|78s\n%'='78.78s\n\n",
+               sprintf("Dump der Suenden-Eintraege (%s):",dtime(time())),
+               "");
+
+    for ( --i,t=0 ; i>=0 ; i-- )
+    {
+        j = sizeof(sns=sins[snr[i]]);
+        t += (j-1);
+        dump += (sns[0]+"\n\n");
+
+        for ( k=1 ; k<j ; k++ )
+            dump += sprintf("%3d: %s\n",k,sns[k]);
+
+        dump += sprintf("%'='78.78s\n\n","");
+    }
+
+    dump += sprintf("Statistik:\n\n"+
+                    "  Es sind %d Suender mit insges. %d Eintraegen vorhanden.\n"+
+                    "  Das macht einen Schnitt von %.6f Eintraegen.\n\n",
+                    s,t,( to_float(t)/to_float(s) ));
+
+    if ( !flag )
+    {
+        write_file(SIN_DUMP,dump);
+        return sprintf("Es wurden %d Suender gedumped.\n",s);
+        
+    }
+    return dump;
+}
diff --git a/secure/syntaxdb.c b/secure/syntaxdb.c
new file mode 100644
index 0000000..572eae6
--- /dev/null
+++ b/secure/syntaxdb.c
@@ -0,0 +1,223 @@
+// MorgenGrauen MUDlib
+/** \file /file.c
+* merkt sich Syntaxen und deren Erfolg
+* Kommt gar nicht mit geschachtelten Befehlen klar...
+* \author Zesstra
+*/
+
+#pragma strong_types,save_types,rtt_checks
+#pragma no_clone,no_inherit,no_shadow
+#pragma pedantic, range_check
+
+#include <defines.h>
+#include <events.h>
+#include <wizlevels.h>
+#include <player/base.h>
+#include <userinfo.h>
+#include <driver_info.h>
+#include <regexp.h>
+
+#define HOME(x) (__PATH__(0)+x)
+#define ZDEBUG(x) tell_room("/players/zesstra/workroom",\
+                    sprintf("syntax: %O\n",x));
+
+mapping blacklist;
+
+struct cmd_s {
+    string verb;
+    string cmd;
+    string uid;
+    int success;
+    int fp;
+    int evalno;
+    string tp_uid;
+    int finished;
+};
+
+// LIste der letzten Kommandos, Queue zum speichern in die DB.
+// Das letzte Element ist das letzte Kommando (ggf. noch nicht abgeschlossen)
+private struct cmd_s *commands = ({});
+
+protected void create()
+{
+  seteuid(getuid());
+  if (sl_open(HOME("ARCH/syntaxen.sqlite")) != 1)
+  {
+    raise_error("Datenbank konnte nicht geoeffnet werden.\n");
+  }
+  // Tabellen und Indices anlegen, falls die nicht existieren.
+  sl_exec("CREATE TABLE IF NOT EXISTS syntaxen("
+          "cmd TEXT UNIQUE, "
+          "verb TEXT NOT NULL, envuid TEXT NOT NULL, "
+          "success INTEGER, count INTEGER, fp INTEGER);");
+  sl_exec("CREATE INDEX IF NOT EXISTS idx_verb ON syntaxen(verb);");
+  // dieses Objekt darf nur per hand (mit this_player()) geladen werden, weil
+  // da ein Teil der Blacklist herkommt... Man beachte, dass dies auch das
+  // ist, was die ueblichen Kommunikationsbefehle ausfiltert.
+  string *tmp = map(this_player()->QueryProp(P_LOCALCMDS),
+                    function string (mixed val)
+                    {return val[0];}
+                   );
+  blacklist = mkmapping(tmp
+                  - ({ "toete","schnupper", "schnuppere", "suche","such",
+                       "unt", "untersuche","untersuch", "riech",
+                       "rieche", "lausch", "lausche", "taste",
+                       "fuehl", "fuehle, beruehre", "schau", "schaue",
+                       "les", "lies", "betrachte", "betr", "betracht", 
+                       "teile" // wird per regexp gefiltert!
+                     })
+                  + ({"mail","frage","frag", "xload", "xeval", "xcall",
+                      "gruppe", "g", "team", "frufe", "fruf", "fwer",
+                      "femote", "rknuddel", "knuddel", "nick", "nicke",
+                      "rnick", "rnicke", "knuddel", "knuddele",
+                      "rknuddel", "rknuddele","denke","denk",
+                      "ffix","fnotiz","fuebertrage", "chaoskontrolle",
+                      "kreis", // Matrixkristall
+                     })
+      );
+}
+
+private void write_db()
+{
+  foreach(struct cmd_s c : commands)
+  {
+    int** row=sl_exec("SELECT rowid, success, count, fp from syntaxen "
+                      "WHERE cmd=?1;",
+                      c->cmd);
+    if (row)
+    {
+      sl_exec("UPDATE syntaxen SET success=?2, count=?3, fp=?4"
+              "WHERE rowid=?1;", row[0][0],
+              c->success || row[0][1],
+              ++row[0][2], c->fp || row[0][3] );
+    }
+    else
+    {
+      sl_exec("INSERT INTO syntaxen(verb, cmd, envuid, success, count, fp) "
+              "VALUES(?1,?2,?3,?4,?5,?6);",
+              c->verb, c->cmd, c->uid, c->success, 1, c->fp);
+    }
+  }
+  commands = ({});
+}
+
+// Beendet ein Kommando, wird nur intern gerufen
+private void commit(struct cmd_s c)
+{
+  c->finished = 1;
+  //printf("Kommando abgeschlossen: %O\n", c);
+  if (get_eval_cost() > 1000000
+      && sizeof(commands) > 5
+      && find_call_out(#'write_db) == -1)
+  {
+    // In DB wegschreiben
+    call_out(#'write_db, 1);
+  }
+}
+
+public varargs int remove(int silent)
+{
+  write_db();
+  destruct(this_object());
+  return 1;
+}
+
+// gerufen, wenn ein Spieler ein Kommando eingetippt hat, aus modify_command()
+// heraus. Nach Parsen und Kommandoblock, aber vor allem anderen.
+// ACHTUNG: wenn ein Kommando erfolgreich ist, wird KEIN end_cmd() von aussen
+// gerufen...
+public void start_cmd(string cmdstr)
+{
+  if (!interactive(previous_object()))
+      return;
+  cmdstr = lower_case(cmdstr);
+  // letztes Kommando notfalls abschliessen
+  struct cmd_s cmd;
+  if (sizeof(commands))
+  {
+    cmd = commands[<1];
+    if (!cmd->finished)
+    {
+      // wir betrachten es uebrigens als erfolgreich!
+      commit(cmd);
+    }
+  }
+  // teile- mit und Ebenen und alle Kommandos, die nur aus verb und einem Wort
+  // bestehen, sind auch unnuetz.
+  //printf("%O\n",cmdstr);
+  if (sizeof(explode(cmdstr, " ")) < 3
+      || sizeof(cmdstr) > 100
+      || regmatch(cmdstr,"^teile.*mit",RE_PCRE)
+      || regmatch(cmdstr,"^-[[:alpha:]-]*[' :]{1}",RE_PCRE)
+      || regmatch(cmdstr,"^[.']", RE_PCRE)
+     )
+  {
+      return;
+  }
+  cmd = (<cmd_s> verb: explode(cmdstr, " ")[0],
+         cmd: cmdstr,
+         uid: getuid(environment(this_player())),
+         evalno: driver_info(DI_EVAL_NUMBER),
+         success: 1,
+         tp_uid: getuid(previous_object()),
+      );
+  // sonstige Blacklist
+  if (member(blacklist, cmd->verb))
+      return;
+
+  commands += ({cmd});
+  //printf("Kommandostart: %O\n", cmd);
+}
+
+// gerufen von _auswerten() im Spielerobjekt - das kommt ziemlich am Ende der
+// Kommandoverarbeitung - wenn es noch gerufen wird und uns ruft, betrachten
+// wir das Kommando als NICHT erfolgreich.
+public void cmd_unsuccessful()
+{
+  if (!sizeof(commands))
+    return;
+  struct cmd_s cmd = commands[<1];
+  // Darf nur vom Spielerobjekt gerufen werden, welches das letzte Kommando
+  // angefangen hat.
+  if (cmd->tp_uid != getuid(previous_object()))
+    return;
+  // und dies gehoert nur zum letzten Kommando, wenn Verb und Eval-No
+  // uebereinstimmen.
+  if (cmd->verb == query_verb()
+      || cmd->evalno == driver_info(DI_EVAL_NUMBER))
+  {
+    cmd->success = 0;
+    //printf("Kommando nicht erfolgreich: %O\n", cmd);
+    commit(cmd);
+  }
+  // Wenn nicht, war das letzte Kommando offenbar doch erfolgreich. Aus
+  // irgendnem Grund haben wir aber den Start des neuen Kommandos verpasst.
+  // Daher verwerfen wir das jetzt. Das alte Kommando war aber offenbar
+  // erfolgreich, daher wird es jetzt abgeschlossen.
+  else
+  {
+    commit(cmd);
+  }
+}
+
+public void LogEP(int type)
+{
+  if (!sizeof(commands))
+    return;
+  struct cmd_s cmd = commands[<1];
+  // Darf nur vom Spielerobjekt gerufen werden, welches das letzte Kommando
+  // angefangen hat.
+  if (cmd->tp_uid != getuid(previous_object()))
+    return;
+  //printf("FP gefunden: %O\n", cmd);
+  // und dies gehoert nur zum letzten Kommando, wenn Verb und Eval-No
+  // uebereinstimmen.
+  if (cmd->verb == query_verb()
+      || cmd->evalno == driver_info(DI_EVAL_NUMBER))
+  {
+    cmd->fp = type+1;
+    // Und wenn es nen FP gab, ist das Kommando auch erfolgreich.
+    commit(cmd);
+  }
+}
+
diff --git a/secure/telnetneg-structs.c b/secure/telnetneg-structs.c
new file mode 100644
index 0000000..3efeeae
--- /dev/null
+++ b/secure/telnetneg-structs.c
@@ -0,0 +1,47 @@
+// MorgenGrauen MUDlib
+//
+// telnetneg-structs.c -- Structs fuer die Telnet Option handler
+//
+#pragma strict_types,save_types
+#pragma range_check
+#pragma no_clone
+#pragma no_shadow
+#pragma pedantic
+
+struct to_state_s {
+  int localside;      // wish for the local side (MUD)
+  int remoteside;     // wish for the remote side (CLIENT)
+  int *sbdata;        // last SB data sent/received by us
+};
+
+struct telopt_s {
+  int option;
+   // Receivehandler, wird gerufen, wenn wir vom Client irgendwas bzgl. dieser
+   // Telnet Option empfangen. Wenn gesetzt, darf der Client die Option
+   // einschalten.
+  closure remotehandler;
+  // Wird gerufen, wenn die Option auf unserer Seite eingeschaltet wurde.
+  // Wenn gesetzt, soll versucht werden, die Option auf Mudseite
+  // einzuschalten
+  closure localhandler;
+  // Die Wuensche _waehrend_ einer Verhandlung (bzw. gesendete (lo_wishes) und
+  // empfangene (re_wishes) SB-Daten auch ausserhalb von Verhandlungen).
+  struct to_state_s lo_wishes;    // our wishes (sent by us)
+  struct to_state_s re_wishes;    // remote wishes (received by us)
+  // currently effective/active state
+  struct to_state_s state;
+  // data used by the handlers - NOT USED BY this program!
+  mixed data;
+};
+/* explanations:
+   telopt_s->lo_wishes->localside: the state we want to be in  (WILL/WONT)
+   telopt_s->lo_wishes->remoteside: the state we want the other side to
+                                    be in  (DO/DONT)
+   telopt_s->re_wishes->localside: the state the other side wants US to be in
+                                   (DO/DONT)
+   telopt_s->re_wishes->remoteside: the state the other side wants to be in
+                                    (WILL/WONT)
+   telopt_s->state:   the currently effective state of the option on the two
+                      sides.
+   */
+
diff --git a/secure/telnetneg.c b/secure/telnetneg.c
new file mode 100644
index 0000000..341e1a0
--- /dev/null
+++ b/secure/telnetneg.c
@@ -0,0 +1,863 @@
+// MorgenGrauen MUDlib
+//
+// telnetneg.c -- Verwaltung von Telnet-Negotiations
+//
+// $Id$
+
+/* Das Original wurde von Marcus@Tapp zur Verfuegung gestellt. */
+/* Angepasst fuer die MG-Mudlib von Ringor@MG */
+/* Weitgehend ueberarbeitet von Zesstra@MG */
+
+#pragma strict_types,save_types
+#pragma range_check
+#pragma no_clone
+#pragma no_shadow
+#pragma pedantic
+
+inherit "/secure/telnetneg-structs.c";
+
+#define NEED_PROTOTYPES
+#include "/secure/telnetneg.h"
+#undef NEED_PROTOTYPES
+
+// unterstuetzte Optionen:
+// TELOPT_EOR, TELOPT_NAWS, TELOPT_LINEMODE, TELOPT_TTYPE
+
+//#define __DEBUG__ 1
+
+#ifdef __DEBUG__
+#define DEBUG(x)        if (interactive(this_object()))\
+          tell_object(this_object(),"TN: " + x + "\n")
+#define DTN(x,y) _debug_print(x,y)
+#else
+# define DEBUG(x)
+# define DTN(x,y)
+#endif
+
+
+
+// Aus mini_props.c:
+public varargs mixed Query( string str, int type );
+public varargs mixed Set( string str, mixed value, int type );
+
+private nosave mapping TN = ([]);
+nosave string *Terminals;
+
+// Prototypen
+private void eval_naws(int *optargs);
+
+#ifdef __DEBUG__
+// Gibts einige Konstanten mit sym. Namen aus.
+private string dtranslate(int i) {
+  switch(i) {
+    case IAC: return "IAC";
+    case DONT: return "DONT";
+    case DO: return "DO";
+    case WONT: return "WONT";
+    case WILL: return "WILL";
+    case SB: return "SB";
+    case SE: return "SE";
+    case EOR: return "EOR";
+    case TELOPT_LINEMODE: return "TELOPT_LINEMODE";
+    case TELOPT_XDISPLOC: return "TELOPT_XDISPLOC";
+    case TELOPT_ENVIRON: return "TELOPT_ENVIRON";
+    case TELOPT_NEWENV: return "TELOPT_NEWENV";
+    case TELOPT_EOR: return "TELOPT_EOR";
+    case TELOPT_NAWS: return "TELOPT_NAWS";
+    case TELOPT_TSPEED: return "TELOPT_TSPEED";
+    case TELOPT_TTYPE: return "TELOPT_TTYPE";
+    case TELOPT_ECHO: return "TELOPT_ECHO";
+    case TELOPT_SGA: return "TELOPT_SGA";
+    case TELOPT_NAMS: return "TELOPT_NAMS";
+    case TELOPT_STATUS: return "TELOPT_STATUS";
+    case TELOPT_TM: return "TELOPT_TM";
+
+    case TELOPT_MSDP: return "TELOPT_MSDP";
+    case TELOPT_COMPRESS2: return "TELOPT_COMPRESS2";
+    case TELOPT_MSP: return "TELOPT_MSP";
+    case TELOPT_MXP: return "TELOPT_MXP";
+    case TELOPT_ATCP: return "TELOPT_ATCP";
+    case TELOPT_GMCP: return "TELOPT_GMCP";
+    case TELOPT_MSSP: return "TELOPT_MSSP";
+  }
+  return to_string(i);
+}
+
+// Gibt <arr> halbwegs lesbar an this_object() aus.
+private void _debug_print(string x, int *arr) {
+  if (arr[1] == SB && arr[<1] != SE)
+    arr += ({IAC, SE});
+  closure map_int = function string (int i)
+    { if (i >= 32 && i <= 126) return sprintf("%c",i);
+      return "["+to_string(i)+"]";
+    };
+  if (sizeof(arr)<=5) {
+    foreach(int c : arr)
+      x += " " + dtranslate(c);
+  }
+  else {
+      x += dtranslate(arr[0]) + " " + dtranslate(arr[1]) + " "
+           + dtranslate(arr[2]) + " "
+           + implode(map(arr[3..<3], map_int)," ")
+           + " " + dtranslate(arr[<2]) + " " + dtranslate(arr[<1]);
+  }
+  DEBUG(x);
+}
+#endif
+
+protected varargs int send_telnet_neg(int *arr, int bm_flags)
+{
+    if ( sizeof(arr) < 2 )
+        return efun::binary_message(arr,bm_flags);
+
+    struct telopt_s opt = TN[arr[1]];
+
+    switch (arr[0]){
+    case DO:
+    case DONT:
+        (opt->lo_wishes)->remoteside = arr[0];
+        arr = ({IAC}) + arr;
+        break;
+    case WILL:
+    case WONT:
+        (opt->lo_wishes)->localside = arr[0];
+        arr = ({IAC}) + arr;
+        break;
+    case SB:
+        (opt->lo_wishes)->sbdata = arr[0..];
+        arr = ({IAC}) + arr + ({IAC, SE});
+        break;
+    default:
+        break;
+    }
+    DTN("send_tn: ",arr);
+    return efun::binary_message(arr, bm_flags);
+}
+
+protected varargs int send_telnet_neg_str(string str, int bm_flags) {
+#ifdef __DEBUG__
+    // Debugausgaben zur Zeit nur fuer arraybasierte Variante
+    return send_telnet_neg(to_array(str), bm_flags);
+#else
+    if ( sizeof(str) < 2 )
+        return efun::binary_message(str, bm_flags);
+
+    struct telopt_s opt = TN[str[1]];
+
+    switch (str[0]) {
+    case DO:
+    case DONT:
+        (opt->lo_wishes)->remoteside = str[0];
+        str=sprintf("%c%s",IAC,str);
+        break;
+    case WILL:
+    case WONT:
+        (opt->lo_wishes)->localside = str[0];
+        str=sprintf("%c%s",IAC,str);
+        break;
+    case SB:
+        (opt->lo_wishes)->sbdata = map(explode(str[0..],""),#'to_int);
+        str=sprintf("%c%s%c%c", IAC, str, IAC, SE);
+        break;
+    default:
+        break;
+    }
+
+    return efun::binary_message(str, bm_flags);
+#endif // __DEBUG__
+}
+
+// Startet eine Verhandlung, um den Status einer Option zu aendern.
+// Wenn bereits eine Verhandlung laeuft, wird nichts gemacht und -1
+// zurueckgeben.
+// Wenn die Verhandlung keine Aenderung vom Status quo zum Ziel hat, wird
+// nichts gemacht und -2 zurueckgegeben.
+// Ansonsten ist die Rueckgabe die Anzahl der uebermittelten Zeichen.
+// <action>: WILL: Option soll auf dieser Seite eingeschaltet werden.
+//           WONT: Option soll auf dieser Seite ausgeschaltet werden.
+//           DO  : Option soll auf der anderen Seite eingeschaltet werden.
+//           DONT: Option soll auf der anderen Seite ausgeschaltet werden.
+protected int do_telnet_neg(int option, int action) {
+  struct telopt_s opt = TN[option];
+  if (!structp(opt))
+  {
+    opt = (<telopt_s> option: option,
+                      re_wishes: (<to_state_s>),
+                      lo_wishes: (<to_state_s>),
+                      state: (<to_state_s>)
+          );
+    TN[option] = opt;
+  }
+  // es wird nur geprueft, ob wir bereits eine Verhandlung begonnen haben
+  // (lo_wishes), weil reinkommende remote Wuensche letztendlich sofort durch
+  // unsere Antwort erledigt sind.
+  switch(action)
+  {
+    case WILL:
+      if (opt->lo_wishes->localside != 0)
+        return -1;
+      if (opt->state->localside)
+        return -2;
+      return send_telnet_neg( ({ WILL, option }) );
+      break;
+    case WONT:
+      if (opt->lo_wishes->localside != 0)
+        return -1;
+      if (!opt->state->localside)
+        return -2;
+      return send_telnet_neg( ({ WONT, option }) );
+      break;
+    case DO:
+      if (opt->lo_wishes->remoteside != 0)
+        return -1;
+      if (opt->state->remoteside)
+        return -2;
+      return send_telnet_neg( ({ DO, option }) );
+      break;
+    case DONT:
+      if (opt->lo_wishes->remoteside != 0)
+        return -1;
+      if (!opt->state->remoteside)
+        return -2;
+      return send_telnet_neg( ({ DONT, option }) );
+      break;
+  }
+  raise_error(sprintf("Unsupported telnet negotation action in "
+      "do_telnet_neg(): %d\n",action));
+}
+
+// LOCAL Standard Handlers //
+private void _std_lo_handler_eor(struct telopt_s opt, int action) {
+  // tatsaechlich nix zu tun. Handler ist nur da, damit die Option auf dieser
+  // Seite aktiviert wird. Die Arbeit erledigt print_prompt.
+  return;
+}
+
+private void _std_lo_handler_mssp(struct telopt_s opt, int action) {
+  // nur einschalten ist interessant.
+  if (action != LOCALON)
+    return;
+  // Krams senden, wenn Objekt geladen. Sonst wieder abschalten (kommt
+  // hoffentlich nicht vor)...
+  object mssp = find_object("/secure/misc/mssp");
+  if (!mssp)
+    send_telnet_neg( ({WONT, TELOPT_MSSP }) );
+  else
+  {
+    send_telnet_neg_str(sprintf("%c%c%s",
+          SB, TELOPT_MSSP, mssp->get_telnegs_str()));
+    // die Daten brauchen wir nicht mehr
+    opt->lo_wishes->sbdata = 0;
+  }
+}
+
+
+// REMOTE Standard Handlers //
+private void _std_re_handler_tm(struct telopt_s opt, int action,
+                                int *data)
+{
+  // egal, was geantwortet wurde, es gibt nen Hinweis auf die round-trip-time.
+  // Wenn ein Array in opt->data[1] steht, rechnen wir das aus und schreiben es
+  // in opt->data[0] als Ergebnis rein.
+  if (pointerp(opt->data) && pointerp(opt->data[1]))
+  {
+    int *ut = utime();
+    int *start = opt->data[1];
+    int res = (ut[0] - start[0]) * 1000000;
+    res += ut[1] - start[1];
+    opt->data[0] = res;
+    opt->data[1] = 0;
+    DEBUG("RTT: "+res);
+  }
+  return;
+}
+
+private void _std_re_handler_naws(struct telopt_s opt, int action,
+                                  int *data)
+{
+  if (action == SB)
+  {
+    eval_naws(data);
+  }
+}
+
+private void _std_re_handler_linemode(struct telopt_s opt, int action,
+                                      int *data)
+{
+  if (action == REMOTEON)
+  {
+    // see /doc/concepts/negotiations. We use only the minimum
+    // needed for linemode: switching on local commandline-editing
+    // for the client.
+    send_telnet_neg(({ SB, TELOPT_LINEMODE, LM_MODE, MODE_EDIT }));
+    // flush on 0d and 0a...
+    // TODO: what does this exactly do?
+    send_telnet_neg(({ SB, TELOPT_LINEMODE, DO, LM_FORWARDMASK, 0,
+                          0x40|0x08 }));
+    //Gna...
+    opt->lo_wishes->sbdata = ({MODE_EDIT});
+  }
+}
+
+private void _std_re_handler_ttype(struct telopt_s opt, int action,
+                                   int *data)
+{
+  if (action == SB)
+  {
+    //TODO: get rid of this hysterical stuff...
+    //NOTE: We do not do multiple SB SENDs due to some weird
+    //bugs in IBM3270 emulating telnets which crash if we
+    //do that.
+    if ( sizeof(data) < 1 )
+        return;
+
+    if ( data[0] != TELQUAL_IS )
+        return;
+
+    string tmpterminal = lower_case( to_string(data[1..]) );
+    if ( !Terminals )
+        Terminals = ({ tmpterminal });
+    else
+        Terminals += ({ tmpterminal });
+
+    if ( Query(P_TTY_TYPE) )
+          Set( P_TTY_TYPE, Terminals[0] );
+  }
+  else if (action == REMOTEON)
+  {
+    send_telnet_neg(({ SB, TELOPT_TTYPE, TELQUAL_SEND }));
+  }
+}
+
+// Bindet/registriert Handler fuer die jew. Telnet Option. (Oder loescht sie
+// auch wieder.) Je nach <initneg> wird versucht, die Option neu zu
+// verhandeln.
+protected int bind_telneg_handler(int option, closure re, closure lo,
+                                  int initneg)
+{
+  struct telopt_s opt = TN[option];
+  if (!structp(opt))
+  {
+    opt = (<telopt_s> option: option,
+                      re_wishes: (<to_state_s>),
+                      lo_wishes: (<to_state_s>),
+                      state: (<to_state_s>)
+          );
+    TN[option] = opt;
+  }
+
+  opt->remotehandler = re;
+  if (initneg)
+  {
+    if (re)
+      do_telnet_neg(option, DO);
+    else
+      do_telnet_neg(option, DONT );
+  }
+
+  opt->localhandler = lo;
+  if (initneg)
+  {
+    if (lo)
+      do_telnet_neg(option, WILL);
+    else
+      do_telnet_neg(option, WONT);
+  }
+  return 1;
+}
+
+
+// Mal unsere Wuensche an den Client schicken und die Standardhandler
+// registrieren. Hierbei bei Bedarf neue Verhandlungen starten.
+// Gerufen aus login.c nach Verbindungsaufbau.
+// Bemerkung: das Spielerobjekt bietet evt. noch zusaetzliche Telnetoptionen
+//            an, die dann ueber startup_telnet_negs() (im Spielerobjekt)
+//            laufen.
+protected void SendTelopts()
+{
+  bind_telneg_handler(TELOPT_EOR, 0, #'_std_lo_handler_eor, 1);
+  if (find_object("/secure/misc/mssp"))
+    bind_telneg_handler(TELOPT_MSSP, 0, #'_std_lo_handler_mssp, 1);
+
+  bind_telneg_handler(TELOPT_NAWS, #'_std_re_handler_naws, 0, 1);
+  bind_telneg_handler(TELOPT_LINEMODE, #'_std_re_handler_linemode, 0, 1);
+  bind_telneg_handler(TELOPT_TTYPE, #'_std_re_handler_ttype, 0, 1);
+  // fuer TELOPT_TM jetzt keine Verhandlung anstossen.
+  bind_telneg_handler(TELOPT_TM, #'_std_re_handler_tm, 0, 0);
+}
+
+
+// Bindet die Standardhandler _aus diesem_ Programm (und ueberschreibt dabei
+// ggf. andere). Hierbei werden nur die Handler neu gebunden, keine neuen
+// Verhandlungen initiiert.
+// gerufen aus base.c indirekt via startup_telnet_negs().
+protected void _bind_telneg_std_handlers() {
+  bind_telneg_handler(TELOPT_EOR, 0, #'_std_lo_handler_eor, 0);
+  // Besondere Situation: MSSP ist nach Spielerlogin eigentlich uninteressant.
+  // Daher sparen wir uns das im Kontext des Spielerobjekts und schalten es
+  // einfach wieder aus.
+  bind_telneg_handler(TELOPT_MSSP, 0, 0, 0);
+
+  bind_telneg_handler(TELOPT_NAWS, #'_std_re_handler_naws, 0, 0);
+  bind_telneg_handler(TELOPT_LINEMODE, #'_std_re_handler_linemode, 0, 0);
+  bind_telneg_handler(TELOPT_TTYPE, #'_std_re_handler_ttype, 0, 0);
+  bind_telneg_handler(TELOPT_TM, #'_std_re_handler_tm, 0, 0);
+}
+
+
+// Ruft die entsprechenden handler von der Telnet Option.
+// Wenn es keinen handler (mehr) gibt, wird die Option auch auf der jeweiligen
+// Seite ausgeschaltet. Deshalb MUSS lo_wishes und re_wishes vom Aufrufer VOR
+// DEM AUFRUF zurueckgesetzt worden sein!
+// <action>: 'LOCALON':   Option wurde auf unserer Seite eingeschaltet
+//           'LOCALOFF':  Option wurde auf unserer Seite ausgeschaltet
+//           'REMOTEON':  Option wurde auf Clientseite eingeschaltet
+//           'REMOTEOFF': Option wurde auf Clientseite ausgeschaltet
+//           'SB':        Suboption negotiation Daten wurden empfangen
+// <data>: die per SB empfangenen Daten (unverarbeitet)
+private void _call_handler(struct telopt_s opt, int action, int *data) {
+  switch(action)
+  {
+    case REMOTEON:
+    case REMOTEOFF:
+    case SB:
+      if (opt->remotehandler)
+      {
+        funcall(opt->remotehandler, opt, action, data);
+      }
+      else
+      {
+        // ok, geht nicht. Ggf. Abschalten (do_telnet_neg passt auf,
+        // dass nur verhandelt wird, wenn die Option an ist.)
+        do_telnet_neg( opt->option, DONT );
+      }
+      break;
+    case LOCALON:
+    case LOCALOFF:
+      if (opt->localhandler)
+      {
+        funcall(opt->localhandler, opt, action);
+      }
+      else
+      {
+      // ok, geht nicht. Ggf. Abschalten (do_telnet_neg passt auf,
+      // dass nur verhandelt wird, wenn die Option an ist.)
+        do_telnet_neg( opt->option, WONT );
+      }
+      break;
+  }
+}
+
+// Gerufen vom Driver, wenn neue telnet options reinkommen.
+void
+telnet_neg(int command, int option, int *optargs)
+{
+    DTN("recv_tn: ", ({IAC, command, option}) + (optargs||({})));
+
+    struct telopt_s opt = TN[option];
+    if (!structp(opt))
+    {
+      opt = (<telopt_s> option: option,
+                        re_wishes: (<to_state_s>),
+                        lo_wishes: (<to_state_s>),
+                        state: (<to_state_s>)
+            );
+      TN[option] = opt;
+    }
+
+    // Was will der Client tun?
+    if (command == WONT)
+    {
+        // Client will die Option auf seiner Seite abschalten. Wir MUESSEN das
+        // akzeptieren.
+        // Wir muessen das allerdings ignorieren, wenn die Option bereits aus
+        // ist.
+        if (opt->state->remoteside==0)
+        {
+          // Ausnahme fuer TELOPT_TM, da das kaum ein Client kann und fuer RTT
+          // es eigentlich auch egal ist, was zurueck kommt: der handler wird
+          // zumindest doch gerufen zum Ausrechnen der RTT
+          if (option == TELOPT_TM)
+            _call_handler(opt, REMOTEOFF, 0);
+          // ansonsten aber wirklich ignorieren. ;)
+          return;
+        }
+        opt->re_wishes->remoteside = command;
+        // Bestaetigung auf ein WONT senden, wenn wir nicht selber schon ein
+        // DONT geschickt hatten.
+        if (opt->lo_wishes->remoteside != DONT) {
+          send_telnet_neg( ({DONT, option}) );
+        }
+        // Wir haben jetzt auf jeden Fall ein DONT gesendet und ein WONT
+        // erhalten. Damit ist die Option jetzt auf der clientseite aus.
+        // Ausserdem setzen wir die Wishes zurueck.
+        opt->re_wishes->remoteside = 0;
+        opt->lo_wishes->remoteside = 0;
+        if (opt->state->remoteside != 0)
+        {
+            opt->state->remoteside = 0;
+            _call_handler(opt, REMOTEOFF, 0);
+        }
+    } // WONT vom Client verarbeitet
+    else if ( command == WILL)
+    {
+        // Wenn die Option bereits an ist, muessen wir dies ignorieren.
+        if (opt->state->remoteside == 1)
+        {
+          // Ausnahme fuer TELOPT_TM, der handler wird zumindest doch gerufen
+          // zum Ausrechnen der RTT. Diese Option laesst sich ohnehin
+          // aktivieren, auch wenn sie schon an ist.
+          if (option == TELOPT_TM)
+            _call_handler(opt, REMOTEON, 0);
+          // sonst aber wirklich ignorieren. ;-)
+          return;
+        }
+        opt->re_wishes->remoteside = command;
+        if ( opt->lo_wishes->remoteside == 0 )
+        {
+            // Der Client will, wir haben noch nix dazu gesagt. (Mit unserer
+            // Antwort ist die Verhandlung uebrigens beendet.)
+            // Wenn es einen remotehandler fuer die Option gibt, schalten wir
+            // sie ein...
+            if (opt->remotehandler)
+            {
+                send_telnet_neg(({DO, option}));
+                // Option jetzt an der Clientseite an.
+                opt->re_wishes->remoteside = 0;
+                opt->lo_wishes->remoteside = 0;
+                if (opt->state->remoteside != 1)
+                {
+                    opt->state->remoteside = 1;
+                    _call_handler(opt, REMOTEON, 0);
+                }
+            }
+            else
+            {
+              // sonst verweigern wir das einschalten (die meisten Optionen
+              // auf Clientseite sind fuer uns eh egal).
+              send_telnet_neg(({DONT, option}));
+              // Option jetzt an der Clientseite aus.
+              opt->re_wishes->remoteside = 0;
+              opt->lo_wishes->remoteside = 0;
+              if (opt->state->remoteside != 0)
+              {
+                  opt->state->remoteside = 0;
+                  _call_handler(opt, REMOTEOFF, 0);
+              }
+            }
+        }
+        else if ( opt->lo_wishes->remoteside == DO)
+        {
+            // Wir haben haben bereits per DO angefordert, d.h. das ist die
+            // Clientbestaetigung - wir duerfen nicht bestaetigen und die
+            // Option ist jetzt clientseitig aktiv. Verhandlung beendet.
+            opt->re_wishes->remoteside = 0;
+            opt->lo_wishes->remoteside = 0;
+            if (opt->state->remoteside != 1)
+            {
+                opt->state->remoteside = 1;
+                _call_handler(opt, REMOTEON, 0);
+            }
+        } // if (DO)
+        else {
+          // Mhmm. Wir hatten ein DONT gesendet, aber der Client hat mit WILL
+          // geantwortet. Das darf er eigentlich gar nicht.
+          //TODO: was sollte man jetzt eigentlich tun? Erstmal wiederholen wir
+          //das DONT...
+          send_telnet_neg( ({DONT, option}) );
+        }
+
+        return;
+    } // WILL vom Client verarbeitet
+    // Was sollen wir (nicht) fuer den Client tun?
+    else if ( command == DONT)
+    {
+        // Client will, dass wir etwas nicht tun. Wir MUESSEN das akzeptieren.
+        // wenn die Option auf unserer Seite aber schon aus ist, muessen wir
+        // dies ignorieren.
+        if (opt->state->localside == 0)
+          return;
+
+        opt->re_wishes->localside = command;
+        // Wenn wir noch kein WONT gesendet haben, senden wir das jetzt als
+        // Bestaetigung.
+        if (opt->lo_wishes->localside = WONT)
+            send_telnet_neg( ({WONT, option}) );
+        // Verhandlung beendet, Option is auf unserer Seite jetzt aus.
+        // Wuensche auch wieder zuruecksetzen.
+        opt->re_wishes->localside = 0;
+        opt->lo_wishes->localside = 0;
+        if (opt->state->localside != 0)
+        {
+          opt->state->localside = 0;
+          _call_handler(opt, LOCALOFF, 0);
+        }
+    }
+    else if ( command == DO )
+    {
+        // Client will, dass wir option tun. Mal schauen, wie wir dazu stehen.
+        // wenn die Option auf unserer Seite aber schon an ist, muessen wir
+        // dies ignorieren.
+        if (opt->state->localside == 1)
+          return;
+
+        opt->re_wishes->localside = command;
+
+        if ( opt->lo_wishes->localside == 0 ) {
+            // wir haben unsere Wuensche noch nicht geaeussert. Sobald wir
+            // bestaetigen, ist die Option auf unserer Seite an/aus und die
+            // Verhandlungen beendet.
+            // in jedem Fall die Wuensche zuruecksetzen
+            opt->re_wishes->localside = 0;
+            opt->lo_wishes->localside = 0;
+            if (opt->localhandler)
+            {
+                send_telnet_neg(({WILL, option}));
+                opt->state->localside = 1;
+                _call_handler(opt, LOCALON, 0);
+            }
+            else
+            {
+                send_telnet_neg(({WONT, option}));
+                opt->state->localside = 0;
+                _call_handler(opt, LOCALOFF, 0);
+            }
+        }
+        else if (opt->lo_wishes->localside == WILL ) {
+            // wir haben schon WILL gesendet, welches der Client jetzt
+            // bestaetigt hat (d.h. die Option ist jetzt auf dieser Seite an),
+            // wir bestaetigen das aber nicht (nochmal).
+            opt->re_wishes->localside = 0;
+            opt->lo_wishes->localside = 0;
+            if (opt->state->localside != 1)
+            {
+              opt->state->localside = 1;
+              _call_handler(opt, LOCALON, 0);
+            }
+        }
+        else {
+            // Mhmm. Wir haben ein WONT gesendet, der Client hat mit DO
+            // geantwortet. Das darf er eigentlich nicht.
+            // TODO: Was tun?
+            send_telnet_neg ( ({WONT, option}) );
+        }
+        // fertig mit DO
+        return;
+    }
+    // bleibt noch SB ueber
+    else if ( command == SB )
+    {
+        opt->re_wishes->sbdata = optargs;
+        _call_handler(opt, SB, optargs);
+        return;
+    } // if ( command == SB )
+}
+
+// wird nur in base.c gerufen, wenn die Verbindung an das Spielerobjekt
+// uebergeben wurde.
+// es uebertraegt unter anderem den Telnet Option Zustand aus login.c (das ist
+// dann previous_object()) in das Spielerobjekt (welches dann this_object())
+// ist!
+protected void
+startup_telnet_negs()
+{
+  int* optargs;
+
+  Set( P_TTY_TYPE, 0 );  //avoid ANY mistakes... Wird unten neu gesetzt.
+  // Daten aus dem Loginobjekt uebertragen. Das ist wichtig! (Dabei wird dann
+  // auch der Status von der letzten Session ueberschrieben.)
+  TN = (mapping) previous_object()->query_telnet_neg();
+  // bevor irgendwas anderes gemacht wird, werden erstmal die Standardhandler
+  // gesetzt. Die sind naemlich in diesem Objekt jetzt erstmal kaputt, weil
+  // sie im Loginobjekt gerufen werden.
+  _bind_telneg_std_handlers();
+  // dann restliche Daten aus dem Loginobjekt holen.
+  Terminals = (string *) previous_object()->query_terminals();
+  Set( P_TTY_COLS, previous_object()->Query(P_TTY_COLS) );
+  Set( P_TTY_ROWS, previous_object()->Query(P_TTY_ROWS) );
+
+  struct telopt_s opt = TN[TELOPT_NAWS];
+  if (optargs = (opt->re_wishes)->sbdata) {
+      eval_naws(optargs);
+  }
+
+  if ( pointerp(Terminals) && sizeof(Terminals)) {
+      if ( Terminals[0][0..3] == "dec-" )
+          Terminals[0] = Terminals[0][4..];
+
+      if ( Terminals[0] == "linux" )
+          Terminals[0] = "vt100";
+
+      Set( P_TTY_TYPE, Terminals[0] );
+  }
+}
+
+// somehow completely out of the ordinary options processing/negotiation. But
+// the only purpose is to transmit something over the wire which is not shown,
+// but (likely) answered by the other device.
+protected void send_telnet_timing_mark() {
+  struct telopt_s opt = TN[TELOPT_TM];
+  if (pointerp(opt->data))
+    opt->data[1] = utime();
+  else
+    opt->data = ({ 0, utime() });
+  // absichtlich nicht do_telnet_ne() verwendet, da dies nicht senden wuerde,
+  // haette der Client schonmal mit WILL geantwortet. TELOPT_TM ist aber eine
+  // Option, bei der man das darf...
+  send_telnet_neg( ({DO, TELOPT_TM}) );
+}
+
+/* Is called from the H_PRINT_PROMPT driver hook and appends the IAC EOR if
+ * the client supports it.
+ */
+void print_prompt(string prompt) {
+//    if (extern_call() && previous_object()!=this_object())
+//        return;
+
+    // ggf. Uhrzeit in den prompt reinschreiben.
+    prompt = regreplace(prompt,"\\t",strftime("%H:%M"),0);
+    // Prompt senden
+    tell_object(this_object(), prompt);
+    // Und EOR senden, falls vom Client gewuenscht.
+    struct telopt_s opt = TN[TELOPT_EOR];
+    if (opt->state->localside == 1)
+    {
+        binary_message(({IAC, EOR}), 1);
+        DTN("tn_eor ",({IAC,EOR}));
+    }
+}
+
+// Helper
+private void eval_naws(int *optargs) {
+  int l, c;
+
+  if ( sizeof(optargs) != 4 )
+  {
+      tell_object(this_object(),
+          break_string( sprintf("Dein Client hat einen Fehler beim"
+                            +"Aushandeln der TELOPT_NAWS - er hat"
+                            +"IAC SB %O IAC SE gesendet!\n",
+                            optargs), 78,
+                    "Der GameDriver teilt Dir mit: " ));
+      // und dem Client sagen, dass er den Schrott nicht mehr uebertragen
+      // soll (falls wir das nicht schon gemacht haben).
+      struct telopt_s opt = TN[TELOPT_NAWS];
+      if (opt->state->remoteside == WILL
+          && opt->lo_wishes->remoteside != DONT)
+        send_telnet_neg(( {DONT, TELOPT_NAWS}) );
+      return;
+  }
+
+  if ( interactive(this_object()) ){
+      if ( !optargs[1] )
+          c = optargs[0];
+      else
+          c = optargs[1] + optargs[0] * 256;
+
+      if ( c < 35 ){
+          if (Query(P_TTY_SHOW))
+              tell_object( this_object(),
+                       break_string("Dein Fenster ist schmaler als"
+                                    +" 35 Zeichen? Du scherzt. ;-)"
+                                    +" Ich benutze den Standardwert"
+                                    +" von 80 Zeichen.\n", 78,
+                                    "Der GameDriver teilt Dir mit: ")
+                       );
+          c = 80;
+      }
+
+      if ( !optargs[3] )
+          l = optargs[2];
+      else
+          l = 256 * optargs[2] + optargs[3];
+
+      if ( l > 100 ){
+          //TODO: remove
+          l = 100;
+          if (Query(P_TTY_SHOW))
+              tell_object( this_object(),
+                       break_string("Tut mir leid, aber ich kann"
+                                    +" nur bis zu 100 Zeilen"
+                                    +" verwalten.\n", (c ? c-2 : 78),
+                                    "Der GameDriver teilt Dir mit: " )
+                       );
+      }
+
+      if ( l < 3 ){
+          if (Query(P_TTY_SHOW))
+              tell_object( this_object(),
+                       break_string("Du willst weniger als drei"
+                                    +" Zeilen benutzen? Glaub ich"
+                                    +" Dir nicht - ich benutze den"
+                                    +" Standardwert von 24"
+                                    +" Zeilen.\n", (c ? c-2 : 78),
+                                    "Der GameDriver teilt Dir mit: " )
+                       );
+          l = 24;
+      }
+
+      if ( ((int) Query(P_TTY_ROWS) != l) ||
+           ((int) Query(P_TTY_COLS) != c) ){
+          Set( P_TTY_ROWS, l );
+          Set( P_TTY_COLS, c );
+
+          if (Query(P_TTY_SHOW))
+              tell_object( this_object(),
+                       break_string("Du hast Deine Fenstergroesse auf"
+                                    +" "+l+" Zeilen und "+c+
+                                    " Spalten geaendert.\n", c-2,
+                                    "Der GameDriver teilt Dir mit: ")
+                       );
+      }
+  }
+}
+
+private void _call_old_SendTelOpts(object po) {
+  if (!objectp(po) || !interactive(po)) return;
+/*
+  closure cl=unbound_lambda( ({}),
+               ({ #'funcall, ({#'symbol_function, "SendTelopts"}) }) );
+
+  funcall(bind_lambda(cl, po));
+*/  
+  // Bloody temporary Hack until next reboot...
+
+  funcall( bind_lambda( #'efun::binary_message, po ),
+           ({ IAC, WILL, TELOPT_EOR,
+              IAC, DO, TELOPT_TTYPE,
+              IAC, DO, TELOPT_NAWS,
+              IAC, DO, TELOPT_LINEMODE,
+           }), 0x1 );
+}
+
+// Query-/Set-Methoden
+// Und wenn hier einer von aussen dran rumpfuscht, werde ich sauer.
+mapping
+query_telnet_neg()
+{
+   if (interactive(previous_object())
+      && program_time(previous_object()) < 1359926079
+      && load_name(this_object()) == "/secure/login" )
+    {
+      call_out(#'_call_old_SendTelOpts, 0, previous_object());
+      // alte Datenstruktur zurueckgeben... Leider leer...
+      return (["sent": m_allocate(3,3), "received": m_allocate(3,3) ]);
+    }
+
+    return TN;
+}
+
+// siehe oben
+string *
+query_terminals() {
+    return Terminals;
+}
+
+public int _query_p_lib_telnet_rttime()
+{
+  struct telopt_s opt = TN[TELOPT_TM];
+  if (opt && pointerp(opt->data))
+    return (opt->data)[0];
+  return 0;
+}
+
diff --git a/secure/telnetneg.h b/secure/telnetneg.h
new file mode 100644
index 0000000..87b6723
--- /dev/null
+++ b/secure/telnetneg.h
@@ -0,0 +1,44 @@
+// MorgenGrauen MUDlib
+//
+// /secure/telnetneg.h -- Definitionen und Prototypes fuer Telnet-Negotiations
+//
+// $Id: telnetneg.h,v 1.1 2003/02/17 11:20:02 Rikus Exp $
+
+#ifndef _SECURE_TELNETNEG_H_
+#define _SECURE_TELNETNEG_H_
+// Dies ist vermutlich der einzige Teil, den andere (z.B. player/base.h)
+// brauchen...
+// Properties fuer die tatsaechliche Fenstergroesse des Telnetclients sowie
+// den eingestellten Terminaltyp. Falls der Client die Daten nicht
+// herausrueckt, bleiben die Properties leer.
+#define P_TTY                "tty"
+#define P_TTY_COLS           "tty_cols"
+#define P_TTY_ROWS           "tty_rows"
+#define P_TTY_TYPE           "tty_type"
+#define P_TTY_SHOW           "tty_show"
+#define P_TELNET_RTTIME      "p_lib_telnet_rttime"
+
+#endif
+
+
+#ifdef NEED_PROTOTYPES
+#ifndef _SECURE_TELNETNEG_H_PROTOS_
+#define _SECURE_TELNETNEG_H_PROTOS_
+// wird eigentlich nur fuer die Implementation gebraucht
+#include "/sys/telnet.h"
+
+// Konstanten fuer die jew. remote und local handler
+#define LOCALON   WILL
+#define LOCALOFF  WONT
+#define REMOTEON  DO
+#define REMOTEOFF DONT
+
+protected int bind_telneg_handler(int option, closure re, closure lo,
+                                  int initneg);
+protected int do_telnet_neg(int option, int action);
+protected varargs int send_telnet_neg(int *arr, int bm_flags);
+protected varargs int send_telnet_neg_str(string str, int bm_flags);
+
+#endif // _SECURE_TELNETNEG_H_PROTOS_
+#endif // NEED_PROTOTYPES
+
diff --git a/secure/topliste.c b/secure/topliste.c
new file mode 100644
index 0000000..2b757e4
--- /dev/null
+++ b/secure/topliste.c
@@ -0,0 +1,280 @@
+// MorgenGrauen MUDlib
+/** \file /file.c
+* Kurzbeschreibung.
+* Langbeschreibung...
+* \author <Autor>
+* \date <date>
+* \version $Id$
+*/
+/* Changelog:
+*/
+#pragma strong_types,save_types,rtt_checks
+#pragma no_clone,no_inherit,no_shadow
+#pragma pedantic, range_check
+
+#include <defines.h>
+#include <events.h>
+#include <wizlevels.h>
+#include <lepmaster.h>
+#include <properties.h>
+#include <userinfo.h>
+
+#define HOME(x) (__PATH__(0)+x)
+#include <living/comm.h>
+#define ZDEBUG(x) if (find_player("zesstra")) \
+  find_player("zesstra")->ReceiveMsg(x,MT_DEBUG,0,object_name()+":",this_object())
+//#define ZDEBUG(x)
+
+object *players = ({});
+
+protected void create()
+{
+  seteuid(getuid());
+  if (sl_open(HOME("ARCH/topliste.sqlite")) != 1)
+  {
+    raise_error("Datenbank konnte nicht geoeffnet werden.\n");
+  }
+  // Tabellen und Indices anlegen, falls die nicht existieren.
+  sl_exec("CREATE TABLE IF NOT EXISTS topliste(name TEXT PRIMARY KEY ASC, "
+          "gilde TEXT NOT NULL, rasse TEXT NOT NULL, "
+          "age DATETIME, wizlevel INTEGER, "
+          "lastupdate DATETIME DEFAULT current_timestamp, "
+          "lep INTEGER, qp INTEGER, xp INTEGER, level INTEGER"
+          "hardcore INTEGER);");
+  sl_exec("CREATE INDEX IF NOT EXISTS idx_gilde ON topliste(gilde);");
+  sl_exec("CREATE INDEX IF NOT EXISTS idx_rasse ON topliste(rasse);");
+  sl_exec("CREATE INDEX IF NOT EXISTS idx_hardcore ON topliste(hardcore);");
+  
+  // Login-Event abonnieren
+  if (EVENTD->RegisterEvent(EVT_LIB_LOGIN,
+                            "listen", this_object()) <= 0)
+  {
+    raise_error("Loginevent konnte nicht abonniert werden.\n");
+  }
+  if (EVENTD->RegisterEvent(EVT_LIB_ADVANCE,
+                            "listen", this_object()) <= 0)
+  {
+    raise_error("EVT_LIB_ADVANCE konnte nicht abonniert werden.\n");
+  }
+  if (EVENTD->RegisterEvent(EVT_LIB_QUEST_SOLVED,
+                            "listen", this_object()) <= 0)
+  {
+    raise_error("EVT_LIB_QUEST_SOLVED konnte nicht abonniert werden.\n");
+  }
+  if (EVENTD->RegisterEvent(EVT_LIB_MINIQUEST_SOLVED,
+                            "listen", this_object()) <= 0)
+  {
+    raise_error("EVT_LIB_MINIQUEST_SOLVED konnte nicht abonniert werden.\n");
+  }
+}
+
+private void process()
+{
+  foreach(object pl : &players)
+  {
+    if (get_eval_cost() < 200000)
+    {
+      call_out(#'process, 2);
+      players -= ({0});
+      return;
+    }
+    sl_exec("INSERT OR REPLACE INTO topliste(name, gilde, rasse, "
+                "age, wizlevel, lastupdate, lep, qp, xp, level, hardcore) "
+                "VALUES(?1,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11);",
+                pl->query_real_name(),
+                pl->QueryProp(P_GUILD) || "unbekannt",
+                pl->QueryProp(P_RACE),
+                pl->QueryProp(P_AGE),
+                query_wiz_level(pl),
+                time(),
+                LEPMASTER->QueryLEPForPlayer(pl),
+                pl->QueryProp(P_QP),
+                pl->QueryProp(P_XP),
+                pl->QueryProp(P_LEVEL),
+                pl->query_hc_play()
+               );
+    pl=0;
+  }
+  players -= ({0});
+}
+
+public void listen(string eid, object trigob, mixed data)
+{
+  if (previous_object() != find_object(EVENTD)
+      || !trigob
+      || !query_once_interactive(trigob)
+      || IS_LEARNER(trigob)
+      || (mixed)trigob->QueryProp(P_TESTPLAYER)
+      || trigob->QueryGuest()
+      || trigob->QueryProp(P_NO_TOPLIST)
+     )
+    return;
+
+  // Dieser handler hat nur 30k Ticks zur Verfuegung, dummerweise kann
+  // QueryLEPForPlayer() locker ueber 30k kosten, wenn der Spieler nicht in
+  // Caches drin ist. Daher muss das jetzt per call_out entkoppelt werden.
+  // *seufz*
+  if (member(players, trigob) == -1)
+  {
+    players += ({trigob});
+    if (find_call_out(#'process) == -1)
+      call_out(#'process, 2);
+  }
+}
+
+public varargs < <string|int>* >* Liste(string rasse, string gilde,
+                                     int limit, string sort)
+{
+  // Defaults:
+  sort ||= "lep";
+  if (!limit || limit > 100)
+    limit=100;
+  else if (limit < 1)
+    limit=1;
+  if (rasse && gilde)
+    return sl_exec(
+        "select name,lep,qp,xp,level,age,rasse,gilde,wizlevel,hardcore from topliste "
+        "WHERE rasse=?1 AND gilde=?2 "
+        "ORDER BY "+sort+" DESC LIMIT "+limit+";",
+        rasse, gilde);
+  else if (rasse)
+    return sl_exec(
+        "select name,lep,qp,xp,level,age,rasse,gilde,wizlevel,hardcore from topliste "
+        "WHERE rasse=?1 "
+        "ORDER BY "+sort+" DESC LIMIT "+limit+";",
+        rasse);
+  else if (gilde)
+    return sl_exec(
+        "select name,lep,qp,xp,level,age,rasse,gilde,wizlevel,hardcore from topliste "
+        "WHERE gilde=?1 "
+        "ORDER BY "+sort+" DESC LIMIT "+limit+";",
+        gilde);
+  return sl_exec(
+      "select name,lep,qp,xp,level,age,rasse,gilde,wizlevel,hardcore from topliste "
+      "ORDER BY "+sort+" DESC LIMIT "+limit+";");
+}
+
+public varargs < <string|int>* >* SpielerListe(string rasse, string gilde,
+                                     int limit, string sort)
+{
+  // Defaults:
+  sort ||= "lep";
+  if (!limit || limit > 100)
+    limit=100;
+  else if (limit < 1)
+    limit=1;
+  if (rasse && gilde)
+    return sl_exec(
+        "select name,lep,qp,xp,level,age,rasse,gilde,wizlevel,hardcore from topliste "
+        "WHERE rasse=?1 AND gilde=?2 AND wizlevel=0 "
+        "ORDER BY "+sort+" DESC LIMIT "+limit+";",
+        rasse, gilde);
+  else if (rasse)
+    return sl_exec(
+        "select name,lep,qp,xp,level,age,rasse,gilde,wizlevel,hardcore from topliste "
+        "WHERE rasse=?1 AND wizlevel=0 "
+        "ORDER BY "+sort+" DESC LIMIT "+limit+";",
+        rasse);
+  else if (gilde)
+    return sl_exec(
+        "select name,lep,qp,xp,level,age,rasse,gilde,wizlevel,hardcore from topliste "
+        "WHERE gilde=?1 AND wizlevel=0 "
+        "ORDER BY "+sort+" DESC LIMIT "+limit+";",
+        gilde);
+  return sl_exec(
+      "select name,lep,qp,xp,level,age,rasse,gilde,wizlevel,hardcore from topliste "
+      "WHERE wizlevel=0 "
+      "ORDER BY "+sort+" DESC LIMIT "+limit+";");
+}
+
+public varargs < <string|int>* >* SeherListe(string rasse, string gilde,
+                                     int limit, string sort)
+{
+  // Defaults:
+  sort ||= "lep";
+  if (!limit || limit > 100)
+    limit=100;
+  else if (limit < 1)
+    limit=1;
+  if (rasse && gilde)
+    return sl_exec(
+        "select name,lep,qp,xp,level,age,rasse,gilde,wizlevel,hardcore from topliste "
+        "WHERE rasse=?1 AND gilde=?2 AND wizlevel=1 "
+        "ORDER BY "+sort+" DESC LIMIT "+limit+";",
+        rasse, gilde);
+  else if (rasse)
+    return sl_exec(
+        "select name,lep,qp,xp,level,age,rasse,gilde,wizlevel,hardcore from topliste "
+        "WHERE rasse=?1 AND wizlevel=1 "
+        "ORDER BY "+sort+" DESC LIMIT "+limit+";",
+        rasse);
+  else if (gilde)
+    return sl_exec(
+        "select name,lep,qp,xp,level,age,rasse,gilde,wizlevel,hardcore from topliste "
+        "WHERE gilde=?1 AND wizlevel=1 "
+        "ORDER BY "+sort+" DESC LIMIT "+limit+";",
+        gilde);
+  return sl_exec(
+      "select name,lep,qp,xp,level,age,rasse,gilde,wizlevel,hardcore from topliste "
+      "WHERE wizlevel=1 "
+      "ORDER BY "+sort+" DESC LIMIT "+limit+";");
+}
+
+public varargs < <string|int>* >* HardcoreListe(string rasse, string gilde,
+                                     int limit, string sort)
+{
+  // Defaults:
+  sort ||= "lep";
+  if (!limit || limit > 100)
+    limit=100;
+  else if (limit < 1)
+    limit=1;
+  if (rasse && gilde)
+    return sl_exec(
+        "select name,lep,qp,xp,level,age,rasse,gilde,wizlevel,hardcore from topliste "
+        "WHERE rasse=?1 AND gilde=?2 AND hardcore>0 "
+        "ORDER BY "+sort+" DESC LIMIT "+limit+";",
+        rasse, gilde);
+  else if (rasse)
+    return sl_exec(
+        "select name,lep,qp,xp,level,age,rasse,gilde,wizlevel,hardcore from topliste "
+        "WHERE rasse=?1 AND hardcore>0 "
+        "ORDER BY "+sort+" DESC LIMIT "+limit+";",
+        rasse);
+  else if (gilde)
+    return sl_exec(
+        "select name,lep,qp,xp,level,age,rasse,gilde,wizlevel,hardcore from topliste "
+        "WHERE gilde=?1 AND hardcore>0 "
+        "ORDER BY "+sort+" DESC LIMIT "+limit+";",
+        gilde);
+  return sl_exec(
+      "select name,lep,qp,xp,level,age,rasse,gilde,wizlevel,hardcore from topliste "
+      "WHERE hardcore>0 "
+      "ORDER BY "+sort+" DESC LIMIT "+limit+";");
+}
+
+varargs int remove(int silent)
+{
+  EVENTD->UnregisterEvent(EVT_LIB_LOGIN, this_object());
+  sl_close();
+  destruct(ME);
+  return 1;
+}
+
+public mixed sql_query(string query)
+{
+  if (ARCH_SECURITY)
+    return sl_exec(query);
+  return 0;
+}
+
+void reset()
+{
+  // Alle Eintraege loeschen, die seit 90 nicht mehr aktualisiert wurden.
+//  sl_exec("DELETE FROM topliste WHERE lastupdate<?1;",
+//                time()-90*24*3600);
+  sl_exec("DELETE FROM topliste WHERE name IN (SELECT name FROM topliste "
+          "ORDER BY lep DESC LIMIT 1000, -1);");
+  set_next_reset(86400);
+}
+
diff --git a/secure/udp/channel.c b/secure/udp/channel.c
new file mode 100644
index 0000000..3592ffe
--- /dev/null
+++ b/secure/udp/channel.c
@@ -0,0 +1,110 @@
+// MorgenGrauen MUDlib
+//
+// channel.c
+//
+// $Id: channel.c 9142 2015-02-04 22:17:29Z Zesstra $
+
+#include <udp.h>
+
+#include <properties.h>
+#include <daemon.h>
+
+#ifdef ZEBEDEE
+inherit "/sys/format";
+#endif
+
+#define COMMAND                "cmd"
+#define CHANNEL                "channel"
+
+private nosave mixed _name_;
+
+int filter_listeners(object ob, string channel) {
+    return ob->QueryProp(P_INTERMUD);
+}
+
+int udp_channel(mapping data) {
+  object *list;
+  string msg;
+  int i, type;
+
+  /* Compatability with older systems. */
+  if (!data[CHANNEL])
+    data[CHANNEL] = data["CHANNEL"];
+  if (!data[COMMAND])
+    data[COMMAND] = data["CMD"];
+  if (!data[DATA])
+    data[DATA]="";
+
+  if (!stringp(data[CHANNEL]) || !sizeof(data[CHANNEL])
+      || !stringp(data[DATA]) || !sizeof(data[DATA])
+      || !stringp(data[NAME]) || !sizeof(data[NAME])
+      || !stringp(data[SENDER]) || !sizeof(data[SENDER]))
+    return 0;
+
+  data[DATA]=
+    implode(filter(explode(data[DATA], ""), #'>=, " "), "");//'))
+  data[NAME]=
+    implode(filter(explode(data[NAME], ""), #'>=, " "), "");//'))
+  switch(data[COMMAND]) {
+  case "list":
+    /* Request for a list of people listening to a certain channel. */
+    list = filter(users(), "filter_listeners",
+                        this_object(), data[CHANNEL]);
+    if (i = sizeof(list)) {
+      msg = "[" + capitalize(data[CHANNEL]) + "@" +
+        LOCAL_NAME + "] Listening:\n";
+      while(i--)
+        msg +=
+          "    " + capitalize(list[i]->query_real_name()) + "\n";
+    }
+    else
+      msg = "[" + capitalize(data[CHANNEL]) + "@" + LOCAL_NAME 
+      + "] Nobody Listening.\n";
+    INETD->_send_udp(data[NAME], ([
+                                  REQUEST: REPLY,
+                                  RECIPIENT: data[SENDER],
+                                  ID: data[ID],
+                                  DATA: msg
+                                ]));
+    return 1;
+  case "emote": /* A channel emote. */
+      type = MSG_EMOTE;
+      break;
+  default: /* A regular channel message. */
+    type = MSG_SAY;
+    break;
+  }
+  _name_ = capitalize(data[SENDER])+"@"+capitalize(data[NAME]);
+  CHMASTER->send(capitalize(data[CHANNEL]), this_object(), 
+                 data[DATA], type);
+  _name_ = 0;
+  return 1;
+}
+
+string name() { return _name_ || "<Intermud>"; }
+string Name() {return capitalize(_name_ || "<Intermud>");}
+
+private void _send(string mud, mixed data, mapping request)
+{
+  if(member(data[HOST_COMMANDS], "channel") != -1 ||
+     member(data[HOST_COMMANDS], "*") != -1)
+    INETD->_send_udp(data[HOST_NAME], request);
+}
+
+void ChannelMessage(mixed m)
+{
+  mapping request;
+  if(m[1] == this_object()) return;
+  request = ([
+              REQUEST : "channel",
+              SENDER  : m[1]->name() || capitalize(getuid(m[1])),
+              "CHANNEL": lower_case(m[0]),
+              DATA    : implode(old_explode(m[2], "\n"), " ")]);
+  if(m[3] == MSG_GEMOTE || m[3] == MSG_EMOTE)
+  { 
+    request["EMOTE"] = 1;
+    request["CMD"] = "emote";
+  }
+  walk_mapping(INETD->query("hosts") - ([lower_case(MUDNAME)]), 
+               #'_send/*'*/, request);
+}
diff --git a/secure/udp/finger.c b/secure/udp/finger.c
new file mode 100644
index 0000000..c9d4c4d
--- /dev/null
+++ b/secure/udp/finger.c
@@ -0,0 +1,31 @@
+// MorgenGrauen MUDlib
+//
+// finger.c
+//
+// $Id: finger.c 6081 2006-10-23 14:12:34Z Zesstra $
+
+#include <udp.h>
+
+string last_finger;
+
+#ifdef ZEBEDEE
+#include <system.h>
+#elif !defined(INETD)
+#define INETD	"/secure/inetd"
+#endif
+
+void udp_finger(mapping data)
+{
+  last_finger=capitalize(to_string(data[SENDER]))+"@"+data[NAME];
+  INETD->_send_udp(data[NAME], ([
+	REQUEST: REPLY,
+	RECIPIENT: data[SENDER],
+	ID: data[ID],
+	DATA: "/p/daemon/finger"->finger_single(data[DATA])
+    ]) );
+}
+
+string QueryLastFinger()
+{
+  return last_finger;
+}
diff --git a/secure/udp/htmlwho.c b/secure/udp/htmlwho.c
new file mode 100644
index 0000000..d9cd899
--- /dev/null
+++ b/secure/udp/htmlwho.c
@@ -0,0 +1,53 @@
+// MorgenGrauen MUDlib
+//
+// htmlwho.c
+//
+// $Id: htmlwho.c 8755 2014-04-26 13:13:40Z Zesstra $
+
+#pragma weak_types
+
+#include <udp.h>
+
+#define TJ(x) if (find_player("jof")) tell_object(find_player("jof"),x)
+
+string adjust(string str,int wid)
+{
+  int w2;
+
+  w2=wid/2;
+  wid=wid-w2;
+  return extract("                                                          ",0,wid-1)+str+
+    extract("                                                         ",0,w2-1);
+}
+
+udp_htmlwho(data)
+{
+  int i,num;
+  string *mdata;
+  string wholiste,tmp,tmp2;
+ 
+  mdata="/obj/werliste"->QueryWhoListe(0,0,1);
+  num=sizeof(mdata);
+  for (i=num-1;i>=0;i--)
+  {
+    tmp=mdata[i][0]->name();
+    if (tmp&&sizeof(tmp))
+    {
+      tmp2=explode(mdata[i][1],tmp);
+      if (sizeof(tmp2)>1)
+      {
+        tmp2[0]="<A HREF=\"/htbin/mudwww?finger?"+getuid(mdata[i][0])+"\"><b>";
+        tmp2[1]="</b></A>"+tmp2[1];
+        mdata[i][1]=implode(tmp2,tmp);
+      }
+      mdata[i]=" <LI> "+mdata[i][1];
+    }
+  }
+  wholiste=implode(mdata,"\n");
+  INETD->_send_udp(data[NAME], ([
+				REQUEST: REPLY,
+				RECIPIENT: data[SENDER],
+				ID: data[ID],
+				DATA: wholiste
+				]) );
+}
diff --git a/secure/udp/locate.c b/secure/udp/locate.c
new file mode 100644
index 0000000..d793d05
--- /dev/null
+++ b/secure/udp/locate.c
@@ -0,0 +1,33 @@
+// MorgenGrauen MUDlib
+//
+// locate.c
+//
+// $Id: locate.c 6081 2006-10-23 14:12:34Z Zesstra $
+
+#include <udp.h>
+
+#define FOUND		"fnd"
+#define USER		"user"
+#define VERBOSE		"vbs"
+
+void udp_locate(mapping data) {
+    mapping ret;
+    object ob;
+
+    ret = ([
+	REQUEST: REPLY,
+	RECIPIENT: data[SENDER],
+	ID: data[ID],
+	USER: data[USER],
+	VERBOSE: data[VERBOSE],
+    ]);
+    if (data[DATA] && (ob = find_player(data[DATA])) &&
+    interactive(ob) && !ob->query_invis()) {
+	ret[FOUND] = 1;
+	ret[DATA] = "locate@" + LOCAL_NAME + ": " + ob->short();
+    }
+    else
+	ret[DATA] = "locate@" + LOCAL_NAME + ": No such player: " +
+	data[DATA] + "\n";
+    INETD->_send_udp(data[NAME], ret);
+}
diff --git a/secure/udp/mail.c b/secure/udp/mail.c
new file mode 100644
index 0000000..04f8651
--- /dev/null
+++ b/secure/udp/mail.c
@@ -0,0 +1,64 @@
+// MorgenGrauen MUDlib
+//
+// mail.c
+//
+// $Id: mail.c 6081 2006-10-23 14:12:34Z Zesstra $
+
+/*
+ * VERSION 1.0
+ * udp module for the UDP MAIL system (Author: Alvin@Sushi)
+ * Requires INETD V0.60 or higher (INETD Author: Nostradamus@Zebedee)
+ */
+
+#include <udp.h>
+#include <udp_mail.h>
+
+/*
+#define DEBUG(msg) if (find_player("hate")) tell_object(find_player("hate"),msg)
+*/
+#undef DEBUG
+#define DEBUG(x)
+
+void udp_mail(mapping data)
+{
+  DEBUG(sprintf("UDPMAIL %O\n",data));
+  if(!member(data,RECIPIENT) || !data[RECIPIENT])
+    {
+      log_file("INETD","Invalid udp_mail packet. No Recipient.\n");
+      return;
+    }
+
+  if(!LOCAL_MAILER->query_recipient_ok(data[RECIPIENT]))
+    {
+      INETD->_send_udp(data[NAME], ([
+	    REQUEST: REPLY,
+	    RECIPIENT: data[SENDER],
+	    UDPM_STATUS: UDPM_STATUS_UNKNOWN_PLAYER,
+	    UDPM_WRITER: data[UDPM_WRITER],
+	    UDPM_SPOOL_NAME: data[UDPM_SPOOL_NAME],
+	    ID: data[ID],
+	    DATA: "Reason: Unknown player \""+capitalize(data[RECIPIENT])+
+		"\"\n\nINCLUDED MESSAGE FOLLOWS :-\n\n"+
+		"Subject: "+data[UDPM_SUBJECT]+"\n"+data[DATA]
+      ]) );
+
+      return;
+    }
+
+  LOCAL_MAILER->deliver_mail(
+	data[RECIPIENT],			/* To */
+	data[UDPM_WRITER]+"@"+data[NAME],	/* From */
+	data[UDPM_SUBJECT],			/* Subj */
+	data[DATA]				/* Mail Body */
+  );
+
+  INETD->_send_udp(data[NAME], ([
+	    REQUEST: REPLY,
+	    RECIPIENT: data[SENDER],
+	    UDPM_STATUS: UDPM_STATUS_DELIVERED_OK,
+	    UDPM_WRITER: data[UDPM_WRITER],
+	    UDPM_SPOOL_NAME: data[UDPM_SPOOL_NAME],
+	    ID: data[ID],
+	    DATA: data[RECIPIENT]
+  ]) );
+}
diff --git a/secure/udp/man.c b/secure/udp/man.c
new file mode 100644
index 0000000..88c4b57
--- /dev/null
+++ b/secure/udp/man.c
@@ -0,0 +1,98 @@
+#pragma strict_types
+#include <udp.h>
+#include <daemon.h>
+
+#define MAX_READ_FILE_LEN 50000
+
+// TEMPORARY
+
+#include <udp_language.h>
+#include <logging.h>
+
+#ifndef LOCAL_NAME
+#define LOCAL_NAME "MorgenGrauen"
+#endif
+
+#ifndef INETD_INVALID_ACCESS
+#define INETD_INVALID_ACCESS INTERMUDLOG("INVALID_ACCESS")
+#endif
+
+#ifndef INVALID_ACCESS
+#define INVALID_ACCESS(x) \
+           log_file(INETD_INVALID_ACCESS, \
+                    sprintf(INETD_INV_ACCESS_MSG "TI: %O PO: %O\n", \
+                            ctime()[4..15],this_interactive(), \
+                            previous_object()))
+#endif
+
+
+// END TEMPORARY
+
+void udp_man(mapping data)
+{
+  mapping pages;
+  int index;
+  string manpage,ret;
+  string|string* tmp;
+
+  if (previous_object()!=find_object(INETD))
+  {
+    INVALID_ACCESS(Man);
+    return;
+  }
+
+  manpage=data[DATA];
+  tmp=explode(manpage,"/");
+  if (sizeof(tmp)>1)
+  {
+    if (file_size(MAND_DOCDIR+manpage)>=0)
+      tmp=({tmp[<1],manpage});
+    else
+      tmp=({});
+  }
+  else
+    tmp=(string *)call_other(MAND,"locate",data[DATA],0);
+  pages=([]);
+  index=sizeof(tmp);
+  while(index--)
+  {
+    if (tmp[1][0..1]!="g.") pages[tmp[index]]=tmp[index-1];
+    index--;
+  }
+  switch(sizeof(pages))
+  {
+    case 0:
+      ret=sprintf(INETD_NO_MANPAGE,LOCAL_NAME,manpage);
+      break;
+    case 1:
+      tmp=m_indices(pages)[0];
+      ret=sprintf(INETD_MANPAGE_FOUND,LOCAL_NAME,pages[tmp]);
+      index=0;
+      while(manpage=read_file(MAND_DOCDIR+tmp,index))
+      {
+        ret+=manpage;
+        index+=MAX_READ_FILE_LEN;
+      }
+      break;
+    default:
+      ret=sprintf(INETD_MANPAGES,LOCAL_NAME,"",
+                  break_string(implode(m_values(pages)," "),78),"");
+      break;
+  }
+  INETD->_send_udp(data[NAME],
+                     ([
+                       REQUEST: REPLY,
+                       RECIPIENT: data[SENDER],
+                       ID: data[ID],
+                       DATA: ret
+                     ]));
+}
+
+string send_request(string mudname, string pagename)
+{
+  return (INETD->_send_udp(mudname,
+                             ([REQUEST: "man",
+                               DATA: pagename,
+                               SENDER: getuid(previous_object())]),1)||
+          sprintf(INETD_MAN_REQUESTED,pagename,mudname));
+}
diff --git a/secure/udp/ping.c b/secure/udp/ping.c
new file mode 100644
index 0000000..f48329d
--- /dev/null
+++ b/secure/udp/ping.c
@@ -0,0 +1,22 @@
+// MorgenGrauen MUDlib
+//
+// ping.c
+//
+// $Id: ping.c 6081 2006-10-23 14:12:34Z Zesstra $
+
+#include <udp.h>
+
+#ifdef ZEBEDEE
+#include <system.h>
+#elif !defined(INETD)
+#define INETD	"/secure/inetd"
+#endif
+
+void udp_ping(mapping data) {
+    INETD->_send_udp(data[NAME], ([
+	REQUEST: REPLY,
+	RECIPIENT: data[SENDER],
+	ID: data[ID],
+	DATA: LOCAL_NAME + " is alive.\n"
+    ]) );
+}
diff --git a/secure/udp/query.c b/secure/udp/query.c
new file mode 100644
index 0000000..f9bc7cd
--- /dev/null
+++ b/secure/udp/query.c
@@ -0,0 +1,88 @@
+// MorgenGrauen MUDlib
+//
+// query.c
+//
+// $Id: query.c 7397 2010-01-26 21:48:11Z Zesstra $
+
+#include <udp.h>
+#include <udp_language.h>
+#include <strings.h>
+
+#ifdef ZEBEDEE
+#include <system.h>
+#elif !defined(INETD)
+#define INETD        "/secure/inetd"
+#endif
+
+/* Mud / Admin email address. */
+#define EMAIL        "mud@mg.mud.de"
+
+void udp_query(mapping data) {
+    mapping ret;
+
+    switch(data[DATA]) {
+        case "commands":
+            ret = INETD->query("hosts");
+            if (ret[lower_case(data[NAME])])
+                ret = ([
+                DATA: implode(ret[lower_case(data[NAME])][LOCAL_COMMANDS], ":")
+                ]);
+            else
+                ret = ([ DATA: implode(INETD->query("commands"), ":") ]);
+            break;
+        case "email":
+            ret = ([ DATA: EMAIL ]);
+            break;
+        case "hosts":
+        {
+            string tmp = "";
+      foreach(string mudname, mixed fields: INETD->query("hosts")) {
+        tmp += fields[HOST_NAME] + ":" +
+             fields[HOST_IP] + ":" +
+             fields[HOST_UDP_PORT] + ":" +
+             implode(fields[LOCAL_COMMANDS], ",") + ":" +
+             implode(fields[HOST_COMMANDS], ",") + "\n";
+      }
+            ret = ([ DATA: trim(tmp,TRIM_RIGHT, "\n") ]);
+            break;
+        }
+        case "inetd":
+            ret = ([ DATA: INETD_VERSION ]);
+            break;
+        case "list":
+            /* List of thingsthat can be queried. */
+            ret = ([ DATA: "commands:email:hosts:inetd:mud_port:time:version" ]);
+            break;
+        case "mud_port":
+            ret = ([ DATA: query_mud_port() ]);
+            break;
+        case "time":
+            ret = ([ DATA: time() ]);
+            break;
+        case "version":
+            ret = ([ DATA: version() ]);
+            break;
+        default:
+            /* Just ignore it for the time being. */
+            return;
+    }
+    INETD->_send_udp(data[NAME], ret + ([
+        REQUEST: REPLY,
+        RECIPIENT: data[SENDER],
+        ID: data[ID],
+        "QUERY": data[DATA]
+    ]) );
+}
+
+string send_request(string mudname, string prop)
+{
+  if (!stringp(mudname) || !stringp(prop))
+    return 0;
+  
+  return (INETD->_send_udp(mudname,
+                             ([REQUEST: "query",
+                               DATA: prop,
+                               SENDER: getuid(previous_object())]),1)||
+          sprintf(INETD_QUERY_REQUESTED, prop, mudname));
+}
+
diff --git a/secure/udp/reply.c b/secure/udp/reply.c
new file mode 100644
index 0000000..d60661a
--- /dev/null
+++ b/secure/udp/reply.c
@@ -0,0 +1,54 @@
+// MorgenGrauen MUDlib
+//
+// reply.c
+//
+// $Id: reply.c,v 1.2 2003/04/08 09:28:17 Rikus Exp $
+
+#include <udp.h>
+
+#ifndef DATE
+#define DATE	ctime()[4..15]
+#endif
+
+void udp_reply(mapping data)
+{
+  string err,recpt,serv;
+  object ob;
+  
+  
+  if (pointerp(data[SYSTEM])&&member(data[SYSTEM],TIME_OUT)>-1)
+  {
+    if (data[SENDER])
+    {
+      if (stringp(data[SENDER])&&(ob=find_player(data[SENDER])))
+      {
+        switch(data[REQUEST])
+        {
+          case "tell": serv="teile mit: ";break;
+          case "who":  serv="wer: ";break;
+          default: serv=data[REQUEST]+": "; break;
+        }
+        tell_object(ob, break_string("Das Mud \'" + data[NAME] +
+                                     "\' konnte nicht erreicht werden.\n",
+                                     78,serv));
+      }
+      else
+        if (objectp(ob = data[SENDER])||(ob = find_object(data[SENDER])))
+          ob->udp_reply(data);
+    }
+    return;
+  }
+  if (data[RECIPIENT])
+  {
+    if (stringp(data[RECIPIENT])&&(ob = find_player(data[RECIPIENT])))
+      tell_object(ob, "\n" + data[DATA]);
+    else if (ob=find_object(data[RECIPIENT]))
+      ob->udp_reply(data);
+    return;
+  }
+  if (data[REQUEST]=="ping")return;
+  if (data[DATA])
+    log_file(INETD_LOG_FILE, DATE + ": Antwort von " + data[NAME] +
+             "\n" + data[DATA] + "\n");
+  return;
+}
diff --git a/secure/udp/tell.c b/secure/udp/tell.c
new file mode 100644
index 0000000..7b1ab61
--- /dev/null
+++ b/secure/udp/tell.c
@@ -0,0 +1,118 @@
+// MorgenGrauen MUDlib
+//
+// tell.c
+//
+// $Id: tell.c 8755 2014-04-26 13:13:40Z Zesstra $
+
+#include <config.h>
+#include <udp.h>
+#include <properties.h>
+
+#ifdef ZEBEDEE
+#include <system.h>
+inherit "/sys/format";
+#elif !defined(INETD)
+#define INETD "/secure/inetd"
+#endif
+
+
+void udp_tell(mapping data) {
+  object ob;
+  string message_string, message_prefix, away;
+  int i,re;
+  string *message_array;
+
+  if (data[RECIPIENT] &&
+       (ob = find_player(lower_case(data[RECIPIENT]))) &&
+       interactive(ob)) {
+    
+    if (!stringp(data[SENDER]) || !sizeof(data[SENDER]))
+	data[SENDER]="<Unknown>";
+    if (!stringp(data[DATA]) || !sizeof(data[DATA]))
+	data[DATA]="<Nichts>";
+   
+    data[SENDER]=
+      implode(filter(explode(data[SENDER], ""),
+                           #'>=,/*'*/ " "), "");
+    data[DATA]=
+      implode(filter(explode(data[DATA], ""),
+                           #'>=,/*'*/ " "), "");
+    message_prefix=capitalize(data[SENDER])+"@"+data[NAME]+
+                   " teilt Dir mit: ";
+    message_string=break_string(data[DATA],78,message_prefix,0);
+
+    /* Die Anzahl der Leerzeilen am Ende eines tm's scheint nicht genormt */
+    while(message_string[<1]=='\n')message_string=message_string[0..<2];
+    message_string += "\n";
+
+    re = ob->Message("\n"+message_string, MSGFLAG_RTELL);
+
+    if (!ob->QueryProp(P_INVIS)){
+      /* Erst testen, ob die Meldung ueberhaupt angekommen ist! */
+      if(re==MESSAGE_DEAF)
+        message_string=sprintf("%s@"MUDNAME" ist momentan leider taub.\n",
+                               capitalize(getuid(ob)));
+      else if(re==MESSAGE_IGNORE_YOU)
+        message_string=sprintf("%s@"MUDNAME" ignoriert Dich.\n",
+                               capitalize(getuid(ob)));
+      else if(re==MESSAGE_IGNORE_MUD)
+        message_string=sprintf("%s@"MUDNAME" ignoriert das Mud '%s'.\n",
+                               capitalize(getuid(ob)),
+                               data[NAME]);
+      else {
+        /* Erst dann die Erfolgs-Rueckmeldung abschicken */
+        message_prefix="Du teilst "+capitalize(data[RECIPIENT]) + "@" 
+                       LOCAL_NAME + " mit: ";
+        message_string=break_string(data[DATA],78,message_prefix,0);
+        if(ob->QueryProp(P_AWAY))
+          message_string=sprintf("%s%s@"MUDNAME" ist gerade nicht "
+                                 "da: %s\n",
+                                 message_string,
+                                 ob->name(WER),
+                                 ob->QueryProp(P_AWAY));
+        else if ((i=query_idle(ob))>=600){ // 10 Mins
+          if (i<3600) away=time2string("%m %M",i);
+          else away=time2string("%h %H und %m %M",i);
+          message_string=
+             sprintf("%s%s@"MUDNAME" ist seit %s voellig untaetig.\n",
+                     message_string,
+                     ob->Name(WER),
+                     away);
+        }
+        switch(re) {
+        case MESSAGE_CACHE:
+          message_string +=
+            sprintf("%s@"MUDNAME" moechte gerade nicht gestoert werden.\n"+
+                    "Die Mittelung wurde von einem kleinen Kobold in Empfang"+
+                    "genommen.\nEr wird sie spaeter weiterleiten.\n",
+                    capitalize(getuid(ob)));
+          break;
+        case MESSAGE_CACHE_FULL:
+          message_string +=
+            sprintf("%s@"MUDNAME" moechte gerade nicht gestoert werden.\n"+
+                    "Die Mitteilung ging verloren, denn der Kobold kann sich "+
+                    "nichts mehr merken.\n",
+                    capitalize(getuid(ob)));
+          break;
+        }
+      }
+    }
+    else message_string="\nRoot@"MUDNAME": Spieler "+
+                        capitalize(data[RECIPIENT])+
+                        " finde ich in "MUDNAME" nicht!\n";
+    INETD->_send_udp(data[NAME],
+                    ([ REQUEST: REPLY,
+                       RECIPIENT: data[SENDER],
+                       ID: data[ID],
+                       DATA: "\n"+message_string ]) );
+  }
+  else
+    INETD->_send_udp(data[NAME],
+                    ([ REQUEST: REPLY,
+                       RECIPIENT: data[SENDER],
+                       ID: data[ID],
+                       DATA: sprintf("Root@"MUDNAME": Spieler %s finde "+
+                                     "ich in "MUDNAME" nicht!\n",
+                                     capitalize(data[RECIPIENT]))
+                    ]) );
+}
diff --git a/secure/udp/who.c b/secure/udp/who.c
new file mode 100644
index 0000000..7be9bb3
--- /dev/null
+++ b/secure/udp/who.c
@@ -0,0 +1,58 @@
+// MorgenGrauen MUDlib
+//
+// who.c
+//
+// $Id: who.c 8755 2014-04-26 13:13:40Z Zesstra $
+
+#include <config.h>
+#include <udp.h>
+
+int last;
+int maxtoday, maxever;
+
+void create()
+{
+  string tmp1, tmp2, dummy;
+
+  if (time()-last<1800) return;
+  last=time();
+  tmp1=read_file("/etc/maxusers.ever",0,1);
+  tmp2=read_file("/etc/maxusers",0,1);
+  if (stringp(tmp1)&&sizeof(tmp1)) sscanf(tmp1,"%d %s",maxever,dummy);
+  if (stringp(tmp2)&&sizeof(tmp2)) sscanf(tmp2,"%d %s",maxtoday,dummy);
+}
+
+string adjust(string str,int wid) {
+  return sprintf("%*|s",wid,str);
+}
+
+void udp_who(mapping data)
+{
+  int i;
+  string *lines;
+  string wholiste,header;
+
+  create();
+  lines="/obj/werliste"->QueryWhoListe(0, 1);
+  wholiste=implode(lines,"\n");
+  lines=({
+    "*------------------------------------------------------------------------*",
+    "","","","",
+    "*------------------------------------------------------------------------*"});
+  header=MUDNAME", LDMud "+__VERSION__;
+  lines[1]="|"+adjust(header,sizeof(lines[0])-2)+"|";
+  header="Adresse: MG.Mud.DE (87.79.24.60) 23 (alternativ 4711)";
+  lines[2]="|"+adjust(header,sizeof(lines[0])-2)+"|";
+  header="Uptime: "+uptime();
+  lines[3]="|"+adjust(header,sizeof(lines[0])-2)+"|";
+  header=_MUDLIB_NAME_"-Mudlib "_MUDLIB_VERSION_;
+  lines[4]="|"+adjust(header,sizeof(lines[0])-2)+"|";
+  header=implode(lines,"\n");
+  wholiste=header+"\n"+wholiste+sprintf("\n*** Anwesende im "MUDNAME": Max. heute %d, Rekord %d\n",maxtoday,maxever);
+  INETD->_send_udp(data[NAME], ([
+				REQUEST: REPLY,
+				RECIPIENT: data[SENDER],
+				ID: data[ID],
+				DATA: wholiste
+				]) );
+}
diff --git a/secure/udp/www.c b/secure/udp/www.c
new file mode 100644
index 0000000..cad4ffc
--- /dev/null
+++ b/secure/udp/www.c
@@ -0,0 +1,130 @@
+// MorgenGrauen MUDlib
+//
+// www.c -- WWW Guest Client
+//
+// $Id: www.c 8755 2014-04-26 13:13:40Z Zesstra $
+
+#pragma strong_types
+#pragma combine_strings
+
+#ifdef MORGENGRAUEN
+# include <properties.h>
+#endif
+
+#include <udp.h>
+#include <www.h>
+
+#undef DEBUG 
+
+private mixed pending; // pending udp requests
+
+// HTMLunescape -- try to resolve %<hex> escape codes from HTML/WWW
+private string HTMLunescape(string char)
+{
+  int len;
+  if(!char || !(len = sizeof(char))) return "";
+  if(char[0] == '%' && len = 3) {
+    int val, ch;
+    while(--len) {
+      switch(char[len]) {
+      case '0'..'9':
+        val = (int)char[len..len];
+        break;
+      case 'A'..'F':
+        val = char[len]-55;
+        break;
+      }
+      if(len < 2) val <<= 4;
+      ch += val;
+    }
+    return sprintf("%c", ch);
+  }
+  return char;
+}
+
+private string translate(string str)
+{
+  return implode(map(regexplode(str, "[%].."), #'HTMLunescape/*'*/), "");
+}
+
+// decode() -- decode the input cmds string
+private mapping decode(string input)
+{
+  mixed tmp; int i;
+  mapping cmds;
+  cmds = ([]);
+  i = sizeof(tmp = old_explode(translate(input), "&"));
+  while(i--)
+  {
+    if(sizeof(tmp[i] = old_explode(tmp[i], "=")) == 2)
+      cmds[tmp[i][0]] = tmp[i][1];
+  }
+  return cmds;
+}
+
+// put() -- put together a key and a value
+private string put(string key, mapping val)
+{
+  return key+"="+val[key];
+}
+// encode() -- encode the input cmds string
+private string encode(mapping input)
+{ return implode(map(m_indices(input), #'put/*'*/, input), "&"); }
+
+void Send(mapping data, string text, mixed back)
+{
+  if(strstr((string)previous_object(), UDPPATH+"/")) return;
+  if(!data && !pending) return;
+  else if(!data && pending)
+  {
+    data = pending[0]; back = pending[1]; pending = 0;
+  }
+  INETD->_send_udp(data[NAME], ([
+      REQUEST: REPLY,
+      RECIPIENT: data[SENDER],
+      ID: data[ID],
+      "URL":data[DATA],
+      DATA: "\n\n"+text+"\n"
+      ]));
+}
+
+private string exch(string str, mapping to)
+{
+  if(!to[str]) return str;
+  return to[str];
+}
+
+private string xcode(string str, mapping to)
+{
+  return implode(map(regexplode(str, implode(m_indices(to), "|")),
+                           #'exch/*'*/, to), "");
+}
+
+void udp_www(mapping data)
+{
+  string text, error;
+  string back; int size;
+  mapping cmds;
+  error = catch(size = sizeof(cmds = decode(data[DATA])));
+  if(cmds[BACK]) back = xcode(cmds[BACK], (["XampX":"&", "XeqX":"="]));
+  cmds[BACK] = xcode(encode(cmds-([BACK])), (["&":"XampX", "=":"XeqX"]));
+  if(error ||
+     error=catch(text=("/"+object_name(this_object())+"."+cmds[REQ])->Request(cmds)))
+  {
+#ifdef DEBUG
+    text = "<H1>Fehler: "+error+"</H1><HR><H3>Kontext:</H3>"
+   + "<PRE>"+sprintf("%O", data)+"</PRE>";
+#else
+    text = "<H1>Fehler: Anfrage konnte nicht bearbeitet werden!</H1>";
+#endif
+    log_file(WWWLOG, "ERROR: "+error[0..<2]+", DATA FOLLOWS:\n");
+  }
+  log_file(WWWLOG, sprintf("[%s] %s\n", ctime(time())[4..15], data[DATA]));
+  if(cmds[REQ] == R_INTERMUD && !text)
+  {
+    pending = ({data, back});
+    return 0;
+  }
+  pending = 0;
+  funcall(#'Send, data, text, back);
+}
diff --git a/secure/udp/www.finger.c b/secure/udp/www.finger.c
new file mode 100644
index 0000000..dbbccaf
--- /dev/null
+++ b/secure/udp/www.finger.c
@@ -0,0 +1,72 @@
+// MorgenGrauen MUDlib
+//
+// www.finger.c
+//
+// $Id: www.finger.c 8755 2014-04-26 13:13:40Z Zesstra $
+
+#pragma strong_types
+#pragma combine_strings
+
+#include <properties.h>
+#include <www.h>
+#include <regexp.h>
+
+string Request(mapping cmds)
+{
+  if(!sizeof(cmds) || !stringp(cmds[USER]))
+    return ERROR("Kein Nutzer angegeben!");
+  /*
+   * Kann ja sein, dass ein Spieler auf die Idee kommt, HTML-Tags
+   * in seine Beschreibung einzubauen. Unsere Seite ist aber schon
+   * interaktiv genug. (Anm: Nur <.*>-Vorkommnisse zu ersetzen nutzt
+   * nix, da man auch mit einzelnen Zeichen Schaden machen kann.
+   */
+  string result = regreplace(FINGER("-a "+cmds[USER]), "<","\\&lt;",1); 
+  result = regreplace(result, ">","\\&gt;",1);
+  string *reslines = explode(result,"\n");
+  /*
+   * Grund des kommenden Codeblocks ist , dass manche Spieler ihre
+   * Homepage mit "http://mg.mud.de" angeben, andere nur"mg.mud.de" 
+   * schreiben. Damit aber der Browser den Link als absolut interpretiert, 
+   * muss das http:// davor stehen, und zwar nur einmal. 
+   */
+  string *tmp = regexp(reslines,"^Homepage:");
+  if (sizeof(tmp)&&stringp(tmp[0])&&sizeof(tmp[0])>16) {
+	  string tmp2;
+    string quoted = regreplace(tmp[0],"([[\\]+*?.\\\\])","\\\\\\1", 1);
+    if (tmp[0][10..16]=="http://")
+      tmp2=sprintf("Homepage: <A HREF=\"%s\">%s</A>",
+		      tmp[0][10..],tmp[0][10..]);
+    else
+      tmp2=sprintf("Homepage: <A HREF=\"http://%s\">%s</A>",
+		      tmp[0][10..],tmp[0][10..]);
+    result = regreplace(result,quoted,tmp2,1);
+  }
+  tmp = regexp(reslines,"^Avatar-URI:");
+  if (sizeof(tmp)) {
+     result = regreplace(result,
+             "Avatar-URI: ([^\n]*)",
+             "Avatar-URI: <a href=\\1>\\1</a>",1); 
+    if (sizeof(regexp(({tmp[0]}),"http[s]{0,1}://",RE_PCRE))) {
+      string uri = regreplace(tmp[0], "Avatar-URI: ([^\n]*)", "\\1",1);
+      result = "<img src=\""+uri+"\" height=150 alt=\"" + capitalize(cmds[USER])
+               + "\" /><br>"
+               + result;
+    }
+  }
+
+  result = regreplace(result,
+		      "E-Mail-Adresse: ([^\n]*)",
+		      "E-Mail-Adresse: Bitte nachfragen...",1);
+
+  result = regreplace(result,
+		      "Messenger: ([^\n]*)",
+		      "Messenger: Bitte nachfragen...", 1);
+
+  result = regreplace(result,
+		      "ICQ: ([^\n]*)",
+		      "ICQ: Bitte nachfragen...", 1);
+
+  return "<H2>Wer ist "+capitalize(cmds[USER])+"?</H2><HR>"
+    +"<PRE>"+result+"</PRE>";
+}
diff --git a/secure/udp/www.news.c b/secure/udp/www.news.c
new file mode 100644
index 0000000..ce068db
--- /dev/null
+++ b/secure/udp/www.news.c
@@ -0,0 +1,214 @@
+// MorgenGrauen MUDlib
+//
+// www.news.c -- WWW frontend for reading mud news
+//
+// $Id: www.news.c 8755 2014-04-26 13:13:40Z Zesstra $
+
+#pragma strong_types
+#pragma combine_strings
+
+#include <www.h>
+
+#define DBG(x)  tell_object(find_player("hate"), sprintf("DBG: %O\n", x))
+
+#define NEWSD "/secure/news"
+
+#define N_GROUP 0
+#define N_AUTHOR 1
+#define N_TID 2
+#define N_DATE 3
+#define N_TITLE 4
+#define N_ARTICLE 5
+
+varargs private string url(string group, string article)
+{
+  return "\""+MUDWWW+"?"+REQ+"="+R_NEWS
+    +(group?"&"+GROUP+"="+group:"")
+    +(article?"&"+ARTICLE+"="+article:"")+"\"";
+}
+
+varargs private string make_link(string text, string group, string article)
+{
+  if(!text || !sizeof(text)) text = "-Unbenannt-";
+  return "<A HREF="+url(group, article)+">"+text+"</A>";
+}
+
+string GroupList()
+{
+  string *list, ret;
+  int i, t;
+
+  list = NEWSD->GetGroups();
+  for (i = 0, ret = ""; i < sizeof(list); i++)
+    ret += sprintf("[%3d Artikel, %-6s] %s\n",
+                   sizeof(NEWSD->GetNotes(list[i])),
+                   dtime(t = NEWSD->GetNewsTime(list[i]))[5..11],
+                   make_link(list[i],list[i]+":"+t));
+  return "<H2>&Uuml;bersicht</H2>"
+        +"<H3>["+sizeof(list)+" Gruppen, "
+        +"letzte &Auml;nderung "+dtime(NEWSD->GetNewsTime())+"]</H3>"
+        +"<PRE>" + ret +"</PRE>";
+}
+
+#define THREAD(a) ("~#! rn="+(a[N_AUTHOR])+" rt="+(a[N_DATE])+ \
+                   " rg="+(a[N_GROUP]))
+#define TEXPR "rn=[^\n ]*|rt=[^\n ]*|rg=[^\n ]*"
+
+private mixed tinfo(mixed article)
+{
+  mixed tmp, info;
+  string rn, rt, rg, tid;
+  int j, k;
+
+  tmp = regexp(old_explode(article[N_ARTICLE], "\n"), "^~#!");
+  for(j = sizeof(tmp); j--;) {
+    mixed line;
+    line = old_explode(tmp[j], " ");
+    for(k = sizeof(line); k--;) {
+      if(line[k][0..1]=="rn") rn = line[k][3..];
+      if(line[k][0..1]=="rt") rt = line[k][3..];
+      if(line[k][0..1]=="rg") rg = line[k][3..];
+      if(line[k][0..2]=="tid") tid = line[k][4..];
+    }
+  }
+  if(!tid) tid = ""+article[N_DATE];
+  return ({ rn, rt, rg, tid });
+}
+
+#define RN  0
+#define RT  1
+#define RG  2
+#define TID 3
+
+private int thread(mixed article, int i, mapping t)
+{
+  mixed info;
+  info = tinfo(article);
+
+  if(info[TID]) {
+    t[info[TID]]++;
+    t[info[TID], 1] = sprintf("%3.3d [%-12s %-6s]%-3s %s\n",
+            i+1,
+            article[N_AUTHOR]+":",
+            dtime(article[N_DATE])[5..11],
+            (t[info[TID]] - 1) ? "+"+(t[info[TID]]-1) : " - ",
+            make_link(article[N_TITLE],
+          article[N_GROUP], to_string(i)))
+      + (t[info[TID], 1] ? t[info[TID], 1] : "");
+    t[info[TID], 2] = info;
+    if(article[N_DATE] > to_int(t[info[TID], 3]))
+      t[info[TID], 3] = ""+article[N_DATE];
+    return 1;
+  }
+}
+
+private int expired(mixed *list, int i)
+{
+  mixed info;
+  info = tinfo(list[i]);
+  for(i--; i >= 0; i--)
+    if(list[i][N_AUTHOR] == info[RN] &&
+       list[i][N_DATE] == to_int(info[RT]))
+      return 0;
+  return 1;
+}
+
+string ArticleList(string group)
+{
+  string *list, ret;
+  mapping t;
+  int i;
+
+  list = NEWSD->GetNotes(group = old_explode(group, ":")[0]);
+  if (!pointerp(list)) {
+  return "<H2>Gruppe: "+group+"</H2>"
+    "<H3>existiert nicht.</H3>"
+    "["+make_link("Gruppen&uuml;bersicht")+"]";
+  }
+  t = m_allocate(0,4);
+  for (i = sizeof(list)-1, ret = ""; i >= 0; i--)
+    if(!thread(list[i], i, t) || expired(list, i))
+    {
+      int ttmp;
+      string tid;
+      mixed tt; tt = tinfo(list[i]);
+      ttmp = t[tid = tt[TID]] - 1;
+      ret = sprintf("%3.3d [%-12s %-6s]%-3s %s\n",
+		    i+1,
+                    list[i][N_AUTHOR]+":",
+                    dtime(list[i][N_DATE])[5..11],
+                    ttmp > 0 ? "+"+ttmp : " - ",
+                    make_link((string)list[i][N_TITLE]
+            +(ttmp > 0 ?
+        " ("+dtime(to_int(t[tid, 3]))[5..11]
+        +dtime(to_int(t[tid, 3]))[17..]+")" : ""),
+            group, to_string(i)+":"+t[tid, 3])) + ret;
+    }
+
+  return "<H2>Gruppe: "+group+"</H2>"
+    +"<H3>["+sizeof(list)+" Artikel, "
+    +"letzte &Auml;nderung "+dtime(NEWSD->GetNewsTime(group))+"]</H3>"
+    +"<PRE>" + ret + "</PRE>"
+    +"["+make_link("Gruppen&uuml;bersicht")+"]";
+}
+
+private varargs string Message(string group, mixed article)
+{
+  mixed text, tmp, ttmp, next, prev, info;
+  string art;
+  mapping t;
+  int i;
+
+  if (!article) article = 0;
+  else article = to_int(old_explode(article, ":")[0]);
+
+  tmp = NEWSD->GetNotes(group = old_explode(group, ":")[0]);
+  if (pointerp(tmp) && (article >= sizeof(tmp)))
+      return("Artikel nicht gefunden, soviele Artikel hat diese Rubrik "
+	  "nicht!\n");
+
+  text = tmp[article];
+
+  t = m_allocate(0,4);
+  for(i = sizeof(tmp)-1; i > article; i--)
+    thread(tmp[i], i, t);
+  next = "N&auml;chster Artikel";
+  prev = "Voriger Artikel";
+
+  art = implode((ttmp = old_explode(text[N_ARTICLE], "\n"))
+                - regexp(ttmp, "^~#!"), "\n");
+
+  art = regreplace(art, "<", "\\&lt;", 1);
+  art = regreplace(art, ">", "\\&gt;", 1);
+  art = regreplace(art, "([a-zA-Z][a-zA-Z]*://[^ \n\t][^ \n\t]*)", "<a href=\"\\1\">\\1</a>", 1);
+
+  info = tinfo(text);
+
+  return "<H1>" + text[N_TITLE] + "</H1><HR>\n"
+       + "<H3>" + group + ": " + text[N_AUTHOR]
+       + " (Artikel " + (article + 1) + ", " + dtime(text[N_DATE]) + ")\n</H3>"
+       + (info[RN] ? ("<H4>Antwort auf "+info[RN]+
+          (expired(tmp, article) ? " (verfallen)" : "")+"</H4>")
+    : "")
+       + "<PRE>" + art + "</PRE>\n"
+       + (t[info[TID]] ?
+          "<HR>Weitere Artikel zu diesem Thema:"
+  + "<PRE>" + t[info[TID], 1] + "</PRE><HR>" : "")
+       + " ["+(article < sizeof(tmp)-1 ? make_link(next, group,to_string(article+1)) :
+              next)+"]"
+       + " ["+(article ? make_link(prev, group, to_string(article-1)) : prev)+"]"
+       + " ["+make_link("Artikel&uuml;bersicht", group)+"]"
+       + " ["+make_link("Gruppen&uuml;bersicht")+"]";
+}
+
+string Request(mapping cmds)
+{
+  string text;
+  if(!cmds[GROUP]) text = GroupList();
+  else
+    if(!cmds[ARTICLE]) text = ArticleList(cmds[GROUP]);
+    else text = Message(cmds[GROUP], cmds[ARTICLE]);
+
+  return "<H2>"+MUDNAME+" Zeitung</H2><HR>"
+        +text;
+}
diff --git a/secure/udp/www.who.c b/secure/udp/www.who.c
new file mode 100644
index 0000000..6646569
--- /dev/null
+++ b/secure/udp/www.who.c
@@ -0,0 +1,41 @@
+// MorgenGrauen MUDlib
+//
+// www.who.c
+//
+// $Id: www.who.c 8755 2014-04-26 13:13:40Z Zesstra $
+
+#pragma strong_types
+#pragma combine_strings
+
+#include <config.h>
+#include <properties.h>
+#include <www.h>
+
+string MakeLink(mixed entry)
+{
+  string nm;
+  int idx;
+
+  entry[1] = regreplace(entry[1], "<", "\\&lt;", 1);
+  entry[1] = regreplace(entry[1], ">", "\\&gt;", 1);
+  nm = getuid(entry[0]);
+  if(nm == " R O O T ") return "<TT>"+entry[1][0..2]+"</TT>"+entry[1][3..];
+  idx = strstr(lower_case(entry[1]), nm);
+  return "<TT>"+entry[1][0..2]+"</TT>"+entry[1][3..idx-1]
+       + "<A HREF=\""+MUDWWW+"?"+REQ+"="+R_FINGER+"&"+USER+"="+nm+"\"><B>"
+       + entry[1][idx..idx = idx+sizeof(nm)]
+       + "</B></A>"
+       + entry[1][idx+1..];
+}
+
+string Request(mapping cmds)
+{
+  string *who, *list; int i, s;
+  if (!sizeof(cmds)) return ERROR("Anfrage ung&uuml;ltig!");
+  who = allocate(s = sizeof(list = WHO));
+  for(i = s; i--; i > 0) 
+    who[i] = MakeLink(list[s - i - 1]);
+  // who = map(WHO, #'MakeLink/*'*/);
+  return "<H2>Wer ist gerade in "MUDNAME"?</H2><HR>"
+       + "<OL><LI>"+implode(who, "\n<LI>")+"</OL>";
+}
diff --git a/secure/udp_mail.c b/secure/udp_mail.c
new file mode 100644
index 0000000..3203fb9
--- /dev/null
+++ b/secure/udp_mail.c
@@ -0,0 +1,285 @@
+/*
+ * VERSION 1.0
+ * UDP MAIL system (Author: Alvin@Sushi)
+ * Requires INETD V0.60 or higher (INETD Author: Nostradamus@Zebedee)
+ */
+#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 <udp.h>
+#include <udp_mail.h>
+
+#ifdef DEBUG
+#undef DEBUG
+#endif
+#define DEBUG(x)
+
+mapping spool_item;
+
+static string *spool;
+
+private int match_mud_name(string mudname, string match_str) {
+    return mudname[0..sizeof(match_str)-1] == match_str;
+}
+
+static void save_spool_item()
+{
+  string name;
+  int count;
+
+  if(!spool_item || !mappingp(spool_item) || spool_item==([]))
+    return;
+
+  do {
+    ++count;
+    name=spool_item[UDPMS_DEST]+"-"+to_string(count);
+  } while(spool && member(spool, name)!=-1);
+
+  save_object(UDPM_SPOOL_DIR+name);
+
+  if(!spool || !sizeof(spool))
+    spool = ({ name });
+  else
+    spool += ({ name });
+}
+
+/* forward declaration */
+void deliver_mail(string recipient,string mud,string from,
+	string subj,string mail_body,int status_flag,string spool_name);
+
+/* forward declaration */
+static void start_retry_callout();
+
+static void remove_from_spool(string spool_file)
+{
+  int idx;
+
+  if(spool && (idx=member(spool,spool_file))!=-1)
+    {
+      spool -= ({ spool_file });
+      if(!sizeof(spool))
+        spool=0;
+    }
+
+  if(file_size("/"+UDPM_SPOOL_DIR+spool_file+".o")>0)
+    if(!rm("/"+UDPM_SPOOL_DIR+spool_file+".o"))
+      log_file(INETD_LOG_FILE,"UPD_MAIL: Can't delete spool file "+
+		"/"+UDPM_SPOOL_DIR+spool_file+".o");
+}
+
+static void retry_send()
+{
+  int i;
+  string msg;
+
+  if(!spool || !sizeof(spool)) return;
+
+  for(i=0;i<sizeof(spool);++i)
+    {
+      if(!restore_object(UDPM_SPOOL_DIR+spool[i]))
+        {
+          log_file(INETD_LOG_FILE,"UDP_MAIL: Falied to restore spool file "+
+		UDPM_SPOOL_DIR+spool[i]);
+          continue;
+        }
+
+      if(time() - spool_item[UDPMS_TIME] > UDPM_SEND_FAIL*60)
+        {
+          msg="Reason: Unable to connect to site \""+spool_item[UDPMS_DEST]+
+		"\"\n\nINCLUDED MESSAGE FOLLOWS :-\n\n"+
+		"To: "+spool_item[UDPMS_TO]+"\n"+
+		"Subject: "+spool_item[UDPMS_SUBJECT]+"\n"+
+		spool_item[UDPMS_BODY];
+
+          LOCAL_MAILER->deliver_mail(
+		spool_item[UDPMS_FROM],	/* TO */
+		"Mailer@"+LOCAL_NAME,	/* FROM */
+		"Bounced Mail",		/* SUBJECT */
+		msg			/* MAIL BODY */
+	  );
+          remove_from_spool(spool[i]);
+          return;
+        }
+
+      deliver_mail(
+	spool_item[UDPMS_TO],
+	spool_item[UDPMS_DEST],
+	spool_item[UDPMS_FROM],
+	spool_item[UDPMS_SUBJECT],
+	spool_item[UDPMS_BODY],
+	UDPM_STATUS_IN_SPOOL,
+	spool[i]);
+    }
+
+  start_retry_callout();
+}
+
+static void start_retry_callout()
+{
+  if(find_call_out("retry_send")!= -1 ) return;
+
+  call_out("retry_send",UDPM_RETRY_SEND*60);
+}
+
+static void failed_to_deliver(mapping data)
+{
+  string msg;
+  object obj;
+
+  if(!data[SYSTEM] || data[SYSTEM] != TIME_OUT)
+    {
+      msg="Reason: Error in connection to remote site \""+data[NAME]+"\"\n\n"+
+	"INCLUDED MESSAGE FOLLOWS :-\n\n"+
+	"To: "+data[RECIPIENT]+"\n"+
+	"Subject: "+data[UDPM_SUBJECT]+"\n"+data[DATA];
+
+      LOCAL_MAILER->deliver_mail(
+		data[UDPM_WRITER],	/* TO */
+		"Mailer@"+LOCAL_NAME,	/* FROM */
+		"Bounced Mail",		/* SUBJECT */
+		msg			/* MAIL BODY */
+      );
+      return;
+    }
+
+  /* OK transmission timed out.. place in mail spool */
+  
+  if((obj=find_player(data[UDPM_WRITER])))
+    {
+      tell_object(obj,"Mail delivery to "+data[RECIPIENT]+"@"+data[NAME]+
+	" Timed Out. Placing mail in spool.\n");
+    }
+
+  spool_item=([
+	UDPMS_TIME:	time(),
+	UDPMS_TO:	data[RECIPIENT],
+	UDPMS_DEST:	data[NAME],
+	UDPMS_FROM:	data[UDPM_WRITER],
+	UDPMS_SUBJECT:	data[UDPM_SUBJECT],
+	UDPMS_BODY:	data[DATA]
+  ]);
+
+  save_spool_item();
+
+  start_retry_callout();
+}
+
+static void get_pending_deliveries()
+{
+  string *entries;
+  int i;
+
+  entries=get_dir(UDPM_SPOOL_DIR+"*.o");
+  if(!entries || !sizeof(entries)) return;
+
+  spool=allocate(sizeof(entries));
+  for(i=0;i<sizeof(entries);++i)
+    spool[i]=entries[i][0..<3];
+
+  start_retry_callout();
+}
+
+void create()
+{
+	seteuid(getuid(this_object()));
+  get_pending_deliveries();
+}
+
+/*
+ * Public routines
+ */
+
+int query_valid_mail_host(string hostname)
+{
+  string *match;
+
+  match=filter(m_indices((mapping)INETD->query("hosts")),
+		#'match_mud_name,lower_case(hostname));
+
+  return (sizeof(match)==1);
+}
+
+void deliver_mail(string recipient,string mud,string from,
+	string subj,string mail_body,int status_flag,string spool_name)
+{
+  mapping data;
+
+  // Geloggt wird, wenn ein aufrufendes Objekt nicht sicher ist.
+  if (object_name(previous_object())[0..7]!="/secure/")
+    write_file("/secure/ARCH/DELIVER_MAIL",
+      sprintf("%s : Aufruf von /secure/udp_mail->deliver_mail()\n"
+              "  Sender: %O Empfaenger: %O@%O\n  PO: %O TI: %O TP:%O\n\n",
+              ctime(time()),from, recipient, mud,
+              previous_object(), this_interactive(), this_player()));
+  
+  data=([
+	REQUEST: "mail",
+	RECIPIENT: recipient,
+        SENDER: this_object(),
+	UDPM_STATUS: status_flag,
+	UDPM_WRITER: lower_case(from),
+	UDPM_SUBJECT: subj,
+        UDPM_SPOOL_NAME: spool_name,
+	DATA: mail_body
+  ]);
+
+  INETD->_send_udp(mud,data,1);
+}
+
+void udp_reply(mapping data)
+{
+  object sender;
+
+  DEBUG(sprintf("MAILER RECEIVED %O\n",data));
+  if (!member(data,UDPM_STATUS))
+  {
+     DEBUG("BOUNCING\n");
+     LOCAL_MAILER->deliver_mail(
+        data[UDPM_WRITER],	/* TO */
+	"INETD@"+data[NAME],	/* FROM */
+	"Bounced Mail(No mail support yet?)",		/* SUBJECT */
+	data[DATA]		/* MAIL BODY */
+	);
+        if(data[UDPM_SPOOL_NAME])
+          remove_from_spool(data[UDPM_SPOOL_NAME]);
+  } else
+  switch(data[UDPM_STATUS])
+    {
+      case UDPM_STATUS_TIME_OUT:
+        failed_to_deliver(data);
+        break;
+
+      case UDPM_STATUS_DELIVERED_OK:
+        if((sender=find_player(data[UDPM_WRITER])))
+          {
+            tell_object(sender,"Mailer@"+data[NAME]+": "+
+		"Mail to "+capitalize(data[DATA])+" delivered ok.\n");
+          }
+        if(data[UDPM_SPOOL_NAME])
+          remove_from_spool(data[UDPM_SPOOL_NAME]);
+
+        break;
+
+      case UDPM_STATUS_UNKNOWN_PLAYER:
+        LOCAL_MAILER->deliver_mail(
+		data[UDPM_WRITER],	/* TO */
+		"Mailer@"+data[NAME],	/* FROM */
+		"Bounced Mail",		/* SUBJECT */
+		data[DATA]		/* MAIL BODY */
+	);
+        if(data[UDPM_SPOOL_NAME])
+          remove_from_spool(data[UDPM_SPOOL_NAME]);
+        break;
+
+	case UDPM_STATUS_IN_SPOOL:
+          /* Do nothing */
+          break;
+    }
+}
diff --git a/secure/wahlmaschine.c b/secure/wahlmaschine.c
new file mode 100644
index 0000000..8705f87
--- /dev/null
+++ b/secure/wahlmaschine.c
@@ -0,0 +1,252 @@
+// da dieses Objekt in /secure liegt und ROOTID inne hat, muessen besondere
+// Sicherheitsvorkehrungen getroffen werden. Insbesondere duerfen keine Files
+// aus /std/ inheritet werden, da es fuer diese Files keiner ROOTID bedarf.
+
+#include <properties.h>
+#include <defines.h>
+#include "/secure/wizlevels.h"
+
+static string in_use;
+
+string thema;
+string *moeglichkeiten;
+string *ergebnis;
+
+#undef SAY
+
+private void SAY( string str )
+{
+  write ( str );
+  write_file("/log/WAHL", str);
+}
+
+void create()
+{
+  if (clonep(ME)) destruct(ME);
+  if (ergebnis) return;
+  seteuid(getuid());
+  in_use=0;
+  ergebnis=({({}),({})});
+  moeglichkeiten=({});
+  restore_object("wahl/wahl");
+  move_object(ME, "/gilden/abenteurer");
+}
+
+varargs string name(int casus, int demon)
+{
+  if (demon==RAW) return "Wahlmaschine";
+  switch(casus) {
+    case WEM:
+    case WESSEN:
+      if (demon==1)
+        return "der Wahlmaschine";
+      return "einer Wahlmaschine";
+    default:
+      if (demon==1)
+        return "die Wahlmaschine";
+      return "eine Wahlmaschine";
+  }
+}
+
+varargs string Name(int casus, int demon)
+{   return capitalize(name(casus, demon));  }
+
+
+public string QueryPronoun(int casus)
+{
+  switch(casus)
+  {
+    case WER:
+      return "sie";
+      break;
+    case WEM: 
+      return "ihr";
+  }
+  return "sie";
+}                                                                                                                   
+
+string short()
+{  return "Eine Wahlmaschine.\n";  }
+
+string long()
+{
+  return "Eine Wahlmaschine. Mit ihr koennen geheime Wahlen durchgefuehrt werden.\n"+
+    "Aktuelles Thema: "+(stringp(thema) ? thema : "keines")+"\n";
+}
+
+mixed Query(mixed param)
+{
+   if (!stringp(param)) return 0;
+   switch (param) {
+     case P_NOGET:   return 1;
+     case P_GENDER:  return FEMALE;
+     case P_NAME:    return "Wahlmaschine";
+   }
+   return 0;
+}
+
+mixed QueryProp(mixed param)
+{  return Query(param);  }
+
+int Set(mixed param)
+{  return -1;  }
+
+int SetProp(mixed param)
+{  return -1;  }
+
+varargs int id(string str, int lvl)
+{  return (member(({"maschine", "waehler", "wahlmaschine"}), str)!=-1);  }
+
+// ab hier kommt der eigentliche Code der Wahlmaschine
+
+void init()
+{
+  add_action("waehle", "waehl",1);
+  add_action("auswertung","auswertung");
+  add_action("wahlthema", "wahlthema");
+  add_action("moeglichkeit","moeglichkeit");
+}
+
+void save_me()
+{  save_object("wahl/wahl");  }
+
+static int waehle()
+{
+  int i;
+  mixed second;
+
+  if (!RPL) return 0;
+  if (RPL->QueryProp(P_LEVEL)<10) {
+    notify_fail("Man kann erst ab Stufe 10 mitwaehlen :(\n");
+    return 0;
+  }
+  if (RPL->QueryProp(P_TESTPLAYER)) {
+    notify_fail("Testspieler sind von der Wahl ausgeschlossen.\n");
+    return 0;
+  }
+  if ((second=RPL->QueryProp(P_SECOND)) &&
+      (!stringp(second) || (file_size("/save/"+lower_case(second[0..0])+"/"+lower_case(second)+".o")<=0))) {
+    notify_fail("Nicht richtig markierte Zweities sind von der Wahl ausgeschlossen.\n");
+    return 0;
+  }
+  if (in_use)
+  {
+    write(in_use+" waehlt gerade, warte also bitte einen Augenblick.\n");
+    return 1;
+  }
+  in_use=capitalize(getuid(RPL));
+  say(in_use+" tritt an die Wahlmaschine.\n");
+  write(long());
+  write("Das steht zur Auswahl:\n");
+  for (i=0;i<sizeof(moeglichkeiten);i++)
+    write(sprintf(" -- %d.) %s\n",i+1,moeglichkeiten[i]));
+  write("Triff nun Deine Wahl (sie ist UNSICHTBAR): ");
+  input_to("waehle_2",1);
+  return 1;
+}
+
+private int falsch()
+{
+  write("Diese Auswahl ist ungueltig. Versuchs bitte gleich nochmal.\n");
+  say(in_use+" hat ausgewaehlt.\n");
+  in_use=0;
+  return 1;
+}
+
+static int waehle_2(string str)
+{
+  int i;
+  mixed name;
+
+  if (!str) return falsch();
+  
+  name=RPL->QueryProp(P_SECOND);
+  if (!stringp(name))
+    name=getuid(RPL);
+  else name=lower_case(name);
+            
+  if (member_array(str, moeglichkeiten)==-1) {
+    if ( !sscanf( str, "%d", i ) ) return falsch();
+    if ( i<1 || i>sizeof(moeglichkeiten) ) return falsch();
+    ergebnis=insert_alist(name,moeglichkeiten[i-1],ergebnis);
+  }
+  else ergebnis=insert_alist(name,str,ergebnis);
+  write("Ok.\n");
+  save_me();
+  say(in_use+" hat ausgewaehlt.\n");
+  in_use=0;
+  return 1;
+}
+
+static int auswertung()
+{
+  int i,j,max,all;
+  mapping stimmen, data;
+  mixed names;
+
+  if (!ARCH_SECURITY || process_call()) return 0;
+
+  rm("/log/WAHL");
+
+  data = mkmapping(ergebnis[0], ergebnis[1]);
+  names = m_indices(data);
+  stimmen = ([]);
+
+  SAY("\n\n\nWahlergebnisse zum Thema: "+thema+"\n");
+  SAY("\nGewaehlt haben folgende Personen: \n");
+  SAY(break_string(CountUp(map(sort_array(names, #'>/*'*/), #'capitalize/*'*/)), 78));
+  SAY("\n");
+
+  while(sizeof(names)) {
+    stimmen[data[names[0]]]++;
+    names[0..0] = ({});
+    all++;
+  }
+
+  SAY("*****************************************\n");
+  SAY("Ergebnis:\n");
+  for (i=0;i<sizeof(moeglichkeiten);i++)
+    SAY(sprintf(" -- %2d.) %20s: %3d (%' '3.2f%%)\n", i+1, moeglichkeiten[i],
+                stimmen[moeglichkeiten[i]], 1.0 * stimmen[moeglichkeiten[i]] * 100.0 / (1.0 * all)));
+  SAY(sprintf("Gesamtstimmen: %d\n", sizeof(m_indices(data))));
+  return 1;
+}
+
+static int wahlthema(string str)
+{
+  if (!ARCH_SECURITY || process_call()) {
+    write("NOT allowed");
+    return 0;
+  }
+  if (!(str=this_interactive()->_unparsed_args())) return 0;
+  thema=str;
+  say(RPL->name(WER)+" setzt ein neues Abstimmungsthema fest.\n");
+  ergebnis=({({}),({})});
+  moeglichkeiten=({});
+  write("Ok.\n");
+  save_me();
+  return 1;
+}
+
+static int moeglichkeit(string str)
+{
+  if (!ARCH_SECURITY || process_call()) {
+    write("NOT allowed");
+    return 0;
+  }
+  if (!(str=RPL->_unparsed_args())) return 0;
+  if (member_array(str, moeglichkeiten) != -1) {
+    write("Schon drin.\n");
+    return 1;
+  }
+  moeglichkeiten+=({str});
+  write("Ok.\n");
+  save_me();
+  return 1;
+}
+
+mixed _internal()
+{
+  if (!ARCH_SECURITY || process_call()) return 0;
+  return ergebnis;
+}
diff --git a/secure/wizlevels.h b/secure/wizlevels.h
new file mode 100644
index 0000000..e785158
--- /dev/null
+++ b/secure/wizlevels.h
@@ -0,0 +1,63 @@
+#ifndef _WIZLEVELS_
+#define _WIZLEVELS_
+/*
+ * wizlevels.h
+ *
+ * This file defines some useful macros to determine the level of
+ * a wizard in terms of ability to do certain wizard things.
+ * See also doc/concepts/levels. The grouping below is taken from there.
+ *
+ */
+#include "/secure/config.h"
+
+#define	SEER_LVL 1
+#define	LEARNER_LVL 10
+#define	WIZARD_LVL 20
+#define DOMAINMEMBER_LVL 25
+#define SPECIAL_LVL 30
+#define	LORD_LVL 40
+#define ELDER_LVL 50
+#define	ARCH_LVL 60
+#define GOD_LVL 100
+
+#define SEER_GRP 1
+#define LEARNER_GRP 2
+#define WIZARD_GRP 3
+#define DOMAINMEMBER_GRP 4
+#define SPECIAL_GRP 5
+#define LORD_GRP 6
+#define ELDER_GRP 7
+#define ARCH_GRP 8
+
+/* user is an object which is to be checked */
+/* for example, use IS_WIZARD(this_player()) to check */
+/* if a player is a wizard. */
+#define IS_SEER(user) (query_wiz_level(user) >= SEER_LVL)
+#define IS_LEARNER(user) (query_wiz_level(user) >= LEARNER_LVL)
+#define IS_SPECIAL(user) (query_wiz_level(user) >= SPECIAL_LVL)
+#define IS_WIZARD(user) (query_wiz_level(user) >= WIZARD_LVL)
+#define IS_DOMAINMEMBER(user) (query_wiz_level(user) >= DOMAINMEMBER_LVL)
+#define IS_DEPUTY(user) (master()->IsDeputy(user))
+#define IS_LORD(user) (query_wiz_level(user) >= LORD_LVL)
+#define IS_ELDER(user) (query_wiz_level(user) >= ELDER_LVL)
+#define IS_ARCH(user) (query_wiz_level(user) >= ARCH_LVL)
+#define IS_GOD(user) (query_wiz_level(user) >= GOD_LVL)
+
+#define IS_LEARNING(user) (query_wiz_level(user) >= LEARNER_LVL &&\
+                           user->QueryProp(P_WANTS_TO_LEARN))
+#define IS_LORD_DOMAIN(user,domain) (master()->domain_master(user,domain))
+
+/*
+ * Interface for enhanced security functions
+ */
+#define SEER_SECURITY (secure_level() >= SEER_LVL)
+#define LEARNER_SECURITY (secure_level() >= LEARNER_LVL)
+#define WIZARD_SECURITY (secure_level() >= WIZARD_LVL)
+#define DOMAINMEMBER_SECURITY (secure_level() >= DOMAINMEMBER_LVL)
+#define SPECIAL_SECURITY (secure_level() >= SPECIAL_LVL)
+#define LORD_SECURITY (secure_level() >= LORD_LVL)
+#define ELDER_SECURITY (secure_level() >= ELDER_LVL)
+#define ARCH_SECURITY (secure_level() >= ARCH_LVL)
+#define GOD_SECURITY (secure_level() >= GOD_LVL)
+
+#endif
diff --git a/secure/zweities.c b/secure/zweities.c
new file mode 100644
index 0000000..89307c2
--- /dev/null
+++ b/secure/zweities.c
@@ -0,0 +1,164 @@
+// MorgenGrauen MUDlib
+/** \file /file.c
+* Kurzbeschreibung.
+* Langbeschreibung...
+* \author <Autor>
+* \date <date>
+* \version $Id$
+*/
+/* Changelog:
+*/
+#pragma strong_types,save_types,rtt_checks
+#pragma no_clone,no_inherit,no_shadow
+#pragma pedantic, range_check
+
+#include <defines.h>
+#include <events.h>
+#include <wizlevels.h>
+#include <player/base.h>
+#include <userinfo.h>
+
+#define HOME(x) (__PATH__(0)+x)
+#define ZDEBUG(x) tell_room("/players/zesstra/workroom",\
+                    sprintf("second: %O\n",x));
+
+protected void create()
+{
+  seteuid(getuid());
+  if (sl_open(HOME("ARCH/second.sqlite")) != 1)
+  {
+    raise_error("Datenbank konnte nicht geoeffnet werden.\n");
+  }
+  // Tabellen und Indices anlegen, falls die nicht existieren.
+  sl_exec("CREATE TABLE IF NOT EXISTS zweities(uuid TEXT PRIMARY KEY ASC, "
+          "name TEXT NOT NULL, erstieuuid TEXT NOT NULL, "
+          "erstie TEXT NOT NULL);");
+  sl_exec("CREATE TABLE IF NOT EXISTS testies(name TEXT PRIMARY KEY ASC, "
+          "magier TEXT NOT NULL, "
+          "lastlogin DATETIME DEFAULT current_timestamp);");
+  sl_exec("CREATE TABLE IF NOT EXISTS familien("
+          "erstieuuid TEXT PRIMARY KEY ASC, familie TEXT NOT NULL);");
+  sl_exec("CREATE INDEX IF NOT EXISTS idx_erstie ON zweities(erstie);");
+  sl_exec("CREATE INDEX IF NOT EXISTS idx_name ON zweities(name);");
+  sl_exec("CREATE INDEX IF NOT EXISTS idx_magiername ON testies(magier);");
+  sl_exec("CREATE INDEX IF NOT EXISTS idx_familie ON familien(familie);");
+
+  // Login-Event abonnieren
+  if (EVENTD->RegisterEvent(EVT_LIB_LOGIN,
+                            "listen", this_object()) <= 0)
+  {
+    raise_error("Loginevent konnte nicht abonniert werden.\n");
+  }
+}
+
+public void listen(string eid, object trigob, mixed data)
+{
+  if (previous_object() != find_object(EVENTD)
+      || !trigob
+      || !query_once_interactive(trigob)
+      || IS_LEARNER(trigob))
+    return;
+  // wenn testie, wird der Char als Testie behandelt und P_SECOND ignoriert.
+  mixed testie=trigob->QueryProp(P_TESTPLAYER);
+  if (stringp(testie)
+      && strstr(testie,"Gilde")==-1)
+  {
+    mixed plinfo = master()->get_userinfo(testie);
+    if (pointerp(plinfo))
+    {
+      sl_exec("INSERT OR REPLACE INTO testies(name, magier, lastlogin) "
+              "VALUES(?1,?2,?3);",
+              trigob->query_real_name(),
+              testie, time());
+      return; // zweitie jetzt auf jeden Fall ignorieren.
+    }
+  }
+
+  mixed erstie=trigob->QueryProp(P_SECOND);
+  if (stringp(erstie))
+  {
+    mixed plinfo = master()->get_userinfo(erstie);
+    if (pointerp(plinfo))
+    {
+      sl_exec("INSERT OR REPLACE INTO zweities(uuid, name, erstieuuid, erstie) "
+              "VALUES(?1,?2,?3,?4);",
+              getuuid(trigob),
+              trigob->query_real_name(),
+              erstie + "_" + plinfo[USER_CREATION_DATE+1],
+              erstie);
+    }
+    //ZDEBUG(sprintf("%O, %O, %O\n",eid,trigob,data));
+  }
+}
+
+varargs int remove(int silent)
+{
+  EVENTD->UnregisterEvent(EVT_LIB_LOGIN, this_object());
+  sl_close();
+  destruct(ME);
+  return 1;
+}
+
+public mixed sql_query(string query)
+{
+  if (ARCH_SECURITY)
+    return sl_exec(query);
+  return 0;
+}
+
+private string get_erstie_data(string datum, object zweitie)
+{
+  if (!zweitie) return 0;
+  mixed res = sl_exec("SELECT " + datum + " FROM zweities WHERE uuid=?1",
+                      getuuid(zweitie));
+  if (sizeof(res))
+    return res[0][0];
+
+  return 0;
+}
+
+public varargs string QueryErstieName(object zweitie)
+{
+  return get_erstie_data("erstie", zweitie || previous_object());
+}
+
+public varargs string QueryErstieUUID(object zweitie)
+{
+  return get_erstie_data("erstieuuid", zweitie || previous_object());
+}
+
+private string* get_zweitie_data(string datum, object erstie)
+{
+  if (!erstie) return 0;
+  mixed tmp = sl_exec("SELECT " + datum + " FROM zweities WHERE erstieuuid=?1",
+                      getuuid(erstie));
+  if (sizeof(tmp))
+  {
+    string *res=({});
+    foreach(string *row: tmp)
+      res+=({row[0]});
+    return res;
+  }
+  return 0;
+}
+
+public varargs string* QueryZweities(object erstie)
+{
+  return get_zweitie_data("name", erstie || previous_object());
+}
+
+public varargs string QueryFamilie(object pl)
+{
+  string erstie = get_erstie_data("erstieuuid",
+                                  pl || previous_object());
+  // Wenn !erstie, dann ist pl kein Zweitie, also ist er selber erstie
+  erstie ||= getuuid(pl);
+  // jetzt noch gucken, ob ne explizite Familie fuer den erstie erfasst ist.
+  mixed tmp = sl_exec("SELECT familie FROM familien WHERE "
+                      "erstieuuid=?1",erstie);
+  if (sizeof(tmp))
+    return tmp[0][0];
+  // wenn nicht, dann ist die Familie die Zweitieuuid 
+  return erstie;
+}
+