Added public files

Roughly added all public files. Probably missed some, though.
diff --git a/p/daemon/ch.h b/p/daemon/ch.h
new file mode 100644
index 0000000..0afed78
--- /dev/null
+++ b/p/daemon/ch.h
@@ -0,0 +1,10 @@
+// ch.h - channel daemon defines
+// --
+// $timestamp::$
+
+// existiert aus Kompatibilitaetsgruenden, weil die haelfte vom Mud dieses und
+// die andere Haelfte channel.h inkludiert. Entscheidung, den eindeutigeren
+// Namen channel.h zu benutzen und dieses einfach channel.h inkludieren zu
+// lassen.
+#include "channel.h"
+
diff --git a/p/daemon/channel.h b/p/daemon/channel.h
new file mode 100644
index 0000000..ad1f506
--- /dev/null
+++ b/p/daemon/channel.h
@@ -0,0 +1,59 @@
+// ch.h - channel daemon defines
+// --
+// $timestamp::$
+
+#ifndef __DAEMON_CHANNEL_H__
+#define __DAEMON_CHANNEL_H__
+
+#define P_CHANNELS		"channels"
+#define P_STD_CHANNEL		"std_channel"
+
+#define CHMASTER		"/p/daemon/channeld"
+
+// Message types
+#define MSG_SAY                 0
+#define MSG_EMOTE               1
+#define MSG_GEMOTE              2
+#define MSG_EMPTY               3
+
+// Errors
+#define E_ACCESS_DENIED         -1
+#define E_ALREADY_JOINED        -2
+#define E_NOT_MEMBER            -3
+#define E_EMPTY_MESSAGE         -4
+#define E_UNKNOWN_TYPE          -5
+
+// Commands
+#define C_NEW			"new"
+#define C_JOIN			"join"
+#define C_LEAVE			"leave"
+#define C_SEND			"send"
+#define C_LIST			"list"
+#define C_FIND			"find"
+
+// definition of the list mapping entry 
+// ([ channelname : ({ I_MEMBER, I_ACCESS, I_INFO, I_MASTER, I_NAME }) ])
+#define I_MEMBER                0
+#define I_ACCESS                1
+#define I_INFO                  2
+#define I_MASTER		3
+#define I_NAME			4
+
+#endif //__DAEMON_CHANNEL_H__
+
+// prototypes
+#ifdef NEED_PROTOTYPES
+
+#ifndef __CHANNEL_H_PROTO__
+#define __CHANNEL_H_PROTO__
+varargs int new(string ch, object pl, mixed info);
+varargs int send(string ch, object pl, string msg, int type);
+
+// ok, keine Prototypen, aber trotzdem nur fuer channeld.c interessant.
+#define MAX_HIST_SIZE   200
+#define MAX_CHANNELS    90
+
+#endif //__CHANNEL_H_PROTO__
+
+#endif //NEED_PROTOTYPES
+
diff --git a/p/daemon/channeld.c b/p/daemon/channeld.c
new file mode 100644
index 0000000..1331d57
--- /dev/null
+++ b/p/daemon/channeld.c
@@ -0,0 +1,647 @@
+// channeld.c
+//
+// $Id: channeld.c 9138 2015-02-03 21:46:56Z Zesstra $
+//
+
+#pragma strong_types
+#pragma no_shadow // keine Shadowing...
+#pragma no_clone
+#pragma no_inherit
+#pragma save_types
+
+#include <sys_debug.h>
+#include <lpctypes.h>
+#include <wizlevels.h>
+
+#include <properties.h>
+#include <config.h>
+#include <language.h>
+
+#define NEED_PROTOTYPES
+#include "channel.h"
+
+#define CMNAME  "<MasteR>"
+#define CHANNEL_SAVE    "/p/daemon/save/channeld"
+
+#define MEMORY "/secure/memory"
+
+// channels - contains the simple channel information
+//            ([ channelname : ({ I_MEMBER, I_ACCESS, I_INFO, I_MASTER })...])
+private nosave mapping lowerch;
+private nosave mapping channels;
+private nosave mapping channelH;
+private nosave mapping stats;
+
+private mapping channelC;
+private mapping channelB;
+
+private int save_me_soon;
+
+// BEGIN OF THE CHANNEL MASTER ADMINISTRATIVE PART
+
+#define RECV    0
+#define SEND    1
+#define FLAG    2
+
+// Channel flags
+// Levelbeschraenkungen gegen Magierlevel (query_wiz_level) pruefen, nicht
+// P_LEVEL.
+#define F_WIZARD 1
+// Keine Gaeste. ;-)
+#define F_NOGUEST 2
+
+private nosave mapping admin = m_allocate(0, 3);
+
+int check(string ch, object pl, string cmd)
+{
+  int level;
+  
+  if((admin[ch, FLAG] & F_NOGUEST) && pl->QueryGuest()) return 0;
+
+  if((admin[ch, FLAG] & F_WIZARD) && query_wiz_level(pl) < SEER_LVL) return 0;
+  level = (admin[ch, FLAG] & F_WIZARD
+           ? query_wiz_level(pl)
+           : pl->QueryProp(P_LEVEL));
+
+  switch(cmd)
+  {
+  case C_FIND:
+  case C_LIST:
+  case C_JOIN:
+    if(admin[ch, RECV] == -1) return 0;
+    if(admin[ch, RECV] <= level) return 1;
+    break;
+  case C_SEND:
+    if(admin[ch, SEND] == -1) return 0;
+    if(admin[ch, SEND] <= level) return 1;
+    break;
+  case C_LEAVE:
+    return 1;
+  default: break;
+  }
+  return(0);
+}
+
+private int CountUser(mapping l)
+{
+  mapping n;
+  n = ([]);
+  walk_mapping(l, lambda(({'i/*'*/, 'a/*'*/, 'n/*'*/}),
+                         ({#'+=/*'*/, 'n/*'*/,
+                              ({#'mkmapping/*'*/,
+                                   ({#'[/*'*/, 'a/*'*/, 0})})})),
+               &n);
+  return sizeof(n);
+}
+
+private void banned(string n, mixed cmds, string res)
+{
+  res += sprintf("%s [%s], ", capitalize(n), implode(cmds, ","));
+}
+
+private mapping Tcmd = ([]);
+#define TIMEOUT (time() - 60)
+
+void ChannelMessage(mixed msg)
+{
+  string ret, mesg;
+  mixed lag;
+  int max, rekord;
+  string tmp;
+  if(msg[1] == this_object() || !stringp(msg[2]) ||
+     msg[0] != CMNAME || previous_object() != this_object()) return;
+
+
+  mesg = lower_case(msg[2]);
+
+  if(!strstr("hilfe", mesg) && sizeof(mesg) <= 5)
+    ret = "Folgende Kommandos gibt es: hilfe, lag, up[time], statistik, bann";
+  else
+  if(!strstr("lag", mesg) && sizeof(mesg) <= 3)
+  {
+    if(Tcmd["lag"] > TIMEOUT) return;
+    Tcmd["lag"] = time();
+    lag = "/p/daemon/lag-o-daemon"->read_ext_lag_data();
+    ret = sprintf("Lag: %.1f%%/60, %.1f%%/15, %.1f%%/5, %.1f%%/1, "
+                       "%.1f%%/20s, %.1f%%/2s",
+                  lag[5], lag[4], lag[3], lag[2], lag[1], lag[0]);
+    call_out(#'send/*'*/, 2, CMNAME, this_object(), ret);
+    ret = query_load_average();
+  } else
+  if(!strstr("uptime", mesg) && sizeof(mesg) <= 6)
+  {
+    if(Tcmd["uptime"] > TIMEOUT) return;
+    Tcmd["uptime"] = time();
+    if(file_size("/etc/maxusers") <= 0) {
+      ret = "Diese Information liegt nicht vor.";
+    } else {
+      sscanf(read_file("/etc/maxusers"), "%d %s", max, tmp);
+      sscanf(read_file("/etc/maxusers.ever"), "%d %s", rekord, tmp);
+      ret = sprintf("Das MUD laeuft jetzt %s. Es sind momentan %d Spieler "
+                  "eingeloggt; das Maximum lag heute bei %d und der Rekord "
+                  "bisher ist %d.", uptime(), sizeof(users()), max, rekord);
+    }
+  } else
+  if(!strstr("statistik", mesg) && sizeof(mesg) <= 9)
+  {
+    if(Tcmd["statistik"] > TIMEOUT) return;
+    Tcmd["statistik"] = time();
+    ret = sprintf(
+    "Im Moment sind insgesamt %d Ebenen mit %d Teilnehmern aktiv.\n"
+    "Der %s wurde das letzte mal am %s von %s neu gestartet.\n"
+    "Seitdem wurden %d Ebenen neu erzeugt und %d zerstoert.\n",
+    sizeof(channels), CountUser(channels), CMNAME,
+    dtime(stats["time"]), stats["boot"], stats["new"], stats["dispose"]);
+  } else
+  if(!strstr(mesg, "bann"))
+  {
+    string pl, cmd;
+    if(mesg == "bann")
+      if(sizeof(channelB))
+      {
+        ret = "";
+        walk_mapping(channelB, #'banned/*'*/, &ret);
+        ret = "Fuer folgende Spieler besteht ein Bann: " + ret;
+      } else ret = "Zur Zeit ist kein Bann aktiv.";
+    else
+    {
+      if(sscanf(mesg, "bann %s %s", pl, cmd) == 2 &&
+         IS_DEPUTY(msg[1]))
+      {
+#     define CMDS ({C_FIND, C_LIST, C_JOIN, C_LEAVE, C_SEND, C_NEW})
+        pl = lower_case(pl); cmd = lower_case(cmd);
+        if(member(CMDS, cmd) != -1)
+        {
+          if(!pointerp(channelB[pl]))
+            channelB[pl] = ({});
+          if(member(channelB[pl], cmd) != -1)
+            channelB[pl] -= ({ cmd });
+          else
+            channelB[pl] += ({ cmd });
+          ret = "Fuer '"+capitalize(pl)+"' besteht "
+            + (sizeof(channelB[pl]) ?
+               "folgender Bann: "+implode(channelB[pl], ", ") :
+               "kein Bann mehr.");
+          if(!sizeof(channelB[pl]))
+            channelB = m_copy_delete(channelB, pl);
+          save_object(CHANNEL_SAVE);
+        }
+        else ret = "Das Kommando '"+cmd+"' ist unbekannt. Erlaubte Kommandos: "
+               + implode(CMDS, ", ");
+      }
+      else
+      {
+        if(!IS_ARCH(msg[1])) return;
+        else ret = "Syntax: bann <name> <kommando>";
+      }
+    }
+  }
+  else if(mesg == "lust")
+  {
+    mixed t, up;
+    if(Tcmd["lag"] > TIMEOUT ||
+       Tcmd["statistik"] > TIMEOUT ||
+       Tcmd["uptime"] > TIMEOUT) return;
+    Tcmd["lag"] = time();
+    Tcmd["statistik"] = time();
+    Tcmd["uptime"] = time();
+    lag = "/p/daemon/lag-o-daemon"->read_lag_data();
+
+    sscanf(read_file("/etc/maxusers"), "%d %s", max, tmp);
+    sscanf(read_file("/etc/maxusers.ever"), "%d %s", rekord, tmp);
+
+    t=time()-last_reboot_time();
+    up="";
+    if(t >= 86400)
+      up += sprintf("%dT", t/86400);
+    if(t >= 3600)
+      up += sprintf("%dh", (t=t%86400)/3600);
+    if(t > 60)
+      up += sprintf("%dm", (t=t%3600)/60);
+    up += sprintf("%ds", t%60);
+
+    ret = sprintf("%.1f%%/15 %.1f%%/1 %s %d:%d:%d E:%d T:%d",
+                  lag[1], lag[2], up, sizeof(users()), max, rekord, 
+                  sizeof(channels), CountUser(channels));
+  } else return;
+
+  call_out(#'send/*'*/, 2, CMNAME, this_object(), ret);
+}
+
+// setup() -- set up a channel and register it
+//            arguments are stored in the following order:
+//            ({ channel name,
+//               receive level, send level,
+//               flags,
+//               description,
+//               master obj
+//            })
+private void setup(mixed c)
+{
+  closure cl;
+  object m;
+  string d;
+  d = "- Keine Beschreibung -";
+  m = this_object();
+  if(sizeof(c) && sizeof(c[0]) > 1 && c[0][0] == '\\')
+    c[0] = c[0][1..];
+
+  switch(sizeof(c))
+  {
+  case 6:
+    if(!stringp(c[5]) || !sizeof(c[5]) 
+        || (catch(m=load_object(c[5]);publish) || !objectp(m) ))
+      m = this_object();
+  case 5: d = stringp(c[4]) || closurep(c[4]) ? c[4] : d;
+  case 4: admin[c[0], FLAG] = to_int(c[3]);
+  case 3: admin[c[0], SEND] = to_int(c[2]);
+  case 2: admin[c[0], RECV] = to_int(c[1]);
+    break;
+  case 0:
+  default:
+    return;
+  }
+  switch(new(c[0], m, d))
+  {
+  case E_ACCESS_DENIED:
+    log_file("CHANNEL", sprintf("[%s] %s: %O: error, access denied\n",
+                           dtime(time()), c[0], m));
+    break;
+  default:
+    break;
+  }
+  return;
+}
+
+void initialize()
+{
+  mixed tmp;
+  tmp = read_file(object_name(this_object())+".init");
+  tmp = regexp(old_explode(tmp, "\n"), "^[^#]");
+  tmp = map(tmp, #'regexplode/*'*/, "[^:][^:]*$|[ \\t]*:[ \\t]*");
+  tmp = map(tmp, #'regexp/*'*/, "^[^: \\t]");
+  map(tmp, #'setup/*'*/);
+}
+
+// BEGIN OF THE CHANNEL MASTER IMPLEMENTATION
+
+void create()
+{
+  seteuid(getuid());
+  restore_object(CHANNEL_SAVE);
+  if(!channelC) channelC = ([]);
+  if(!channelB) channelB = ([]);
+  channels = ([]);
+
+  /* Die Channel-History wird nicht nur lokal sondern auch noch im Memory 
+     gespeichert, dadurch bleibt sie auch ueber ein Reload erhalten. 
+     Der folgende Code versucht, den Zeiger aus dem Memory zu holen. Falls
+     das nicht moeglich ist, wird ein neuer erzeugt und gegebenenfalls im
+     Memory abgelegt. */
+
+  // Hab ich die noetigen Rechte im Memory?
+  if (call_other(MEMORY,"HaveRights")) {
+    // Objektpointer laden
+    channelH = (mixed) call_other(MEMORY,"Load","History");
+
+    // Wenns nich geklappt hat, hat der Memory noch keinen Zeiger, dann
+    if (!mappingp(channelH)){
+      // Zeiger erzeugen
+      channelH = ([]);
+      // und in den Memory schreiben
+      call_other(MEMORY,"Save","History",channelH);
+    }
+  } else {
+    // Keine Rechte im Memory, dann wird mit einem lokalen Zeiger gearbeitet.
+    channelH = ([]);
+  }
+
+  stats = (["time": time(),
+            "boot": capitalize(getuid(previous_object())||"<Unbekannt>")]);
+  new(CMNAME, this_object(), "Zentrale Informationen zu den Ebenen");
+  initialize();
+  map_objects(efun::users(), "RegisterChannels");
+  this_object()->send(CMNAME, this_object(),
+                      sprintf("%d Ebenen mit %d Teilnehmern initialisiert.",
+                              sizeof(channels),
+                              CountUser(channels)));
+}
+
+// reset() and cache_to() - Cache Timeout, remove timed out cached channels
+// SEE: new, send
+private int cache_to(string key, mapping m, int t)
+{
+  if(!pointerp(m[key]) || m[key][2] + 43200 > t) return 1;
+  return(0);
+}
+
+varargs void reset(int nonstd)
+{
+  channelC = filter_indices(channelC, #'cache_to/*'*/, channelC, time());
+  if (save_me_soon)
+  {
+    save_me_soon=0;
+    save_object(CHANNEL_SAVE);
+  }
+}
+
+// name() - define the name of this object.
+string name() { return CMNAME; }
+string Name() { return CMNAME; }
+
+// access() - check access by looking for the right argument types and
+//            calling access closures respectively
+// SEE: new, join, leave, send, list, users
+// Note: <pl> is usually an object, only the master supplies a string during
+//       runtime error handling.
+varargs private int access(mixed ch, mixed pl, string cmd, string txt)
+{
+  mixed co, m;
+
+  if(!stringp(ch) || !sizeof(ch = lower_case(ch)) || !channels[ch])
+    return 0;
+  if(!channels[ch][I_ACCESS]||!previous_object(1)||!extern_call()||
+     previous_object(1)==this_object()||
+     (stringp(channels[ch][I_MASTER])&&
+      previous_object(1)==find_object(channels[ch][I_MASTER]))||
+     getuid(previous_object(1)) == ROOTID)
+    return 2;
+  if(!objectp(pl) || 
+     ((previous_object(1)!=pl) &&(previous_object(1)!=this_object()))) 
+     return 0;
+   if(pointerp(channelB[getuid(pl)]) &&
+     member(channelB[getuid(pl)], cmd) != -1)
+    return 0;
+   if(stringp(channels[ch][I_MASTER]) &&
+     (!(m = find_object(channels[ch][I_MASTER])) ||
+      (!to_object(channels[ch][I_ACCESS]) ||
+       get_type_info(channels[ch][I_ACCESS])[1])))
+  {
+    string err;
+    if(!objectp(m)) err = catch(load_object(channels[ch][I_MASTER]);publish);
+    if(!err &&
+       ((!to_object(channels[ch][I_ACCESS]) ||
+         get_type_info(channels[ch][I_ACCESS])[1]) &&
+        !closurep(channels[ch][I_ACCESS] =
+                  symbol_function("check",
+                                  find_object(channels[ch][I_MASTER])))))
+    {
+      log_file("CHANNEL", sprintf("[%s] %O -> %O\n",
+                                  dtime(time()), channels[ch][I_MASTER],
+                                  err));
+      channels = m_copy_delete(channels, ch);
+      return 0;
+    }
+    this_object()->join(ch, find_object(channels[ch][I_MASTER]));
+  }
+  if(closurep(channels[ch][I_ACCESS]))
+      return funcall(channels[ch][I_ACCESS],
+                     channels[ch][I_NAME], pl, cmd, &txt);
+}
+
+// new() - create a new channel
+//         a channel with name 'ch' is created, the player is the master
+//         info may contain a string which describes the channel or a closure
+//         to display up-to-date information, check may contain a closure
+//         called when a join/leave/send/list/users message is received
+// SEE: access
+
+#define IGNORE  "^/xx"
+
+varargs int new(string ch, object pl, mixed info)
+{
+  mixed pls;
+
+  if(!objectp(pl) || !stringp(ch) || !sizeof(ch) || channels[lower_case(ch)] ||
+     (pl == this_object() && extern_call()) ||
+     sizeof(channels) >= MAX_CHANNELS ||
+     sizeof(regexp(({ object_name(pl) }), IGNORE)) ||
+     (pointerp(channelB[getuid(pl)]) &&
+      member(channelB[getuid(pl)], C_NEW) != -1))
+    return E_ACCESS_DENIED;
+
+  if(!info) {
+    if(channelC[lower_case(ch)]) {
+      ch = channelC[lower_case(ch)][0];
+      info = channelC[lower_case(ch)][1];
+    }
+    else return E_ACCESS_DENIED;
+  }
+  else channelC[lower_case(ch)] = ({ ch, info, time() });
+
+  pls = ({ pl });
+
+  channels[lower_case(ch)] = ({ pls,
+                                symbol_function("check", pl) ||
+                                #'check/*'*/, info,
+                                (!living(pl) &&
+                                 !clonep(pl) &&
+                                 pl != this_object()
+                                 ? object_name(pl)
+                                 : pl),
+                                 ch,
+                             });
+
+  // ChannelH fuer einen Kanal nur dann initialisieren, wenn es sie noch nich gibt.
+  if ( !pointerp(channelH[lower_case(ch)]) )
+         channelH[lower_case(ch)] = ({});
+
+  if(pl != this_object())
+    log_file("CHANNEL.new", sprintf("[%s] %O: %O %O\n",
+                                    dtime(time()), ch, pl, info));
+  if(!pl->QueryProp(P_INVIS))
+    this_object()->send(CMNAME, pl,
+                        "laesst die Ebene '"+ch+"' entstehen.", MSG_EMOTE);
+  stats["new"]++;
+ 
+  save_me_soon=1;
+  return(0);
+}
+
+// join() - join a channel
+//          this function checks whether the player 'pl' is allowed to join
+//          the channel 'ch' and add if successful, one cannot join a channel
+//          twice
+// SEE: leave, access
+int join(string ch, object pl)
+{
+  if(!funcall(#'access,&ch, pl, C_JOIN)) return E_ACCESS_DENIED;
+  if(member(channels[ch][I_MEMBER], pl) != -1) return E_ALREADY_JOINED;
+  channels[ch][I_MEMBER] += ({ pl });
+  return(0);
+}
+
+// leave() - leave a channel
+//           the access check in this function is just there for completeness
+//           one should always be allowed to leave a channel.
+//           if there are no players left on the channel it will vanish, unless
+//           its master is this object.
+// SEE: join, access
+int leave(string ch, object pl)
+{
+  int pos;
+  if(!funcall(#'access,&ch, pl, C_LEAVE)) return E_ACCESS_DENIED;
+  channels[ch][I_MEMBER] -= ({0}); // kaputte Objekte erstmal raus
+  if((pos = member(channels[ch][I_MEMBER], pl)) == -1) return E_NOT_MEMBER;
+  if(pl == channels[ch][I_MASTER] && sizeof(channels[ch][I_MEMBER]) > 1)
+  {
+    channels[ch][I_MASTER] = channels[ch][I_MEMBER][1];
+    if(!pl->QueryProp(P_INVIS))
+      this_object()->send(ch, pl, "uebergibt die Ebene an "
+                          +channels[ch][I_MASTER]->name(WEN)+".", MSG_EMOTE);
+  }
+  channels[ch][I_MEMBER][pos..pos] = ({ });
+
+
+  if(!sizeof(channels[ch][I_MEMBER]) &&
+     !stringp(channels[ch][I_MASTER]))
+  {
+    // delete the channel that has no members
+    if(!pl->QueryProp(P_INVIS))
+      this_object()->send(CMNAME, pl,
+                          "verlaesst als "
+                          +(pl->QueryProp(P_GENDER) == 1 ? "Letzter" :
+                            "Letzte")
+                          +" die Ebene '"
+                          +channels[ch][I_NAME]
+                          +"', worauf diese sich in einem Blitz oktarinen "
+                          +"Lichts aufloest.", MSG_EMOTE);
+    channelC[lower_case(ch)] = ({ channels[ch][I_NAME],
+                                  channels[ch][I_INFO], time() });
+    m_delete(channels, lower_case(ch));
+
+    // Wird ein Channel entfernt, wird auch seine History geloescht
+    channelH = m_copy_delete(channelH, lower_case(ch));
+
+    stats["dispose"]++;
+    save_me_soon=1;
+  }
+  return(0);
+}
+
+// send() - send a message to all recipients of the specified channel 'ch'
+//          checks if 'pl' is allowed to send a message and sends if success-
+//          ful a message with type 'type'
+//          'pl' must be an object, the message is attributed to it. e.g.
+//            ignore checks use it. It can be != previous_object()
+// SEE: access, ch.h
+varargs int send(string ch, object pl, string msg, int type)
+{
+  int a;
+
+  if(!(a = funcall(#'access,&ch, pl, C_SEND, &msg))) return E_ACCESS_DENIED;
+  if(a < 2 && member(channels[ch][I_MEMBER], pl) == -1) return E_NOT_MEMBER;
+  if(!msg || !stringp(msg) || !sizeof(msg)) return E_EMPTY_MESSAGE;
+  map_objects(channels[ch][I_MEMBER],
+              "ChannelMessage", ({ channels[ch][I_NAME], pl, msg, type }));
+  if(sizeof(channelH[ch]) > MAX_HIST_SIZE)
+    channelH[ch] = channelH[ch][1..];
+  channelH[ch] += ({ ({ channels[ch][I_NAME],
+                        (stringp(pl)
+                         ? pl
+                         : (pl->QueryProp(P_INVIS)
+                            ? "/("+capitalize(getuid(pl))+")$" : "")
+                         + (pl->Name(WER, 2) || "<Unbekannt>")),
+                         msg+" <"+strftime("%a, %H:%M:%S")+">\n",
+                         type }) });
+  return(0);
+}
+
+// list() - list all channels, that are at least receivable by 'pl'
+//          returns a mapping,
+// SEE: access, channels
+private void clean(string n, mixed a) { a[0] -= ({ 0 }); }
+mixed list(object pl)
+{
+  mapping chs;
+  
+  chs = filter_indices(channels, #'access/*'*/, pl, C_LIST);
+  walk_mapping(chs, #'clean/*'*/);
+  if(!sizeof(chs)) return E_ACCESS_DENIED;
+  return deep_copy(chs);
+}
+
+// find() - find a channel by its name (may be partial)
+//          returns an array for multiple results and 0 for no matching name
+// SEE: access
+mixed find(string ch, object pl)
+{
+  mixed chs, s;
+  if(stringp(ch)) ch = lower_case(ch);
+  if( !sizeof(regexp(({ch}),"^[<>a-z0-9#-]*$")) ) return 0; // RUM
+  if(!sizeof(chs = regexp(m_indices(channels), "^"+ch+"$")))
+    chs = regexp(m_indices(channels), "^"+ch);
+  if((s = sizeof(chs)) > 1)
+    if(sizeof(chs = filter(chs, #'access/*'*/, pl, C_FIND)) == 1)
+      return channels[chs[0]][I_NAME];
+    else return chs;
+  return ((s && funcall(#'access,chs[0], pl, C_FIND)) ? channels[chs[0]][I_NAME] : 0);
+}
+
+// history() - get the history of a channel
+// SEE: access
+mixed history(string ch, object pl)
+{
+  if(!funcall(#'access,&ch, pl, C_JOIN))
+    return E_ACCESS_DENIED;
+  return deep_copy(channelH[ch]);
+}
+
+// remove - remove a channel (wird aus der Shell aufgerufen)
+// SEE: new
+mixed remove(string ch, object pl)
+{
+  mixed members;
+
+  if(previous_object() != this_object())
+    if(!stringp(ch) ||
+       pl != this_player() || this_player() != this_interactive() ||
+       this_interactive() != previous_object() ||
+       !IS_ARCH(this_interactive()))
+      return E_ACCESS_DENIED;
+
+  if(channels[lower_case(ch)]) {
+    channels[lower_case(ch)][I_MEMBER] =
+        filter_objects(channels[lower_case(ch)][I_MEMBER],
+                       "QueryProp", P_CHANNELS);
+    map(channels[lower_case(ch)][I_MEMBER],
+        lambda(({'u/*'*/}), ({#'call_other/*'*/, 'u, /*'*/
+                                   "SetProp", P_CHANNELS,
+                                   ({#'-/*'*/,
+                                          ({#'call_other/*'*/, 'u, /*'*/
+                                                 "QueryProp", P_CHANNELS}),
+                                          '({ lower_case(ch) })/*'*/,})
+                                   })));
+    channels = m_copy_delete(channels, lower_case(ch));
+
+    // Wird ein Channel entfernt, wird auch seine History geloescht
+    if( pointerp(channelH[lower_case(ch)] ))
+     channelH = m_copy_delete(channelH, lower_case(ch));
+
+    stats["dispose"]++;
+  }
+  if(!channelC[lower_case(ch)])
+    return E_ACCESS_DENIED;
+  channelC = m_copy_delete(channelC, lower_case(ch));
+  save_me_soon=1;
+  return(0);
+}
+
+// Wird aus der Shell aufgerufen 
+mixed clear_history(string ch)
+{
+  mixed members;
+  // Sicherheitsabfragen
+  if(previous_object() != this_object())
+    if(!stringp(ch) ||
+       this_player() != this_interactive() ||
+       this_interactive() != previous_object() ||
+       !IS_ARCH(this_interactive()))
+      return E_ACCESS_DENIED;
+
+  // History des Channels loeschen
+  if( pointerp(channelH[lower_case(ch)] ))
+    channelH[lower_case(ch)]=({});
+
+  return 0;
+}
diff --git a/p/daemon/channeld.init b/p/daemon/channeld.init
new file mode 100644
index 0000000..f099d2d
--- /dev/null
+++ b/p/daemon/channeld.init
@@ -0,0 +1,65 @@
+# CHANNEL MASTER INIT FILE
+# To create a new channel:
+# <name>:<recv>:<send>:<flags>:<info>:<master>
+Allgemein:   0: 0: 0:Allgemeine Unterhaltungsebene
+Abenteuer:   0: 0: 0:Fragen die Abenteuer betreffen:	/secure/questmaster
+Grats:	     0: 0: 0:Gratulationen zu geloesten Abenteuern etc
+Beileid:     0: 0: 0:Beileidsbekundungen jeglicher Art
+Fraternitas: 0: 0: 0:Fraternitas dono Archmagorum
+Tod:         0:-1: 0:Wer stirbt wann?:/room/death/death_room
+TdT:         15:15:1:Tod den Testies!:/room/death/death_room
+Moerder:     0:-1: 0:Guck mal wer da spricht...:	/std/corpse
+Seher:       1: 1: 1:Diskussionsebene fuer Seher
+ZT:          0: 0: 0:Nuetzliche Tips fuer die Zaubertranksuche
+#
+# Guild channels
+#
+Klerus:      0: 0: 0:Die Priester:			/gilden/klerus
+Bierschuettler: 0: 0: 0:Die Bierschuettler:		/gilden/bierschuettler
+Werwoelfe:   0: 0: 0:Die Werwoelfe:                     /gilden/werwoelfe
+AbGilde:     0: 0: 0:Abenteurer, und solche, die es werden wollen: /gilden/abenteurer
+Zauberer:    0: 0: 0:Die Zauberer:			/gilden/zauberer
+Tanjian:     0: 0: 0:Die Tanjian:                       /gilden/tanjian
+Karate:      0: 0: 0:Die Karateka:			/gilden/karate
+Chaos:       0: 0: 0:Chaoten...:			/gilden/chaos
+Trves:       0: 0: 0:Neues vom Koru Tschakar Struv:     /p/kaempfer/std/k_master
+Magus:       0: 0: 0:Der magische Convent zu Castalla Rosso:  /gilden/magus
+Dunkelelfen: 0: 0: 0:Die Dunkelelfen:			/gilden/dunkelelfen
+Uruk-Hai:    0: 0: 0:Fuer echte Orks:			/gilden/urukhai
+#
+# Wizard channels
+#
+Magier:     15:15: 1:Diskussionsebene fuer Magier
+Erzmagier:  60:60: 1:Erzmagier
+Goetter:   100:100: 1:Goetter
+LPC:        15:15: 1:Fragen zur Programmierung und LPC
+#
+Debug:      20:60: 1:Debug und Fehlermeldungen:		/p/daemon/debug
+Entwicklung: 20:60:1:Fehler rund um Eigenentwicklungen: /p/daemon/debug
+Warnungen:   20:60:1:Laufzeit-Warnungen:		/p/daemon/debug
+Snoop:      60:-1: 1:Wer snoopt denn hier?:		/secure/simul_efun/simul_efun
+FTP:        60:-1: 1:Wer macht denn FTP?:		/secure/bbmaster
+#
+# Intermud channels
+#
+Intermud:    0: 5: 2:Intermud chat (Englisch):		/secure/udp/channel
+Intercode:  20:20: 3:Programmierfragen (Englisch):	/secure/udp/channel
+Interadmin: 45:55: 3:Administration (Englisch):		/secure/udp/channel
+D-chat:	     0: 5: 2:Verbindet Deutsche MUDs:		/secure/udp/channel
+D-linux:     0: 5: 2:Linux im D-Chat:    		/secure/udp/channel
+D-tv-alles:  0: 5: 2:Deutsches TV Programm:		/secure/udp/channel
+D-news:      0: 5: 2:Deutsches Nachrichten:		/secure/udp/channel
+D-code:	    20:20: 3:MUD Programmier Forum:		/secure/udp/channel
+D-adm:      60:60: 3:Deutschsprachige Administration:	/secure/udp/channel
+#
+# special
+#
+Wissenschaft:0: 0: 0:Wissenschaftliche Dispute
+Kultur:	     0: 0: 0:Kulturelle Ereignisse und Erguesse
+Sport:	     0: 0: 0:Sport und Spiel
+Anfaenger:   0: 0: 0:Die Ebene fuer Anfaengerfragen
+twitter:     0: 0: 1:Ein Vogel im Mud?: /secure/misc/twitter
+#
+# Gebietspezifisches
+#
+Metzelorakel:0: 0: 0:Fuer die Metzelwuetigen:		/d/ebene/arathorn/orakel/secure/schamane
diff --git a/p/daemon/channeld.trusted b/p/daemon/channeld.trusted
new file mode 100644
index 0000000..8f19633
--- /dev/null
+++ b/p/daemon/channeld.trusted
@@ -0,0 +1,12 @@
+# CHANNELD MASTER TRUSTED OBJECTS FILE
+# <object> : <channels> : <name>
+/secure/master                       ::
+/secure/udp/channel                  :IOAWqP:
+/secure/simul_efun                   ::
+/room/death/death_room               :T:
+/secure/questmaster                  :b:
+/d/inseln/anthea/room/land/muehlstein:T:
+/std/corpse                          :M:
+/p/daemon/inews                      :aNqW:DW
+/gilden/karate                       :K:
+/gilden/chaos                        :c:
diff --git a/p/daemon/classdb.c b/p/daemon/classdb.c
new file mode 100644
index 0000000..2db40ae
--- /dev/null
+++ b/p/daemon/classdb.c
@@ -0,0 +1,91 @@
+// MorgenGrauen MUDlib
+//
+// /obj/classd.c -- master object for classes.
+//
+// Soll irgendwann mal Klassen aehnlich der Material-DB verwalten, momentan
+// machts es erstmal einfach eine Ersetzung von Aliasen und ein Hinzufuegen
+// von impliziten Klassen.
+//
+// $Id$
+#pragma strict_types,save_types
+#pragma no_clone, no_shadow, no_inherit
+#pragma verbose_errors,combine_strings
+#pragma pedantic, range_check, warn_deprecated
+
+#include <class.h>
+
+// Mapping von Aliasen fuer Klassen. Aliase duerfen nicht rekursiv sein.
+private nosave mapping classaliases =
+  ([
+   ]);
+// Mapping von impliziten Klassen fuer eine gegebene Klasse. Implizite Klassen
+// sind als Array von Klassennamen gegeben. Implizite Klassen duerfen zur Zeit
+// nicht rekursiv sein und keine Aliase enthalten.
+private nosave mapping implicitclasses =
+  ([
+     CL_ZOMBIE:       ({CL_UNDEAD}),
+     CL_SKELETON:     ({CL_UNDEAD}),
+     CL_GHOUL:        ({CL_UNDEAD}),
+     CL_VAMPIRE:      ({CL_UNDEAD}),
+     CL_HOBGOBLIN:    ({CL_GOBLIN}),
+     CL_MAMMAL_LAND:  ({CL_MAMMAL, CL_ANIMAL}),
+     CL_MAMMAL_WATER: ({CL_MAMMAL, CL_ANIMAL}),
+     CL_SNAKE:        ({CL_REPTILE}),
+     CL_ARACHNID:     ({CL_ANIMAL}),
+     CL_BIRD:         ({CL_ANIMAL}),
+     CL_FISH:         ({CL_ANIMAL}),
+     CL_FROG:         ({CL_ANIMAL}),
+     CL_INSECT:       ({CL_ANIMAL}),
+     CL_MAMMAL:       ({CL_ANIMAL}),
+     CL_REPTILE:      ({CL_ANIMAL}),
+     CL_SNAKE:        ({CL_ANIMAL}),
+     CL_GOLEM:        ({CL_CONSTRUCT}),
+   ]);
+
+protected void create() {
+}
+
+/** Gibt den Alias der Klasse <cls> zurueck.
+ * @param[in] cls string - Klassenname
+ * @return string* - Alias von Klasse <cls>
+ */
+public string * QueryClassAlias(string cls) {
+  if (!stringp(cls))
+      raise_error(sprintf(
+            "Got wrong argument, expected string, got %.50O\n",cls));
+  return copy(classaliases[cls]) || cls;
+}
+
+/** Gibt die impliziten Klassen der Klasse <cls> zurueck.
+ * @param[in] cls string - Klassenname
+ * @return string* - Liste von impliziten Klassen von <cls>
+ */
+public string * QueryImplicitClasses(string cls) {
+  if (!stringp(cls))
+      raise_error(sprintf(
+            "Got wrong argument, expected string, got %.50O\n", cls));
+  return copy(implicitclasses[cls]) || ({});
+}
+
+/** Adds implicit classes to the classes given in <classes>.
+ * Ausserdem werden Klassenaliase aufgeloest.
+ * @param[in] classes string* - Klassenliste.
+ * @return string* - neue Klassenliste
+ * @attention Kann das uebergebene Array aendern. Das Ergebnisarray muss nicht
+ * unique sein, sondern kann Dubletten enthalten.
+ */
+public string * AddImplicitClasses(string *classes) {
+  if (!pointerp(classes))
+      raise_error(sprintf(
+          "Got wrong argument, expected array, got %.50O\n",classes));
+  foreach(string cls : &classes) {
+      // zuerst Aliase ersetzen
+      if (member(classaliases, cls))
+          cls = classaliases[cls];
+      // dann implizite Klassen addieren.
+      if (member(implicitclasses, cls))
+          classes += implicitclasses[cls];
+  }
+  return classes;
+}
+
diff --git a/p/daemon/debug.c b/p/daemon/debug.c
new file mode 100644
index 0000000..0ad6975
--- /dev/null
+++ b/p/daemon/debug.c
@@ -0,0 +1,128 @@
+
+#pragma strict_types,save_types
+#pragma no_clone,no_shadow
+
+#include <daemon.h>
+#include <logging.h>
+
+static int d_start, d_end, e_start, e_end;
+
+protected void create()
+{    
+    d_end = d_start = e_end = e_start = file_size(__DEBUG_LOG__);
+}
+
+int check( string ch, object pl, string cmd, string txt )
+{
+    mixed tmp;
+    
+    if ( ch != "Debug" && ch != "Entwicklung" && ch != "Warnungen" )
+        return 0;
+    
+    if( objectp(pl) && query_once_interactive(pl) && query_wiz_level(pl) > 15 )
+        switch(cmd){
+        case C_FIND:
+        case C_LIST:
+        case C_JOIN:
+        case C_LEAVE:
+            return 1;
+        case C_SEND:
+            // Wer (noch) nicht auf dem Kanal ist, bekommt auch keine
+            // Ausgabe - sonst gibt es selbige doppelt, da der CHMASTER
+            // einen automatisch den Kanal betreten laesst ...
+            if ( !objectp(pl) || !pointerp(tmp=(mixed)pl->QueryProp(P_CHANNELS)) ||
+                 member( tmp, lower_case(ch) ) == -1 )
+                return 1;
+
+            switch( lower_case(txt) ){
+            case "backtrace":
+                {
+                    string bt, log;
+                    int start, end;
+                    
+                    if ( ch == "Debug" ) {
+                        start = d_start;
+                        end = d_end;
+                    }
+                    else if (ch == "Entwicklung") {
+                        start = e_start;
+                        end = e_end;
+                    }
+		    else if (ch == "Warnungen") {
+			pl->Message( "[" + ch + ":] Backtrace fuer Warnungen "
+			    "nicht verfuegbar!\n");
+			return 0;
+		    }
+		    else return(1);
+                    
+                    if( start >= end ) 
+                        bt = "[" + ch + ":] Kein Backtrace verfuegbar!\n";
+                    else {
+                        log = regreplace( read_bytes( __DEBUG_LOG__, start,
+                                                      (end-start>10000)?10000:
+						      end - start ) || "\nFehler beim Einlesen des Debuglogs: Kein Backtrace verfuegbar!\n",
+                                          "(Misplaced|current_object|"
+                                          "Connection|Host)[^\n]*\n",
+                                          "", 1 );
+                        bt = "[" + ch + ":] -- BACKTRACE BEGIN --\n"
+                            + log
+                            + "[" + ch + ":] -- BACKTRACE END --\n";
+                    }
+                    
+                    call_out( symbol_function( "Message", pl ), 0, bt );
+                }
+                break;
+
+            default:
+		if ( ch != "Warnungen" ) {
+		  pl->Message( "[" + ch + ":] Hilfe...\n"
+                             "[" + ch + ":] Folgende Kommandos stehen zur "
+                             "Verfuegung:\n"
+                             "[" + ch + ":] 'backtrace' -- sendet den "
+                             "Backtrace zum Fehler\n"
+                             "[" + ch + ":] 'hilfe'     -- sendet diese "
+                             "Hilfeseite\n");
+		}
+		else {
+		  pl->Message( "[" + ch + ":] Keine Kommandos verfuegbar!\n");
+		}
+            }
+	}
+    
+    return 0;
+}
+
+string name() { return "<Debugger>"; }
+string Name() { return "<Debugger>"; }
+
+void ChannelMessage( mixed a )
+{
+    int fs;
+
+    fs = file_size(__DEBUG_LOG__);
+    
+    switch( a[0] ){
+    case "Debug":
+        if (  fs > d_end && fs > e_end ){
+	    d_start = max((d_end > e_end ? d_end : e_end),0);
+            d_end = fs;
+        }
+        else if (fs < d_start || fs <= d_end) {
+	    // Debuglog wurde wohl neu geoeffnet.
+	    d_start = d_end = e_start = e_end = fs;
+	}
+        break;
+        
+    case "Entwicklung":
+        if (  fs > d_end && fs > e_end ){
+	    e_start = max((e_end > d_end ? e_end : d_end),0);
+            e_end = fs;
+        }
+        else if (fs < e_start || fs <= e_end) {
+	    // Debuglog wurde wohl neu geoeffnet.
+	    d_start = d_end = e_start = e_end = fs;
+	}
+        break;
+    }
+}
+ 
diff --git a/p/daemon/dnslookup.c b/p/daemon/dnslookup.c
new file mode 100644
index 0000000..a952324
--- /dev/null
+++ b/p/daemon/dnslookup.c
@@ -0,0 +1,101 @@
+// MorgenGrauen MUDlib
+//
+// dnslookup.c - kleines DNS-Lookup-Objekt mit Cache.
+//               Braucht externen DNS-Resolver, der per UDP ansprechbar ist.
+//
+// Autor: Zesstra
+
+/* Im Mudlibmaster in receive_udp() braucht es noch etwas a la:
+  if( message[0..9]=="DNSLOOKUP\n" ) {
+    "/p/daemon/dnslookup"->update( message );
+    return;
+  }
+
+  ext. Objekt erwartet ganz simpel <hostname>\n als Eingabe und sendet zurueck:
+  DNSLOOKUP\n<hostname>\n<ip-adresse>|UNKNOWN\n
+  (Das | steht fuer oder.)
+*/
+
+#pragma strong_types,rtt_checks
+
+// externer server, der den DNS-Resolve macht.
+#define IPLOOKUP_HOST "127.0.0.1"
+#define IPLOOKUP_PORT 8712
+
+mapping cache = ([]);
+
+mapping _query_cache() {return cache;}
+
+protected void create()
+{
+  seteuid(getuid());
+  set_next_reset(3600);
+}
+
+// ja... total simpel... nicht mehr aufwand als noetig.
+void reset()
+{
+  cache = ([]);
+  set_next_reset(10800);
+}
+
+// eigentliche Anfrage an den externen Resolver senden.
+protected void make_request(string hostname)
+{
+  send_udp( IPLOOKUP_HOST, IPLOOKUP_PORT, hostname+"\n" );
+  if (sizeof(cache) > 1000)
+    reset();
+  cache[hostname]=0; // cached request, but unknown result (yet)
+}
+
+// Hostnamen asynchron aufloesen.
+public string resolve( string hostname )
+{
+  if (member(cache, hostname))
+    return cache[hostname];
+  make_request( hostname );
+  return 0;  // leider...
+}
+
+// Pruefung, ob von <ip> Verbindungen zu unserer IP nach <localport>
+// weitergeleitet werden.
+public int check_tor(string ip, int localport)
+{
+  string *arr=explode(ip,".");
+  if (!sizeof(arr)==4)
+    return 0;
+  string req =
+    sprintf("%s.%s.%s.%s.%d.60.24.79.87.ip-port.exitlist.torproject.org",
+            arr[3],arr[2],arr[1],arr[0],localport);
+  
+  return resolve(req) == "127.0.0.2";
+}
+
+// Pruefung, ob <ip> auf einigen Blacklists steht.
+public int check_dnsbl(string ip)
+{
+  string *arr=explode(ip,".");
+  if (!sizeof(arr)==4)
+    return 0;
+  string req =
+    sprintf("%s.%s.%s.%s.dnsbl.dronebl.org",
+            arr[3],arr[2],arr[1],arr[0]);
+  
+  return resolve(req) != 0;
+}
+
+/* wird vom master aufgerufen, wenn eine antwort vom externen
+ * iplookup dienst eintrudelt.
+ */
+public void update( string udp_reply )
+{
+  if( previous_object()!=master() ) return;
+  string *reply = explode(udp_reply,"\n");
+  if( sizeof(reply)<3 ) return;
+
+  if (reply[2] == "UNKNOWN")
+    cache[reply[1]] = 0;
+  else
+    cache[reply[1]] = reply[2];
+}
+
diff --git a/p/daemon/eventd.c b/p/daemon/eventd.c
new file mode 100644
index 0000000..b81d38b
--- /dev/null
+++ b/p/daemon/eventd.c
@@ -0,0 +1,334 @@
+// MorgenGrauen MUDlib
+//
+// /p/daemon/eventd.c -- Event Dispatcher
+//
+// $Id$
+#pragma strict_types,save_types
+#pragma no_clone
+#pragma no_shadow
+#pragma pedantic
+#pragma range_check
+
+#include <wizlevels.h>
+#include <defines.h>
+#include <events.h>
+
+// Fuer Statistiken
+#define STATISTICS
+
+#define HOME(x) (__PATH__(0)+x)
+#define STORE HOME("save/eventd")
+
+
+#define LOG(x) log_file("EVENTS", sprintf(\
+      "[%s] %s\n",dtime(time()),x))
+//#define LOGEVENT(x,y,z) log_file("EVENTS", sprintf(\
+//      "[%s] Event %s triggered by %O (Args: %.40O)\n",dtime(time()),x,y,z))
+#define LOGEVENT(x,y,z)
+//#define LOGREGISTER(w,x,y,z) log_file("EVENTLISTENER",sprintf(\
+//      "[%s] %O (Fun: %.25s) registered for %s by %O\n",dtime(time()),w,x,y,z))
+#define LOGREGISTER(w,x,y,z)
+//#define LOGUNREGISTER(x,y,z) log_file("EVENTLISTENER",sprintf(\
+//      "[%s] %O was unregistered from %s by %O\n",dtime(time()),x,y,z))
+#define LOGUNREGISTER(x,y,z)
+//#define LOGEVENTFINISH(x,y) log_file("EVENTS", sprintf(\
+//      "[%s] Event %s (triggered by %O) finished\n",dtime(time()),x,y))
+#define LOGEVENTFINISH(x,y)
+
+#ifndef DEBUG
+#define DEBUG(x)  if (find_player("zesstra"))\
+          tell_object(find_player("zesstra"),\
+                      "EDBG: "+x+"\n")
+#endif
+
+#define TICK_RESERVE 300000
+#define TICKSPERCALLBACK 30000
+
+// Indizes fuer Callback-Infos (events, active)
+#define CB_FUN        0
+#define CB_CLOSURE    1
+#define CB_OBJECT     2
+// Indizes fuer Pending
+#define P_EID         0
+#define P_TRIGOB      1
+#define P_TRIGOBNAME  2
+#define P_ARGS        3
+#define P_TIME        4
+
+// Indizes fuer Active
+#define A_EID         0
+#define A_TRIGOB      1
+#define A_TRIGOBNAME  2
+#define A_ARGS        3
+#define A_LISTENERS   4
+#define A_TIME        5
+
+/* alle events, die er kennt.
+   Datenstruktur events:
+   ([id:([obname:fun;closure;object, ... ]),
+     id2: ([....]),
+   ]) */
+mapping events=([]);
+/* abzuarbeitende Events, Datenstruktur:
+   ({ ({id, trigob, trigobname, args}), 
+      ({id2, trigob, trigobname, args}), ... })  */
+nosave mixed pending=({});
+/* Der gerade aktive Event, der wird gerade abgearbeitet.
+   Datenstruktur active:
+   ({ id:trigob;trigobname;args;([obname:fun;closure;object, ...]), trigtime})
+*/
+nosave mixed active=({});
+
+int lastboot; // Zeitstempel des letzten Reboots
+// Flag, wenn gesetzt, zerstoert sich der Eventd, wenn keine Events
+// abzuarbeiten sind.
+nosave int updateme; 
+
+// einfache Statistik, gespeichert wird ein Histogramm. KEys sind die
+// Zeitdifferenzen zwschen Eintragen und Erledigen des Events, Values die
+// Haeufigkeiten.
+nosave mapping stats=([]);
+
+
+protected void process_events();
+protected void save_me();
+varargs int remove(int silent);
+
+// ist der Event-Typ "eid" schon bekannt, d.h. gib es min. 1 Listener?
+// Liefert Anzahl der Listener zurueck.
+int CheckEventID(string eid) {
+    if (!stringp(eid) || !member(events,eid))
+        return 0;
+    return(sizeof(events[eid]));
+}
+
+// entscheidet, ob ein Objekt fuer einen Event eingetragen werden darf. Zum
+// Ueberschreiben in geerbten Dispatchern, in diesem Scheduler sind alle
+// Events oeffentlich. Bekommt die Event-ID, das einzutragende Objekt und das
+// eintragende Objekt uebergeben.
+// 1 fuer Erlaubnis, 0 sonst.
+protected int allowed(string eid, object ob, object po) {
+    return(1);
+}
+
+// entscheidet, ob ein Objekt einen bestimmten Event triggern darf. Zum
+// Ueberschreiben in geerbten Dispatchern, in diesem Scheduler sind alle
+// Events oeffentlich. Bekommt die Event-ID und das triggernde Objekt
+// uebergeben.
+// 1 fuer Erlaubnis, 0 sonst.
+protected int allowedtrigger(string eid, object trigger) {
+    return(1);
+}
+
+// registriert sich fuer einen Event. Wenn es den bisher nicht gibt, wird er
+// implizit erzeugt. Wenn das Objekt ob schon angemeldet war, wird der
+// bisherige Eintrag ueberschrieben.
+// 1 bei Erfolg, <=0 bei Misserfolg
+int RegisterEvent(string eid, string fun, object ob) {
+    object po;
+    if (!stringp(eid) || !stringp(fun) || 
+        !objectp(ob) || !objectp(po=previous_object()))
+        return -1;
+    if (!allowed(eid, ob, po)) return -2;
+    closure cl=symbol_function(fun,ob);
+    if (!closurep(cl))
+        return -3;
+    if (!mappingp(events[eid]))
+        events[eid]=m_allocate(1,3); // 3 Werte pro Key
+    events[eid]+=([object_name(ob):fun;cl;ob]);
+//    if (find_call_out(#'save_me)==-1)
+//        call_out(#'save_me,15);
+    LOGREGISTER(ob,fun,eid,po);
+    return 1;
+}
+
+// entfernt das Objekt als Listener aus dem Event eid
+// Mehr oder weniger optional, wenn ein event verarbeitet wird und das Objekt
+// ist nicht mehr auffindbar, wird es ebenfalls geloescht. Bei selten
+// getriggerten Events muellt es aber bis dahin den Speicher voll.
+// +1 fuer Erfolg, <= 0 fuer Misserfolg
+int UnregisterEvent(string eid, object ob) {
+    object po;
+    if (!stringp(eid) || !objectp(ob) || 
+        !objectp(po=previous_object()) || !mappingp(events[eid]))
+        return -1;
+    string oname=object_name(ob);
+    if (!member(events[eid],oname)) 
+        return -2;
+    m_delete(events[eid],oname);
+    if (!sizeof(events[eid]))
+        m_delete(events,eid);
+    // aus aktivem Event austragen, falls es drin sein sollte.
+    if (sizeof(active) && active[A_EID] == eid)
+    {
+      m_delete(active[A_LISTENERS], object_name(ob));
+    }
+//    if (find_call_out(#'save_me)==-1)
+//      call_out(#'save_me,15);
+    LOGUNREGISTER(ob, eid, po);
+    return 1;
+}
+
+// Loest einen Event aus.
+// 1 fuer Erfolg, <= 0 fuer Misserfolg
+varargs int TriggerEvent(string eid, mixed args) {
+    object trigger;
+    if (!stringp(eid) || 
+        !objectp(trigger=previous_object())) 
+        return -1;
+    if (!allowedtrigger(eid, trigger)) return -2;
+    if (!member(events,eid)) return -3;
+    if (sizeof(pending) > __MAX_ARRAY_SIZE__/5)
+        return -4;
+    pending+=({ ({eid,trigger,object_name(trigger), args, time()}) });
+    if (find_call_out(#'process_events) == -1)
+      call_out(#'process_events,0);
+    LOGEVENT(eid,trigger,args);
+    //DEBUG(sprintf("%O",pending));
+    return 1;
+}
+
+protected void process_events() {
+  // erstmal wieder nen call_out eintragen.
+  call_out(#'process_events, 1);
+
+  // solange ueber active und pending laufen, bis keine Ticks mehr da sind,
+  // bzw. in der Schleife abgebrochen wird, weil keine Events mehr da sind.
+  while(get_eval_cost() > TICK_RESERVE) {
+    // HB abschalten, wenn nix zu tun ist.
+    if (!sizeof(active)) {
+      if (!sizeof(pending)) {
+        remove_call_out(#'process_events);
+        break;
+      }
+      // scheint noch min. ein Eintrag in pending zu sein, nach active kopieren,
+      // plus die Callback-Infos aus events
+      active=({pending[0][P_EID],
+               pending[0][P_TRIGOB],pending[0][P_TRIGOBNAME],
+               pending[0][P_ARGS],
+               deep_copy(events[pending[0][P_EID]]),
+               pending[0][P_TIME] });
+
+      if (sizeof(pending)>1)
+        pending=pending[1..]; // und aus pending erstmal loeschen. ;-)
+      else
+        pending=({});
+      //DEBUG(sprintf("Pending: %O",pending));
+      //DEBUG(sprintf("Active: %O",active));
+    }
+    // jetzte den aktiven Event abarbeiten.
+    // Infos aus active holen...
+    string eid=active[A_EID];
+    object trigob=active[A_TRIGOB];
+    string trigobname=active[A_TRIGOBNAME];
+    mixed args=active[A_ARGS];
+    mapping listeners=active[A_LISTENERS];
+    // und ueber alle Listener iterieren
+    foreach(string obname, string fun, closure cl, object listener: 
+                 listeners) {
+      // erst pruefen, ob noch genug Ticks da sind. wenn nicht, gehts im
+      // naechsten Zyklus weiter.
+      if (get_eval_cost() < TICK_RESERVE) {
+        return;
+      }
+      // wenn Closure und/oder zugehoeriges Objekt nicht existieren, versuchen
+      // wir erstmal, es wiederzufinden. ;-)
+      if (!objectp(query_closure_object(cl))) {
+        if (objectp(listener=find_object(obname)) &&
+            closurep(cl=symbol_function(fun,listener))) {
+            //geklappt, auch in events wieder ergaenzen
+            events[eid][obname,CB_CLOSURE]=cl;
+            events[eid][obname,CB_OBJECT]=listener;
+        }
+        else {
+          // Objekt nicht gefunden oder Closure nicht erzeugbar, austragen
+          m_delete(listeners,obname);
+          // und aus events austragen.
+          m_delete(events[eid],obname);
+          // und naechster Durchgang
+          continue;
+        }
+      }
+      // Objekt noch da, Closure wird als ausfuehrbar betrachtet. 
+      catch(limited(#'funcall,({TICKSPERCALLBACK}),cl,eid,trigob,args);publish);
+      // fertig mit diesem Objekt.
+      m_delete(listeners,obname);
+    }
+    // Statistik? Differenzen zwische Erledigungszeit und Eintragszeit bilden
+    // die Keys, die Values werden einfach hochgezaehlt.
+#ifdef STATISTICS
+    stats[time()-active[A_TIME]]++;
+#endif // STATISTICS
+    // ok, fertig mit active.
+    active=({});
+    //DEBUG(sprintf("Fertig: %O %O",eid, trigobname));
+    LOGEVENTFINISH(eid,trigobname);
+  }  // while(get_eval_cost() > TICK_RESERVE)
+
+  // Soll dies Ding neugeladen werden? Wenn ja, Selbstzerstoerung, wenn keine
+  // Events mehr da sind.
+  if (updateme && !sizeof(active) && !sizeof(pending)) {
+    DEBUG(sprintf("Update requested\n"));
+    remove(1);
+  }
+}
+
+protected void create() {
+    seteuid(getuid(ME));
+    restore_object(STORE);
+    if (lastboot != __BOOT_TIME__) {
+      // Oh. Reboot war... Alle Events wegschmeissen (es koennten zwar die
+      // Eventanmeldungen von BPs ueberleben, aber auch die sollen sich lieber
+      // im create() anmelden.)
+      events=([]);
+      lastboot=__BOOT_TIME__;;
+    }
+    LOG("Event-Dispatcher loaded");
+}
+
+
+protected void save_me() {
+  save_object(STORE);
+}
+
+varargs int remove(int silent) {
+    save_me();
+    DEBUG(sprintf("remove() called by %O - destructing\n", previous_object())); 
+    LOG(sprintf("remove called by %O - destructing",previous_object()));
+    destruct(ME);
+    return 1;
+}
+
+public void reset() {
+  if (updateme && !sizeof(active) && !sizeof(pending)) {
+    DEBUG(sprintf("Update requested\n"));
+    remove(1);
+  }
+}
+
+
+// fuer Debugzwecke. Interface und Verhalten kann sich jederzeit ohne
+// Vorankuendigung aendern.
+mapping QueryEvents() {
+    return(deep_copy(events));
+}
+
+mixed QueryPending() {
+    return(deep_copy(pending));
+}
+
+mixed QueryActive() {
+    return(deep_copy(active));
+}
+
+mapping QueryStats() {
+    return(copy(stats));
+}
+
+int UpdateMe(int flag) {
+    updateme=flag;
+    if (find_call_out(#'process_events)==-1)
+      reset();
+    return flag;
+}
+
diff --git a/p/daemon/finger.c b/p/daemon/finger.c
new file mode 100644
index 0000000..d4b602a
--- /dev/null
+++ b/p/daemon/finger.c
@@ -0,0 +1,443 @@
+/* a finger
+ * Original (c) 14.03.1993 by Taube @Nightfall
+ * Umsetzung fuer Morgengrauen 09.08.1993 by Loco
+
+   Verwendung ausserhalb von Morgengrauen ist gestattet unter folgenden
+   Bedingungen:
+   - Benutzung erfolgt auf eigene Gefahr. Jegliche Verantwortung wird
+     abgelehnt.
+   - Auch in veraenderten oder abgeleiteten Objekten muss ein Hinweis auf
+     die Herkunft erhalten bleiben.
+   Ein Update-Service besteht nicht.
+
+ * 29.Okt 1993 Seherversion.
+ * spaeter auch fuer externen Aufruf verwendbar gemacht
+ * 13.Okt .plan und .project koennen auch in ~loco/plans sein.
+ * 15.Jan 1994 angepasst an neues Speicherformat
+ * 02-05.Dez94 -n, -p
+ *
+ * Gelegentlich minor changes, zuletzt 04.Okt.95
+ */
+
+#pragma strong_types,save_types
+#pragma no_clone, no_shadow
+
+#include <config.h>
+#include <properties.h>
+#include <wizlevels.h>
+#include <new_skills.h>
+#include <userinfo.h>
+#include <config.h>
+
+#define HBINT    2   /* interval between two heart_beats in seconds */
+#define MINIDLE 60   /* minimum idle time in seconds to be stated idle */
+#define TBANISH_EXTRACT 71..  /* Der benoetigte Teil des TBanish-strings */
+
+#define TP this_player()
+#define wiz (local && IS_LEARNER(TP))
+#define seer (local && IS_SEER(TP))
+#define IN ((properties[P_GENDER]==2)?"in":"")
+
+
+#define FLAG_NOPLAN 1
+#define FLAG_LONG 2
+#define FLAG_SPONSOR 4
+#define FLAG_VIS_LOGOUT 8
+#define FLAG_AVATAR 16
+
+
+mapping properties;
+int age,invis,hc_play;
+int filetime;
+mapping relatives;
+
+
+void create()
+{
+  seteuid(getuid());
+  filetime=0;
+}
+
+
+string timediff(int time)
+{
+  string ret;
+
+  ret="";
+  if ( time >= 86400*365 ) {
+    ret+=time/(86400*365)+"a ";
+    time%=(86400*365);
+  }
+  if(time>=86400) {
+    ret+=time/86400+"d ";
+    time%=86400;
+  }
+  if(time<36000) ret+="0";
+  ret+=time/3600+":";
+  time%=3600;
+  if(time<600) ret+="0";
+  ret+=time/60+":";
+  time%=60;
+  if(time<10) ret+="0";
+  ret+=time+"";
+  return ret;
+}
+
+string sponsoring(string name)
+{
+  int i,w;
+  string *s,s2,s3,s4;
+  // Daten einlesen, wenn die daten aelter als 1 Tag sind oder sich
+  // /log/SPONSORS geaendert hat.
+  if ((time() > filetime+86400) ||
+      filetime!=file_time("/log/SPONSOR")) 
+  {
+      relatives=m_allocate(0,2);
+      filetime=file_time("/log/SPONSOR");
+      s=explode(read_file("/log/SPONSOR"),"\n");
+      foreach(string str: s) { 
+	  sscanf(str,"%s: %s macht %s zum Learner.",s2,s3,s4);
+	  if (IS_LEARNER(lower_case(s3)) && IS_LEARNER(lower_case(s4)))
+          {
+             relatives[lower_case(s4),0]=s3;
+             s3=lower_case(s3);
+             s4+=" ("+query_wiz_level(lower_case(s4))+")";
+             if (!relatives[s3,1]) relatives[s3,1]=({s4});
+             else relatives[s3,1]+=({s4});
+          }
+      }  
+  }
+  s2="";
+  if (relatives[name,0])
+    s2+="Vorfahre:     "+relatives[name,0]+"\n";
+  if (relatives[name,1])
+    s2+="Nachfahre(n): "+break_string(implode(relatives[name,1],", "),78,14)[14..];
+  return s2;
+}
+
+varargs string finger_single(string str,int local)
+{
+  mixed  *userinfo;
+  string ip,text,ipnum,filename,away;
+  int wizlevel,playerlevel,idle,pos,flags,last;
+  mixed h,data,tmp;
+  object player,ob;
+
+  /*DEBUG###  tell_object((find_player("loco")||this_object()),"Finger request: '"+str+"'("+local+")\n");/**/
+  str=lower_case(str);
+  text="";
+  away="";
+  hc_play=0;
+  
+  h=explode(str," ");
+  if (sizeof(h)==1) h=explode(str,"+");
+  if (member(h,"-n")>=0) flags=FLAG_NOPLAN;
+  if (member(h,"-p")>=0) flags=0;
+  if (member(h,"-l")>=0) flags|=FLAG_LONG;
+  if (member(h,"-s")>=0) flags|=FLAG_SPONSOR;
+  if (member(h,"-v")>=0) flags|=FLAG_VIS_LOGOUT;
+  if (member(h,"-a")>=0) flags|=FLAG_AVATAR;
+
+  h=(h-({"-n","-p","-l","-s", "-v","-a"}));
+  if (!sizeof(h)) {
+    text="Du solltest schon sagen, wer Dich interessiert!\n";
+    if (local) return write(text),0;
+    else return text;
+  }
+  str=h[0];
+  if (!sizeof(str) || str[0]<'a' || str[0]>'z') {
+    text="Also, so ("+str+") heisst bestimmt kein Spieler hier!\n";
+    if (local) return write(text),0;
+    else return text;
+  }
+
+  /* does this player exist? */
+  str=old_explode(str,".")[0];
+  userinfo=MASTER->get_userinfo(str);
+  player=find_player(str)||find_netdead(str);
+
+  if( (!pointerp(userinfo) || userinfo[USER_LEVEL+1]==-1)
+	&& !player) {
+    text="Hmm... diesen Namen gibt es im "MUDNAME" nicht.\n";
+    if (tmp="/secure/master"->QueryBanished(str)){
+      text="Hoppla - dieser Name ist reserviert oder gesperrt (\"gebanisht\")!\n";
+      if ( tmp != "Dieser Name ist gesperrt." )
+          text += "Grund fuer die Sperrung: " + tmp +
+              (member( ({'!','?','.'}), tmp[<1] ) != -1 ? "" : ".") + "\n";
+    }
+    if (local) return write(text),0;
+    else return text;
+  }
+
+  if (player) {
+    hc_play=player->query_hc_play();
+    properties=player->QueryProperties();
+    properties[P_RACE]=player->QueryProp(P_RACE);
+    properties[P_VISIBLE_GUILD]=player->QueryProp(P_VISIBLE_GUILD);
+    properties[P_TITLE]=player->QueryProp(P_TITLE);
+    tmp = player->QueryProp(P_PRESAY);
+    properties[P_PRESAY]=(stringp(tmp) && sizeof(tmp)>1) ? tmp[0..<2] : 0;
+  }
+  else
+    restore_object("/save/"+str[0..0]+"/"+str);
+  if (!properties)
+  {
+    text+="Mist!!! Das Einlesen der Daten klappt nicht wie es soll :-(\n";
+    properties=0;
+    if (!local)
+      return text;
+    write(text);
+    return "";
+  }
+  if ( player && interactive(player) )
+      ipnum = query_ip_number(player);
+  else
+      ipnum = properties[P_CALLED_FROM_IP];
+  // frueher stand in P_CALLED_FROM_IP evtl. auch der Name direkt drin
+  // anstelle der numerischen IP
+  ip=query_ip_name(ipnum)||properties[P_CALLED_FROM_IP];
+  if(player) {
+      if (!interactive(player) || (idle=query_idle(player))<MINIDLE)
+          idle=0;
+      if (!(invis=age=player->QueryProp(P_INVIS)))
+          age=player->QueryProp(P_AGE);
+  } else {
+    if (properties[P_INVIS]) age=properties[P_INVIS];
+    idle=properties[P_LAST_LOGOUT];
+  }
+
+   wizlevel=query_wiz_level(str);
+   if ( (tmp = file_time("/save/"+str[0..0]+"/"+str+".o")) <= 0 )
+       // Hack, um bei ganz "frischen" Spielern (noch kein Savefile vorhanden)
+       // die Ausgabe von 1.1.1970 zu verhindern
+       tmp = time();
+   last=properties[P_LAST_LOGOUT];
+   if ( last <= 0 || (!(flags&FLAG_VIS_LOGOUT) && wiz && wizlevel > 10
+                  && properties[P_INVIS] && tmp - last > 300) )
+       last = tmp;
+
+  /* output routine for all */
+  if(player) {
+    h=player->QueryProp(P_RACE);
+    if(interactive(player) && (wiz || !invis)) {
+      text+=capitalize(str)+" ist anwesend,\n"+
+        "und zwar von: "+
+          (wiz ? (ip+(ipnum?" ("+ipnum+")":"")):"")+
+          (stringp(properties[P_LOCATION]) ? (wiz ? "\n              [" : "")
+           +capitalize(properties[P_LOCATION])+
+           ((properties[P_LOCATION] != country(ip, ipnum))
+            ? " (ueber "+country(ip, ipnum)+")" : "" )
+           : (wiz?" [":"")+country(ip, ipnum))+(wiz ? "]":"")+".\n";
+      if(idle)
+        text+="Passiv seit: "+timediff(idle)+"\n";
+     if (local)
+      text+="Eingeloggt seit: "+dtime(properties[P_LAST_LOGIN])+"\n";
+     if (properties[P_AWAY])
+       away="z.Z. abwesend, Grund  : "+properties[P_AWAY]+"\n";
+    } else
+      text+=capitalize(str)+" ist nicht anwesend.\nZuletzt eingeloggt von: "+
+        (wiz ? (ip+(ipnum&&ipnum!=ip?" ("+ipnum+")":"")):"")+
+        (stringp(properties[P_LOCATION]) ?
+         (wiz ? "\n                        [": "")
+         +capitalize(properties[P_LOCATION])+" (ueber "+country(ip, ipnum)+")":
+         (wiz?" [":"")+country(ip, ipnum))+(wiz ? "]":"")+".\n"+
+        "Zuletzt ausgeloggt: "+dtime(last)+" ("+timediff(time()-last)+").\n";
+  }
+  else {
+    text+=capitalize(str)+" ist nicht anwesend.\nZuletzt eingeloggt von: "+
+        (wiz ? (ip+(ipnum&&ipnum!=ip?" ("+ipnum+")":"")):"")+
+        (stringp(properties[P_LOCATION]) ?
+         (wiz ? "\n                        [": "")
+         +capitalize(properties[P_LOCATION])+" (ueber "+country(ip, ipnum)+")":
+         (wiz?" [":"")+country(ip, ipnum))+(wiz ? "]":"")+".\n"+
+        "Zuletzt ausgeloggt: "+dtime(last)+" ("+timediff(time()-last)+").\n";
+  }
+  text+="Voller Name: "+(((h=properties[P_PRESAY]) && h!="") ? h+" " : "")+
+    capitalize((h=properties[P_NAME]) ? h : str)+" "+
+     ((h=properties[P_TITLE]) ? h :
+    ((mappingp(h=properties["guild_title"]) && (h=(h[properties[P_GUILD]?properties[P_GUILD]:"abenteurer"])) ) ? h : "") )
+       +"\n";
+
+  if (properties[P_GHOST]) text+="Hoppla, ein Geist!\n";
+  if ((flags&FLAG_LONG)&&properties[P_LONG])
+    text+="Beschreibung: \n"+break_string(properties[P_LONG],78,2);
+
+  if(wiz ||
+     (properties[P_SHOWEMAIL]=="alle") ||
+     ( (properties[P_SHOWEMAIL]=="freunde") &&
+       objectp(player) &&
+       this_player() &&
+       (ob=present("\n\bfband",player)) &&
+       pointerp(tmp=ob->QueryProp(P_AUTOLOADOBJ)) &&
+       pointerp(tmp=tmp[1]) &&
+       (member(tmp,getuid(this_player()))!=-1))) tmp = 1;
+  else tmp=0;
+
+
+  if (!(h=properties[P_RACE]) && userinfo && pointerp(userinfo) && sizeof(userinfo)>4 &&
+      stringp(userinfo[4]) && sizeof(h=old_explode(userinfo[4],"/"))>2) {
+    h=capitalize(h[2]);
+    h=(["Human":"Mensch","Dwarf":"Zwerg","Darkelf":"Dunkelelf","Orc":"Ork"])[h] || h;
+  }
+
+  if (!stringp(h)) h="<keine>";
+
+  text+="Rasse: "+h+",  Gilde: "+
+                     ((h=properties[P_VISIBLE_GUILD])?capitalize(h):((h=properties[P_GUILD])?capitalize(h):"Abenteurer"))+
+                     ((h=properties[P_VISIBLE_SUBGUILD_TITLE])?" ("+capitalize(h)+")":((h=properties[P_SUBGUILD_TITLE])?" ("+capitalize(h)+")":""))+
+                     ",  Geschlecht: "+({"neutral ?!","maennlich","weiblich","<verdammt seltsam>"})[properties[P_GENDER]]+"\n"+
+                      (seer ? "Alter: "+timediff(age*HBINT)+",   " : "")+
+                      (wizlevel>=10?"Magierlevel: "+wizlevel+
+          (wizlevel>=GOD_LVL?" (Mudgott)":str=="boing"?" (Mudgott a.D.)":str=="muadib"?" (Apostolischer Visitator)":wizlevel>=ARCH_LVL?" (Erzmagier)":IS_DEPUTY(str)?" (Hilfssheriff)":wizlevel>=ELDER_LVL?" (Weiser)":wizlevel>=LORD_LVL?" (Regionsmagier)":wizlevel>=SPECIAL_LVL?" (Hilfsmagier)":wizlevel>=DOMAINMEMBER_LVL?" (Regionsmitarbeiter)":wizlevel>WIZARD_LVL?" (Vollmagier)":" (Lehrling)"):
+                       ("Spielerlevel: "+properties[P_LEVEL]+( wizlevel ? " (Seher"+IN+")" : "" )+
+                        (((h=properties[P_GUILD_LEVEL]) && h=h[properties[P_GUILD]]) ?
+                         (",   Gildenlevel: "+h) : "" )
+                        ))+((userinfo&&pointerp(userinfo))?
+                        (sprintf("\nDatum des ersten Login: %s",
+                                 (userinfo[5] > 0) ? dtime(userinfo[5]) : "vor dem 10. Nov 1995")):"")+
+                         (tmp ? ("\nE-Mail-Adresse: "+((h=properties[P_MAILADDR]) ? h : "keine")+"\n") : "\n");
+
+  if (properties[P_HOMEPAGE])
+    text+="Homepage: "+properties[P_HOMEPAGE]+"\n";
+
+  if (stringp(data=properties[P_MESSENGER])) {
+    text+=sprintf("Messenger: %s", data);
+    if (intp(data=properties[P_ICQ])) {
+      if (data<0 && IS_WIZARD(this_player())) data*=-1;
+      if (data>0) text += sprintf(", ICQ: %d", data);
+    }
+    text+="\n";
+  } else 
+    if (intp(data=properties[P_ICQ])) {
+      if (data<0 && IS_WIZARD(this_player()))
+        data*=-1;
+      if (data>0)
+        text+=sprintf("ICQ: %d\n",data);
+    }
+
+  if (properties[P_MARRIED])
+    text+="Verheiratet mit: "+capitalize(properties[P_MARRIED])+"\n";
+
+  if ( pointerp(properties[P_SIBLINGS]) )
+      text += ({ "Es", "Er", "Sie" })[properties[P_GENDER]] + " ist Bluts" +
+          ({ "verwandt mit ", "bruder von ", "schwester von " })
+          [properties[P_GENDER]] +
+          CountUp(properties[P_SIBLINGS]) + ".\n";
+
+  text+=away;
+  
+  if(MASTER->check_late_player(str))
+  {
+    text+=capitalize(str)+" hat uns leider fuer immer verlassen.\n";
+  }
+  else
+  {
+    if (h=MASTER->QueryTBanished(str))
+      text+=capitalize(str)+" will fruehestens "+h[TBANISH_EXTRACT];
+  }
+  
+  if (h=properties[P_TESTPLAYER])
+  {
+    text+=capitalize(str)+" ist Testspieler"+IN;
+    if (stringp(h)) text+=" ("+h+")";
+    text+=".\n";
+  }
+  if ( h=properties[P_SECOND])
+  {
+    if (IS_WIZARD(this_player())) {
+       text+=capitalize(str)+" ist";
+       switch((int)properties[P_SECOND_MARK]) {
+         case -1: text+=" unsichtbar markierte"
+                       +((int)properties[P_GENDER]!=FEMALE ? "r": "");
+                  break;
+         case  0: text+=" nicht namentlich markierte"
+                       +((int)properties[P_GENDER]!=FEMALE ? "r": "");
+                  break;
+         default:
+       }
+       text+=" Zweitspieler"+IN;
+       if (stringp(h))
+         text+=" ("+capitalize(h)+")";
+       text+=".\n";
+    }
+    else if ((int)properties[P_SECOND_MARK]>0)
+    {
+      text+=capitalize(str)+" ist Zweitspieler"+IN;
+      if (stringp(h))
+        text+=" ("+capitalize(h)+")";
+      text+=".\n";
+    }
+    else if ((int)properties[P_SECOND_MARK]>-1)
+      text+=capitalize(str)+" ist Zweitspieler"+IN+".\n";
+  }
+  if (properties[P_DEADS])
+  {
+    text+="Bisher bereits "+properties[P_DEADS]+" mal gestorben\n";
+    // Bezieht sich nur auf den Zeitraum ab dem 30.11.'94
+  }
+  if(hc_play==1)
+  {
+    text+=capitalize(str)+" ist sehr mutig.\n";
+  }
+  if(hc_play>1)
+  {
+    text+=capitalize(str)+" ist am "+dtime(hc_play)+" in das Nirvana eingegangen.\n";
+  }
+
+  if (/*wiz && */userinfo) {
+    data=userinfo[3];
+    if (sizeof(data))
+      text+="Regionsmagier"+IN+" von     : "+implode(map(data,#'capitalize),", ")+".\n";
+    data="/secure/master"->get_domain_homes(str);
+data=filter(data-({"erzmagier"}),#'stringp); 
+    if ((wizlevel>=DOMAINMEMBER_LVL) && (sizeof(data)))
+      text+="Regionsmitarbeiter"+IN+" von: "+implode(map(data,#'capitalize),", ")+".\n";  /* #' */ 
+    }
+
+  if (userinfo) {
+    data=userinfo[8];
+    if (sizeof(data))
+      text += "Gildenmagier"+IN+" von      : "+implode(map(data, #'capitalize), ", ")+".\n";   /* #' */
+  }
+
+  // ggf. Avatar-URI mit ausgeben.
+  if (flags & FLAG_AVATAR
+      && stringp(properties[P_AVATAR_URI]))
+    text += "Avatar-URI: " + properties[P_AVATAR_URI] + "\n";
+
+  if (flags & FLAG_SPONSOR)
+    text+=sponsoring(str);
+
+  filename="/players/"+str+"/.project";
+  if(file_size(filename)>=0)
+    text+="Projekt: "+explode(read_file(filename), "\n")[0]+"\n";
+  else {
+    filename="/p/service/loco/plans/"+str+".project";
+    if(file_size(filename)>=0)
+      text+="Projekt: "+explode(read_file(filename), "\n")[0]+"\n";
+  }
+  if (seer && !(flags&FLAG_NOPLAN)) {
+    filename="/players/"+str+"/.plan";
+    if(file_size(filename)>=0)
+      text+="Plan:\n"+read_file(filename);
+    else {
+      filename="/p/service/loco/plans/"+str+".plan";
+      if(file_size(filename)>=0)
+        text+="Plan:\n"+read_file(filename);
+//      else
+//      text+="Keine .plan-Datei.\n";
+    }
+  }
+  properties=0;
+  if (!local)
+    return text;
+  this_player()->More(text);
+  return "";
+}
+
+string Finger(string str)
+{
+  if(!str || str=="") { notify_fail("Wen denn?\n"); return 0; }
+  else
+    return finger_single(str,1);
+}
diff --git a/p/daemon/fishmaster.c b/p/daemon/fishmaster.c
new file mode 100644
index 0000000..3997584
--- /dev/null
+++ b/p/daemon/fishmaster.c
@@ -0,0 +1,165 @@
+/*Fisch-Master, (c) Vanion@MG, 26.05.02
+
+  Fische die im MG geangelt werden koennen, sollen nicht als endlose
+  Geldquelle dienen. Daher habe ich einen Master gebaut, der sich merkt
+  Welcher Spieler wieviel Fisch geangelt hat. Je mehr Fisch der Spieler
+  faengt, desto billiger wird der Fisch.
+
+  Schnittstelle:
+
+  Der Master hat abgesehen von Standard-Funktionen nur eine Schnittstelle
+
+  public int PriceOfFish(object player, object fish)
+
+  Der erste Parameter ist der Spieler, der den Fisch gecloned hat, der
+  zweite ist der Fisch, den der Spieler gefangen hat. Der Fisch muss
+  hierbei von Fraggles Standardfisch erben.
+
+  Der Preis des Fisches faellt linear je naeher der Spieler an die
+  Maximal-Grenze fuer Fisch kommt (Diese ist durch die Konstante MAX_FISH
+  festgelegt.) Der Master merkt sich ueber einen Zeitraum von FORGET_IT
+  Sekunden, dass der Spieler diesen Fisch gefangen hat. Ist der Zeitraum
+  vergangen, geht der Fisch nicht mehr in die Rechnung ein.
+ */
+
+#pragma strong_types, save_types, rtt_checks
+#pragma no_clone, no_shadow, no_inherit
+
+#define SAVE_FILE __DIR__+"save/fishes"
+
+#define FORGET_IT 1728000 // Nach 20 Tagen
+#define MAX_FISH  20000   // Wenn Spieler Max-Fish geholt hat, hat der
+                          // Fisch keinen Wert mehr. (Einheit Gramm)
+
+#include <properties.h>
+
+// Hier wird gespeichert, welcher Spieler wieviel Fisch gefangen hat.
+mapping fishes = m_allocate(0,1);
+static int save_me_soon;
+
+protected void create() {
+  // Ohne das darf dieses Objekt hier nicht schreiben.
+  seteuid(getuid());
+  // Globale Variablen einlesen/initialisieren.
+  restore_object(SAVE_FILE);
+  set_next_reset(4*3600);
+}
+
+static void CleanupData() {
+  int expire_date = time()-FORGET_IT;
+  // Ueber alle Spielernamen laufen.
+  foreach(string pl, mixed *fishlist: fishes) {
+    // Ueber alle Fischdaten aller Spieler laufen.
+    foreach(int *fish: fishlist) {
+      if (fish[0]<expire_date) {
+        fishlist-=({fish});
+        save_me_soon=1;
+      }
+    }
+    if ( !sizeof(fishlist) )
+      fishes = m_delete(fishes, pl);
+    else
+      fishes += ([pl:fishlist]);
+  }
+}
+
+//ZZ: man koennte hier ggf. das neue Gewicht bestimmen statt es bei jedem
+//ZZ:Abruf zu tun.
+// Fuegt einen neuen Fisch in die Liste mit Fischen ein
+static int AddFish(string player_id, int weight) {
+  // Fisch hinzufuegen
+  if ( member(fishes, player_id) )
+    fishes[player_id] +=  ({({ time(),weight})});
+  else 
+    fishes += ([ player_id: ({ ({time(),weight}) }) ]);
+  return 1;
+}
+
+// Hier wird das Gewicht des bereits gefangenen Fisches umgewandelt
+// in den Preis-Prozentsatz, den der Fisch noch kostet.
+static int WeightToPrice(int weight) {
+  if (weight >= MAX_FISH) return 0;
+  if (weight <= 0       ) return 100;
+  // Eine lineare Kurve. Je mehr Fisch geholt, desto billiger
+  // wird der. // Arathorn: Spannend, so eine lineare Kurve. :-)
+  return 100-(weight*100/MAX_FISH);
+}
+
+// Gibt das Gesamtgewicht des gefangenen Fisches zum Spieler zurueck
+static int QueryWeight(string player_id) {
+  int weight;
+  mixed list = fishes[player_id];
+
+  // Erstmal werden ungueltige Fische aus der Liste entfernt
+  //ZZ: ich finde, das muss nicht unbedingt bei jeden Abruf getan werden
+  //ZZ::und nicht fuer alle Spieler. reicht nicht einfach alle 4h? 
+  CleanupData();
+
+  // Zusammenzaehlen der Einzelgewichte der gueltigen Fische.
+  foreach(int *fish : fishes[player_id]) {
+    weight += fish[1];
+  }
+  return weight;
+}
+
+// Wenn ein Spieler einen neuen Fisch bekommt, wird der Preis fuer den
+// Fisch bestimmt. Dieser resultiert aus der Menge (Gewicht) des Fisch,
+// die der Spieler in letzter Zeit schon gefangen hat. Zurueckgegeben
+// wird der Preis in Teilen von 100.
+public int PriceOfFish(object player, object fish) {
+  string player_id;
+  int price_of_new_fish;
+
+  // Sind Spieler und Fisch uebergeben?
+  if ( !player || !fish ) return 0;
+
+  // Spieler-ID finden.
+  player_id = getuuid(player);
+
+  // Ist player ein echter Spieler?
+  // NPC clonen sich Fische, die 100% kosten.
+  if (member(users(),player)==-1) return 100;
+
+  // Is der Fisch ein Fisch von Fraggle?
+  if (member(inherit_list(fish), __PATH__(1)+"/fish.c")==-1)
+    return 0;
+
+  // Feststellen wieviel Fisch ein Spieler gefangen hat, und
+  // daraus den Preis berechnen.
+  price_of_new_fish = WeightToPrice(QueryWeight(player_id));
+
+  // Diesen Fisch in die Datenbank eintragen
+  AddFish(player_id, fish->QueryProp(P_WEIGHT));
+
+  // Ich bin neugierig.
+  /*DEBUG(player->Name()+" hat einen Fisch mit "+fish->QueryProp(P_WEIGHT)+
+        " g gefangen. Der Wert liegt bei "+price_of_new_fish+"%");*/
+
+  // Die Aenderung soll persistent sein.
+  save_me_soon=1;
+
+  // Rueckgabe des Fischpreises.
+  return price_of_new_fish;
+}
+
+// Speichern der globalen Variablen
+static void save() {
+  save_object(SAVE_FILE);
+  return;
+}
+
+// Da nichts geerbt wird, muss das destruct() selbst gemacht werden
+// Wir speichern auf jeden Fall.
+varargs int remove(int silent) {
+  save();
+  destruct(this_object());
+  return 1;
+}
+
+// Daten aufraeumen und ggf. wegspeichern
+void reset() {
+  CleanupData();
+  if ( save_me_soon )
+    save();
+  set_next_reset(4*3600); // alle 4 Stunden reicht.
+}
diff --git a/p/daemon/iplookup.c b/p/daemon/iplookup.c
new file mode 100644
index 0000000..7608d8f
--- /dev/null
+++ b/p/daemon/iplookup.c
@@ -0,0 +1,225 @@
+/*
+ * Mudlibseite des iplookup systems, mit dem aus numerischen IP Adressen
+ * der Hostname und der Ort, an dem sich der Rechner befinden berechnet wird.
+ * Ergebnisse werden gecachet.
+ *
+ * (c)2010 rumata @ morgengrauen
+ */
+
+#pragma strict_types,save_types
+#pragma no_clone,no_shadow
+
+// Format der ipmap:
+// Key: numerische ip als string (ohne fuehrende nullen)
+// Data:
+//  [ip,0] Hostname, der vom externen Server geliefert wurde.
+//  [ip,1] Locationinformation, die vom externen Server kommt.
+//    dieser sollte den Ort, oder falls es nicht besser geht, das Land
+//    im Klartext liefern.
+//  [ip,2] Zeitstempel, bei dessen erreichen der Wert geloescht werden
+//    soll.
+//  [ip,3] Status der Anfrage
+
+private mapping ipmap;
+#define IPMAP_HOST   0
+#define IPMAP_CITY   1
+#define IPMAP_EXPIRE 2
+#define IPMAP_STATUS 3
+
+// externer server, der der lokationdaten berechnet
+#define IPLOOKUP_HOST "127.0.0.1"
+#define IPLOOKUP_PORT 8711
+#define IPLOOKUP_SAVE "/p/daemon/save/iplookup"
+
+// moegliche statuswerte (platz zum zaehlen der anfragen gelassen)
+#define STATUS_QUERYING      1
+#define STATUS_AUTHORITATIVE 128
+
+// so lange werden die daten im cache gehalten
+#define CACHE_TIME    86400  /* 1 day */
+
+// Ab dieser Groesse betrache ich den cache als "gross genug",
+// um einen cache update in etappen zu rechtfertigen.
+#define LARGE_CACHE_LIMIT 2000
+
+// so lange sind wir bereit auf die antwort einer anfrage beim server zu warten
+#define QUERY_TIME    60     /* 1 min */
+
+#include <config.h>
+#if MUDNAME == "MorgenGrauen"
+#define LOG(x) log_file("rumata/iplookup.log",strftime("%Y-%m-%d %H:%M:%S: ")+(x)+"\n")
+#else
+#define LOG(x)
+#endif
+
+// PROTOTYPES
+
+public void create();
+public void reset();
+static int active_entry( string key, mixed* data );
+static void expire_cache_small();
+static void expire_cache_large( string* keys );
+public void update( string udp_reply );
+
+// IMPLEMENTATION
+
+public void create() {
+	seteuid(getuid());
+	restore_object( IPLOOKUP_SAVE );
+
+	if( !mappingp(ipmap) || widthof(ipmap)!=4 ) {
+		ipmap = m_allocate( 0, 4 );
+		LOG("restore failed, creating new ipmap");
+	} else {
+	  LOG(sprintf("created (%d entries)",sizeof(ipmap)));
+	}
+}
+
+public void reset() {
+	if( sizeof(ipmap) < LARGE_CACHE_LIMIT ) {
+		expire_cache_small();
+	} else {
+		LOG(sprintf("reset %d ->",sizeof(ipmap)));
+		expire_cache_large( m_indices(ipmap) );
+	}
+  set_next_reset(10800);
+}
+
+static int active_entry( string key, mixed* data ) {
+	return time() < data[IPMAP_EXPIRE];
+}
+
+// Den cache auf einfache weise aufraeumen.
+static  void expire_cache_small() {
+	int s = sizeof(ipmap);
+	ipmap = filter( ipmap, #'active_entry );
+	save_object( IPLOOKUP_SAVE );
+	LOG(sprintf("reset (%d -> %d)",s,sizeof(ipmap)));
+}
+
+// Kompliziertere routine, die den cache etappenweise abarbeitet.
+static	void expire_cache_large( string* keys ) {
+	if( !pointerp(keys) ) return;
+	int next=0;
+	foreach( string key: keys ) {
+		if( get_eval_cost() < 100000 ) break;
+		if( ipmap[key,IPMAP_EXPIRE] < time() ) {
+			m_delete( ipmap, key );
+		}
+		next++;
+	}
+	LOG(sprintf("checking %d of %d",next,sizeof(keys)));
+	if( next<sizeof(keys) ) {
+		call_out( #'expire_cache_large, 6, keys[next..] );
+	} else {
+		save_object( IPLOOKUP_SAVE );
+		LOG(sprintf("reset -> %d (done)",sizeof(ipmap)));
+	}
+}
+
+#define SEARCHING "<auf der Suche...>"
+
+/* Erzeugt einen temporaeren Eintrag, der fuer eine Suche steht.
+ * Wenn die Antwort kommt, wird der Eintrag mit den entgueltigen
+ * Daten ueberschrieben.
+ */
+static void make_request( string ipnum ) {
+	send_udp( IPLOOKUP_HOST, IPLOOKUP_PORT, ipnum );
+	ipmap += ([ ipnum : ipnum ; SEARCHING ;
+		time()+QUERY_TIME ; STATUS_QUERYING
+	]);
+}
+
+/* Liefert zu einer gegebenen ipnum den Ort.
+ * diese Funktion wird von simul_efun aufgerufen.
+ * @param ipnum eine numerische ip-adresse oder ein interactive
+ * @return den Ort (oder das Land) in dem sich die ip-adresse
+ * laut externem Server befindet.
+ */
+public string country( mixed ipnum ) {
+	string host,city;
+	int expire,state;
+
+	if( objectp(ipnum) ) {
+		ipnum = query_ip_number(ipnum);
+	}
+	if( !stringp(ipnum) ) {
+		return "<undefined>";
+	}
+	if( !m_contains( &host, &city, &expire, &state, ipmap, ipnum ) ) {
+		make_request( ipnum );
+		return SEARCHING;
+	}
+
+	return city;
+}
+
+/* Liefert zu einer gegebenen ipnum den Hostnamen.
+ * diese Funktion wird von simul_efun aufgerufen.
+ * @param ipnum eine numerische ip-adresse oder ein interactive
+ * @return den Hostnamen der zu der angegebenen ip-adresse gehoert.
+ * wenn der hostname nicht bekannt ist, wird die ipadresse zurueckgegeben.
+ */
+public string host( mixed ipnum ) {
+	string host,city;
+	int expire,state;
+
+	if( objectp(ipnum) ) {
+		ipnum = query_ip_number(ipnum);
+	}
+	if( !stringp(ipnum) ) {
+		return "<undefined>";
+	}
+	if( !m_contains( &host, &city, &expire, &state, ipmap, ipnum ) ) {
+		make_request( ipnum );
+		return ipnum;
+	}
+
+	return host;
+}
+
+/* wird vom master aufgerufen, wenn eine antwort vom externen
+ * iplookup dienst eintrudelt.
+ */
+public void update( string udp_reply ) {
+	string* reply;
+	if( previous_object()!=master() ) return;
+	reply = explode(udp_reply,"\n");
+	if( sizeof(reply)<4 ) return;
+
+	if( reply[3] == "<unknown>" ) reply[3] = reply[1];
+	if( reply[2] == "<unknown>" ) reply[2] = "irgendwoher";
+	ipmap += ([ reply[1] : reply[3] ; reply[2] ; time()+CACHE_TIME ;
+		STATUS_AUTHORITATIVE ]);
+	//save_object( IPLOOKUP_SAVE );
+}
+
+int remove(int silent) {
+  save_object( IPLOOKUP_SAVE );
+  destruct(this_object());
+  return 1;
+}
+
+// DEBUGGING CODE
+
+#if 0
+public void dump( string key, mixed* data ) {
+	printf( "%s: %s (%s) s=%d bis %d\n", key, data[0], data[1],
+		data[3], data[2] );
+}
+
+public void load(string s) {
+	restore_object(s);
+	if( !mappingp(ipmap) || widthof(ipmap)!=4 ) {
+		ipmap = m_allocate( 0, 4 );
+		LOG("restore failed, creating new ipmap");
+	} else {
+	  LOG(sprintf("created (%d entries)",sizeof(ipmap)));
+	}
+}
+
+public void debug() {
+	map( ipmap, #'dump );
+	printf( "= %d Eintraege\n", sizeof(ipmap) );
+}
+#endif
diff --git a/p/daemon/lag-o-daemon.c b/p/daemon/lag-o-daemon.c
new file mode 100644
index 0000000..6d23860
--- /dev/null
+++ b/p/daemon/lag-o-daemon.c
@@ -0,0 +1,272 @@
+#pragma strong_types,save_types,rtt_checks
+#pragma no_clone
+#pragma no_shadow
+#pragma no_inherit
+#pragma pedantic
+
+#include <driver_info.h>
+
+#ifndef DEBUG
+#define DEBUG(x)        if (find_player("zesstra"))\
+        tell_object(find_player("zesstra"),"LAG: "+x+"\n")
+#endif
+
+#define SAVEFILE __DIR__+"save/lag-o-daemon"
+#define LOG(x) log_file("LAG",x,200000);
+#define LOGMAX(x) log_file("LAGMAX",x,300000);
+
+// Anzahl an zu merkenden Werten (1800==3600s)
+#define MESSWERTE 1800
+
+nosave float hbstat, obstat;
+nosave mapping lastwarning=([]);
+nosave int *lasttime = utime();
+nosave int lasthbcount = absolute_hb_count();
+float *lagdata = allocate(MESSWERTE,0.0);
+// Lags fuer die letzten   2s (1 HB), 20s (10 HB), 60s (30 HB),
+//                       300s (150 HB), 900s (450 HB), 3600s (1800 HB)
+float *lag = allocate(6,0.0);
+int *hbdata = allocate(MESSWERTE);
+// Lags fuer die letzten   2s (1 HB), 20s (10 HB), 60s (30 HB),
+//                       300s (150 HB), 900s (450 HB), 3600s (1800 HB)
+int *hbs = allocate(6);
+int *codata = allocate(MESSWERTE);
+// Lags fuer die letzten   2s (1 HB), 20s (10 HB), 60s (30 HB),
+//                       300s (150 HB), 900s (450 HB), 3600s (1800 HB)
+int *callouts = allocate(6);
+
+void create() {
+  seteuid(getuid());
+  // Bei nicht einlesbaren Savefile und nach Reboot Werte neu initialisieren.
+  // Denn dann ist die Differenz der HBs bedeutungslos. Erkennung ploetzlich
+  // kleinere HB-Zahl.
+  if (!restore_object(SAVEFILE)
+      || efun::driver_info(DI_NUM_HEARTBEAT_TOTAL_CYCLES) < lasthbcount)
+  {
+    lagdata = allocate(MESSWERTE,0.0);
+    lag = allocate(6,0.0);
+    hbdata = allocate(MESSWERTE);
+    hbs = allocate(6);
+    codata = allocate(MESSWERTE);
+    callouts = allocate(6);
+  }
+  set_heart_beat(1);
+}
+
+int remove(int silent) {
+  save_object(SAVEFILE);
+  destruct(this_object());
+  return 1;
+}
+
+// liefert Mittelwertarray zurueck
+//TODO: <int|string>* results 
+private mixed calc_averages(mixed data, mixed results, int index, float scale) { 
+  float tmp;
+  int t;
+
+  if (!scale) scale=1.0;
+  
+  // 2s
+  results[0] = to_float(data[index]) * scale;
+
+  // alle 10s / 5 HBs den 20s-Wert neu berechnen
+  if ( index%5 == 1) {
+    if (index<9) {
+      foreach(int i: index-4+MESSWERTE .. MESSWERTE-1) {
+        tmp+=data[i];
+        t++;
+      }
+      foreach(int i: 0 .. index) {
+        tmp+=data[i];
+        t++;
+      }
+    }
+    else {
+      foreach(int i: index-9 .. index) {
+        tmp+=data[i];
+        t++;
+      }
+    }
+    results[1]=tmp/to_float(t) * scale;
+  }
+
+  // alle 10s / 5 HBs den 60s-Wert neu berechnen, aber in anderem HB als den
+  // 10s-Wert
+  if ( index%5 == 3) {
+    tmp=0.0;
+    t=0;
+    if (index<29) {
+      foreach(int i: index-14+MESSWERTE .. MESSWERTE-1) {
+        tmp+=data[i];
+        t++;
+      }
+      foreach(int i: 0 .. index) {
+        tmp+=data[i];
+        t++;
+      }
+    }
+    else {
+      foreach(int i: index-29 .. index) {
+        tmp+=data[i];
+        t++;
+      }
+    }
+    results[2]=tmp/to_float(t) * scale;
+  }
+
+  // 300s
+  // alle 40s / 20 HBs den 300s-Wert neu berechnen.
+  if ( index%20 == 12) {
+    tmp=0.0;
+    t=0;
+    if (index<149) {
+      foreach(int i: index-74+MESSWERTE .. MESSWERTE-1) {
+        tmp+=data[i];
+        t++;
+      }
+      foreach(int i: 0 .. index) {
+        tmp+=data[i];
+        t++;
+      }
+    }
+    else {
+      foreach(int i: index-149 .. index) {
+        tmp+=data[i];
+        t++;
+      }
+    }
+    results[3]=tmp/to_float(t) * scale;
+  }
+
+
+  // 900s
+  // alle 80s / 40 HBs den 900s-Wert neu berechnen.
+  if ( index%40 == 31) {
+    tmp=0.0;
+    t=0;
+    if (index<449) {
+      foreach(int i: index-224+MESSWERTE .. MESSWERTE-1) {
+        tmp+=data[i];
+        t++;
+      }
+      foreach(int i: 0 .. index) {
+        tmp+=data[i];
+        t++;
+      }
+    }
+    else {
+      foreach(int i: index-449 .. index) {
+        tmp+=data[i];
+        t++;
+      }
+    }
+    results[4]=tmp/to_float(t) * scale;
+  }
+
+  // 3600s
+  // alle 300s / 150 HBs den 3600s-Wert neu berechnen.
+  if ( index%150 == 128) {
+    tmp=0.0;
+    t=0;
+    if (index<1799) {
+      foreach(int i: index-899+MESSWERTE .. MESSWERTE-1) {
+        tmp+=data[i];
+        t++;
+      }
+      foreach(int i: 0 .. index) {
+        tmp+=data[i];
+        t++;
+      }
+    }
+    else {
+      foreach(int i: index-1799 .. index) {
+        tmp+=data[i];
+        t++;
+      }
+    }
+    results[5]=tmp/to_float(t) * scale;
+  }
+  return results;
+}
+
+protected void heart_beat() {
+  int hbcount=efun::driver_info(DI_NUM_HEARTBEAT_TOTAL_CYCLES);
+  // per Modulo Index auf 0 - 449 begrenzen
+  int index=hbcount%MESSWERTE;
+
+  // mass fuer das lag ist das Verhaeltnis von vergangener Sollzeit (Differenz
+  // HB-Counts * 2) und vergangener Istzeit (Differenz von time()). Und das
+  // ganze wird dann als Abweichung von 1 gespeichert (0, wenn kein Lag)
+  int *nowtime = utime();
+  float timediff = to_float((nowtime[0]-lasttime[0])) 
+                 + to_float(nowtime[1]-lasttime[1])/1000000.0;
+
+//  lagdata[index] = 1.0 -
+//                   (to_float((hbcount - lasthbcount) * __HEART_BEAT_INTERVAL__)
+//                     / timediff);
+//  lagdata[index] = (timediff -
+//    to_float((hbcount - lasthbcount) * __HEART_BEAT_INTERVAL__)) /
+//    to_float((hbcount - lasthbcount) * __HEART_BEAT_INTERVAL__);
+  lagdata[index] = abs(to_float((hbcount - lasthbcount) * __HEART_BEAT_INTERVAL__)
+                    - timediff)  /
+                   to_float((hbcount - lasthbcount) * __HEART_BEAT_INTERVAL__);
+
+
+  hbdata[index] = efun::driver_info(DI_NUM_HEARTBEATS_LAST_PROCESSED);
+  codata[index] = efun::driver_info(DI_NUM_CALLOUTS);
+
+  /*                     
+  DEBUG(sprintf("Index: %d: %d/%.6f => %:6f /1\n",index,
+        (hbcount - lasthbcount)*2,
+        timediff, lagdata[index]));
+  */      
+  lasthbcount=hbcount;
+  lasttime=nowtime;
+ 
+  lag = calc_averages(lagdata, lag, index, 100.0);
+  // Multiplizieren mit 100
+  //DEBUG(sprintf("%O\n",lag));
+  //lag = map(lag, function float (float f) {return f*100.0;} );
+  //DEBUG(sprintf("%O\n",lag));
+  hbs = map(calc_averages(hbdata, hbs, index,1.0),#'to_int);
+  callouts = map(calc_averages(codata, callouts, index,1.0),#'to_int);
+
+  // und ausserdem mal etwas loggen fuer den Moment
+  if (lag[0] > 1000 || callouts[0] > 1000)
+  {
+  LOGMAX(sprintf("%s, %d, %d, %:6f, %:6f, %:6f, %:6f, %:6f, %:6f, "
+              "%d, %d, %d, %d, %d, %d, "
+              "%d, %d, %d, %d, %d, %d, \n",
+          strftime("%y%m%d-%H%M%S"),time(),hbcount,
+          lag[0],lag[1],lag[2],lag[3],lag[4],lag[5],
+          hbs[0],hbs[1],hbs[2],hbs[3],hbs[4],hbs[5],
+          callouts[0],callouts[1],callouts[2],callouts[3],callouts[4],
+          callouts[5]));
+  }
+  else if (hbcount%80 == 0 || lag[0] > 10)
+  {
+  LOG(sprintf("%s, %d, %d, %:6f, %:6f, %:6f, %:6f, %:6f, %:6f, "
+              "%d, %d, %d, %d, %d, %d, "
+              "%d, %d, %d, %d, %d, %d, \n",
+          strftime("%y%m%d-%H%M%S"),time(),hbcount,
+          lag[0],lag[1],lag[2],lag[3],lag[4],lag[5],
+          hbs[0],hbs[1],hbs[2],hbs[3],hbs[4],hbs[5],
+          callouts[0],callouts[1],callouts[2],callouts[3],callouts[4],
+          callouts[5]));
+  }
+}
+
+float *read_lag_data() {
+  return ({lag[2],lag[4],lag[5]});
+}
+float *read_ext_lag_data() {
+  return copy(lag);
+}
+int *read_hb_data() {
+  return copy(hbs);
+}
+int *read_co_data() {
+  return copy(callouts);
+}
+
diff --git a/p/daemon/mand.c b/p/daemon/mand.c
new file mode 100644
index 0000000..e80c50e
--- /dev/null
+++ b/p/daemon/mand.c
@@ -0,0 +1,169 @@
+//
+// mand.c - Manpage Daemon
+//
+// Erweitert und umgearbeitet fuer die Anbindung durch
+// die Magiershell
+//
+// von Mandragon@MG
+//
+// Basiert auf der Arbeit von Hadra,Mupfel und Tyron
+// (alle @Anderland)
+//
+
+#define DEBUGGER "mandragon"
+
+#define DEBUG(x) if (find_player(DEBUGGER)) \
+                         tell_object(find_player(DEBUGGER),x+"\n")
+
+
+#pragma strict_types,save_types
+#pragma no_clone,no_shadow
+
+#include <daemon/mand.h>
+
+private nosave mapping pages;
+private nosave string *keys;
+private nosave mapping pages_case;
+private nosave string *keys_case;
+
+static string glob2regexp(string str)
+{
+  str=regreplace(str,"([\\.\\^\\$\\[\\]\\(\\)])","\\\\\\1",1);
+  str=regreplace(str,"\\*",".*",1);
+  return regreplace("^"+str+"$","?",".",1);
+}
+
+void cache_entry(string entry, string dir, string file)
+{
+  string *data;
+  int j;
+
+  if (m_contains(&data,pages,lower_case(entry)))
+  {
+    j=sizeof(data);
+    while(j--) data[--j]=data[j];
+    pages[lower_case(entry)]=data+({ dir+"/"+file, dir+"/"+file});
+  }
+  else
+  {
+    pages[lower_case(entry)]=({entry,dir+"/"+file});
+    keys+=({lower_case(entry)});
+  }
+  if (m_contains(&data,pages_case,entry))
+  {
+    j=sizeof(data);
+    while(j--) data[--j]=data[j];
+    pages_case[entry]=data+({ dir+"/"+file, dir+"/"+file});
+  }
+  else
+  {
+    pages_case[entry]=({entry,dir+"/"+file});
+    keys_case+=({entry});
+  }
+  return;
+}
+
+static void load_synonyms(string dir)
+{
+  string filedata,*syn;
+
+  if (file_size(MAND_DOCDIR+dir+"/.synonym")<=0) return;
+  if (!(filedata=read_file(MAND_DOCDIR+dir+"/.synonym"))) return;
+  syn=regexplode(filedata, "([ \t][ \t]*|\n)")-({""});
+  syn-=regexp(syn,"^([ \t][ \t]*|\n)$");
+  while(sizeof(syn))
+  {
+    cache_entry(syn[0],dir,syn[1]);
+    syn=syn[2..];
+  }
+  return;
+}
+
+static void cache_directory(string dir)
+{
+  string file;
+  int i,j;
+  mixed *files;
+
+  files = get_dir(MAND_DOCDIR+dir+"/*",3)||({});
+  i=sizeof(files);
+  for (j=0;j<i;)
+  {
+    file=files[j++];
+    if (file[0]=='.')
+    {
+      j++;
+      continue;
+    }
+    if (files[j++]==-2)
+    {
+      cache_directory(dir+"/"+file);
+      continue;
+    }
+    cache_entry(file,dir,file);
+  }
+  load_synonyms(dir);
+}
+
+void update_cache()
+{
+  int i;
+  string *docdirs;
+  mixed *tmp;
+
+  tmp=get_dir(MAND_DOCDIR "*",3);
+  docdirs=({});
+  pages=([]);
+  pages_case=([]);
+  keys_case=({});
+  keys=({});
+  i=sizeof(tmp);
+  while(i--)
+    if (tmp[i]==-2)
+      docdirs+=({ tmp[(i--)-1] });
+  docdirs-=({".",".."});
+  docdirs-=MAND_EXCLUDE;
+  i=sizeof(docdirs);
+  while(i--) cache_directory(docdirs[i]);
+  return;
+}
+
+void create()
+{
+  seteuid(getuid());
+  update_cache();
+  set_next_reset(10080); //Einmal pro Woche langt
+  return;
+}
+
+void reset()
+{
+  update_cache();
+  set_next_reset(10080);
+  return;
+}
+
+//
+// locate(): Manpage im Cache suchen:
+// match = 1 -> glob-matching, match=2 -> regexp-matching
+// Pruefung ob str gueltige regexp erfolgt in der Shell
+//
+
+string *locate(string str,int match)
+{
+  string *matches,*ret,str2;
+  int i;
+
+  if (!match&&member(keys_case,str)!=-1) return pages_case[str];
+  str=lower_case(str);
+  if (match==1) str=glob2regexp(str);
+  if (match) matches=regexp(keys,str);
+  else if (member(keys,str)!=-1) matches=({ str });
+  else return ({});
+
+  ret=({});
+  i=sizeof(matches);
+  while(i--)
+    ret+=pages[matches[i]];
+  return ret;
+}
diff --git a/p/daemon/moneylog.c b/p/daemon/moneylog.c
new file mode 100644
index 0000000..f3a59bf
--- /dev/null
+++ b/p/daemon/moneylog.c
@@ -0,0 +1,246 @@
+// Log-Daemon, um Geldbugs zu finden.
+//
+// Noch experimentell, ich feile noch fleissig an den Werten&Co.
+// Wer mir ohne triftigen Grund daran herumpfuscht, der wird standesrechtlich
+// mit faulen Eiern beworfen. ]:->
+//
+// Tiamak
+
+#pragma strong_types,save_types
+#pragma no_clone,no_shadow
+
+#include <wizlevels.h>
+#include <money.h>
+
+#define LOGNAME "ADDMONEY"
+#define SAVEFILE "/p/daemon/save/moneylog"
+
+#define THRESHOLD_HOURLY 10000 // wieviel Geld darf man an einer Stelle pro
+#define THRESHOLD_DAILY  50000 // Stunde/Tag bekommen, ohne dass geloggt wird?
+
+
+private mapping mon, grenzen;
+private int check, next_reset;
+
+
+public void create()
+{    
+    // wir muessen Savefile schreiben duerfen
+    seteuid(getuid());
+    
+    if ( !restore_object(SAVEFILE) ){
+        mon = m_allocate( 0, 2 );
+        grenzen = m_allocate( 0, 2 );
+        check = 0;
+        next_reset = 3600;
+    }
+
+    if ( next_reset > 3600 )
+        next_reset = 3600;
+
+    if ( next_reset < 60 )
+        next_reset = 60;
+
+    // Auswertung jede Stunde
+    set_next_reset( next_reset );
+    next_reset += time();
+}
+
+
+// Sortierfunktionen
+
+static int _sort_hourly( string fn1, string fn2 )
+{
+    return mon[fn1, 0] > mon[fn2, 0];
+}
+
+
+static int _sort_daily( string fn1, string fn2 )
+{
+    return mon[fn1, 1] > mon[fn2, 1];
+}
+
+
+// im reset() wird die Auswertung vorgenommen
+public void reset()
+{
+    int i;
+    string *files, txt;
+
+    // keinen Aufruf per Hand bitte
+    if ( time() < next_reset )
+        return;
+
+    // Auswertung jede Stunde
+    set_next_reset(3600);
+    next_reset = time() + 3600;
+    check++;
+    txt = "";
+    
+    files = sort_array( m_indices(mon), #'_sort_hourly/*'*/ );
+
+    for ( i = sizeof(files); i--; ){
+        if ( !(grenzen[files[i], 0] < 0) &&
+             (grenzen[files[i], 0] && mon[files[i], 0] > grenzen[files[i], 0])
+             || (!grenzen[files[i], 0] && mon[files[i], 0] > THRESHOLD_HOURLY) )
+            txt += sprintf( "%12d --- %s\n", mon[files[i], 0], files[i] );
+    
+        mon[files[i], 0] = 0;
+    }
+
+    // nur loggen, wenn es auch etwas zu loggen gibt
+    if ( txt != "" )
+        log_file( LOGNAME, sprintf( "%s: =================================="
+                                    "==============(stuendlich)===\n",
+                                    ctime(time())[4..15] ) + txt );
+
+    // der "grosse" Check kommt nur einmal pro Tag
+    if ( check < 24 ){
+        save_object(SAVEFILE);
+        return;
+    }
+
+    check = 0;
+    txt = "";
+    files = sort_array( m_indices(mon), #'_sort_daily/*'*/ );
+    
+    for ( i = sizeof(files); i--; )
+        if ( !(grenzen[files[i], 1] < 0) &&
+             (grenzen[files[i], 1] && mon[files[i], 1] > grenzen[files[i], 1])
+             || (!grenzen[files[i], 1] && mon[files[i], 1] > THRESHOLD_DAILY) )
+            txt += sprintf( "%12d --- %s\n", mon[files[i], 1], files[i] );
+
+    if ( txt != "" )
+        log_file( LOGNAME, sprintf( "%s: =================================="
+                                    "==============(taeglich)=====\n",
+                                    ctime(time())[4..15] ) + txt );
+
+    mon = m_allocate( 1, 2 );
+    save_object(SAVEFILE);
+}
+
+
+// die eigentliche Logg-Funktion
+public void AddMoney( object ob, int amount )
+{
+    string fn;
+
+    // keine Manipulationen per Hand bitte
+    if ( !objectp(ob) || amount < 0 || !previous_object() ||
+         (!query_once_interactive(previous_object()) &&
+          load_name(previous_object()) != GELD)
+         || IS_LEARNER(previous_object()) )
+        return;
+
+    fn = explode( object_name(ob), "#" )[0];
+
+    mon[fn, 0] += amount;
+    mon[fn, 1] += amount;
+}
+
+
+// Savefile noch schnell abspeichern - wir wollen ja keine Daten verlieren ;-)
+public int remove()
+{
+    next_reset = query_next_reset(this_object()) - time();
+    save_object(SAVEFILE);
+
+    destruct(this_object());
+    return 1;
+}
+
+
+// fuer bestimmte Files kann man die Grenzwerte einzeln setzen
+public varargs int modify_threshold( string fn, int amount1, int amount2 )
+{
+    if ( !this_interactive() || this_interactive() != this_player() ||
+         !IS_ARCH(this_interactive()) || process_call() )
+        return -1;
+
+    if ( !stringp(fn) || !intp(amount1) || !intp(amount2) )
+        return -2;
+
+    if ( !amount1 && !amount2 ){
+        m_delete( grenzen, fn );
+        write( "Eintrag " + fn + " geloescht.\n" );
+        return 1;
+    }
+
+    grenzen[fn, 0] = amount1;
+    grenzen[fn, 1] = amount2;
+    
+    write( break_string( "Die Grenzen fuer " + fn + " betragen jetzt " +
+                         ((amount1 < 0) ? "unendlich viele" :
+                          to_string(amount1)) + " Muenzen fuer den "
+                         "stuendlichen und " +
+                         ((amount2 < 0) ? "unendlich viele" :
+                          to_string(amount2)) + " Muenzen fuer den taeglichen "
+                         "Check.", 78 ) );
+
+    save_object(SAVEFILE);
+    return 1;
+}
+
+
+// einzeln gesetzte Grenzwerte abfragen
+public varargs mixed query_thresholds( string str )
+{
+    int i;
+    string *files, txt;
+    
+    if ( !this_player() )
+        return deep_copy(grenzen);
+
+    if ( stringp(str) && member( grenzen, str ) ){
+        write( break_string( "Die Grenzen fuer " + str + " betragen " +
+                             ((grenzen[str, 0] < 0) ? "unendlich viele" :
+                              to_string(grenzen[str, 0])) + " Muenzen fuer den "
+                             "stuendlichen und " +
+                             ((grenzen[str, 1] < 0) ? "unendlich viele" :
+                              to_string(grenzen[str, 1])) +
+                             " Muenzen fuer den taeglichen Check.", 78 ) );
+        return 0;
+    }
+
+    if ( stringp(str) && str != "" ){
+        write( "Es sind keine Grenzen fuer " + str + " eingetragen!\n" );
+        return 0;
+    }
+
+    files = sort_array( m_indices(grenzen), #'</*'*/ );
+    txt = "Eingetragene Grenzwerte:\n================================="
+        "=============================================\n";
+
+    for ( i = sizeof(files); i--; )
+        txt += sprintf( "%'.'-48s : %8d (h) %10d (d)\n",
+                        files[i], grenzen[files[i], 0], grenzen[files[i], 1] );
+
+    this_player()->More( txt );
+    return 1;
+}
+
+
+// bisher geloggte Daten abfragen
+public mixed query_status()
+{
+    int i;
+    string *files, txt;
+    
+    if ( !this_player() )
+        return deep_copy(mon);
+
+    files = sort_array( m_indices(mon), #'</*'*/ );
+    txt = "Geldquellen:\n================================="
+        "=============================================\n";
+
+    for ( i = sizeof(files); i--; )
+        txt += sprintf( "%'.'-48s : %8d (h) %10d (d)\n",
+                        files[i], mon[files[i], 0], mon[files[i], 1] );
+
+    this_player()->More( txt );
+    return 1;
+}
+
+
+// keine Shadows bitte
+public varargs int query_prevent_shadow( object ob ) { return 1; }
diff --git a/p/daemon/namefake.c b/p/daemon/namefake.c
new file mode 100644
index 0000000..f4146b2
--- /dev/null
+++ b/p/daemon/namefake.c
@@ -0,0 +1,54 @@
+/* Dieses Objekt dient zum Faken von Namen fuer den Channeld.
+ * Hintergrund: der Channeld braucht beim send() ein Objekt (bzw. die in der
+ * Verarbeitung dann folgenden Programme). Der Mudlibmaster sendet Meldung in
+ * anderer Leute Namen (der darf das auch). Dafuer clont und konfiguriert er
+ * dieses Objekt und uebergibt es an den channeld. Nach 3s raeumen sich Clone
+ * dieses Objektes wieder auf.
+ * Falls der Spieler/NPC existiert, wenn die meldung gesendet wird, wird vom
+ * Master das richtige Objekt uebergeben, nicht dieses hier.
+ */
+
+#include <config.h>
+#include <wizlevels.h>
+
+inherit "/std/secure_thing";
+
+// Envcheck-Mechanismus missbrauchen. ;-)
+protected void check_for_environment(string cloner)
+{
+  if (clonep(this_object()))
+    remove(1);
+}
+
+// Zur Sicherheit auch im Reset
+void reset()
+{
+  if (clonep(this_object()))
+    remove(1);
+}
+
+// Kopie aus /std/thing/properties.c. Man braucht davon wohl kein
+// zugaengliches in jedem thing...
+// Welche externen Objekte duerfen zugreifen?
+nomask private int allowed()
+{
+    if ( (previous_object() && IS_ARCH(getuid(previous_object())) &&
+          this_interactive() && IS_ARCH(this_interactive())) ||
+         (previous_object() && getuid(previous_object()) == ROOTID &&
+          geteuid(previous_object()) == ROOTID) )
+        return 1;
+    return 0;
+}
+
+// Sollte nur von ROOT oder EM+ manipuliert werden.
+// Sprich, das tut so, als seien alle Props SECURED
+public varargs mixed Set( string name, mixed value, int type, int extern )
+{
+  if ((extern || extern_call())
+      && previous_object() != this_object()
+      && !allowed()) // aus thing/properties.c
+    return -1;
+
+  return ::Set(name, value, type, extern);
+}
+
diff --git a/p/daemon/object.h b/p/daemon/object.h
new file mode 100644
index 0000000..7f7e20f
--- /dev/null
+++ b/p/daemon/object.h
@@ -0,0 +1,38 @@
+// MorgenGrauen MUDlib
+//
+// OBJECT.H -- persistent object handling
+//
+// $Date: 1995/03/31 13:30:33 $
+// $Revision: 1.1 $
+/* $Log: object.h,v $
+ * Revision 1.1  1995/03/31  13:30:33  Hate
+ * Initial revision
+ *
+ * Revision 1.1  1994/03/20  17:07:28  Hate
+ * Initial revision
+ *
+ */
+ 
+#ifndef __OBJECT_H__
+#define __OBJECT_H__
+
+// defines
+#define OBJECTD       "/p/daemon/objectd"
+#define OBJECTD_SAVE  "/p/daemon/save/objectd"
+
+#endif // __OBJECT_H__
+
+#ifdef NEED_PROTOTYPES
+
+#ifndef __OBJECT_H_PROTO__
+#define __OBJECT_H_PROTO__
+
+// prototypes
+mixed AddObject(string obj, string env);
+mixed RemoveObject(string obj, string env);
+varargs void QueryObject(mixed env);
+
+
+#endif // __OBJECT_H_PROTO__
+
+#endif	// NEED_PROTOYPES
diff --git a/p/daemon/objectd.c b/p/daemon/objectd.c
new file mode 100644
index 0000000..1b5d821
--- /dev/null
+++ b/p/daemon/objectd.c
@@ -0,0 +1,150 @@
+// MorgenGrauen MUDlib
+//
+// OBJECTD.C -- object daemon
+//
+// $Date: 1995/04/03 14:47:02 $
+// $Revision: 9510 $
+/* $Log: objectd.c,v $
+ * Revision 1.2  1995/04/03 14:47:02  Wargon
+ * QueryObject() verwendet bei BluePrints jetzt auch AddItem.
+ *
+ * Revision 1.1  1995/03/31  13:30:33  Hate
+ * Initial revision
+ *
+ */
+
+#pragma strong_types,save_types,rtt_checks
+#pragma no_clone, no_shadow
+
+#include <rooms.h>
+#include <properties.h>
+#include <defines.h>
+#include <daemon.h>
+
+mapping objects = ([]);
+private nosave int do_save;
+
+#define CLASS   0
+#define DATA    1
+
+protected void create()
+{
+  seteuid(getuid(ME));
+  restore_object(OBJECTD_SAVE);
+}
+
+protected void reset() {
+  if (do_save)
+  {
+    save_object(OBJECTD_SAVE);
+    do_save=0;
+  }
+}
+
+string AddObject(object obj, string env)
+{
+  if(!obj || !env || !objectp(obj) || !stringp(env))
+    return 0;
+
+  // save information
+  if(!member(objects, env))
+    objects[env] = ({ ({  object_name(obj), obj->QueryProp(P_AUTOLOAD) }) });
+  else
+    objects[env] += ({ ({ object_name(obj), obj->QueryProp(P_AUTOLOAD) }) });
+
+  do_save=1;
+  return env;
+}
+
+int RemoveObject(object|string obj, string env)
+{
+  if(!obj || !env || !stringp(env))
+    return 0;
+
+  // save information
+  if(member(objects, env))
+  {
+    mixed oblist = objects[env];
+    foreach(mixed arr: &oblist)
+    {
+      if (load_name(arr[CLASS]) == load_name(obj))
+      {
+        arr = 0;
+        // nur eins Austragen, nicht alle, falls mehr als einmal angemeldet
+        break;
+      }
+    }
+    objects[env] = oblist - ({0});
+  }
+  if(!sizeof(objects[env]))
+    m_delete(objects, env);
+
+  do_save=1;
+  return 1;
+}
+
+// Fragt nicht wirklich ab, sondern erstellt die angemeldeten Objekt in env.
+// Wenn kein env, wird previous_object() genommen.
+varargs void QueryObject(string envname)
+{
+  object env;
+  // take the caller as the questioning object
+  if(!envname || !stringp(envname))
+    env = previous_object();
+  else
+    env = find_object(envname);
+
+  // target must be a blueprint
+  if(IS_CLONE(env)) return 0;
+
+  envname=object_name(env);
+  if(member(objects, envname))
+  {
+    mixed oblist = objects[envname];
+    foreach(mixed arr : &oblist)
+    {
+       // Wir muessen unterscheiden, ob ein Clone angemeldete wurde oder eine
+       // Blueprint.
+       object o;
+       string *n = explode(arr[CLASS], "#");
+       if (sizeof(n) > 1 && sizeof(n[1]) > 0)
+         o = env->AddItem(n[0], REFRESH_DESTRUCT);
+       else
+         o = env->AddItem(n[0], REFRESH_DESTRUCT, 1);
+       o->SetProp(P_AUTOLOAD, arr[DATA]);
+       // Und das neue Objekt merken (ne Verwendung hat das zur Zeit aber
+       // nicht)
+       arr[CLASS] = object_name(o);
+    }
+  }
+}
+
+// Liefert eine Liste der in env angemeldeten Objekte. Wenn kein env, wird
+// previous_object() genommen.
+public varargs string QueryObjects(string envname)
+{
+  object env;
+  // take the caller as the questioning object
+  if(!envname || !stringp(envname))
+    env = previous_object();
+  else
+    env = find_object(envname);
+
+  // target must be a blueprint
+  if(IS_CLONE(env)) return 0;
+
+  mixed arrarr = objects[object_name(env)];
+  if (arrarr)
+  {
+    return CountUp(map(arrarr, #'[, CLASS),", ",", ");
+  }
+  return 0;
+}
+
+public varargs int remove(int silent)
+{
+  save_object(OBJECTD_SAVE);
+  destruct(ME);
+  return 1;
+}
+
diff --git a/p/daemon/pathd.c b/p/daemon/pathd.c
new file mode 100644
index 0000000..17d34ad
--- /dev/null
+++ b/p/daemon/pathd.c
@@ -0,0 +1,1000 @@
+// Daemon zum automatisierten Erstellen von Karten.
+// Soll spaeter mal wichtige Wege berechnen koennen fuer Anfaenger.
+//
+//
+// Wer mir ohne triftigen Grund daran herumpfuscht, der wird standesrechtlich
+// mit faulen Eiern beworfen. ]:->
+//
+// Tiamak
+
+#pragma strong_types,save_types,rtt_checks
+#pragma no_clone,no_inherit,no_shadow
+#pragma pedantic,range_check,warn_deprecated
+#pragma warn_empty_casts,warn_missing_return,warn_function_inconsistent
+
+#include <strings.h>
+#include <wizlevels.h>
+#include <player.h>
+#include <properties.h>
+#include <rooms.h>
+#include <moving.h>
+
+#define LOGNAME "PATHD"
+#define SAVEFILE "/p/daemon/save/pathd"
+#define DUMPFILE "/p/daemon/save/pathd-dump"
+//#define DEBUG
+
+// 10 verschiedene Leute werden gebraucht, um eine Verbindung zu verifizieren
+#define NEEDED 10
+#define TIMEOUT_NEW 1209600  // 14 Tage
+#define TIMEOUT_OLD 7776000 // 90 Tage
+
+// Art des Ausgangs
+#define NORMAL  0
+#define SPECIAL 1
+#define COMMAND 2
+
+// Kosten fuer unterschiedliche Wege
+#define COST_EXITS ({ 1, 10, 100 })
+#define COST_MANY   3
+#define COST_FEW    5
+#define COST_ONE   10
+// Kosten fuer Weltenwechsel (Para -> Normal)
+#define COST_WORLDCHANGE 100
+
+#ifdef DEBUG
+#define DBG(x) if ( find_player("zesstra") ) \
+                   tell_object( find_player("zesstra"), (x)+"\n" );
+#define TELL_USER(x) if ( this_player() ) \
+                   tell_object( this_player(), (x)+"\n" );
+#else
+#define DBG(x)
+#define TELL_USER(x)
+#endif
+
+#define LOG(x) log_file( "PATHD", (x), 100000 );
+
+// Variablen
+nosave int time_to_clean_up; // Zeit fuer naechstes Cleanup der Daten
+
+mapping pathmap;
+/* Prefix : ([ <submap> ]),
+   <submap> ist ein Mapping von Raeumen mit einem Array von Verbindungen:
+   ([<room>: ({ conn1, conn2, conn3, ...}), ...])
+   Jede Verbindung ist ein Array mit folgenden Indizes:
+   */
+#define CONN_DEST     0   // Zielraum
+#define CONN_CMD      1   // Kommando
+#define CONN_USERS    2   // Benutzer: ({spieler, seher})
+#define CONN_EXITTYPE 3   // Ausgangsart (s.o.)
+#define CONN_TIMES    4   // Zeit letzter Benutzung: ({spieler,seher})
+#define CONN_PARA     5   // Parawelt
+#define CONN_FLAGS    6   // Flags fuer die Verbindung (z.B. permanent)
+// Indizes in CONN_USERS. Beide Eintraege sind entweder gehashte UIDs ODER -1,
+// wenn die Verbindung von mehr als NEEDED unterschiedlichen gegangen wurde.
+#define CONN_U_PLAYERS 0  // Spieler-UIDs
+#define CONN_U_SEERS   1  // Seher-UIDS
+// Indizes von CONN_TIMES. Zeitstempel der letzten Benutzung...
+#define CONN_T_PLAYERS CONN_U_PLAYERS  // ... durch Spieler
+#define CONN_T_SEERS   CONN_U_SEERS  // ... durch Seher
+
+// Konstanten fuer CONN_FLAGS
+#define CFLAG_PERMANENT   0x1  // Verbindung nicht expiren
+#define CFLAG_DONTUSE     0x2  // Verbindung nicht im Routing nutzen
+#define CFLAG_DUPLICATE   0x4  // Kommando hat mehrere Verbindungen
+
+// laufende Pfadsuchen
+nosave mapping searchlist = ([]);
+/*
+   ([ <uuid> : SL_VISITED; SL_DESTINATION;... ... ])
+ */
+// Indizes in <data>:
+#define SL_START       0
+#define SL_DESTINATION 1  // Zielraum
+#define SL_VISITED     2  // abgelaufene/gepruefte Raeume
+#define SL_CANDIDATES  3  // moegliche Ziele
+#define SL_WIZLEVEL    4  // Wizlevel
+#define SL_PARA        5  // Parawelt
+#define SL_TODO        6  // noch zu pruefende Raume (SL_CANDIDATES-SL_VISITED)
+#define SL_CALLBACK    7  // Closure fuer Callback nach Routenberechnung
+// SL_CANDIDATES: ([<raum>: ({verbindungsdaten}), ...])
+// Indizes im Array <verbindungsdaten>:
+#define SLT_COSTS     0   // Kosten fuer Verbindung bis hier
+#define SLT_WAY       1   // bisheriger Weg von Start bis hier
+#define SLT_COMMANDS  2   // Kommandos fuer Weg von Start bis hier
+
+// cache fuer berechnete Wege
+nosave mapping pathcache = ([]);
+// Aufbau: ([ "startraum": <dests>, ...])
+// <dests>: (["zielraum": <wege>, ...])
+// <wege>: ([parawelt: (<struct path_s>) ])
+
+struct path_s {
+    int costs;      // Kosten des kompletten Pfades
+    string *rooms;  // Liste von durchlaufenen Raeumen
+    string *cmds;   // Liste von Kommandos, um Raeume zu durchlaufen
+    int ttl;        // time-to-live, Ablaufzeit des Pfads im Cache
+    int para;       // Parawelt
+    int flags;      // Flags zu dem Pfad
+};
+// Flags fuer Pfade im Pfadcache
+#define PFLAG_PERMANENT CFLAG_PERMANENT
+#define PFLAG_DONTUSE CFLAG_DONTUSE
+
+
+// Prototypes
+public void create();
+public void add_paths( mixed connections );
+public varargs void show_pathmap( string path );
+public varargs int show_statistic( int verbose, int silent );
+public int change_pathmap( string pfad, mixed *verbindungen );
+public mapping get_pathmap();
+public void reset();
+public varargs int remove( int silent );
+public varargs int query_prevent_shadow( object ob );
+static int insert_paths( mixed *path );
+static mixed *format_paths( mixed buf );
+static void _search( string fname, string start );
+private string _get_prefix( string path );
+protected void cleanup_data( string *names, int idx );
+protected void expire_path_cache(string *startrooms);
+
+
+public void create()
+{
+    // wir muessen das Savefile schreiben duerfen
+    seteuid(getuid());
+
+    if ( !restore_object(SAVEFILE) ) {
+        pathmap = ([]);
+    }
+}
+
+
+// Wird von Spielern aufgerufen mit den Wegen, die sie gelaufen sind
+public void add_paths( mixed connections )
+{
+    mixed paths;
+
+    // keinen Aufruf per Hand bitte
+    if ( !previous_object() || !this_player()
+         || this_interactive() && getuid(this_interactive()) != "zesstra"
+         || file_size( "/std/shells" +
+                       explode( object_name(previous_object()), ":" )[0]
+                       + ".c" ) < 1 )
+        return;
+
+#if __BOOT_TIME__ < 1357042092
+    // alte Spielerobjekte liefern noch ein Array von Strings statt eines
+    // Arrays von Arrays.
+    connections = map(connections, function mixed (string path)
+        {
+          // Alle Daten kommen als ein String mit '#' als Trenner an.
+          // Muster: Startraum#Zielraum#Verb#Methode der Bewegung#Parawelt
+          mixed buf = explode( path, "#" );
+          // Falls im Verb auch # vorkam (unwahrscheinlich, aber moeglich):
+          buf[2..<3] = ({ implode( buf[2..<3], "#" ) });
+          return buf;
+        } 
+        );
+#endif
+
+    // Daten aufbereiten
+    paths = map( connections, #'format_paths/*'*/ ) - ({0});
+    // Neue Raeume eintragen
+    filter( paths, #'insert_paths/*'*/ );
+}
+
+public mixed get_path_from_cache(string from, string to, int para) {
+    // wenn im Cache, aus dem Cache liefern.
+    mapping targets = pathcache[from];
+    if (targets) {
+        mapping paths = targets[to];
+        if (paths) {
+            if (member(paths, para)) {
+                struct path_s p = paths[para];
+                if (p->ttl > time())
+                    return ({ from, to, para, p->costs,
+                             copy(p->rooms), copy(p->cmds) });
+                else {
+                    m_delete(paths, para);
+                }
+            }
+        }
+    }
+    return 0;
+}
+
+// Suchfunktion, um die kuerzeste Verbindung zwischen zwei Raeumen zu finden
+public int SearchPath( string from, string to, int para,
+                       closure callback)
+{
+    // Pro UID darf es nur einen Suchauftrag geben.
+    string uid=getuid(previous_object());
+    if ( member(searchlist,uid) )
+        return -1;
+    // und mehr als 5 gleichzeitig erstmal auch nicht.
+    if (sizeof(searchlist)>4)
+        return -2;
+
+    // Anfrage loggen
+    LOG(sprintf("%s: Suche %s -> %s (%d) von %s (%s, %s, %s)\n",
+          strftime("%y%m%d-%H%M%S"), from, to, para, uid,
+          object_name(previous_object()),
+          object_name(this_player()) || "NO-PL",
+          object_name(this_interactive()) || "NO-TI") );
+
+    // wenn Start- oder Zielraum nicht bekannt sind, kann es auch keine Route
+    // geben.
+    if (!member(pathmap[_get_prefix(to)],to)
+        || !member(pathmap[_get_prefix(from)],from) )
+        return -3;
+
+    mixed path = get_path_from_cache(from, to, para);
+    if (path) {
+        if (callback)
+            apply(callback,path); 
+        return 2;
+    }
+
+    // Datenstrukturerklaerung: s.o.
+    mapping targets = m_allocate(1200,3);
+    m_add(targets,from,0,({}),({}) );
+    searchlist+= ([ uid: from; to; ({});
+                     targets;
+                     (query_wiz_level(this_interactive())?1:0);
+                     para; ({}); callback]);
+
+    // Die eigentliche Suche starten
+    _search( uid, from );
+
+    return 1;
+}
+
+// Gespeicherte Informationen zu einem Raum oder einem kompletten Gebiet
+// abfragen. Gebiete lassen sich aber nur als "Organisationseinheiten" abfragen.
+// Dabei werden Gebiete unterhalb von /d/* als /d/region/magiername
+// abgespeichert und der Rest unter den ersten beiden Teilpfaden.
+public varargs void show_pathmap( string path )
+{
+    string pre;
+
+    if ( path )
+    {
+        pre = _get_prefix(path);
+
+        if ( pre == path )
+            // Ganzen Teilbaum ausgeben
+            printf( "Pathmap[%O]: %O\n", path, pathmap[path] );
+        else
+            // Nur Infos ueber einen Raum ausgeben
+            printf( "Pathmap[%O]: %O\n", path,
+                    pathmap[pre] ? pathmap[pre][path] : 0 );
+    }
+    else
+        // Ohne Argument wird die gesamte Map ausgegeben.
+        // Klappt aber eher nicht (mehr) wegen Buffer overflow. ;^)
+        printf( "Pathmap: %O\n", pathmap );
+}
+
+
+// Statistic zu den gespeicherten Daten anzeigen.
+// Mit 'verbose' wird jede Organisationseinheit einzeln aufgelistet, ohne
+// wird grob nach Regionen zusammengefasst.
+public varargs int show_statistic( int verbose, int silent )
+{
+    int i, ges;
+    string *ind, name;
+    mapping m;
+
+    if ( verbose ){ // Stur jeden Eintrag auflisten
+        ind = sort_array( m_indices(pathmap), #'</*'*/ );
+        for ( i = sizeof(ind); i--; ){
+            if ( !silent )
+                printf( "%-30s: %4d\n", ind[i], sizeof(pathmap[ind[i]]) );
+            ges += sizeof(pathmap[ind[i]]);
+        }
+    }
+    else {          // Regionen zusammenfassen
+        ind = m_indices(pathmap);
+        m = ([]);
+
+        for ( i = sizeof(ind); i--; ){
+            if (strstr(ind[i],"/d/") == 0)
+                // /d... jeweils nach zwei Teilpfaden
+                name = implode( explode( ind[i], "/" )[0..2], "/" );
+            else if ( strstr(ind[i],"/players") == 0)
+                // Alle Playerverzeichnisse zusammenfassen ...
+                name = "/players";
+            else
+                // ... den Rest nach einem Teilpfad
+                name = implode( explode( ind[i], "/" )[0..1], "/" );
+
+            if ( !m[name] )
+                m[name] = sizeof( pathmap[ind[i]] );
+            else
+                m[name] += sizeof( pathmap[ind[i]] );
+        }
+
+        ind = sort_array( m_indices(m), #'</*'*/ );
+        for ( i = sizeof(ind); i--; ){
+            if ( !silent )
+                printf( "%-30s: %4d\n", ind[i], m[ind[i]] );
+            ges += m[ind[i]];
+        }
+    }
+
+    if ( !silent )
+        printf( "\nGesamt: %d Raeume.\n", ges );
+
+    return ges;
+}
+
+
+// Manipulieren der internen Pathmap. Nur zum Debuggen und somit
+// nur fuer Zesstra erlaubt. Sonst verhunzt mir noch wer meine Daten. ;^)
+public int change_pathmap( string pfad, mixed *verbindungen )
+{
+    if ( !this_interactive() || getuid(this_interactive()) != "zesstra"
+         || !previous_object() || getuid(previous_object()) != "zesstra" )
+        return -1;
+
+    if ( mappingp(verbindungen) ){
+        if ( !pathmap[_get_prefix(pfad)] )
+            return 0;
+
+        pathmap[_get_prefix(pfad)] = verbindungen;
+    }
+    else {
+        if ( !pathmap[_get_prefix(pfad)][pfad] )
+            return 0;
+
+        pathmap[_get_prefix(pfad)][pfad] = verbindungen;
+    }
+
+    return 1;
+}
+
+// Die Funktion gibt alle dem pathd bekannten Raeume als Mapping von Arrays
+// zurueck. Schlüssel des Mappings sind dabei Gebiete.
+public mapping get_rooms()
+{
+  string *submaps;
+  mapping roommap;
+  int i;
+
+  roommap=([]);
+
+  submaps=m_indices(pathmap);
+
+  i=sizeof(submaps);
+
+  while (i--)
+    if (sizeof(m_indices(pathmap[submaps[i]])))
+        roommap[submaps[i]]=m_indices(pathmap[submaps[i]]);
+
+  return roommap;
+}
+
+// Daten zu einer Verbindung sammeln und aufbereiten
+static mixed *format_paths( mixed buf )
+{
+    string cmd, uid;
+    object ob;
+    mapping exits;
+    int art;
+
+    // Magier und Testspieler koennen auch in nicht angeschlossene Gebiete
+    // und werden deshalb nicht beachtet.
+    if ( IS_LEARNER(previous_object(1)) ||
+         IS_LEARNER(lower_case( previous_object(1)->Query(P_TESTPLAYER)||"" )) )
+        return 0;
+
+    cmd = trim(buf[2],TRIM_BOTH);
+#if __BOOT_TIME__ < 1357042092
+    // " 0" am Ende des Befehls abschneiden. *grummel*
+    if (cmd[<2..]==" 0")
+      cmd = cmd[..<3];
+#endif
+
+    // Wenn der Zielraum als String angegeben wurde, kann der fuehrende
+    // Slash fehlen!
+    if ( buf[1][0] != '/' )
+        buf[1] = "/" + buf[1];
+
+    // Zum Abfragen der zusaetzlichen Daten brauchen wir das Objekt selber
+    catch(ob = load_object(buf[0]);publish);
+    if (!objectp(ob))
+        return 0;
+
+    // Kleiner Hack - bei Transportern etc. ist 'ob' die nicht initialisierte
+    // Blueprint. Jede Abfrage von P_EXITS wuerde nett auf -Debug scrollen.
+    // Da P_IDS im create() auf jeden Fall auf ein Array gesetzt wird,
+    // missbrauche ich das hier als "Ist initialisiert"-Flag.
+    if ( !ob->QueryProp(P_IDS) )
+        return 0;
+
+    // Art des Ausgangs feststellen. 
+    if ( mappingp(exits = ob->QueryProp(P_EXITS)) && exits[cmd] )
+        art = NORMAL;   // "normaler" Ausgang
+    else if ( mappingp(exits = ob->QueryProp(P_SPECIAL_EXITS)) && exits[cmd] )
+        art = SPECIAL;  // SpecialExit
+    else {
+        // Kommandos, die einen in einen anderen Raum bringen
+        art = to_int(buf[3]);   // Move-Methode
+
+        // Es zaehlen aber nur Bewegungen, die halbwegs "normal" aussehen,
+        // d.h. kein M_TPORT, M_NOCHECK und M_GO muss gesetzt sein.
+        if ( art & (M_TPORT | M_NOCHECK) || !(art & M_GO) )
+            return 0;
+        else
+            art = COMMAND;
+    }
+
+    // Die UID der Spieler/Seher wird verschluesselt.
+    // Schliesslich brauchen wir sie nur fuer statistische Zwecke und nicht,
+    // um Bewegungsprofile zu erstellen. crypt() reicht fuer diese Zwecke
+    // wohl erstmal.
+    uid = getuid(previous_object(1));
+    uid = crypt( uid, "w3" );
+
+    // Start, Ziel, Verb, Wizlevel(TP), UID(TP), Art des Ausgangs, Parawelt
+    return ({ buf[0], buf[1], cmd, query_wiz_level(previous_object(1)) ? 1 : 0,
+                  uid, art, to_int(buf[4]) });
+}
+
+// Prueft alle Verbindungen im Array <conns>, welche das gleiche Kommando wie
+// <conn> haben, ob sie in unterschiedliche Raeume fuehren. Wenn ja, sind sie
+// fuer das Routing nicht nutzbar. Ausnahme: Verbindungen, die in
+// unterschiedliche Parawelten fuehren.
+// Aendert <conns> direkt.
+private void check_and_mark_duplicate_conns_by_conn(mixed conns, mixed conn) {
+    foreach(mixed c: conns) {
+        if ( c[CONN_CMD] == conn[CONN_CMD]
+            && c[CONN_DEST] != conn[CONN_DEST]
+            && c[CONN_PARA] == conn[CONN_PARA] )
+        {
+            c[CONN_FLAGS] |= CFLAG_DUPLICATE;
+            conn[CONN_FLAGS] |= CFLAG_DUPLICATE;
+        }
+    }
+}
+
+private void check_and_mark_duplicate_conns(mixed conns) {
+    foreach(mixed c: conns)
+        check_and_mark_duplicate_conns_by_conn(conns, c);
+}
+
+
+// Neue Verbindung in der Datenbank eintragen
+static int insert_paths( mixed *path )
+{
+    string pre = _get_prefix(path[0]);
+
+    // Falls noch gar kein Eintrag existiert, neu initialisieren
+    if ( !mappingp(pathmap[pre]) )
+        pathmap[pre] = ([]);
+
+    if ( !pointerp(pathmap[pre][path[0]]) )
+        pathmap[pre][path[0]] = ({});
+
+    // Aufbau von 'path':
+    // ({ Start, Ziel, Verb, Wizlevel(TP), UID(TP), Art des Ausgangs, Para })
+    // Aufbau von 'conn' (s. auch ganz oben)
+    // ({ Ziel, Verb, ({ Spieler-UIDs, Seher-UIDs }), Art des Ausgangs,
+    //   ({ Letztes Betreten durch Spieler, durch Seher }), Parawelt })
+
+    // Alle Verbindungen des Raumes durchgehen
+    mixed conns = pathmap[pre][path[0]];
+    // Kommandos der Verbindungen sammeln. Wenn es zwei Verbindungen mit dem
+    // gleichen Kommando gibt, sind beide unbenutzbar.
+    mixed cmds = m_allocate(10);
+    int i;
+    for ( i = sizeof(conns); i--; )
+    {
+        mixed conn = conns[i];
+        m_add(cmds,conn[CONN_CMD]); // Kommandos aller Conns merken
+        // Wenn Zielraum, Verb und Parawelt passen ...
+        if ( conn[CONN_DEST] == path[1] && conn[CONN_CMD] == path[2]
+             && conn[CONN_PARA] == path[6] )
+        {
+            // Wenn schon genug Leute diese Verbindung genutzt haben, einfach
+            // nur die Zeit der letzten Benutzung aktualisieren.
+            int index = path[3] ? CONN_U_SEERS : CONN_U_PLAYERS; // Seher?
+            if ( conn[CONN_USERS][index] == -1 )
+            {
+                conn[CONN_TIMES][index] = time();
+                break;
+            }
+            // Ansonsten die (neue?) UID hinzufuegen und die Zeit aktualisieren.
+            else
+            {
+                conn[CONN_USERS][index] =
+                    conn[CONN_USERS][index] - ({ path[4] }) + ({ path[4] });
+                if ( sizeof(conn[CONN_USERS][index]) >= NEEDED )
+                    conn[CONN_USERS][index] = -1;
+
+                conn[CONN_TIMES][index] = time();
+                break;
+            }
+        }
+    }
+
+    // Falls keine Verbindung gepasst hat, eine neue erzeugen.
+    if ( i < 0 ) {
+        int index = path[3] ? CONN_U_SEERS : CONN_U_PLAYERS; // Seher?
+        mixed uids = ({ ({}), ({}) });
+        uids[index] = ({ path[4] });
+        mixed times = ({ 0, 0 });
+        times[index] = time();
+
+        // Aufbau siehe oben
+        mixed conn = ({ path[1], path[2], uids, path[5], times, path[6], 0 });
+        conns += ({ conn });
+        // wenn die neue Verbindung nen Kommando benutzt, welches schon fuer
+        // eine andere Verbindung aus diesem Raum benutzt wird, sind beide
+        // nicht nutzbar, da das Ziel nicht eindeutig ist. In diesem Fall
+        // muessen alle diese als unbenutzbar fuer das Routing markiert
+        // werden.
+        if (member(cmds, conn[CONN_CMD])) {
+            check_and_mark_duplicate_conns_by_conn(conns,conn);
+        }
+    }
+
+    pathmap[pre][path[0]] = conns;
+
+    // Ob 0 oder 1 ist eigentlich total egal, da das Ergebnis-Array sowieso
+    // verworfen wird.
+    return 0;
+}
+
+private void add_to_cache(string from, string to, int para, int costs,
+                          string *rooms, string *cmds) {
+    struct path_s p = (<path_s> costs : costs, rooms: rooms, cmds : cmds,
+                         para: para, flags: 0, ttl: time()+86400);
+    if (!member(pathcache, from)) {
+        pathcache[from] = m_allocate(1);
+        pathcache[from][to] = m_allocate(1);
+    }
+    else if (!member(pathcache[from], to)) {
+        pathcache[from][to] = m_allocate(1);
+    }
+    pathcache[from][to][para] = p;
+}
+
+// Diese Funktion berechnet die Kosten einer Verbindung.
+// Ein Seherstatus schliesst die Moeglichkeiten von Spielern ein (seher
+// kann Spielerpfade problemlos nutzen).
+// Wenn eine Verbindung in eine andere Parawelt fuehrt, als in <para>
+// angefragt, wird sie erheblich teurer.
+protected int _calc_cost(mixed* path, int seer, int para)
+{
+    mixed* interest=path[CONN_USERS];
+
+    // Basiskosten nach Ausgangsart
+    int costs = COST_EXITS[path[CONN_EXITTYPE]];
+
+    // Verbindungen in andere als die gewuenschte Parawelt werden bei der
+    // suche nicht beruecksichtigt, aber Verbindung von Para nach Normal und
+    // umgekehrt sind erlaubt. Para->Normal soll aber etwas teurer sein, damit
+    // es bevorzugt wird, in der angefragten Welt zu bleiben.
+    // (Hintergrund: wenn man in Para ist, laeuft man meistens trotzdem durch
+    // Normalweltraeume, weil es keine passenden Pararaeume gibt).
+    if (para != path[CONN_PARA])
+        costs += COST_WORLDCHANGE;
+
+    // wenn schon genug unterschiedliche Leute die Verbindung gelaufen sind,
+    // kostet es die Basiskosten. Fuer Seher auch die Spieler einschliessen.
+    if (interest[CONN_U_PLAYERS]==-1
+        || (seer && interest[CONN_U_SEERS]==-1))
+        return costs;
+    else
+    {
+        // Sonst wird die Verbindung teurer.
+        // Anzahl Spielernutzer kann nicht -1 sein (s.o.).
+        int usercount = sizeof(interest[CONN_U_PLAYERS]);
+        // wenn <seer>, wird ggf. die Anzahl an Sehernutzern drauf addiert.
+        // Wert fuer Seher kann nicht -1 sein (s.o.)
+        if (seer)
+            usercount += sizeof(interest[CONN_U_SEERS]);
+        switch (usercount)
+        {
+            case 0:
+                costs=1000000;
+                break;
+            case 1..2:
+                costs*=COST_ONE;
+            case 3..4:
+                costs*=COST_FEW;
+            default:
+                costs*=COST_MANY;
+        }
+        return costs;
+    }
+    DBG(sprintf("Huch, keine Ahnung, Kosten unbestimmbar: %O",path));
+    return 1000;
+}
+
+// Neuimplementation von Humni, benutzt einfach simpel einen
+// Dijkstra-Algorithmus. "Simpel" schreibe ich, bevor ich die
+// Funktion schreibe, fluchen werde ich gleich.
+static void _search(string fname, string from)
+{
+    // <from> ist jetzt anzuguckende/zu bearbeitende Raum.
+
+    // Die aktuelle Liste an abgesuchten Raeumen, Raeume, in denen
+    // alle Wege bereits eingetragen sind.
+    string* rooms_searched=searchlist[fname,SL_VISITED];
+
+    // Zielort
+    string to = searchlist[fname,SL_DESTINATION];
+
+    // moegliche Ziele
+    mapping targets=searchlist[fname,SL_CANDIDATES];
+
+    // wizlevel
+    int seer = searchlist[fname,SL_WIZLEVEL];
+
+    // para
+    int para=searchlist[fname,SL_PARA];
+
+    // Noch zu bearbeitende Raeume
+    string* to_do=searchlist[fname,SL_TODO];
+
+    DBG("_search() start.");
+
+    while ((get_eval_cost()>900000) && from && (from != to))
+    {
+        // Fuege den aktuellen Raum (from) als Weg hinzu
+        DBG(sprintf("Fuege hinzu: %s",from));
+        rooms_searched+=({from});
+        // Aus den Raeumen drumrum werden die Ausgaenge gesucht
+        // und zur Wegliste hinzugefuegt.
+
+        // Pfade von <from> weg aus der Liste holen
+        mixed* paths=pathmap[_get_prefix(from)][from];
+
+        // Liste der Verbindungen von <from> durchgehen
+        foreach (mixed conn:paths)
+        {
+            int newcost;
+            // wenn der Raum schonmal gesehen wurde, ist hier nix mehr noetig.
+            if (member(rooms_searched,conn[CONN_DEST])>-1)
+            {
+                DBG(sprintf("Raum %s hab ich schon!",conn[CONN_DEST]));
+            }
+            else
+            {
+                // Verbindung je nach Flag nicht nutzen. Ausserdem:
+                // wenn die Verbindung in eine Parawelt fuehrt, aber kein
+                // Seherstatus gegeben ist, existiert die Verbindung nicht.
+                // Weiterhin kommt eine Verbindung nicht in Frage, wenn sie in
+                // eine andere Parawelt fuehrt, weil der Weltuebergang nur an
+                // bestimmten Orten moeglich ist. Ausnahme davon sind Wechsel
+                // zwischen Normalwelt und Parawelt.
+                if (
+                    (conn[CONN_FLAGS] & CFLAG_DONTUSE)
+                    || (conn[CONN_FLAGS] & CFLAG_DUPLICATE)
+                    || (!seer && conn[CONN_PARA])
+                    || (conn[CONN_PARA] && conn[CONN_PARA] != para)
+                    )
+                    continue;
+                newcost = targets[from,SLT_COSTS]; // kosten bis hierher
+                // + Kosten in naechsten Raum.
+                newcost += _calc_cost(conn,seer,para);
+                // es koennte sein, dass es Verbindungen mit gleichem
+                // Start/Ziel gibt, aber unterschiedlichen Kosten. In diesem
+                // Fall wuerde das Ziel der aktuellen Verbindung mehrfach
+                // hinzugefuegt und das letzte gewinnt. Daher sollte diese
+                // Verbindung nur hinzugefuegt werden, wenn noch keine
+                // Verbindung in den Zielraum existiert, die billiger ist.
+                if (member(targets,conn[CONN_DEST])
+                    && newcost >= targets[conn[CONN_DEST],SLT_COSTS])
+                    continue;
+                // Weg bis hierher plus naechsten Raum
+                string* new_way = targets[from,SLT_WAY];
+                new_way += ({conn[CONN_DEST]});
+                // Kommandos bis hierher plus naechstes Kommando
+                string* new_commands = targets[from,SLT_COMMANDS];
+                new_commands += ({conn[CONN_CMD]});
+                // naechsten Raum in Todo fuer die Pruefung unten vermerken
+                to_do += ({conn[CONN_DEST]});
+                // Und Kosten/Weg/Kommandos bis zum naechsten Raum unter
+                // targets/Kandidaten ablegen.
+                targets += ([conn[CONN_DEST]:newcost;new_way;new_commands]);
+            }
+        }
+        // Nachdem die Raeume alle zu den Kandidaten an moeglichen Wegen
+        // hinzugefuegt wurden, wird nun der neueste kuerzeste Weg gesucht.
+        // Natuerlich nur aus denen, die schon da sind.
+        if (sizeof(to_do)>0)
+        {
+            // Kosten des ersten todo als Startwert nehmen
+            string minraum;
+            int mincosts=__INT_MAX__;
+            // und ueber alle todos laufen um das Minimum zu finden.
+            foreach(string raum: to_do)
+            {
+                if (mincosts>targets[raum,SLT_COSTS])
+                {
+                    minraum = raum;
+                    mincosts=targets[raum,SLT_COSTS];
+                }
+            }
+            DBG(sprintf("Neuer kuerzester Raum: %s",minraum));
+
+            // der billigst zu erreichende Raum wird das neue <from> fuer den
+            // naechsten Durchlauf
+            from=minraum;
+            // und natuerlich aus dem to_do werfen.
+            to_do-=({minraum});
+            DBG(sprintf("Anzahl untersuchter Raeume: %d, Todo: %d, "
+                  "Kandidaten: %d",
+                  sizeof(rooms_searched),sizeof(to_do),sizeof(targets)));
+        }
+        else
+        {
+            from=0;
+        }
+    } // while ende
+
+    if (!from)
+    {
+        // Hmpf...
+        TELL_USER("Kein Weg gefunden!");
+        if (searchlist[fname, SL_CALLBACK])
+            funcall(searchlist[fname,SL_CALLBACK],
+                    searchlist[fname,SL_START], to, para,
+                    0,0,0);
+        m_delete( searchlist, fname );  // suchauftrag entsorgen
+        return;
+    }
+    else if (from==to)
+    {
+        // JUCHU!
+        TELL_USER(sprintf("Weg gefunden! %O,%O,%O",
+              targets[to,SLT_COSTS],targets[to,SLT_WAY],
+              targets[to,SLT_COMMANDS]));
+        add_to_cache(searchlist[fname,SL_START], to, para,
+                     targets[to,SLT_COSTS],targets[to,SLT_WAY],
+                     targets[to,SLT_COMMANDS]);
+        if (searchlist[fname, SL_CALLBACK])
+            funcall(searchlist[fname,SL_CALLBACK],
+                    searchlist[fname,SL_START], to, para,
+                    targets[to,SLT_COSTS],targets[to,SLT_WAY],
+                    targets[to,SLT_COMMANDS]);
+        m_delete( searchlist, fname );  // suchauftrag entsorgen
+        return;
+    }
+
+    // Wenn das alles nicht war, waren es die Eval-Ticks. Mist. Spaeter
+    // weitersuchen...
+    searchlist[fname,SL_VISITED]=rooms_searched;
+    searchlist[fname,SL_CANDIDATES]=targets;
+    searchlist[fname,SL_TODO]=to_do;
+    call_out("_search",2,fname,from);
+}
+
+
+// Die Daten ueber Raeume werden quasi gehasht abgespeichert, damit wir nicht
+// so schnell an die Begrenzung bei Arrays und Mappings stossen.
+// Dabei ist der 'Hash' der Anfang des Pfades.
+private string _get_prefix( string path )
+{
+    string *tmp;
+
+    tmp = explode( path, "/" );
+
+    // Bei Raeumen unterhalb von /d/* wird /d/region/magiername benutzt
+    if ( strstr(path, "/d/") == 0)
+        return implode( tmp[0..3], "/" );
+    // Ansonsten die ersten beiden Teilpfade, falls soviele vorhanden sind
+    else if ( sizeof(tmp) < 4 )
+        return implode( tmp[0..1], "/" );
+    else
+        return implode( tmp[0..2], "/" );
+}
+
+// Reset und Datenhaltung
+// reset wird alle 3h gerufen, um das Savefile immer mal wieder zu speichern.
+// Datenaufraeumen aber nur einmal pro Tag.
+public void reset()
+{
+    if ( time_to_clean_up <= time() )
+    {
+        // Einmal pro Tag Reset zum Aufraeumen der Datenbank
+        rm(DUMPFILE".raeume");
+        rm(DUMPFILE".conns");
+        time_to_clean_up = time() + 86400;
+        expire_path_cache(m_indices(pathcache));
+        call_out(#'cleanup_data, 30,
+                  sort_array(m_indices(pathmap), #'</*'*/), 0 );
+
+    }
+    save_object(SAVEFILE);
+    set_next_reset(10800);
+}
+
+protected void expire_path_cache(string *startrooms) {
+    foreach(string start : startrooms) {
+        mapping targets = pathcache[start];
+        foreach (string target, mapping paths: targets) {
+            foreach(int para, struct path_s p: paths) {
+                if (p->ttl < time()
+                    || !(p->flags & PFLAG_PERMANENT) )
+                    m_delete(paths,para);
+            }
+            if (!sizeof(paths))
+                m_delete(targets, target);
+        }
+        startrooms-=({start});
+        if (!sizeof(targets))
+            m_delete(pathcache, start);
+        // recht grosser Wert, weil immer komplette Startraeume in einem
+        // erledigt werden.
+        if (get_eval_cost() < 1000000)
+            break;
+    }
+    if (sizeof(startrooms))
+        call_out(#'expire_path_cache, 4, startrooms);
+}
+
+// Datenbank aufraeumen
+protected void cleanup_data( string *names, int idx )
+{
+    int size, i, j, k;
+    string *rooms;
+    mixed *paths;
+
+    size = sizeof(names);
+
+    // Ein bisserl mitloggen, damit wir Schwachstellen im System auch finden.
+    if ( !idx ){
+        LOG( sprintf("=== %s: Starte cleanup_data(): %O Gebiete, %O Raeume "
+                     + "...\n", strftime("%y%m%d-%H%M%S"), size,
+                     show_statistic(1, 1) ) );
+    }
+    else {
+        LOG( sprintf("%s: Setze cleanup_data() fort.\n",
+              strftime("%y%m%d-%H%M%S") ) );
+    }
+
+    // Brav gesplittet, damit es kein Lag gibt.
+    // Die Grenze ist recht hoch angesetzt, da immer gleich komplette
+    // Teilbaeume aufgeraeumt werden.
+    while ( get_eval_cost() > 1100000 && idx < size ){
+        rooms = sort_array( m_indices(pathmap[names[idx]]), #'</*'*/ );
+
+        for ( i = sizeof(rooms); i--; ){
+            paths = pathmap[names[idx]][rooms[i]];
+            int conn_count = sizeof(paths);
+            mapping cmds=m_allocate(sizeof(paths));
+
+            for ( j = conn_count; j--; ) {
+                mixed conn = paths[j];
+                for ( k = 0; k < 2; k++ ) { // Spieler/Sehereintraege
+                    // Diese Verbindung hat noch keiner genutzt bisher
+                    if ( !conn[CONN_TIMES][k] )
+                        continue;
+                    // Wenn Verbindung permanent, ueberspringen
+                    if (conn[CONN_FLAGS] & CFLAG_PERMANENT)
+                        continue;
+
+                    if ( conn[CONN_USERS][k] == -1 // 'bekanntes' Gebiet
+                         && time() - conn[CONN_TIMES][k] > TIMEOUT_OLD ){
+                        // LOG( sprintf("*** Loesche alte "
+                        //             + (k ? "Seher" : "Spieler")
+                        //             + "-Verbindung %O.\n", paths[j]) );
+                        conn[CONN_USERS][k] = ({});
+                        conn[CONN_TIMES][k] = 0;
+                    }
+                    else if ( conn[CONN_USERS][k] != -1 // 'neues' Gebiet
+                              && time() - conn[CONN_TIMES][k] > TIMEOUT_NEW ){
+                        conn[CONN_USERS][k] = ({});
+                        conn[CONN_TIMES][k] = 0;
+                    }
+                }
+
+                // Wenn eine Verbindung weder von Spielern noch von Sehern
+                // benutzt wurde in letzter Zeit und sie keine permanented
+                // ist, Verbindung ganz loeschen.
+                if (!(conn[CONN_FLAGS] & CFLAG_PERMANENT)
+                    && !conn[CONN_TIMES][CONN_T_PLAYERS]
+                    && !conn[CONN_TIMES][CONN_T_SEERS] ) {
+                    paths[j..j] = ({}); // conn rauswerfen
+                    conn_count--;
+                }
+                else {
+                    // Connections nach Kommandos speichern.
+                    if (member(cmds, conn[CONN_CMD]))
+                        cmds[conn[CONN_CMD]] += ({conn});
+                    else
+                        cmds[conn[CONN_CMD]] = ({conn});
+                    // und das duplicate flag loeschen, das wird spaeter neu
+                    // gesetzt, wenn noetig.
+                    // (There is no chance that a concurrent search will check
+                    // these connections until they are marked again.)
+                    conn[CONN_FLAGS] &= ~CFLAG_DUPLICATE;
+                    // Connections dumpen
+                    if (!(conn[CONN_FLAGS] & CFLAG_DUPLICATE))
+                    {
+                      mixed u=({conn[CONN_USERS][CONN_U_PLAYERS],
+                              conn[CONN_USERS][CONN_U_SEERS] });
+                      write_file(DUMPFILE".conns",sprintf(
+                           "%s|%s|%s|%d|%d|%d|%d\n",
+                           rooms[i],conn[CONN_DEST],conn[CONN_CMD],
+                           conn[CONN_EXITTYPE],conn[CONN_PARA],
+                           (u[0]==-1 ? 0 : NEEDED-sizeof(u[0])),
+                           (u[0]==-1 ? 0 : NEEDED-sizeof(u[0]))
+                           ));
+                    }
+                }
+            }
+
+            // Ein Raum ohne Verbindungen frisst nur Platz in der Datenbank
+            if ( !conn_count ){
+                // LOG( sprintf("*** Loesche kompletten Raum %O\n", rooms[i]) );
+                m_delete( pathmap[names[idx]], rooms[i] );
+            }
+            else {
+                // wenn Kommandos nicht eindeutig sind, dann werden die
+                // entsprechenden Verbindungen gekennzeichnet. Alle in Frage
+                // kommenden Verbindungen sind jeweils schon zusammensortiert.
+                foreach(string cmd, mixed conns : cmds) {
+                    if (sizeof(conns)>1)
+                        check_and_mark_duplicate_conns(conns);
+                }
+                // ggf. geaendertes Verbindungsarray wieder ins Mapping
+                // schreiben.
+                pathmap[names[idx]][rooms[i]] = paths;
+                // Raum/Node ins Dump schreiben
+                write_file(DUMPFILE".raeume",sprintf(
+                      "%s\n",rooms[i]));
+            }
+        }
+        idx++;
+    }
+    if ( idx >= size ) {
+        LOG( sprintf("=== %s: Beende cleanup_data()! Uebrig: %O Raeume.\n",
+                     strftime("%y%m%d-%H%M%S"), show_statistic(1, 1) ) );
+    }
+    else {
+        call_out( #'cleanup_data, 2, names, idx );
+        LOG( sprintf("%s: WARTE 20s bei Evalcost %O\n",
+                     strftime("%y%m%d-%H%M%S"), get_eval_cost()) );
+    }
+}
+
+// Beim Entladen speichern
+public varargs int remove( int silent )
+{
+    // Vor dem Entfernen noch schnell die Daten sichern
+    save_object(SAVEFILE);
+    destruct(this_object());
+
+    return 1;
+}
+
+
+// Zum Debuggen. Nur für Tiamak. Bzw. nun fuer Zook, Vanion, Rumata
+#define ALLOWED ({"zook","vanion","rumata","zesstra","humni"})
+#define MATCH(x,y) (sizeof(regexp(x,y)))
+public mapping get_pathmap()
+{
+  if ( !this_interactive() || !MATCH(ALLOWED,getuid(this_interactive()))
+       || !previous_object() || !MATCH(ALLOWED,getuid(previous_object())))
+        return ([]);
+
+    return pathmap;
+}
+public mapping get_pathcache()
+{
+  if ( !this_interactive() || !MATCH(ALLOWED,getuid(this_interactive()))
+       || !previous_object() || !MATCH(ALLOWED,getuid(previous_object())))
+        return ([]);
+
+    return pathcache;
+}
+
+#undef ALLOWED
+#undef MATCH
diff --git a/p/daemon/routingd.c b/p/daemon/routingd.c
new file mode 100644
index 0000000..b293382
--- /dev/null
+++ b/p/daemon/routingd.c
@@ -0,0 +1,202 @@
+#pragma strong_types,save_types
+#pragma no_clone,no_shadow
+
+#include <wizlevels.h>
+
+#define DB_NAME "/p/daemon/save/routingd"
+#define MAX_LEN 50
+#define TP this_player()
+
+mapping domains;
+mapping exits;
+mapping targets;
+static mapping directions;
+static mapping reverse_directions;
+
+void create() {
+    seteuid(getuid());
+  if (!restore_object(DB_NAME)) {
+    domains=(["/d/":1]);
+    exits=([]);
+    targets=([]);
+  }
+
+  // Umsetzung zur Speicherersparnis:
+  directions=(["norden":1,
+	       "nordosten":2,
+	       "osten":3,
+	       "suedosten":4,
+	       "oben":5,
+	       "sueden":-1,
+	       "suedwesten":-2,
+	       "westen":-3,
+	       "nordwesten":-4,
+	       "unten":-5
+	       ]);
+  reverse_directions=([1:"norden",
+		       2:"nordosten",
+		       3:"osten",
+		       4:"suedosten",
+		       5:"oben",
+		       -1:"sueden",
+		       -2:"suedwesten",
+		       -3:"westen",
+		       -4:"nordwesten",
+		       -5:"unten"
+		       ]);
+}
+
+// Fuer Debug-Zecke:
+mixed query_exits(string room) {return exits[room];}
+mixed query_targets(string typ) {return targets[typ];}
+
+int AddDomain(string dom, int val) {
+  // Fuegt neue Domain hinzu, in der Routing moeglich sein soll.
+  // +1: Einschliessen
+  // -1: Ausschliessen
+  if (!IS_ARCH(TP)
+      || val<-1
+      || val>1)
+    return 0;
+  if (!val)
+    domains=m_copy_delete(domains,dom);
+  else
+    domains[dom]=val;
+  save_object(DB_NAME);
+  return 1;
+}
+
+int RegisterTarget(string type, string fname) {
+  // Registriert Raum als zu einer Gruppe von Zielen zugehoerig
+  // (z.B. RegisterTarget("pub","/d/ebene/room/PortVain/pub2"))
+  mapping map_ldfied;
+
+  if (!type || !fname)
+    return 0;
+  type=lower_case(type);
+  if (type[0..1]!="##")
+    type="##"+type;
+  if (!mappingp(map_ldfied=targets[type]))
+    map_ldfied=([]);
+  map_ldfied[fname]=1;
+  targets[type]=map_ldfied;
+  return 1;
+}
+
+int RegisterExit(string start, mixed richtungen, string ziel) {
+  // Registriert Ausgang in der angegebenen Richtung von Start nach Ziel,
+  // wenn Routing dort moeglich sein soll.
+  string *dirs;
+  int i,sz,ok,pos;
+  mixed ex,x,richtung;
+  
+  if (stringp(richtungen))
+    richtungen=({richtungen});
+  if (!start || !pointerp(richtungen) || !ziel)
+    return 0;
+  dirs=old_explode(start,"/");sz=sizeof(dirs)-1;x="/";ok=0;
+  for (i=0;i<sz;i++) {
+    x=x+dirs[i]+"/";
+    if (domains[x]==-1) // Subdomain ausgeschlossen?
+      return 0;
+    if (domains[x]==1) // Domain erlaubt?
+      ok=1;
+  }
+  if (!ok)
+    return 0;
+
+  if (!pointerp(ex=exits[start])) {
+    if (file_size(start+".c")<=0) // Beim ersten Ausgang auf VC testen
+      return 0; // Keine Virtual Compiler Raeume routen
+    ex=({});
+  }
+  for (i=sizeof(richtungen)-1;i>=0;i--) {
+    richtung=richtungen[i];
+    if (!(x=directions[richtung]))
+      x=richtung;
+  
+    pos=member(ex,x);
+    if (pos>=0 && pos+1<sizeof(ex)) // Richtung schon eingetragen?
+     ex[pos+1]=ziel; // Wurde umdefiniert
+    else
+      ex+=({x,ziel}); // ({ richtung, ziel })
+  }
+  exits[start]=ex;
+  return 1;
+}
+
+// Low-Level FindRoute :-)
+mapping do_find_route(string start, mapping is_target) {
+  mapping erreicht;
+  int i,j,k;
+  mixed *newx, *actx, ex;
+  string room, dir, x;
+  
+  erreicht=([start:"##START"]);
+  newx=({start});
+  for (k=MAX_LEN;k>=0;k--) {
+    actx=newx; // In diesem Schritt die neu erreichten Raeume pruefen
+    newx=({}); // Noch keine Raeume neu erreicht
+    for (i=sizeof(actx)-1;i>=0;i--) {
+      room=actx[i];
+      // printf("%O\n",room);
+      if (!pointerp(ex=exits[room]))
+	continue;
+      // printf("-> %O\n",ex);
+      for (j=sizeof(ex)-1;j>=1;j-=2) {
+	x=ex[j]; // Zielraum
+	// printf("-> %O\n",x);
+	if (!erreicht[x]) { // Wurde bisher noch nicht erreicht
+	  erreicht[x]=({room,ex[j-1]});
+	  if (is_target[x]) { // Ein Ziel wurde erreicht
+	    erreicht["##ZIEL"]=erreicht[x]; // Kann ja auch Gruppe sein
+	    return erreicht;
+	  }
+	  newx+=({x});
+	}
+      }
+    }
+  }
+  return 0;
+}
+
+varargs mixed FindRoute(string start, string ziel, int etyp) {
+  mapping res,ziele;
+  string x;
+  mixed ex,r,result;
+  int k;
+  
+  if (!start || !ziel)
+    return 0;
+  if (!mappingp(ziele=targets[ziel]))
+    ziele=([ziel:1]);
+  if (!mappingp(res=do_find_route(start, ziele)))
+    return 0; // nicht gefunden
+  if (etyp)
+    result=({});
+  else
+    result=([]);
+  x="##ZIEL";
+  for (k=MAX_LEN+2;k>=0;k--) {
+    ex=res[x];
+    if (ex=="##START") // Fertig.
+      return result;
+    if (!(r=reverse_directions[ex[1]])) // richtige Richtungen eintragen.
+      r=ex[1];
+    x=ex[0];
+    if (etyp)
+      result=({r})+result;
+    else
+      result[x]=r;
+  }
+  return 0;
+}
+
+void save() {
+	save_object(DB_NAME);
+}
+
+void reset() {
+	set_next_reset(43200); // Alle 12 Stunden speichern reicht
+	save();
+}
diff --git a/p/daemon/ruestungen.c b/p/daemon/ruestungen.c
new file mode 100644
index 0000000..52029fb
--- /dev/null
+++ b/p/daemon/ruestungen.c
@@ -0,0 +1,56 @@
+#pragma strong_types,save_types
+#pragma no_clone,no_shadow
+
+#include <wizlevels.h>
+
+#define SAVEFILE  "/p/daemon/save/ruestungen"
+#define DUMPFILE "/log/RUESTUNGEN"
+
+mapping armours;
+
+void create()
+{
+    seteuid(getuid(this_object()));
+
+    if (!restore_object(SAVEFILE))
+    {
+        armours = ([]);
+    }
+}
+
+void save_me()
+{
+    save_object(SAVEFILE);
+}
+
+void RegisterArmour()
+{   object  ob;
+    string  id;
+
+    if (!objectp(ob=previous_object()) || 
+      member(inherit_list(ob),"/std/armour.c")==-1)
+        return;
+    id = old_explode(object_name(ob),"#")[0];
+    if (member(armours,id))
+        return;
+    armours += ([ id : 1]);
+    save_me();
+}
+
+int Dump()
+{   string  *ind;
+    int     i;
+
+    if (!this_interactive() || !IS_LORD(this_interactive()))
+        return -1;
+    if (file_size(DUMPFILE)>=0)
+        rm(DUMPFILE);
+
+    ind = sort_array(m_indices(armours),#'>);
+    if ((i=sizeof(ind))<1)
+        return 0;
+    write_file(DUMPFILE,sprintf("Dumped: %s\n",dtime(time())));
+    for (--i;i>=0;i--)
+      write_file(DUMPFILE,ind[i]+"\n");
+    return 1;
+}
diff --git a/p/daemon/sastatd.c b/p/daemon/sastatd.c
new file mode 100644
index 0000000..c02873e
--- /dev/null
+++ b/p/daemon/sastatd.c
@@ -0,0 +1,135 @@
+// MorgenGrauen MUDlib
+/** \file /p/daemon/sastatd.c
+* Statistikdaemon fuer Skill-Attribute
+* Der Daemon loggt SA-Modifier und schreibt sie von Zeit zu Zeit auf die
+* Platte.
+* \author Zesstra
+* \date 06.08.2008
+* \version $Id$
+*/
+/* Changelog:
+*/
+#pragma strong_types,save_types
+#pragma no_clone,no_inherit,no_shadow
+#pragma pedantic,range_check
+
+#include <defines.h>
+
+/** anfaengliche Groesse des Log-Puffers
+  */
+#define DEFAULT_BUFFER_LEN 200
+/** ab dieser Anzahl an Eintraegen wird im naechsten Backend-Zyklus vom Driver
+ * per Callout der Puffer weggeschrieben. */
+#define BUFFERTHRESHOLD 150
+
+/** Logfile fuer die Stats
+  */
+#define STATLOG "SASTATS"
+/** max. Logfilegroesse (3 MB)
+  */
+#define MAXLOGSIZE 3145728
+
+struct log_entry_s {
+  int timestamp;
+  string liv;
+  string caster;
+  string atrname;
+  mixed value;
+  int duration;
+};
+
+struct log_buffer_s {
+  mixed buf;  // Array von log_entry
+  int bufferlen; // Laenge des Buffers, kann dyn. vergroessert werden.
+  int sp; // oberster Eintrag im Buffer (letztendlich nen Stack).
+};
+
+/** speichert Logeintraege mit indiv. Modifiereintraegen.
+ * Array von log_entry_s mit Info ueber Pufferlaenge und zuletzt
+ * geschriebenem Arrayeintrag.
+*/
+struct log_buffer_s logbuf = (<log_buffer_s>
+    buf : allocate(DEFAULT_BUFFER_LEN),
+    bufferlen : DEFAULT_BUFFER_LEN,
+    // -1, weil der sp beim Loggen zuerst erhoeht wird und sonst Elemente 0
+    // leer bleibt.
+    sp : -1 );
+
+/** \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"),\
+                                      "EDBG: "+x+"\n")
+#endif
+
+private string FormatLogEntry(struct log_entry_s entry) {
+  return sprintf("[%s] %s, %s, %s, %s, %d\n", 
+      strftime("%d%m%y-%H%M%S",entry->timestamp),
+      entry->liv, entry->caster, entry->atrname,
+      entry->value, entry->duration);
+}
+
+private void WriteBuffer() {
+  // relevanten Teil des Puffers extrahieren.
+  mixed buf = logbuf->buf[0..logbuf->sp];
+  // dann alle Eintraege in Strings mappen
+  buf = map(buf, #'FormatLogEntry);
+  // wegschreiben
+  log_file(STATLOG, sprintf("%@s",buf), MAXLOGSIZE);
+  // sp auf den Pufferanfang setzen
+  logbuf->sp = -1;
+}
+
+public void LogModifier(object caster, string atrname,
+                        mixed value, int duration) {
+  // nur Spieler sind interessant
+  if (!query_once_interactive(previous_object()))
+    return;
+
+  // sp um eins erhoehen und schauen, ob die Puffergroesse ueberschritten
+  // wurde. Wenn ja, Puffer vergroessern.
+  logbuf->sp++;
+  if (logbuf->sp >= logbuf->bufferlen) {
+    logbuf->buf = logbuf->buf + allocate(50);
+    logbuf->bufferlen += 50;
+  }
+  // eintragen. Interessanterweise ist es tatsaechlich kein Problem, wenn
+  // Structmember und lokale Variablen hier den gleichen Namen habem. ;-)
+  logbuf->buf[logbuf->sp] = (<log_entry_s>
+      timestamp : time(),
+      liv : getuid(previous_object()), // nur Spieler
+      caster : object_name(caster),
+      atrname : atrname,
+      value : to_string(value), // wegen (fluechtiger) Closures
+      duration : duration );
+  // wegschreiben veranlassen
+  if (logbuf->sp > BUFFERTHRESHOLD
+      && find_call_out(#'WriteBuffer) == -1)
+    call_out(#'WriteBuffer, 0);
+}
+
+protected void create() {
+  seteuid(getuid(ME));
+}
+
+varargs public int remove(int silent) {
+  if (logbuf->sp > -1)
+    WriteBuffer();
+  destruct(ME);
+  return 1;
+}
+
+void reset() {
+  // ggf. Puffer wegschreiben.
+  if (logbuf->sp > BUFFERTHRESHOLD)
+    WriteBuffer();
+  // ggf. sehr grosse Puffer verkleinern
+  if (logbuf->bufferlen > DEFAULT_BUFFER_LEN * 2) {
+    logbuf->buf = allocate(DEFAULT_BUFFER_LEN);
+    logbuf->bufferlen = DEFAULT_BUFFER_LEN;
+    logbuf->sp = -1;
+  }
+}
+
diff --git a/p/daemon/svn2news.c b/p/daemon/svn2news.c
new file mode 100644
index 0000000..8cece79
--- /dev/null
+++ b/p/daemon/svn2news.c
@@ -0,0 +1,55 @@
+// MorgenGrauen MUDlib
+//
+// svn2news.c -- Setzt die Subversion-Eintraege in magier.mudlib
+//
+// $Id$
+
+// Erstellt auf Basis von rcs2news von Zook.
+
+#pragma strict_types,save_types
+#pragma no_clone,no_shadow
+
+#include <daemon.h>
+
+#define NEWSFILE "/p/daemon/save/svn2news.txt"
+#define NEWSFILEOLD "/p/daemon/save/svn2news.old"
+
+#define DEBUG(str) if (find_player("zook"))  \
+  tell_object(find_player("zook"), sprintf("svn2news: DEBUG: %O\n",str))
+
+static void checkrcs()
+{
+  string str;
+  mixed art;
+  // Letzte Eintrag sollte ein wenig her sein, damit es keine
+  // Ueberschneidungen gibt.
+  if (file_time(NEWSFILE)+1800 > time()) return;
+  if (!(str=read_file(NEWSFILE,0,10000)) || (str=="")) return;
+  art=({"magier.mudlib","SVN2NEWS",0,0,
+        "SVN-Eintraege vom "+dtime(time()),str});
+  if (("/secure/news"->WriteNote(art,1)>=1) ||
+      (("/secure/news"->RemoveNote("magier.mudlib",0)>=1) &&
+       ("/secure/news"->WriteNote(art,1)>=1)))
+    catch(rename(NEWSFILE,NEWSFILEOLD);publish);
+  else log_file("SVN2NEWS",dtime(time())+
+		" : Kann RCS-Eintraege nicht nach magier.mudlib posten!\n");
+}
+
+void reset()
+{
+  // call_out() noetig, da sonst kein weiteres reset() gerufen wird
+  // reset() wird nur gerufen bei einem call einer anderen Funktion
+  // nach einem Reset
+  call_out("checkrcs",1);
+}
+
+void create()
+{
+  seteuid(getuid());
+  reset();
+}
+
+void clean_up(int refc)
+{
+  return 0;
+}
diff --git a/p/daemon/teammaster.c b/p/daemon/teammaster.c
new file mode 100644
index 0000000..76e8ca0
--- /dev/null
+++ b/p/daemon/teammaster.c
@@ -0,0 +1,84 @@
+#pragma strong_types,save_types
+#pragma no_shadow,no_clone
+
+#include <living/team.h>
+#include <wizlevels.h>
+#define ME this_object()
+#define PO previous_object()
+
+static mapping team_names;
+static mapping team_names_reverse;
+
+void create() {
+
+  team_names=([]);         // Objekt -> Name
+  team_names_reverse=([]); // Name -> Objekt
+}
+
+static mixed DoRegisterTeam(object ob, string name) {
+  mixed old;
+
+  old=team_names[ob];
+  m_delete(team_names_reverse,old); // Namen Freigeben
+  if (!name) { // UnRegister
+    m_delete(team_names,ob);
+    return name;
+  }
+  team_names[ob]=name;
+  team_names_reverse[name]=ob;
+  return name;
+}
+
+varargs string RegisterTeam(string name) {
+  int i,min;
+
+  if (member(inherit_list(PO),TEAM_OBJECT+".c")<0)
+    return 0;
+  if (stringp(name)){
+    if (!team_names_reverse[name]) // Name noch nicht vergeben
+      return DoRegisterTeam(PO,name);
+    return 0;
+  }
+  min=sizeof(m_indices(team_names_reverse))+2;
+  for (i=1;i<min;i++)
+    if (!team_names_reverse[sprintf("%d",i)]) {
+      min=i;
+      break;
+    }
+  name=sprintf("%d",i);
+  return DoRegisterTeam(PO,name);
+}
+
+void UnregisterTeam() {
+  if (member(inherit_list(PO),TEAM_OBJECT+".c")>=0)
+    DoRegisterTeam(PO,0);
+}
+
+object *ListTeamObjects() {
+  return(m_indices(team_names)-({0}));
+}
+
+string *ListTeamNames() {
+  return(m_indices(team_names_reverse)-({0}));
+}
+
+string *SortedListTeamNames() {
+  return(sort_array(ListTeamNames(),#'<));
+}
+
+void ShowTeamInfos() {
+  object ob;
+  string *ind;
+  int i;
+  
+  write(" --- Teamliste: ---\n");
+  ind=SortedListTeamNames();
+  for (i=sizeof(ind)-1;i>=0;i--)
+    if (objectp(ob=team_names_reverse[ind[i]]))
+      ob->ShowTeamInfo();
+  write(" --- (Ende Teamliste) ---\n");
+}
+
+varargs int remove(int silent) {
+  return 0;
+}
diff --git a/p/daemon/tmp_prop_master.c b/p/daemon/tmp_prop_master.c
new file mode 100644
index 0000000..0ce1de7
--- /dev/null
+++ b/p/daemon/tmp_prop_master.c
@@ -0,0 +1,338 @@
+#pragma strong_types,save_types
+#pragma no_clone,no_shadow
+
+#include <thing/properties.h>
+
+#define SAVEFILE "/p/daemon/save/tmp_prop_master"
+#define PROPMASTER "/p/daemon/tmp_prop_master"
+#define INDEX(a,b) (object_name(a)+"##"+b)
+
+
+#ifdef DEBUG
+#   undef DEBUG
+#endif
+
+#ifdef DEBUG
+#   define DBG(x) if ( find_player("rikus") ) \
+                     tell_object( find_player("rikus"), x );
+#else
+#   define DBG(x)
+#endif
+
+
+mixed *reset_times;
+mapping in_progress;
+
+
+varargs public int remove( int silent );
+public void reset();
+public void print_debug_info();
+static void call_next_update();
+static void do_update();
+public int IsTmpProp( object ob, string prop );
+public void RemoveTmpProp( string prop );
+varargs public void SetTmpProp( object ob, string prop, mixed newval,
+                                int tm, int sp );
+
+
+public void create()
+{
+
+    seteuid(getuid(this_object()));
+    
+    if ( !restore_object(SAVEFILE) ){
+        in_progress = ([]);
+        reset_times = ({});
+    }
+
+    call_next_update();
+}
+
+
+varargs public int remove( int silent )
+{
+    save_object(SAVEFILE);
+    destruct(this_object());
+    
+    return 1;
+}
+
+
+public void reset()
+{
+    save_object(SAVEFILE);
+}
+
+
+public void print_debug_info()
+{
+    printf( "in_progress: %O\nreset_times: %O\n", in_progress, reset_times );
+}
+
+
+static void call_next_update()
+{
+    int dt;
+    
+    while ( remove_call_out("do_update") != -1 )
+        ;
+    
+    if ( sizeof(reset_times) ){
+        dt = reset_times[0][0] - time();
+
+        if ( dt <= 0 )
+            dt = 5;
+        
+        call_out( "do_update", dt );
+    }
+}
+
+
+static void do_update()
+{
+    int i, max;
+    string index;
+    mixed *val;
+
+    DBG( "do_update() aufgerufen!\n" );
+
+    while ( remove_call_out("do_update") != -1 )
+        ;
+    
+    call_out( "do_update", 30 ); // Zur Sicherheit...
+
+    if ( !(max = sizeof(reset_times)) )
+        return;
+    
+    for ( i = 0; i < max && reset_times[i][0] <= time(); i++ ){
+        index = reset_times[i][1];
+
+        if ( !pointerp(val = in_progress[index]) || !objectp(val[0]) )
+	{
+            m_delete( in_progress ,index );
+            continue;
+        }
+
+        DBG( sprintf("Entferne Eintrag %O!\n", index) );
+
+        if ( val[4] ){
+            if ( val[0]->Query(val[1], F_VALUE) == val[3] ){
+                DBG( "Alte Property per SetProp() restauriert!\n" );
+                val[0]->SetProp( val[1], val[2] );
+            }
+        }
+        else {
+            if ( val[0]->Query( val[1], F_QUERY_METHOD ) == val[2] ){
+                val[0]->Set( val[1], 0, F_QUERY_METHOD );
+                DBG( "F_QUERY_METHOD entfernt!\n" );
+            }
+            
+            if ( val[0]->Query( val[1], F_SET_METHOD ) == val[3] ){
+                val[0]->Set( val[1], 0, F_SET_METHOD );
+                DBG( "F_SET_METHOD entfernt!\n" );
+            }
+        }
+
+        m_delete( in_progress ,index );
+    }
+    
+    reset_times = reset_times[i..];
+    call_next_update();
+}
+
+
+// Zur Abfrage, ob eine temporaere Property schon gesetzt ist
+public int IsTmpProp( object ob, string prop )
+{
+    mixed oldval;
+
+    if ( !objectp(ob) || !prop )
+        return 0;
+    
+    if ( (oldval = in_progress[INDEX(ob, prop)])
+         && ob == oldval[0] && prop == oldval[1] )
+        return 1;
+    
+    return 0;
+}
+
+
+// Entfernen einer temporaeren Property.
+// Ist nicht zum Entfernen per Hand gedacht, sondern kann nur vom Objekt selber
+// aufgerufen werden. Geschieht, wenn bei gesetzter temporaerer F_SET_METHOD
+// ein fremdes SetProp() auf die Property kommt.
+public void RemoveTmpProp( string prop )
+{
+    string index;
+    mixed *val;
+    int i;
+    
+    DBG( "RemoveTmpProp!\n");
+    if ( !previous_object() || !stringp(prop) || !sizeof(prop) )
+        return;
+
+    index = INDEX(previous_object(), prop);
+    DBG( sprintf("RemoveTmpProp(%O)!\n", index) );
+
+    if ( !(val = in_progress[index]) ){
+        DBG( "Kein Eintrag vorhanden!\n" );
+        return;
+    }
+
+    if ( objectp(val[0]) ){
+        DBG( "Restauriere alten Zustand ...\n" );
+
+        if ( val[4] ){
+            if ( val[0]->Query(val[1], F_VALUE) == val[3] ){
+                DBG( "Alte Property per SetProp() restauriert!\n" );
+                val[0]->SetProp( val[1], val[2] );
+            }
+        }
+        else {
+            if ( val[0]->Query( val[1], F_QUERY_METHOD ) == val[2] ){
+                val[0]->Set( val[1], 0, F_QUERY_METHOD );
+                DBG( "F_QUERY_METHOD entfernt!\n" );
+            }
+            
+            if ( val[0]->Query( val[1], F_SET_METHOD ) == val[3] ){
+                val[0]->Set( val[1], 0, F_SET_METHOD );
+                DBG( "F_SET_METHOD entfernt!\n" );
+            }
+        }
+    }
+
+    for ( i = sizeof(reset_times); i--; )
+        if ( reset_times[i][1] == index ){
+            reset_times[i..i] = ({});
+            break;
+        }
+    
+    DBG( "Eintrag entfernt!\n" );
+    m_delete( in_progress, index );
+}
+
+
+// Property 'prop' im Objekt 'ob' fuer 'tm' Sekunden auf Wert 'newval' setzen.
+// Wenn der Schalter 'sp' gesetzt ist, wird dafuer SetProp() verwendet,
+// anstatt eine F_QUERY_METHOD zu benutzen.
+//
+// ACHTUNG:
+//
+// Das Setzen von 'sp' ist hoechst fehleranfaellig und sollte nur in wenigen
+// Spezialfaellen geschehen, in denen eine SET-Methode aufgerufen werden muss
+// (wie bei P_LIGHT z.B.). Ansonsten bitte den Default-Aufruf ohne 'sp'
+// verwenden!
+// 
+// Es besteht sonst die Gefahr, dass die Property nicht korrekt zurueckgesetzt
+// wird (wenn dieser Master oder 'ob' zerstoert wird - oder beides wie bei einem
+// Crash) oder dass durch Wechselwirkungen mit anderen Query- oder Set-Methoden
+// ein falscher Wert "restauriert" wird.
+
+varargs public void SetTmpProp( object ob, string prop, mixed newval,
+                                int tm, int sp )
+{
+    string index;
+    mixed oldval, tmp;
+    int max, pos, l, r;
+    closure a, b;
+
+    if ( !objectp(ob) || !stringp(prop) || !sizeof(prop) || tm <= 0 )
+        return;
+
+    index = INDEX(ob, prop);
+    DBG( sprintf("SetTmpProp( %O, %O, %O, %O, %O )!\n", ob, prop, newval,
+                 tm, sp) );
+
+    // wenn bereits ein temporaerer Prozess laeuft, diesen nun beenden!
+    if ( (oldval = in_progress[index])
+         && ob == oldval[0] && prop == oldval[1] ){
+        DBG( "Es laeuft bereits ein Vorgang fuer diese Property!\n" );
+        
+        // Wurde die alte temporaere Property per SetProp() gesetzt, muss der
+        // urspruengliche Wert erst restauriert werden. 
+        if ( oldval[4] ){
+            if ( oldval[0]->Query(oldval[1], F_VALUE) == oldval[3] ){
+            DBG( "Alte Property per SetProp() restauriert!\n" );
+            oldval[0]->SetProp( oldval[1], oldval[2] );
+            }
+        }
+        // Ansonsten einfach die F_QUERY- und F_SET_METHOD loeschen.
+        else {
+            if ( oldval[0]->Query( oldval[1], F_QUERY_METHOD ) == oldval[2] ){
+                oldval[0]->Set( oldval[1], 0, F_QUERY_METHOD );
+                DBG( "F_QUERY_METHOD entfernt!\n" );
+            }
+
+            if ( oldval[0]->Query( oldval[1], F_SET_METHOD ) == oldval[3] ){
+                oldval[0]->Set( oldval[1], 0, F_SET_METHOD );
+                DBG( "F_SET_METHOD entfernt!\n" );
+            }
+        }
+        
+        for ( l = sizeof(reset_times); l--; )
+            if ( reset_times[l][1] == index ){
+                reset_times[l..l] = ({});
+                DBG( "Timer-Eintrag entfernt!\n" );
+                break;
+            }
+    }
+
+    if ( tm < 1000000 )
+        tm += time();
+
+    if ( tm < time() )
+        return;
+
+    if ( !sp ){
+        // Default: F_QUERY_METHOD setzen
+        oldval = ob->Set( prop, newval,  F_QUERY_METHOD );
+
+        // F_SET_METHOD, damit die temporaere Property keinen zwischenzeitlich
+        // gesetzten anderen Wert wieder "restauriert".
+
+        a=symbol_function("RemoveTmpProp",this_object());
+        b=symbol_function("SetProp",ob);
+        tmp=ob->Set(prop,lambda(({'set_value,'prop_name}),({#',,({a,'prop_name}),({#'return,({b,'prop_name,'set_value})})})),F_SET_METHOD);
+    }
+    else{
+        // Fuer Spezialfaelle (P_LIGHT z.B.) wird die Property direkt mit
+        // SetProp() gesetzt und spaeter mit dem alten Wert restauriert.
+        // Achtung - extrem fehleranfaellig und wirklich nur fuer Spezialfaelle!
+
+        // Alten Wert mit Query() sichern anstatt mit QueryProp(), um nicht
+        // irgendwelchen F_QUERY_METHODs auf den Leim zu gehen.
+        oldval = ob->Query( prop, F_VALUE );
+
+        // Rueckgabewert des SetProp() speichern, um spaeter erkennen zu
+        // koennen, ob die Property zwischenzeitlich veraendert wurde.
+        tmp = ob->SetProp( prop, newval );
+    }
+
+    in_progress[index] = ({ ob, prop, oldval, tmp, sp });
+    
+    if ( !(max = sizeof(reset_times)) )
+        pos = 0;
+    else{
+        l = 0;
+        r = max - 1;
+        
+        while ( l < r ){
+            pos = (l + r) / 2;
+            
+            if ( tm < reset_times[pos][0] )
+                r = --pos;
+            else
+                l = ++pos;
+        }
+        
+        while ( pos >= 0 && pos < max && tm < reset_times[pos][0] )
+            pos--;
+    }
+    
+    reset_times = reset_times[0..pos] + ({ ({tm, index}) })
+        + reset_times[pos+1..];
+
+    DBG( sprintf("Property an Stelle %O eingefuegt!\n", pos+1) );
+
+    call_next_update();
+}
diff --git a/p/daemon/traveld.c b/p/daemon/traveld.c
new file mode 100644
index 0000000..747fd38
--- /dev/null
+++ b/p/daemon/traveld.c
@@ -0,0 +1,285 @@
+/** Doku:
+public int AddRoute(string transporter, string* l_stops)
+  Beschreibung:
+    Setzt die aktuelle Route eines Transporters.
+    Die Route hier ist allerdings nur die Liste von Haltepunkten - nicht der
+    Rest, der in den Routen in den Transporter abgelegt ist.
+    Eine neue Route ersetzt immer die bisherige Route
+    Wird von /std/transport gerufen, wenn P_NO_TRAVELING nicht gesetzt ist.
+  Rueckgabewerte:
+    - 1: Transporter und Haltepunkt Eingetragen.
+    - 0: Transporter oder Haltepunkt kein string|string*.
+    - -1: Es wurde versucht einen Transporter in /p/ oder /players/
+          einzutragen 
+          und der Eintragende ist kein EM.
+
+public int RemoveTransporter(string|object transporter)
+public int RemoveStop(string|object stop)
+  Beschreibung:
+    Entfernt einen kompletten Transporter oder einen Haltepunkt aus dem
+    Daemon.
+  Rueckgabewerte:
+    - 1: Erfolg.
+    - 0: Transporter|Haltepunkt existiert nicht.
+    - -1: Keine Berechtigung.
+
+public varargs int|object* HasTransporter(object stop, string transporter_id)
+  Beschreibung:
+    Gibt eine Liste der Transporter aus, den den Haltepunkt anlaufen, oder
+    prueft auf einen bestimmten Transporter.
+  Rueckgabewerte:
+    - 0: Haltepunkt ist kein Objekt, dem Haltepunkt sind keine Transporter
+         zugeordnet,
+         die Transporter sind nicht geladen
+         oder, bei angabe von <transporter>: <transporter> faehrt den Hafen
+         nicht an.
+    - object*: Liste mit einem oder mehreren Transportern.
+  Bemerkung:
+    Auch bei der Angabe von <transporter_id> koennen mehrere Objekte
+    zurueckgegeben werden, es wird mittels id() gefiltert.
+
+public mixed RouteExists(object transporter, string dest)
+  Beschreibung:
+    Prueft auf korrekte Parameter und leitet die Anfrage an
+    /std/transport weiter.
+  Rueckgabewerte:
+    - 0: <transporter> ist kein Objekt oder <dest> ist kein String.
+    - Das Ergebnis von transporter->HasRoute(dest);
+
+public mapping QueryTransporters()
+public mapping QueryAllStops() 
+public mapping QueryTransporterByStop(string stop)
+public mapping QueryStopsByTransporter(string transporter)
+  Beschreibung:
+    Gibt das jewilige gefilterte oder ungefilterte Mapping aus.
+  Rueckgabewerte:
+    Mapping mit dem Ergebnis.
+*/
+
+#pragma strong_types,save_types,rtt_checks
+#pragma pedantic,range_check,warn_deprecated
+#pragma warn_empty_casts,warn_missing_return,warn_function_inconsistent
+#pragma no_clone,no_shadow
+
+#include <transport.h>
+#include <wizlevels.h>
+#include <daemon.h>
+
+#define TRANSPORTER 0
+#define STOP 1
+
+#define MEMORY "/secure/memory"
+
+// Key: string Schiff.
+// Value0: string* Haefen, die von <Schiff> angefahren werden.
+mapping transporters = ([]);
+// Key: string Hafen
+// Value0: string* Schiffe, die <Hafen> anfahren.
+mapping stops = ([]);
+
+protected void create()
+{
+  // Vom MEMORY die Daten abholen.
+  // Wenn keine da, war vermutlich Reboot, dann muessen wir warten, bis die
+  // Transporter sich wieder melden. Aber die Mappings muessen ins Memory
+  // geschrieben werden.
+  mapping tmp = MEMORY->Load("stops");
+  if (mappingp(tmp))
+  {
+    stops = tmp;
+  }
+  else
+  {
+    if (MEMORY->Save("stops",stops) != 1)
+      raise_error("Could not save memory to /secure/memory.");
+  }
+  tmp = MEMORY->Load("transporters");
+  if (mappingp(tmp))
+  {
+    transporters = tmp;
+  }
+  else
+  {
+    if (MEMORY->Save("transporters",transporters) != 1)
+      raise_error("Could not save memory to /secure/memory.");
+  }
+}
+
+varargs int remove(int s)
+{
+  destruct(this_object());
+  return 1;
+}
+
+// Loeschfunktion
+private int _remove_data(string|object key, int what)
+{
+  mapping first,second;
+  // Erstmal die Arbeitsmappings befuellen.
+  if(what==TRANSPORTER)
+  {
+    first=transporters;
+    second=stops;
+  }
+  else
+  {
+    first=stops;
+    second=transporters;
+  }
+
+  // Key auf einen String normalisieren.
+  if(objectp(key))
+  {
+    key=object_name(key);
+  }
+
+  if(!member(first,key))
+  {
+    return 0;
+  }
+
+  // Erstmal aus dem jeweils anderen Mapping austragen.
+  foreach(string s : first[key])
+  {
+    // Nicht existent oder nicht zugeordnet -> weiter.
+    if(!member(second,s) || member(second[s],key)==-1)
+    {
+      continue;
+    }
+    second[s]-=({key});
+    if (!sizeof(second[s]))
+      m_delete(second,s);
+  }
+  // Jetzt noch aus dem eigenen Mapping austragen.
+  m_delete(first,key);
+  return 1;
+}
+
+// Ein komplettes Schiff entfernen.
+public int RemoveTransporter(object|string transporter)
+{
+  if (extern_call() && !IS_ARCH(getuid(previous_object()))) return -1;
+  return _remove_data(transporter,TRANSPORTER);
+}
+
+// Entfernt einen kompletten Hafen aus dem Daemon
+public int RemoveStop(object|string stop)
+{
+  if (extern_call() && !IS_ARCH(getuid(previous_object()))) return -1;
+  return _remove_data(stop,STOP);
+}
+
+// Setzt die aktuelle Route eines Transporters.
+// Die Route hier ist allerdings nur die Liste von Haltepunkten - nicht der
+// Rest, der in den Routen in den Transporter abgelegt ist.
+// Wird von /std/transport gerufen, wenn P_NO_TRAVELING nicht gesetzt ist.
+public int AddRoute(string transporter, string* l_stops)
+{
+  if (!stringp(transporter) || !pointerp(l_stops) || !sizeof(l_stops))
+    return 0;
+
+  if ((transporter[0..2] == "/p/" || transporter[0..8] == "/players/")
+      && !IS_ARCH(getuid(previous_object()))) 
+  {
+    return -1;
+  }
+
+  // Damit sind alle Abfragen auf find_object(transporter|stop) ueberfluessig,
+  // im Zweifelsfall knallt es naemlich hier. ;-)
+  transporter = object_name(load_object(transporter));
+
+  // Wenn die route bereits existiert, austragen. Dies ist noetig, weil die
+  // Haltepunkte ja auch bereinigt werden muessen.
+  if (member(transporters, transporter))
+    RemoveTransporter(transporter);
+
+  // Transporter eintragen, die Route wird dabei immer ueberschrieben, wenn
+  // der Transporter schon bekannt ist. Ziel: hier ist immer die aktuell
+  // konfigurierte Route drin.
+  m_add(transporters, transporter, l_stops);
+
+  // Nach obigen Schema, nur diesmal Haltepunkte und zugehoerige Schiffe 
+  // eintragen.
+  foreach(string stop : l_stops)
+  {
+    if (!member(stops,stop))
+    {
+      m_add(stops, stop, ({transporter}));
+    }
+    else if (member(stops[stop],transporter) == -1)
+    {
+      stops[stop] += ({ transporter });
+    }
+  }
+  return 1;
+
+}
+
+// Abfrage ob ein Schiff einen Hafen anfaehrt.
+public varargs int|object* HasTransporter(object stop,
+                                          string transporter_id)
+{
+  if (!objectp(stop)) return 0;
+
+  <string|object>* trans=stops[object_name(stop)];
+  if (!pointerp(trans)) return 0;
+
+  // Eigentlich sollten sich Transporter ordentlich abmelden... Aber zur
+  // Sicherheit die nicht gefundenen Objekte ausfiltern.
+  trans = map(trans, #'find_object) - ({0});
+
+  // Wenn ne bestimmte ID gesucht wird, nur diese Transporter liefern.
+  if (stringp(transporter_id))
+    trans = filter_objects(trans, "id", transporter_id);
+
+  return sizeof(trans) ? trans : 0;
+}
+
+public mixed RouteExists(object transporter, string dest)
+{
+  if (!objectp(transporter) || !stringp(dest))
+  {
+    return 0;
+  }
+  return transporter->HasRoute(dest);
+}
+
+public mapping QueryTransporters()
+{
+  return deep_copy(transporters);
+}
+
+public mapping QueryAllStops()
+{
+  return deep_copy(stops);
+}
+
+// Liefert alle Transporter mit ihren Haltepunkten zurueck, die diesen Halt ansteuern.
+public mapping QueryTransportByStop(string stop)
+{
+  mapping res=m_allocate(4);
+  foreach(string s, string* arr : transporters)
+  {
+    if(member(arr,stop)!=-1)
+    {
+      m_add(res,s,arr);
+    }
+  }
+  return res;
+}
+
+// Liefert alle Haltepunkte mit ihren Transportern zurueck, die dieser
+// transporter ansteuert.
+public mapping QueryStopsByTransporter(string transporter)
+{
+  mapping res=m_allocate(4);
+  foreach(string s, string* arr : stops)
+  {
+    if(member(arr,transporter)!=-1)
+    {
+      m_add(res,s,arr);
+    }
+  }
+  return res;
+}
+
diff --git a/p/daemon/uptime_master.c b/p/daemon/uptime_master.c
new file mode 100644
index 0000000..84a7124
--- /dev/null
+++ b/p/daemon/uptime_master.c
@@ -0,0 +1,110 @@
+#pragma strong_types, save_types
+#pragma no_clone, no_shadow
+
+#include "/p/daemon/ch.h"
+#include "/p/daemon/uptime_master.h"
+
+/// Prototypen ///
+
+void create();
+void reset(); 
+
+void SaveMaxUptime();
+mapping QuerMaxUptime();
+
+/// Globale Variablen ///
+static int next_save_uptime;
+static int max_uptime_reached;
+static int last_record_boot;
+
+/// Funktionen ///
+
+void create()
+{
+  seteuid(getuid(this_object()));
+  reset();
+}
+
+void reset()
+{
+  call_out("SaveMaxUptime",1);
+  set_next_reset(300);
+}
+
+// Ist eine Rekord-Uptime erreicht, wird diese hier gespeichert.
+void SaveMaxUptime()
+{
+  int max_shut, max_boot;
+  
+  // Lohnt es, schon wieder zu pruefen?
+  if (time()>next_save_uptime)
+  {
+    // Initialisieren
+    if (next_save_uptime==0)
+    {
+      DEBUG("Masterinit");
+      
+      // Zeit berechnen, ab der ein Rekord aufgestellt ist.
+      // Vorher braucht nixhts mehr geprueft werden
+      sscanf(read_file(MAX_UPTIME)||"0 0","%d %d",last_record_boot, max_shut);
+      next_save_uptime=last_reboot_time()+(max_shut-last_record_boot)+1; 
+      
+    } else {
+      DEBUG("Rekord neu speichern.");
+	    
+      // Neuen Rekord in Datei schreiben.
+      catch(rm(MAX_UPTIME);publish);
+      write_file(MAX_UPTIME, last_reboot_time()+" "+time());
+      
+      // Jetzt checken, ob die Uptime grade geknackt wurde ...
+      // Wenn ja, dann eine Meldung an den Master
+      if(last_reboot_time()!=last_record_boot)
+      {
+	DEBUG("Rekordmeldung. MB:"+last_record_boot+" LU:"+last_reboot_time());
+
+	// Nur einmal pro Rekord Uptime melden ...
+	last_record_boot=last_reboot_time();
+
+	// Hurraaah :)
+        CHMASTER->join(RECORD_CHANNEL,this_object());
+	CHMASTER->send(RECORD_CHANNEL,this_object(),
+	"[<MasteR>:Lars] Wow, eine Rekord-Uptime.\n"
+	"[<MasteR>:Merlin] Das erlebt man auch nicht alle Tage ...\n"
+	"[<MasteR>:Der GameDriver] Dann wird es wohl Zeit fuer einen Reboot, oder?\n"
+	"[<MasteR>:Merlin] Faules Stueck, arbeite weiter!\n"
+	"[<MasteR>:Der GameDriver] Och menno!",
+	  MSG_EMPTY);
+	CHMASTER->leave(RECORD_CHANNEL,this_object());
+      }
+    }
+  }
+}
+
+// Gibt Daten zur bisher maximalen Uptime zurueck: (299 Ticks)
+// 1.) Wann wurde gebootet
+// 2.) Wann endete die Uptime
+// 3.) Wann wird dieser Rekord gebrochen?
+// 4.) Dauer der Uptime als lesbarer String.
+mapping QueryMaxUptime()
+{
+  int t, tmp, max_boot, max_shut;
+  string s;
+
+  sscanf(read_file(MAX_UPTIME)||"0 0","%d %d",max_boot, max_shut);
+
+  t=max_shut-max_boot;
+
+  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"));
+  s+=sprintf("%d Sekunde%s",t=t%60,(t==1?"":"n"));
+
+  return (["beginn":max_boot, 
+           "ende":max_shut, 
+	   "naechster rekord":last_reboot_time()+(max_shut-max_boot), 
+	   "rekord als string":s]);
+}
diff --git a/p/daemon/uptime_master.h b/p/daemon/uptime_master.h
new file mode 100644
index 0000000..862e44a
--- /dev/null
+++ b/p/daemon/uptime_master.h
@@ -0,0 +1,14 @@
+#ifndef __UPTIME_MASTER__
+#define __UPTIME_MASTER__
+
+#define UPTIME_MASTER  "/p/daemon/uptime_master"
+#define MAX_UPTIME     "/p/daemon/save/maxuptime"
+#define RECORD_CHANNEL "<MasteR>"
+
+#define DEBUG(x)
+/*
+#define DEBUG(x) if(find_player("vanion")) tell_object(find_player("vanion"),\
+		                "UPTIME: "+x+"\n");
+*/
+
+#endif /* __UPTIME_MASTER__ */
diff --git a/p/daemon/zentralbank.c b/p/daemon/zentralbank.c
new file mode 100644
index 0000000..53074d0
--- /dev/null
+++ b/p/daemon/zentralbank.c
@@ -0,0 +1,236 @@
+// MorgenGrauen MUDlib
+//
+// zentralbank.c -- Zentrale Geld-Verwaltung der Haendler im MG
+// $Id: zentralbank.c 9223 2015-05-27 21:46:58Z Zesstra $
+//
+/*
+ * $Log: zentralbank.c,v $
+ * Revision 1.5  2003/12/30 11:17:34  Vanion
+ * query_prevent_shadow fehlte. Danke fuer den Hinweis an Ogo.
+ *
+ * Revision 1.4  2003/12/30 11:12:02  Jof
+ * Ich hab Dir das File mal geklaut.
+ * Vanion
+ *
+ * Revision 1.3  1995/07/10  07:31:04  Jof
+ * typo fixed
+ *
+ * Revision 1.2  1995/07/10  07:27:59  Jof
+ * Use bank.h
+ * _query_current_money
+ *
+ * Revision 1.1  1995/07/05  16:29:26  Jof
+ * Initial revision
+ *
+ */
+#pragma no_shadow
+#pragma no_clone
+#pragma no_inherit
+#pragma strong_types,save_types
+
+#include <wizlevels.h>
+#include <properties.h>
+#include <money.h>
+
+#define NEED_PROTOTYPES
+#include <bank.h>
+
+#define TIME strftime("%H:%M",time())
+#define LS 1000000
+
+int allmoney,bank_default_percent,store_default_percent,shop_default_percent;
+int falling;
+// Geldmengen: in NPC, in Boersen, rumliegen, auf Seherkarten, Gesamtsumme,
+// Zeitpunkt der Berechnung.
+int *summen = ({0,0,0,0,0,0});
+
+nosave int last_save;
+nosave int counter;
+
+void save_me()
+{
+  save_object("/p/daemon/save/zentralbank");
+  last_save=time();
+}
+
+protected void create()
+{
+  seteuid(getuid());
+  allmoney=500000;
+  bank_default_percent=BANK_DEFAULT_PERCENT;
+  store_default_percent=STORE_PERCENT_LEFT;
+  shop_default_percent=SHOP_PERCENT_LEFT;
+  restore_object("/p/daemon/save/zentralbank");
+}
+
+private void adjust_percents()
+{
+  if (allmoney<50000   && falling>=0) falling=-1;
+  if (allmoney>1000000 && falling<=0) falling=1;
+  if (allmoney<200000 && falling>0 ) falling=0;
+  if (allmoney>80000  && falling<0 ) falling=0;
+  if (falling<0 && !((++counter)%5))
+  {
+    bank_default_percent=bank_default_percent+random(3)+1;
+    store_default_percent=store_default_percent+random(3)+1;
+    shop_default_percent=shop_default_percent+random(3)+1;
+    log_file("ZENTRALBANK",
+	sprintf("%s: PERCENTS SET TO BANK %d STORE %d SHOP %d\n",
+	  TIME,bank_default_percent,store_default_percent,
+	  shop_default_percent),LS);
+  }
+  if (falling>0)
+  {
+    bank_default_percent/=2;
+    store_default_percent/=2;
+    shop_default_percent/=2;
+    log_file("ZENTRALBANK",
+	sprintf("%s: PERCENTS SET TO BANK %d STORE %d SHOP %d\n",
+	  TIME,bank_default_percent,store_default_percent,
+	  shop_default_percent),LS);
+  }
+  if (bank_default_percent<1) bank_default_percent=1;
+  if (bank_default_percent>90) bank_default_percent=90;
+  if (store_default_percent<1) store_default_percent=1;
+  if (store_default_percent>90) store_default_percent=90;
+  if (shop_default_percent<1) shop_default_percent=1;
+  if (shop_default_percent>90) shop_default_percent=90;
+  save_me();
+}
+
+void reset()
+{
+  adjust_percents();
+}
+
+private void real_log()
+{
+  log_file("ZBANKSTATUS",sprintf("%s: %d\n",dtime(time()),allmoney),LS);
+}
+
+private void log_state()
+{
+  // nur alle 15min nen Status-Logeintrag. Reicht voellig.
+  if (find_call_out(#'real_log) == -1)
+    call_out(#'real_log,900);
+}
+
+public varargs void PayIn(int amount, int percent)
+{
+  if (amount<=0) return;
+  percent|=bank_default_percent;
+  allmoney+=amount*percent/100;
+  log_file("ZENTRALBANK",
+      sprintf("%s: Einzahlung: %d (%d brutto) von %O\n",
+	TIME,amount*percent/100,amount,previous_object()),LS);
+  log_state();
+}
+
+public int WithDraw(int amount)
+{
+  int got;
+
+  if (amount<=0) return 0;
+  if (allmoney<0) allmoney=0;
+  if (!allmoney)
+    got=0;
+  else
+  {
+    if (amount*3<allmoney)
+      got=amount;
+    else
+    {
+      got=allmoney/3;
+      if (!got) got=1;
+    }
+    allmoney-=got;
+  }
+  log_file("ZENTRALBANK",sprintf("%s: Abhebung: %6d/%6d von %O\n",
+	TIME,got, amount,previous_object()),LS);
+  log_state();
+  return got;
+}
+
+int _query_current_money()
+{
+  return allmoney;
+}
+
+int _query_bank_default_percent()
+{
+  return bank_default_percent;
+}
+
+int _query_shop_percent_left()
+{
+  return shop_default_percent;
+}
+
+int _query_store_percent_left()
+{
+  return store_default_percent;
+}
+
+// Geldmenge im Spiel
+public int *geldmenge() {
+  
+  if (summen[5] > time()-3600 || !IS_ELDER(this_player()))
+      return summen;
+
+  summen = allocate(6);
+  object *geld = clones(GELD,2);
+  foreach(object ob: geld) {
+    if (!environment(ob) || IS_LEARNER(environment(ob)))
+      continue;
+    if (environment(ob)->QueryProp(P_NPC))
+      // in NPC
+      summen[0]+=ob->QueryProp(P_AMOUNT);
+    else if (load_name(environment(ob)) == BOERSE)
+      // in boersen
+      summen[1]+=ob->QueryProp(P_AMOUNT);
+    else
+      // sonst rumliegend
+      summen[2]+=ob->QueryProp(P_AMOUNT);
+  }
+  // Seherkarten
+  geld = clones(SEHERKARTE,2);
+  mapping cards=m_allocate(sizeof(geld),1);
+  foreach(object ob: geld) {
+    cards[ob->query_owner()] = ob->QueryProp(P_AMOUNT);
+  }
+  foreach(string owner, int amount: cards)
+    summen[3]+=amount;
+  summen[4]=summen[0]+summen[1]+summen[2]+summen[3];
+  summen[5]=time();
+  return summen;
+}
+
+public void PrintGeldmenge() {
+  int *sum = geldmenge();
+  printf("NPC: %d\n"
+         "Boersen: %d\n"
+         "rumliegend: %d\n"
+         "Seherkarten: %d\n"
+         "Gesamt: %d\n"
+         "Zeit: %s\n",
+         sum[0],sum[1],sum[2],sum[3],sum[4],strftime(sum[5]));
+}
+
+void set_percents(int store,int shop,int bank)
+{
+  store_default_percent=store;
+  shop_default_percent=shop;
+  bank_default_percent=bank; 
+  log_file("ZENTRALBANK",
+      sprintf("%s: PERCENTS SET TO BANK %d STORE %d SHOP %d\n",
+	TIME,bank_default_percent,store_default_percent,
+	shop_default_percent),LS);
+}
+
+int remove()
+{
+  save_me();
+  destruct(this_object());
+  return 1;
+}
+