Added public files

Roughly added all public files. Probably missed some, though.
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