Added public files

Roughly added all public files. Probably missed some, though.
diff --git a/d/seher/haeuser/modules/haustuer.c b/d/seher/haeuser/modules/haustuer.c
new file mode 100644
index 0000000..b9d8784
--- /dev/null
+++ b/d/seher/haeuser/modules/haustuer.c
@@ -0,0 +1,215 @@
+//  haustuer.c -- Beschreibung, Oeffnen und Schliessen
+//
+//  (c) 1995 Wargon@MorgenGrauen
+//
+// $Id: haustuer.c,v 1.1.1.1 2000/08/20 20:22:42 mud Exp $
+//
+
+#define NEED_PROTOTYPES
+#include "../haus.h"
+#include <properties.h>
+#include <thing/properties.h>
+#include <thing/description.h>
+#include <thing/commands.h>
+
+static int validHouse = 0;
+static string owner;
+
+void create()
+{
+  Set( H_DOOR, "Die Haustuer ist %s.\n" );
+  Set( H_DOORSTAT, 0);
+  Set( H_DOORLSTAT, ({ "geoeffnet", "geschlossen", 0, "abgeschlossen" }));
+
+  Set( H_DOOR, SAVE, F_MODE);
+  Set( H_DOORSTAT, SAVE, F_MODE);
+  Set( H_DOORLSTAT, SAVE, F_MODE);
+
+  AddCmd( ({"schliess", "schliesse"}), "schliesse");
+  AddCmd("oeffne","oeffne");
+
+  owner = getuid(this_object());
+}
+
+varargs string long(int mode)
+{
+  return sprintf(Query(H_DOOR), Query(H_DOORLSTAT)[Query(H_DOORSTAT)]);
+}
+
+static string SetTuerOwner(string o)
+{
+  validHouse=1;
+  Set( H_DOORSTAT, D_CLOSED | D_LOCKED);
+  return owner=o;
+}
+
+int oeffne(string str)
+{
+  int doorstat;
+
+  notify_fail("Was willst Du oeffnen?\n");
+  doorstat = Query(H_DOORSTAT);
+
+  if (!id(str) && str != "tuer" && str != "haustuer")
+    return 0;
+
+  if (!(doorstat & D_CLOSED)) {
+    notify_fail(capitalize(name(WER,1))+" ist gar nicht geschlossen!\n");
+    return 0;
+  }
+  if (doorstat & D_LOCKED) {
+    notify_fail(capitalize(name(WER,1))+" ist abgeschlossen.\n");
+    return 0;
+  }
+
+  doorstat &= ~D_CLOSED;
+  Set(H_DOORSTAT, doorstat & ~D_CLOSED);
+
+  write("Du oeffnest "+name(WEN,1)+" von "+capitalize(owner)+".\n");
+  tell_room(environment(this_object()),
+	    sprintf("%s oeffnet %s von %s.\n",
+		    this_player()->name(WER),
+		    name(WEN,1),
+		    capitalize(owner) ),
+	    ({this_player()}));
+  if (validHouse) {
+    tell_room(find_object(RAUMNAME(owner,0))||this_object(),
+	      this_player()->name(WER)+" oeffnet "+name(WEN)+".\n",
+	      ({this_player()}));
+    Save();
+  }
+  return 1;
+}
+
+private int close_door()
+{
+  if (Query(H_DOORSTAT) & D_CLOSED) {
+    notify_fail(capitalize(name(WER,1))+" ist doch schon zu!\n");
+    return 0;
+  }
+  Set(H_DOORSTAT, Query(H_DOORSTAT) | D_CLOSED);
+
+  write("Du schliesst "+name(WEN,1)+" von "+capitalize(owner)+".\n");
+  tell_room(environment(this_object()),
+	    sprintf( "%s schliesst %s von %s.\n",
+		     capitalize(this_player()->name(WER)),
+		     name(WEN,1), capitalize(owner)),
+	    ({this_player()}));
+  if (validHouse) {
+    tell_room(find_object(RAUMNAME(owner,0))||this_object(),
+	      capitalize(this_player()->name(WER))+" schliesst "+name(WEN)+".\n",
+	      ({this_player()}));
+    Save();
+  }
+  return 1;
+}
+
+private int lock_door()
+{
+  object tp, home;
+  string nam;
+  int doorstat;
+
+  tp = this_player();
+  nam = capitalize(tp->name(WER));
+
+  if (validHouse) {
+    call_other(RAUMNAME(owner,0), "???");
+    home = find_object(RAUMNAME(owner,0));
+
+    if (!home->allowed_check(this_player())) {
+      notify_fail("Du darfst "+name(WEN,1)+" von "+capitalize(owner)+" aber nicht abschliessen.\n");
+      return 0;
+    }
+  }
+  if ((doorstat = Query(H_DOORSTAT)) & D_LOCKED) {
+    notify_fail("Die Tuer ist abgeschlossen.\n");
+    return 0;
+  }
+  doorstat |= (D_LOCKED|D_CLOSED);
+  write("Du schliesst "+name(WEN)+" ab.\n");
+  Set(H_DOORSTAT, doorstat);
+  tell_room(environment(this_object()),
+	    sprintf( "%s schliesst %s ab.\n",
+		    nam,
+		    (getuid(tp)==owner ?
+		     (tp->QueryPossPronoun(this_object(), WEN)+" "+name(RAW)) :
+		     name(WEN,1) )),
+	    ({tp}));
+  if (validHouse) {
+    tell_room(home, nam+" schliesst "+name(WEN)+" ab.\n",({tp}));
+    Save();
+  }
+  return 1;
+}
+
+private int unlock_door()
+{
+  object tp, home;
+  string nam;
+  int doorstat;
+
+  tp = this_player();
+  nam = capitalize(tp->name(WER));
+
+  if (validHouse) {
+    call_other(RAUMNAME(owner,0), "???");
+    home = find_object(RAUMNAME(owner,0));
+
+    if (!home->allowed_check(this_player())) {
+      notify_fail("Du darfst "+name(WEN,1)+" von "+capitalize(owner)+" aber nicht aufschliessen.\n");
+      return 0;
+    }
+  }
+
+  if (!((doorstat = Query(H_DOORSTAT)) & D_LOCKED)) {
+    notify_fail("Die Tuer ist nicht abgeschlossen.\n");
+    return 0;
+  }
+
+  Set(H_DOORSTAT, doorstat & ~D_LOCKED);
+  write("Du schliesst "+name(WEN)+" auf.\n");
+  tell_room(environment(this_object()),
+	    sprintf("%s schliesst %s auf.\n",
+		     nam,
+		     (getuid(tp)==owner ?
+		      (tp->QueryPossPronoun(this_object(), WEN)+" "+name(RAW)) :
+		      name(WEN,1) )),
+	    ({tp}));
+  if(validHouse) {
+    tell_room(home,nam+" schliesst "+name(WEN)+" auf.\n",({tp}));
+    Save();
+  }
+  return 1;
+}
+
+int schliesse(string str)
+{
+  string was, wen, *s;
+
+  notify_fail("Was willst Du schliessen ?\n");
+  if (!str||str=="")
+    return 0;
+  s = old_explode(str, " ");
+  if ((was = s[<1]) == "auf" || was == "ab")
+    s = s[0..<2];
+  else
+    was = 0;
+
+  wen=implode(s, " ");
+  if (!id(wen) && wen != "tuer" && wen != "haustuer")
+    return 0;
+  if (!was || was=="")
+    return close_door();
+  switch (was)
+  {
+    case "auf": return unlock_door();
+    case "ab":  return lock_door();
+  }
+  return 0;
+}
+
+// $Log: haustuer.c,v $
+// Revision 1.1.1.1  2000/08/20 20:22:42  mud
+// Ins CVS eingecheckt
+//
diff --git a/d/seher/haeuser/modules/losa.c b/d/seher/haeuser/modules/losa.c
new file mode 100644
index 0000000..893ca51
--- /dev/null
+++ b/d/seher/haeuser/modules/losa.c
@@ -0,0 +1,378 @@
+//  losa.c -- Modul fuer Laden und Speichern der Hausdaten
+//
+//  (c) 1995 Wargon@MorgenGrauen
+//      2006 Vanion@MorgenGrauen, fuer die rebootfesten Moebel
+// $Id: losa.c,v 1.1.1.1 2000/08/20 20:22:42 mud Exp $
+//
+#pragma strong_types,rtt_checks
+
+#define NEED_PROTOTYPES
+#include "../haus.h"
+#include <container.h>
+#include <thing/properties.h>
+#include <room/exits.h>
+#include <thing/description.h>
+#undef NEED_PROTOTYPES
+#include <properties.h>
+#include <wizlevels.h>
+#include <moving.h>
+
+static void start_reload_furniture();
+
+private int csaved;
+
+// Variablen zur Verwaltung der Moebel im Raum.
+mapping furniture;
+mapping broken_furniture;
+
+protected void
+create()
+{
+  Set(H_CHEST, SAVE, F_MODE);
+  Set(H_CHEST, 0);
+
+  Set(H_FURNITURE, SAVE, F_MODE);
+  Set(H_FURNITURE, 0);
+
+//  Set(H_SPECIAL, SAVE|SECURED, F_MODE);
+//  Set(H_SPECIAL, ([:2]), F_VALUE);
+}
+
+/* Scheint nirgendwo benutzt zu werden...
+varargs int AddSpecial(int typ, string key, string extra)
+{
+  if (!this_interactive() || !IS_ARCH(this_interactive()) ||
+      !(PATH+"access_rights")->access_rights(geteuid(this_interactive()),""))
+    return -1;
+
+  if (typ != HS_EXIT && typ != HS_ITEM)
+    return 0;
+
+  Set(H_SPECIAL, Query(H_SPECIAL) + ([ key : typ; extra ]));
+  Save();
+  return 1;
+}
+
+void RemoveSpecial(string key)
+{
+  if (!this_interactive() || !IS_ARCH(this_interactive()) ||
+      !(PATH+"access_rights")->access_rights(geteuid(this_interactive()),""))
+    return;
+
+  Set(H_SPECIAL, m_delete(Query(H_SPECIAL), key));
+  Save();
+}
+*/
+
+void
+reset()
+{
+  if (QueryOwner() &&
+      !sizeof(filter(all_inventory(),#'interactive)) &&
+      !csaved)
+    Save(1);
+}
+
+// crunched komprimiert das Savefile
+varargs void
+Save(int crunched)
+{
+  mixed o1, o2, o3;
+  closure pc;
+  object *obs;
+  int i, found;
+
+  o3 = 0;
+
+  if (!(o1 = Query(P_DETAILS)))
+    Set(P_DETAILS, QueryProp(P_DETAILS), F_VALUE);
+
+  if (!(o2 = Query(P_READ_DETAILS)))
+    Set(P_READ_DETAILS, QueryProp(P_READ_DETAILS), F_VALUE);
+
+  if (csaved = crunched)
+  {
+    pc = symbol_function("PCrunch", VERWALTER);
+    Set(P_DETAILS, funcall(pc, Query(P_DETAILS)), F_VALUE);
+    Set(P_READ_DETAILS, funcall(pc, Query(P_READ_DETAILS)), F_VALUE);
+    o3 = Query(H_COMMANDS, F_VALUE);
+    Set(H_COMMANDS, funcall(pc, o3), F_VALUE);
+  }
+
+  // Autoload-Einrichtung identifizieren und speichern
+  // Code in Anlehnung an dem Autoload-Mechanismus fuer Spieler
+  furniture=([]);
+
+  // Alle Autoloader filtern
+  obs=filter_objects(all_inventory(this_object()), "QueryProp", H_FURNITURE);
+  found = 0;
+
+  // Ueber alle Moebel iteritieren
+  for( i=sizeof(obs)-1;i>=0;i--)
+  {
+    if( clonep(obs[i]))
+    {
+      if ( ++found <= MAX_FURNITURE_PER_ROOM )
+        furniture += ([ object_name(obs[i]):obs[i]->QueryProp(H_FURNITURE) ]);
+    }
+  }
+  if (found > MAX_FURNITURE_PER_ROOM)
+  {
+    tell_object(this_player(), 
+      break_string("Du hast "+found+" Moebelstuecke im Raum stehen. "
+                   "Gespeichert werden nur "+MAX_FURNITURE_PER_ROOM+". "
+                   "Du solltest Dich von einigen Einrichtungsgegenstaenden "
+                   "trennen.",78));
+
+  }
+  HDEBUG("Saving "+ sizeof (furniture) +" (plus "+sizeof(broken_furniture)+
+         " broken) objects in room "+
+         object_name(this_object()) + ".");
+
+  save_object( HAUSSAVEPATH+QueryOwner(1));
+
+  Set(P_DETAILS, o1, F_VALUE);
+  Set(P_READ_DETAILS, o2, F_VALUE);
+  if (o3)
+    Set(H_COMMANDS, o3, F_VALUE);
+}
+
+void
+Load()
+{
+  mixed prop;
+  int i;
+
+  restore_object( HAUSSAVEPATH+QueryOwner(1));
+
+  // Details und Kommandos werden beim Speichern de-dupliziert und in einem
+  // speziellen Format abgespeichert (s. PCrunch() im Hausverwalter). Sie
+  // muessen nach dem Laden durch die entsprechenden Add...()-Aufrufe
+  // wieder eingetragen werden.
+  prop=Query(P_DETAILS, F_VALUE);
+  RemoveDetail(0);
+  if (pointerp(prop))
+  {
+    foreach(<string*|string>* item : prop)
+      AddDetail(item[0], item[1]);
+  }
+  else if (mappingp(prop))
+  {
+    foreach(string key, mixed val : prop)
+      AddDetail(key, val);
+  }
+  else
+    SetProp(P_DETAILS, prop);
+
+  prop = Query(P_READ_DETAILS, F_VALUE);
+  RemoveReadDetail(0);
+  if (pointerp(prop))
+  {
+    foreach(<string*|string>* item : prop)
+      AddDetail(item[0], item[1]);
+  }
+  else if (mappingp(prop))
+  {
+    foreach(string key, mixed val : prop)
+      AddReadDetail(key, val);
+  }
+  else
+    SetProp(P_READ_DETAILS, prop);
+
+  prop = Query(P_EXITS, F_VALUE);
+  RemoveExitNoCheck(0);
+  if (mappingp(prop))
+  {
+    if (widthof(prop) <= 1)
+    {
+      foreach(string key, string dest : prop)
+        AddExitNoCheck(key, dest);
+    }
+    else
+    {
+      foreach(string key, string dest, string msg : prop)
+      {
+        if (stringp(msg))
+          _AddExit(key, dest, msg);
+        else if (stringp(dest) && strstr(dest,"#") != -1)
+          AddExitNoCheck(key, dest);
+        else
+          _AddExit(key, dest, 0);
+      }
+    }
+  }
+
+  prop=Query(H_COMMANDS, F_VALUE);
+  if (pointerp(prop))
+  {
+    Set(H_COMMANDS, ([]), F_VALUE);
+    for (i=sizeof(prop)-1; i>=0; i--)
+      this_object()->AddUserCmd(prop[i][0], 0, prop[i][1], prop[i][2]);
+  }
+
+  if (environment())
+    environment()->SetProp(P_NEVER_CLEAN, 1);
+
+  if (previous_object() && object_name(previous_object())==VERWALTER)
+  {
+    if (Query(H_CHEST))
+      this_object()->AddItem(PATH+"truhe",REFRESH_NONE,
+                             ([ "owner" : QueryOwner() ]));
+/* Das scheint nirgendwo benutzt zu werden und in allen Seherhaeusern leer zu
+ * sein.
+    mapping special = Query(H_SPECIAL, F_VALUE);
+    if (special)
+    {
+      foreach(string key, int type, string extra : special)
+      {
+        switch(type)
+        {
+          case HS_ITEM:
+            AddItem(SPECIALPATH + extra, REFRESH_DESTRUCT);
+            break;
+          case HS_EXIT:
+            AddExitNoCheck(key, extra);
+            break;
+        }
+      }
+    }
+*/
+  }
+
+  // Das Laden der Autoloader wird erst am Ende angestossen. 
+  // Dann ist es nicht schlimm, wenn alle Eval Ticks verbraucht werden.
+  start_reload_furniture();
+}
+
+// Mehrere Save-Anforderungen zusammenfassen.
+static void queued_save()
+{
+        HDEBUG("QS");
+        while (remove_call_out("Save")!=-1);
+        call_out("Save",3);
+}
+
+static int reload_error(string file, mixed data,  string message)
+{
+        HDEBUG(message);
+        broken_furniture+=([file:data]);
+        log_file("seher/haeuser/autoloader_error",
+                 dtime(time())+"\n"+
+                 break_string(object_name(this_object())+" ("+QueryOwner(1)+")",78, " FILE: ",1)+
+                 break_string(message, 78, " MSG:  ",1)+
+                 break_string(sprintf("%O", data),78, " DATA: ",1)+"\n");
+                 
+        return 0; // 0 fuer das filter, damit dieser Eintrag 
+                  // aus furniture geloescht wird.
+}
+
+// Laedt ein einzelnes Moebelstuecks
+static int load_furniture_object( string file, mixed data )
+{
+    object ob;
+    string error;
+    string blueprint;
+    closure pc;
+    // mixed data;
+    
+    // Wenn genug Ticks frei sind, wird versucht, das Objekt zu erzeugen.
+    // Ansonsten ist die Gefahr zu gross, dass ein Erzeugungs-Prozess abbricht.
+    if (get_eval_cost() < 500000) 
+    {
+      // HDEBUG("Suspending Object: "+file+". Only "+to_string(get_eval_cost())+" ticks left.");
+      return 1; // 1 bedeutet, dass dieser Eintrag es im Mapping bleibt.
+    }
+
+    // HDEBUG("Processing Object: "+file+" with Data: "+sprintf("%O",data)+".");
+    
+    // Nummern der Clones sind beim Speichern noetig, um die Identitaeten
+    // der Objekte zu bestimmen (mehrere Objekte vom gleichen Blueprint 
+    // speichern). Hier braucht man sie nicht mehr 
+            
+    blueprint = explode(file,"#")[0];
+    
+    // Data aus dem Mapping holen
+    // data=furniture[file];
+    
+    // Muss ich die Blueprint suchen?
+    ob = find_object(file);
+    
+    // Nein.
+    if (!ob)
+    {
+            // Existiert die BP oder ein VC fuers File?
+        if (file_size(blueprint+".c")<0&&
+           file_size(implode(explode(blueprint,"/")[0..<2],"/")+
+                     "/virtual_compiler.c")<0)
+        {
+           return reload_error(file, data, "Error in file: "+ file +
+                               ". File does not exist.");
+
+        }
+
+        // File gefunden. Versuch, es zu laden.
+        if (error = catch(call_other( blueprint,"???")))
+        {
+           return reload_error(file, data, "Error loading file: "+file+". "+error);
+        }
+    }
+    
+    // Clone erzeugen
+    if ( error = catch(ob = clone_object(blueprint)) )
+    {
+        return reload_error(file, data, "Error cloning object: "+file+". "+error);
+    }
+ 
+    HDEBUG(sprintf("%O",furniture));
+    // Autoload-Daten setzen
+    HDEBUG(object_name(ob)+"->SetProp("+sprintf("%O", data)+")");
+    if (ob)
+        catch(ob->SetProp( H_FURNITURE, data ));
+           
+    // Furniture in das Seherhaus moven
+    if ( error = catch(ob->move( this_object(), M_NOCHECK )) ) {
+        ob->remove();
+        if(ob) destruct(ob);
+        return reload_error(file, data, "Error moving object: "+file+". "+error);
+    }
+
+    // post_create anstossen
+    pc=symbol_function("post_create", ob);
+    if (closurep(pc))
+      call_out(pc, 1);
+    
+    return 0; // 0 bedeutet hier, dieses Objekt nicht noch einmal anstossen.
+}
+
+static void load_furniture()
+{
+        int i;
+        string rv;
+        string current_key;
+        
+        // Abbruchbedingung ist, dass nichts mehr zu laden ist.
+        if (sizeof(furniture)==0) return; 
+        
+        // Anstoßen des naechsten Durchlaufs, falls die Ticks nicht reichen.
+        while (remove_call_out(#'load_furniture) != -1);
+        call_out(#'load_furniture, 1);
+        
+        // Laden aller Moebel anstoßen
+    furniture=filter(furniture, #'load_furniture_object);
+}
+
+// Diese Funktion bereitet das Reloaden der Einrichtung vor
+static void start_reload_furniture()
+{  
+  // Wenn es keine Moebel gibt, ist das Laden beendet.
+  if (!mappingp(furniture)) return;
+  if (broken_furniture==0) broken_furniture=([]);
+  
+  // Falls ein Key von furniture 0 ist, wird dieser geloescht. 
+  m_delete(furniture,0);
+  
+  // Laden des Furniture anstossen
+  load_furniture();  
+}
+
+
diff --git a/d/seher/haeuser/modules/usercmd.c b/d/seher/haeuser/modules/usercmd.c
new file mode 100644
index 0000000..a83b819
--- /dev/null
+++ b/d/seher/haeuser/modules/usercmd.c
@@ -0,0 +1,341 @@
+//  usercmd.c -- Modul fuer benutzerdefinierte Befehle
+//
+//  (c) 1995 Wargon@MorgenGrauen
+//
+// $Id: usercmd.c,v 1.4 2003/11/15 13:48:46 mud Exp $
+//
+
+#define NEED_PROTOTYPES
+#include "../haus.h"
+#include <properties.h>
+#include <wizlevels.h>
+#include <thing/properties.h>
+
+private string ucFilter(string str);
+private string ucText(string str);
+private void splitCmd(string *cmd, string *verb, string *para);
+
+create()
+{
+  Set(H_COMMANDS, SAVE, F_MODE);
+  Set(H_COMMANDS, ([]));
+
+  AddCmd("", "userCommands", 1);
+}
+
+/*
+ * AddUserCmd: eigenes Kommando hinzufuegen
+ *  cmd: String oder Array von Strings. Enthaelt entweder nur die Verben oder
+ *         die kompletten Befehle (Verb + Parameter). Siehe auch pa.
+ *  pa:  Zahl oder Array von Strings. Falls pa eine Zahl ist, so enthaelt cmd
+ *         die kompletten Befehle (samt Parametern). Ansonsten enthaelt pa die
+ *         Parameter fuer das entsprechende Verb in cmd.
+ *         Ist pa = ({ "@NF@"}) , so handelt es sich um ein notify_fail, das an
+ *         die Beschreibung des Verbs (falls vorhanden) angehaengt wird.
+ *  me:  Text fuer den Ausfuehrenden. Der Text muss schon geparsed worden
+ *         sein!
+ *  oth: Text fuer die Umstehenden oder 0. Der Text muss schon geparsed wor-
+ *         den sein!
+ */
+varargs void
+AddUserCmd(mixed cmd, mixed pa, string me, string oth)
+{
+  int v,p;
+  mapping cmds, desc;
+  string *verb, *para, txt;
+
+  cmds = Query(H_COMMANDS);
+  if (stringp(cmd))
+    verb = ({ cmd });
+  else
+    verb = cmd;
+
+  if (intp(pa))
+    splitCmd(verb[0..], &verb, &para);
+  else {
+    if (stringp(pa))
+      para = ({ pa });
+    else if (pointerp(pa))
+      para = pa;
+    for (desc = ([]), p=sizeof(para)-1; p>=0; p--)
+      desc += ([ para[p] : me; oth ]);
+  }
+
+  for (v = sizeof(verb)-1; v>= 0; v--) {
+    if (member(cmds, verb[v])) {
+      if (intp(pa))
+        cmds[verb[v]] += ([para[v] : me; oth ]);
+      else
+        cmds[verb[v]] += desc;
+    }
+    else {
+      if (intp(pa))
+        cmds += ([ verb[v] : ([para[v] : me; oth ]) ]);
+      else
+        cmds += ([ verb[v] : desc ]);
+    }
+  }
+  Set(H_COMMANDS, cmds);
+}
+
+/*
+ * RemUserCmd: Kommando(s) wieder entfernen
+ *  com: String oder Array von Strings. Enthaelt das zu loeschende Kommando
+ *         (Verb oder Verb + Parameter).
+ *  all: Falls != 0, so werden die Verben aus com komplett mit allen Para-
+ *         metern geloescht.
+ */
+varargs void
+RemUserCmd(mixed com, int all)
+{
+  mapping cmd, tmp;
+  string *verb, *para;
+  int v, p;
+
+  cmd = Query(H_COMMANDS);
+  splitCmd(stringp(com) ? ({com}) : com, &verb, &para);
+
+  if (all)
+    for (v=sizeof(verb)-1; v>=0; v--)
+      cmd = m_copy_delete(cmd, verb[v]);
+  else {
+    for (v=sizeof(verb)-1; v>=0; v--) {
+      if (tmp = cmd[verb[v]]) {
+        for (p=sizeof(para)-1; p>=0; p--)
+          tmp = m_copy_delete(tmp, para[p]);
+        cmd[verb[v]] = tmp;
+      }
+    }
+  }
+  Set(H_COMMANDS,cmd);
+}
+
+/*
+ * userCommands: Auswertung der benutzerdefinierten Befehle.
+ */
+static int
+userCommands(string str)
+{
+  mapping ucmd, uparm;
+  string *parts, text;
+  int i;
+
+  ucmd = QueryProp(H_COMMANDS);
+  str = this_player()->_unparsed_args(1)||"";
+  if (uparm = ucmd[query_verb()]) {
+    if (member(uparm, str)) {
+      text = ucText(uparm[str,0]);
+      if (sizeof(old_explode(text, "\n")) >= this_player()->QueryProp(P_SCREENSIZE))
+        this_player()->More(capitalize(text)[0..<2]);
+      else
+        write(capitalize(text));
+
+      if (uparm[str,1])
+        say(ucText(uparm[str,1]));
+
+      if (member(m_indices(QueryProp(P_EXITS)), query_verb()) == -1)
+        return 1;
+    }
+    else {
+      if (str && str != "" && text = uparm["@NF@"])
+        notify_fail(implode(old_explode(text,"@F"),str));
+    }
+  }
+  return 0;
+}
+
+/*** Functions private to this module... ***/
+
+/*
+ * ucFilter: Ersetzen von Name, Personal- und Possessivpronomen.
+ *  Gibt den ersetzten String zurueck.
+ */
+private string
+ucFilter(string str)
+{
+  int p,g;
+
+  switch(str[0..1]) {
+    case "@W": // Name in entsprechendem Fall...
+      return this_player()->name(to_int(str[2..2]));
+    case "@P": // Personalpronomen in entprechendem Fall...
+      return this_player()->QueryPronoun(to_int(str[2..2]));
+    case "@B": // Possesivpronomen in entprechendem Fall...
+      p = to_int(str[4..4]);
+      g = to_int(str[3..3]);
+      return this_player()->QueryPossPronoun(g, to_int(str[2..2]), p);
+  }
+  return str;
+}
+
+/*
+ * ucText: Rassen- und geschlechtsspezifische Teile bearbeiten sowie
+ *  Namen und Pronomina einsetzen.
+ *
+ *  str enthaelt den String, der beim Beschreiben des Befehls
+ *  eingegeben wurde. Bis auf den Default-Text sind alle Teile
+ *  optional. Der String kann folgenden Aufbau haben:
+ *  ------ schnipp ------
+ *  Default-Text, der ausgegeben wird, wenn die folgenden spezielleren
+ *  Texte nicht auftreten oder auf den Ausfuehrenden nicht zutreffen
+ *  @NAME:nameA
+ *  Text, der ausgegeben wird, wenn der Spieler "nameA" das Kommando
+ *  eingegeben hat
+ *  @NAME:nameB
+ *  Und so weiter fuer Spieler "nameB" etc...
+ *  @RA
+ *  Text, der ausgegeben wird, wenn der Ausfuehrende auf der Erlaube-
+ *  Liste des Hauses steht.
+ *  @RE
+ *  Text, der ausgegeben wird, wenn der Ausfuehrende ein Elf
+ *  ist. Ebenso gibt es @RD fuer Dunkelelfen, @RF fuer Felinen,
+ *  @RH fuer Hobbits, @RM fuer Menschen, @RG fuer Goblins  und @RZ 
+ *  fuer Zwerge.
+ *  ------ schnapp ------
+ *
+ *  Der Default-Text muss immer am Anfang stehen. Die anderen Bloecke
+ *  koennen in beliebiger Reihenfolge folgen. Die Reihenfolge, in der
+ *  die Bloecke betrachtet werden, ist dabei folgende:
+ *  - zuerst werden @NAME-Bloecke untersucht
+ *  - dann wird @RA (Erlaube-Liste) getestet
+ *  - danach wird die Rasse ueberprueft (@RD/@RE/@RF/@RH/@RM/@RZ/@RG)
+ *  - zuletzt wird der Default-Text betrachtet
+ *
+ *  Innerhalb jedes der Bloecke kann man noch mit @G zwischen
+ *  maennlichen und weiblichen Vertretern unterscheiden (bei @NAME:
+ *  macht das aber wenig Sinn). Beispiel:
+ *  ------ schnipp ------
+ *  @RZ
+ *  Der Zwerg war maennlich
+ *  @G
+ *  Der Zwerg war weiblich
+ *  ------ schnapp ------
+ *
+ *  Die Funktion gibt den fuer den Ausfuehrenden zutreffenden Text
+ *  zurueck.
+ */
+private string
+ucText(string str)
+{
+  string *parts, *names, *lnames;
+  int i, n;
+
+  // Text nach Namen- und Rassentrennern aufteilen
+  parts = regexplode(str, "(@NAME:[A-Za-z1-9]*\n)|(@R[A-Z]\n)");
+  i = -1;
+
+  if (sizeof(parts) > 1) {
+
+    // Zuerst wird nach Namenstrennern gesucht
+    names = regexp(parts, "@NAME:");
+
+    if (sizeof(names) > 0) {
+      // ein kleiner Umweg, da der Name im Eingabestring nicht
+      // notwendigerweise in Kleinbuchstaben vorliegt.
+      lnames = map(names, #'lower_case);
+      n = member(lnames, "@name:"+getuid(this_player())+"\n");
+
+      if (n >= 0) {
+        i = member(parts, names[n]);
+      }
+    }
+
+    // Kein passender Namenstrenner gefunden: Erlaube-Liste
+    // ueberpruefen
+    if (i<0 && allowed_check(this_player())) {
+      i=member(parts, "@RA\n");
+    }
+
+    // Weder Namenstrenner noch Erlaube-Liste passen: Rasse
+    // ueberpruefen
+    if (i<0) {
+      switch(this_player()->QueryProp(P_RACE)) {
+        case "Zwerg":
+          i=member(parts, "@RZ\n");
+          break;
+        case "Elf":
+          i=member(parts, "@RE\n");
+          break;
+        case "Feline":
+          i=member(parts, "@RF\n");
+          break;
+        case "Hobbit":
+          i = member(parts, "@RH\n");
+          break;
+        case "Dunkelelf":
+          i = member(parts, "@RD\n");
+          break;
+        case "Goblin":
+          i = member(parts, "@RG\n");
+          break;
+        case "Ork": 
+          i = member(parts, "@RO\n");
+          break;
+        default:
+          i=member(parts, "@RM\n");
+          break;
+      }
+    }
+    
+    // Den richtigen Teil des Strings herauspicken
+    if (i>-1)
+      str = parts[i+1];
+    else
+      str = parts[0];
+  }
+  if (sizeof(parts = old_explode(str, "@G\n"))==2)
+    str = parts[(this_player()->QueryProp(P_GENDER) == MALE ? 0 : 1)];
+
+  parts = regexplode(str, "(@W[0-3]|@P[0-3]|@B[0-3][0-2][0-1])");
+  parts = map(parts, #'ucFilter);
+  return implode(parts, "");
+}
+
+/*
+ * splitCmd: Komplettes Kommando in Arrays von Verben und Parametern zerlegen.
+ *  cmd: Array von Strings. Dieses Array enthaelt die aufzuspaltenden Befehle.
+ *  verb: Referenz auf ein Array von Strings fuer die Verben.
+ *  para: Referenz auf ein Array von Strings fuer die Parameter.
+ */
+private void splitCmd(string *cmd, string *verb, string *para)
+{
+  int c, sp;
+
+  for (verb = ({}), para = ({}), c = sizeof(cmd)-1; c>=0; c--) {
+    if ((sp=member(cmd[c], ' ')) >= 0) {
+      verb += ({ cmd[c][0..sp-1] });
+      para += ({ cmd[c][sp+1..] });
+    }
+    else {
+      verb += ({ cmd[c] });
+      para += ({ "" });
+    }
+  }
+}
+
+// $Log: usercmd.c,v $
+// Revision 1.4  2003/11/15 13:48:46  mud
+// @RD als Trenner fuer Dunkelelfen
+//
+// Revision 1.3  2000/12/03 17:15:40  mud
+// ucText: Neuer Trenner @NAME:foo, mit dem man in Seherhausbefehlen
+// Texte fuer bestimmte Spieler vorsehen kann.
+//
+// Revision 1.2  2000/08/20 20:38:40  mud
+// @RF als Trenner fuer Felinen
+//
+// Revision 1.1.1.1  2000/08/20 20:22:42  mud
+// Ins CVS eingecheckt
+//
+// Revision 1.4  1996/04/19  23:10:52  Wargon
+// @RA fuer Leute mit Erlaubnis
+//
+// Revision 1.3  1995/10/31  12:59:52  Wargon
+// @RH fuer Hobbits
+//
+// Revision 1.2  1995/06/22  19:48:31  Wargon
+// Bugfix in userCommands()
+//
+// Revision 1.1  1995/04/21  09:22:50  Wargon
+// Initial revision
+//