Added public files

Roughly added all public files. Probably missed some, though.
diff --git a/items/kraeuter/eiszahn.c b/items/kraeuter/eiszahn.c
new file mode 100644
index 0000000..8f023de
--- /dev/null
+++ b/items/kraeuter/eiszahn.c
@@ -0,0 +1,36 @@
+// (c) 2001 by Padreic (Padreic@mg.mud.de)
+
+#pragma strong_types,rtt_checks
+
+#include <properties.h>
+#include <items/kraeuter/kraeuter.h>
+#include <items/kraeuter/kraeuterliste.h>
+
+inherit STDPLANT;
+void create()
+{ 
+  ::create();
+  customizeMe(EISZAHN);
+  SetProp(P_NAME,     "Eiszahn");
+  SetProp(P_NAME_ADJ, "kalt");
+  SetProp(P_GENDER,   MALE);
+  SetProp(P_LONG,
+    "Der Eiszahn ist ein ausgesprochen merkwuerdiges Kraut.\n"
+   +"Typisch sind die wie kleine Zaehne angeordneten Blaetter, die\n"
+   +"am Hauptstengel entlang wachsen und sich stets zum Himmel recken.\n"
+   +"Ausserdem strahlt das Kraut spuerbar Kaelte aus.\n");
+  SetProp(PLANT_ROOMDETAIL,
+     "Der Eiszahn fuehlt sich auf dem Eisboden sichtlich wohl.\n");
+  SetProp(P_SHORT,    "Ein Eiszahn");
+  AddId(({ "zahn", "eiszahn" }));
+  AddDetail("kraut",
+     "Der Eiszahn ist da sehr eigen: Er ist kein Pilz, er ist ein Kraut.\n");
+  AddDetail("kaelte", "Huah, ganz schoen kalt.\n");
+  AddDetail("blaetter",
+    "Die Blaetter sehen aus wie kleine Zaehne. Sie sind ganz weiss und\n"
+   +"ordentlich aufgereiht.\n");
+  AddDetail("hauptstengel",
+     "Eigentlich hat das Kraut nur einen Staengel, der leicht zur Seite haengt\n"
+    +"weil die 'Zaehne' ausreichend Gewicht haben.\n");
+  AddDetail("zaehne", "So nennt man die Blaetter des Eiszahns.\n");
+}
diff --git a/items/kraeuter/glockenblume.c b/items/kraeuter/glockenblume.c
new file mode 100644
index 0000000..163ad63
--- /dev/null
+++ b/items/kraeuter/glockenblume.c
@@ -0,0 +1,49 @@
+// (c) 2001 by Padreic (Padreic@mg.mud.de)
+// beschreibung magdalena@morgengrauen.de - 8.12.2003
+
+#pragma strong_types,rtt_checks
+
+#include <properties.h>
+#include <items/kraeuter/kraeuter.h>
+#include <items/kraeuter/kraeuterliste.h>
+
+inherit STDPLANT;
+
+#define BS(x) break_string(x, 78)
+
+
+void create()
+{
+  ::create();
+  customizeMe(GLOCKENBLUME);
+  SetProp(P_NAME,     "Glockenblume");
+  SetProp(P_NAME_ADJ, "schoen");
+  SetProp(P_GENDER,   FEMALE);
+  SetProp(P_LONG, BS(
+    "Die Bluetensterne in blau-violett sind ein toller Blickfang, wie "
+   +"sie am langen Stiel im Wind hin- und hernicken. Die "
+   +"glockenfoermige Blumenkrone mit ihren fuenf breiten, nicht bis zur "
+   +"Mitte reichenden Zipfeln erfreut Dich mit ihrer Schoenheit."));
+  SetProp(PLANT_ROOMDETAIL, 
+    "Eine anmutige, schoene Glockenblume nickt dir auffordernd zu. Ob man sie "
+   +"pfluecken kann?\n");
+  SetProp(P_SHORT,    "Eine Glockenblume");
+  AddId(({ "blume", "glockenblume" }));
+  
+  AddDetail(({"blickfang","schoenheit"}),BS(
+     "Diese Glockenblume besticht durch ihre Schoenheit. Ihre Farben sind " 
+    +"sind ein wahrer Blickfang.\n"));
+  AddDetail(({"farben","blueten"}),BS(
+     "Die Blueten der Glockenblume sind blau-violett.\n"));
+  AddDetail("blumenkrone",BS(
+     "Die Blume ist glockenfoermig. Daher hat die Pflanze ihren Namen.\n"));
+  AddDetail("pflanze",BS(
+     "Diese Pflanze ist eindeutig eine Glockenblume.\n"));
+  AddDetail(({"bluetensterne","sterne","zipfel","zipfeln"}),BS(
+     "Bemerkenswert an der Pflanze sind die Bluetensterne. Die blau-violetten "
+    +"Zipfel bilden die Bluete der Pflanze, was sehr auffaellig ist. Diese "
+    +"Bluetensterne gaben der Blume ihren Namen.\n"));
+
+}
+
+
diff --git a/items/kraeuter/kessel.c b/items/kraeuter/kessel.c
new file mode 100644
index 0000000..909eead
--- /dev/null
+++ b/items/kraeuter/kessel.c
@@ -0,0 +1,335 @@
+// (c) 2003 by Thomas Winheller (padreic@mg.mud.de)
+// Bei diesem File handelt es sich um einen universellen
+// Kessel zum Brauen von Traenken. Damit er sich in den
+// Raum entsprechend einpassen kann, ist er invis.
+
+// Ein Raum, in dem Traenke und Gift gebraut werden koennen
+// soll, braucht nichts weiter machen als:
+// -  eine Moeglichkeit zu implementieren Wasser in den Kessel
+//    zu fuellen. Hierzu muss dann mittels AddWater(3000)
+//    insgesamt 3l Wasser eingefuellt werden. AddWater gibt die
+//    Menge des tatsaechlich noch eingefuelltn Wassers zurueck.
+// Anmerkung: bisher enthaelt /obj/flasche noch keine Moeglichkeit
+//            Fluessigkeiten von einem Fluessigkeitencontainer in
+//            einen anderen zu schuetten, aber vielleicht aendert
+//            das ja irgendwann mal wer - der Kessel kann dann
+//            durch alle diese Flaschen befuellt werden!
+#pragma strong_types,rtt_checks
+
+inherit "/std/container";
+
+#include <defines.h>
+#include <properties.h>
+#include <moving.h>
+#include <fishing.h>
+#include <items/kraeuter/kraeuter.h>
+#include <items/flasche.h>
+
+#ifndef BS
+#  define BS(x)             break_string(x, 78)
+#endif
+
+private int wassermenge;
+
+// Aktueller Nutzer des Kessels in Form der Spieler-UID 
+private string current_user;
+
+// Zeitpunkt, wann die Sperre endet. 
+private int timeout;
+
+protected void create()
+{
+  ::create();
+  SetProp(P_SHORT, "Ein schwerer Kessel");
+  // \n werden in long() eingefuegt
+  SetProp(P_LONG,
+    "Ein kleiner, aber sehr schwerer Kessel, in dem die verschiedensten "
+    "Traenke gebraut werden koennen."); 
+  SetProp(P_NAME, "Kessel");
+  SetProp(P_NAME_ADJ, "klein");
+  SetProp(P_MATERIAL, MAT_BRONCE);
+  SetProp(P_NOGET, "Der Kessel ist zu schwer, um ihn einfach mitnehmen "
+     "zu koennen.\n");
+  SetProp(P_MAX_WEIGHT, 100000); // ein _wirklich_ grosser Kessel ;o)
+  SetProp(P_WEIGHT, 50000);
+  SetProp(P_VALUE, 25000);
+  SetProp(P_MAX_OBJECTS, 9); // max. 8 Kraeuter + Wasser
+  SetProp(P_GENDER, MALE);
+  SetProp(P_LIQUID, 3000); // in den Kessel passen 3l :o)
+  AddId(({"kessel", KESSELID}));
+  AddAdjective(({"klein", "kleiner"}));
+  AddCmd("leer|leere&@ID&aus", "cmd_leeren",
+    "Was moechtest Du leeren?|Moechtest Du den Kessel etwa ausleeren?");
+  AddCmd(({"brau", "braue", "koch", "koche"}), "cmd_brauen");
+  AddCmd("fuell|fuelle&trank|heiltrank|ergebnis|kesselinhalt|inhalt&in&"
+    "@PRESENT", "cmd_fuellen",
+    "Was willst Du fuellen?|Willst Du etwas in etwas fuellen?|"
+    "Worein willst Du den Kesselinhalt fuellen?");
+  AddCmd(({"tauch", "tauche"}), "cmd_tauchen");
+}
+
+#define TRANKFERTIG "_lib_p_krauttrankfertig"
+
+private int check_busy(int useronly)
+{
+  if (useronly)
+    return current_user && current_user != getuid(PL);
+  // Timeout darf noch nicht abgelaufen sein. Wird beim Entleeren geprueft.
+  return current_user && timeout > time() && current_user != getuid(PL);
+}
+
+private void clear_kessel()
+{
+  all_inventory()->remove(1);
+  // gespeicherte Daten nullen
+  wassermenge = current_user = timeout = 0;
+  SetProp(P_WATER, 0);
+  SetProp(TRANKFERTIG, 0);
+}
+
+int AddWater(int menge)
+{
+  // Wenn ein User eingetragen ist, dieser nicht PL ist und die Sperre
+  // auch noch nicht abgelaufen ist, dann wird das Einfuellen von Wasser
+  // verhindert. Nutzer der Funktion muessen die Rueckgabewerte pruefen
+  // und entsprechende Meldungen ausgeben.
+  if (check_busy(1))
+    return -1;
+
+  int old = wassermenge;
+  wassermenge = min(wassermenge+menge, QueryProp(P_LIQUID));
+
+  if (wassermenge<=0)
+  {
+    wassermenge=0; // wasser entnahme
+    SetProp(P_WATER, 0);
+  }
+  else
+     SetProp(P_WATER, W_DEAD);
+  return wassermenge-old;
+}
+
+static int cmd_leeren(string str)
+{
+  if (!QueryProp(P_WATER) && !sizeof(all_inventory()))
+  {
+    write(BS("Im Kessel ist bisher noch nichts enthalten, was Du ausleeren "
+             "koenntest."));
+  }
+  // Es gibt einen aktuellen User, dieser ist nicht PL, und der Timeout
+  // ist auch noch nicht abgelaufen => Finger weg.
+  else if (check_busy(0))
+  {
+    tell_object(PL, BS(
+      "Der Inhalt des Kessels wurde erst kuerzlich von jemand anderem dort "
+      "hineingefuellt. Du solltest Dich nicht daran zu schaffen machen."));
+  }
+  write("Vorsichtig nimmst Du den Kessel und schuettest seinen Inhalt in den Abfluss.\n");
+  say(BS(PL->Name(WER)+" nimmt den Kessel und schuettet den Inhalt in den Abfluss."));
+  clear_kessel();
+  return 1;
+}
+
+/*#include "/d/erzmagier/boing/balance/balance.h"
+#include <wizlevels.h>
+#define TESTER (BTEAM+({"elendil","saray", "huraxprax"}))*/
+static int cmd_brauen(string str)
+{
+  /*if (!IS_ARCH(this_interactive())
+      && !member(TESTER, PL->query_real_name()))
+    return 0;*/
+  
+  notify_fail("WAS moechtest Du brauen?\n");
+  if (!str) return 0;
+/*  if (str=="zaubertrank") {
+     write("Ohne passendes Rezept duerfte dies schwierig werden...\n");
+     return 1;
+  }*/
+  if (member(({"trank","zaubertrank","kraeutertrank","tee","kraeutertee"}),
+             str)<0) 
+    return 0;
+
+  if (check_busy(1)) {
+    tell_object(PL, BS(
+      "An dem Trank in dem Kessel arbeitet gerade noch "+
+      capitalize(current_user)+". Du kannst hoechstens in ein paar "
+      "Minuten versuchen, den Inhalt des Kessels auszuleeren. Selbst "
+      "Hand anzulegen, wuerde man Dir sicherlich uebelnehmen."));
+  }
+  else if (!QueryProp(P_WATER)) {
+    write("Vielleicht solltest Du zunaechst noch Wasser in den Kessel "
+      "fuellen...\n");
+  }
+  else if (wassermenge<QueryProp(P_LIQUID)) {
+    write("Vielleicht solltest Du zunaechst noch etwas mehr Wasser in "
+      "den Kessel\nfuellen...\n");
+  }
+  else if (sizeof(all_inventory())<3) {
+    write("Derzeit ist Dein Trank noch ein wenig waessrig.\n"
+         +"Mindestens drei Zutaten muessen in einen Trank schon hinein.\n");
+  }
+  else {
+    write(BS("Vorsichtig laesst Du den Kessel etwas naeher zur Feuerstelle "
+      "runter und wartest unter gelegentlichem Ruehren, bis er kocht. "
+      "Dein Trank sollte nun fertig sein und Du kannst ihn nun abfuellen. "
+      "Was er wohl fuer eine Wirkung haben wird?"));
+    say(BS(PL->Name()+" laesst den Kessel zur Feuerstelle herunter und "
+      "ruehrt langsam darin herum. Nach einer Weile kocht die Fluessigkeit "
+      "darin, und "+PL->Name(WER)+" stellt das Ruehren wieder ein."));
+    SetProp(TRANKFERTIG, 1);
+  }
+  return 1;
+}
+
+static int cmd_fuellen(string str,mixed* params)
+{
+/*  if (!IS_ARCH(this_interactive())
+      && !member(TESTER, this_player()->query_real_name()))
+    return 0;*/
+ 
+  if ( !QueryProp(TRANKFERTIG) ) 
+  {
+    write("Im Kessel befindet sich aber gar kein Trank.\n");
+  }
+  // Abfuellen ist nur fuer den Spieler moeglich, der die Kraeuter
+  // reingetan hat.
+  else if (check_busy(1))
+  {
+    tell_object(PL, BS("Diesen Trank hast Du doch gar nicht selbst "
+      "gebraut! Du solltest noch eine Weile warten, ob "+
+      capitalize(current_user)+" ihn nicht doch noch selbst abfuellen "
+      "will. Wenn nicht, koenntest Du nur noch versuchen, den Kessel "
+      "auszuleeren - jedenfalls es erscheint Dir viel zu riskant, das "
+      "Gebraeu selbst zu trinken, das "+capitalize(current_user)+
+      " da zusammengeruehrt hat."));
+  }
+  else if (BLUE_NAME(params[2])==TRANKITEM)
+  {
+    int ret = params[2]->Fill(all_inventory());
+    switch( ret ) {
+      case -3:
+      case -1:
+        write(BS("Fehler beim Fuellen der Phiole. Bitte sag einem Magier "
+          "Bescheid und nenne den Fehlercode "+ret+"."));
+        break;
+      case -2:
+        write(BS("Die Phiole ist bereits gefuellt."));
+        break;
+      default:
+        write(BS("Du nimmst den Kessel und fuellst seinen konzentrierten "
+          "Inhalt in Deine Glasflasche. Hoffentlich ist Dir hier ein "
+          "toller Trank gelungen."));
+        say(BS(PL->Name(WER)+" nimmt den Kessel und fuellt dessen "
+          "konzentrierten Inhalt in eine kleine Glasflasche. Was "+
+          PL->QueryPronoun(WER)+" da wohl gebraut hat?"));
+        clear_kessel();
+        break;
+    }
+  }
+  else {
+    write("Darein kannst Du den Trank leider nicht fuellen.\n");
+  }
+  return 1;
+}
+
+varargs string long(int mode)
+{
+  string inv_desc = make_invlist(PL, all_inventory(ME));
+  if (inv_desc=="") {
+    if (QueryProp(P_WATER))
+      return BS(Query(P_LONG)+" Derzeit ist er lediglich mit Wasser "
+        "gefuellt.");
+    return BS(Query(P_LONG)+" Er ist im Moment leer.");
+  }
+  if (QueryProp(P_WATER))
+    return BS(Query(P_LONG)+" Er ist mit Wasser gefuellt, und Du siehst "
+              +"folgende Kraeuter in ihm schwimmen:")+inv_desc;
+  return BS(Query(P_LONG))+"Er enthaelt:\n"+inv_desc;
+}
+
+static int _query_invis()
+{
+  if (member(({"nimm", "nehm", "nehme", "leg", "lege",
+               "steck", "stecke"}), query_verb())!=-1) return 0;
+  return Query(P_INVIS, F_VALUE);
+}
+/*
+varargs string name(int casus, int demon)
+{
+  SetProp(P_INVIS, 0);
+  string ret=::name(casus, demon);
+  SetProp(P_INVIS, 1);
+  return ret;
+}
+*/
+varargs int PreventInsert(object ob)
+{
+  int plantid = ob->QueryPlantId(); 
+  int *inv = all_inventory(ME)->QueryPlantId();
+  
+  // es koennen natuerlich nur echte Kraeuter in den Kessel gelegt werden
+  if ( plantid<=0 || !IS_PLANT(ob) )
+    return 1;
+  
+  if (QueryProp(TRANKFERTIG))
+  {
+    tell_object(PL, break_string(
+      "Im Kessel ist ein fertiger Trank. Wenn Du etwas neues machen "
+      "willst, leere den Kessel oder fuelle den Trank ab."));
+    return 1;
+  }
+  // Reintun darf nur der aktuelle User, es sei denn, ein anderer Spieler 
+  // faengt frisch an, wenn der Kessel gerade unbenutzt ist.
+  else if ( check_busy(1) )
+  {
+    tell_object(PL, BS("Dieser Kessel wurde bis gerade eben noch von "+
+      capitalize(current_user)+" genutzt. Warte besser, bis der Kessel "
+      "wieder frei ist."));
+    return 1;
+  }
+  else if ( !SECURE("krautmaster")->CanUseIngredient(PL, plantid) ) 
+  {
+    // mit Kraeutern ueber die man nichts weiss, kann man nicht brauen
+    tell_object(PL, BS("Ueber die Wirkungsweise von "+ob->name(WEM)+
+      " weisst Du bisher leider wirklich ueberhaupt nichts."));
+    return 1;
+  }
+  else if ( sizeof(inv) >= 8 ) 
+  {
+    tell_object(PL, BS("Mehr als acht Zutaten sollte man nie zu einem "
+      "Trank vereinigen, und es sind schon acht im Kessel."));
+    return 1;
+  }
+  else if (member(inv, plantid)>-1) 
+  {
+    tell_object(PL, BS("Im Kessel befindet sich bereits "+ob->name(WER)+
+      ". Du kannst kein Kraut mehr als einmal verwenden."));
+    return 1;
+  }
+  current_user = getuid(PL);
+  timeout = time()+120;
+  return ::PreventInsert(ob);
+}
+
+int PreventLeave(object ob, mixed dest)
+{
+  if (QueryProp(P_WATER)) {
+    tell_object(PL, BS("Es befindet sich bereits Wasser im Kessel, die "
+      "einzelnen Zutaten kannst Du nun leider nicht mehr einzeln "
+      "rausholen, ohne den ganzen Kessel auszuleeren."));
+      return 1;
+  }
+  // Rausnehmen ist nur fuer den aktuellen User moeglich. Alle anderen
+  // koennen auch nach Ablauf der Zeitsperre nur ausleeren.
+  else if ( check_busy(1) ) {
+    tell_object(PL, BS("Du hast "+ob->name(WEN,1)+" nicht dort hineingetan, "
+      "also kannst Du "+ob->QueryPronoun(WEN)+" auch nicht herausnehmen. "
+      "Zumindest vorerst nicht. Sollte "+capitalize(current_user)+
+      "nicht innerhalb der naechsten paar Minuten weiterbrauen, kannst "
+      "Du den Kesselinhalt zumindest mit einem guten Schluck Wasser "
+      "rausspuelen."));
+    return 1;
+  }
+  return ::PreventLeave(ob, dest);
+}
+
diff --git a/items/kraeuter/kicherpilz.c b/items/kraeuter/kicherpilz.c
new file mode 100644
index 0000000..a4b10a3
--- /dev/null
+++ b/items/kraeuter/kicherpilz.c
@@ -0,0 +1,51 @@
+// (c) 2001 by Padreic (Padreic@mg.mud.de)
+// Beschrieben von Magdalena :o) 08.08.03
+
+#pragma strong_types,rtt_checks
+
+#include <properties.h>
+#include <items/kraeuter/kraeuter.h>
+#include <items/kraeuter/kraeuterliste.h>
+
+inherit STDPLANT;
+
+#define BS(x) break_string(x, 78)
+
+void create()
+{
+  ::create();
+  customizeMe(KICHERPILZ);
+  SetProp(P_NAME,     "Kicherpilz");
+  SetProp(P_NAME_ADJ, "lustig");
+  SetProp(P_GENDER,   MALE);
+  SetProp(P_LONG,     
+    "Er hat ein gruenes Kaeppchen auf und ist fuer einen Pilz relativ gross. Ab und\n"
+   +"an zuckt er und kichert albern. Daher hat er wohl auch seinen Namen.\n");
+  SetProp(PLANT_ROOMDETAIL, 
+    "Ein besonders praechtiges Exemplar eines Kicherpilzes winkt dir\n"
+   +"aufdringlich zu, also ob es scharf darauf waere, gepflueckt zu werden.\n");
+  SetProp(P_SHORT,    "Ein Kicherpilz");
+  AddId(({ "pilz", "kicherpilz" }));
+  
+  AddDetail("kaeppchen",BS(
+     "Der Pilz scheint sich fuer die neueste Mode zu interessieren. "
+    +"Mit dem gruenen Kaeppchen ist er nach dem letzten Schrei gekleidet."));
+  AddDetail("mode",BS(
+     "Du schaust an deiner Kleidung hinunter - naja, mit dem Pilz "
+    +"kannst du nicht mithalten."));
+  AddDetail("schrei",BS(
+     "Sei ehrlich: Bei dem Gruen kann man nur schreien!"));
+  AddDetail("gruen",BS(
+     "Eigentlich ist es doch ganz gut, sich nicht fuer Mode zu "
+    +"interessieren, denn das Gruen schmerzt in den Augen."));
+ 
+  set_next_reset(200+random(200));
+}
+
+void reset()
+{
+   set_next_reset(200+random(200));
+   if (environment()) 
+      tell_object(environment(), "Der Kicherpilz kichert Dich an.\n"); 
+   ::reset();
+}
diff --git a/items/kraeuter/kraut.c b/items/kraeuter/kraut.c
new file mode 100644
index 0000000..55b3429
--- /dev/null
+++ b/items/kraeuter/kraut.c
@@ -0,0 +1,26 @@
+#pragma strong_types,rtt_checks
+
+#include <items/kraeuter/kraeuter.h>
+
+inherit STDPLANT;
+
+void create()
+{
+  ::create();
+  //printf("create(): %O\n",load_name());
+  //replace_program();
+  // Wenn es das Standardfile geclont wird, wird customizeMe(0) gerufen, was
+  // den Kraeuter-VC dazu bringt, dieses mit den Daten des Krautes zu
+  // konfigurieren, was der VC gerade erzeugt hat.
+  // load_name() bleibt fuer alle VC-erzeugten Kraeuter-Blueprints das
+  // PLANTITEM.
+  // hier darf _nicht_ previous_object()->CustomizeObject() verwandt werden,
+  // da nur die Blueprint wirklich vom VC erzeugt wird. Fuer die Clones
+  // ruft der Driver den VC nicht jedesmal erneut auf.
+  if (load_name() == PLANTITEM)
+    customizeMe(0);
+}
+
+string GetOwner() {
+  return "Padreic";
+}
diff --git a/items/kraeuter/trank.c b/items/kraeuter/trank.c
new file mode 100644
index 0000000..757b42f
--- /dev/null
+++ b/items/kraeuter/trank.c
@@ -0,0 +1,13 @@
+#pragma strong_types,rtt_checks
+
+#include <items/kraeuter/kraeuter.h>
+
+inherit STDTRANK;
+/*
+void create()
+{
+  ::create();
+  replace_program();
+}
+*/
+
diff --git a/items/kraeuter/trockner.c b/items/kraeuter/trockner.c
new file mode 100644
index 0000000..62e7417
--- /dev/null
+++ b/items/kraeuter/trockner.c
@@ -0,0 +1,475 @@
+#pragma strong_types, save_types, rtt_checks
+#pragma no_inherit, no_shadow
+
+inherit "/std/container";
+
+#include <properties.h>
+#include <defines.h>
+#include <items/kraeuter/kraeuter.h>
+
+#define BS(x) break_string(x, 78, 0, BS_LEAVE_MY_LFS)
+
+// Enthaelt die raumabhaengig variable Kurzbeschreibung, die der Trockner 
+// annimmt, wenn gerade ein Kraut getrocknet wird.
+private string short_desc;
+
+// Globale Variable fuer die Qualitaet, damit man den Wert nicht im 
+// call_out() mitgeben muss, und sie somit auch nicht mit call_out_info()
+// abfragbar ist.
+private int drying_quality;
+
+private void dry_plant(object kraut, string *msgs);
+private void destroy_herb(object kraut);
+private string my_short();
+private int|string my_noget();
+private string my_long();
+private string* my_ids();
+private string|string* my_name();
+private mixed my_mat();
+
+protected void create() {
+  if ( !clonep(ME) ) {
+    set_next_reset(-1);
+    return;
+  }
+  ::create();
+
+  Set(P_SHORT, #'my_short, F_QUERY_METHOD);
+  Set(P_LONG, #'my_long, F_QUERY_METHOD);
+  Set(P_NOGET, #'my_noget, F_QUERY_METHOD);
+  Set(P_IDS, #'my_ids, F_QUERY_METHOD);
+  Set(P_NAME, #'my_name, F_QUERY_METHOD);
+  Set(P_MATERIAL, #'my_mat, F_QUERY_METHOD);
+  // Properties zu securen ist vielleicht etwas sehr paranoid.
+  Set(P_SHORT, SECURED|NOSETMETHOD, F_MODE_AS);
+  Set(P_LONG, SECURED|NOSETMETHOD, F_MODE_AS);
+  Set(P_NOGET, SECURED|NOSETMETHOD, F_MODE_AS);
+  Set(P_IDS, SECURED|NOSETMETHOD, F_MODE_AS);
+  Set(P_NAME, SECURED|NOSETMETHOD, F_MODE_AS);
+  SetProp(P_MAX_OBJECTS,1);
+  SetProp(P_MAX_WEIGHT,100000);
+  SetProp(P_TRANSPARENT,0);
+
+  AddCmd("trockne&@PRESENT", "cmd_trocknen", "Was willst Du trocknen?");
+}
+
+private string my_short() {
+  if ( first_inventory(ME) )
+    return short_desc;
+  return 0;
+}
+
+// Querymethoden, die Langbeschreibung und weitere Properties des im 
+// Trockner enthaltenen Krautobjektes nach draussen weiterreichen, um die
+// Illusion zu erzeugen, dass wirklich das echte Kraut im Raum zu sehen sei.
+// Alle Funktionen gehen davon aus, dass das zu trocknende Kraut das erste
+// (und einzige) Objekt im Inventar des Trockners ist und geben dessen
+// Properties zurueck.
+private string my_long() {
+  object inv = first_inventory(ME);
+  if ( objectp(inv) )
+    return inv->QueryProp(P_LONG)+inv->Name(WER,1)+
+      " wird gerade getrocknet.\n";
+  return 0;
+}
+
+private string|string* my_name() {
+  object inv = first_inventory(ME);
+  if ( objectp(inv) )
+    return inv->QueryProp(P_NAME);
+  return Query(P_NAME,F_VALUE);
+}
+
+private mixed my_mat() {
+  object inv = first_inventory(ME);
+  if ( objectp(inv) )
+    return inv->QueryProp(P_MATERIAL);
+  return ([]);
+}
+
+private int|string my_noget() {
+  object inv = first_inventory(ME);
+  if ( objectp(inv) )
+    return inv->Name(WER,1)+" wird gerade getrocknet, Du solltest "+
+      inv->QueryPronoun(WEN)+" liegenlassen, bis "+
+      inv->QueryPronoun(WER)+" fertig ist.";
+  return 1;
+}
+
+private string* my_ids() {
+  object inv = first_inventory(ME);
+  if ( objectp(inv) )
+    return inv->QueryProp(P_IDS);
+  return Query(P_IDS,F_VALUE);
+}
+
+// Kommandofunktion zum Starten des Tocknungsvorganges. Holt sich die
+// relevanten Daten aus dem Krautmaster ab, setzt Meldungen und Texte und
+// wirft den Trocknungs-Callout an.
+static int cmd_trocknen(string str, mixed *param) {
+  // Master liefert leeres Array oder eins mit 2 Elementen ({delay, quality})
+  // environment(ME) liest er selbstaendig aus
+  int *drying_data = PLANTMASTER->QueryDryingData();
+  object kraut = param[0];
+
+  // Der Trockner taeuscht vor, selbst das Kraut zu sein, das zum Trocknen
+  // im Raum liegt. Daher wird hier noch geprueft, ob der Spieler vielleicht
+  // den Trockner selbst zu trocknen versucht. 
+  if ( kraut == ME ) {
+    tell_object(PL, BS(kraut->Name(WER,1)+" wird bereits getrocknet, Du "
+      "solltest "+kraut->QueryPronoun(WEN)+" besser liegenlassen."));
+  }
+  // Es muss sich auch um ein Kraut handeln und nicht irgendwas anderes.
+  else if ( load_name(kraut) != PLANTITEM ) {
+    return 0;
+  }
+  // Spieler muss das Kraut im Inventar haben.
+  else if ( environment(kraut) != PL ) {
+    tell_object(PL, BS(
+      "Du musst "+kraut->name(WEN,1)+" schon in die Hand nehmen, um "+
+      kraut->QueryPronoun(WEN)+" sorgfaeltig trocknen zu koennen."));
+  }
+  // Das Kraut darf nicht unwirksam sein, was durch eine Plant-ID von -1 
+  // gekennzeichet ist.
+  else if ( param[0]->QueryPlantId() == -1 ) {
+    tell_object(PL, BS(
+      kraut->Name(WER,1)+" haette ohnehin keine Wirkung, da kannst Du Dir "
+      "die Muehe sparen, "+kraut->QueryPronoun(WEN)+" noch aufwendig zu "
+      "trocknen."));
+  }
+  // Master hat keine Daten geliefert, also befindet sich der Trockner
+  // offenbar in einem unzulaessigen Raum.
+  else if ( sizeof(drying_data) != 2 ) {
+    tell_object(PL, BS(
+      "Dieser Ort ist nicht geeignet, um "+kraut->name(WEN,1)+" hier zu "
+      "trocknen."));
+  }
+  // Kraut ist schon getrocknet? Dann waere eine weitere Trocknung unsinnig.
+  else if ( kraut->QueryDried() ) {
+    tell_object(PL, BS(kraut->Name(WER,1)+" ist schon getrocknet, eine "
+      "weitere Behandlung wuerde "+kraut->QueryPronoun(WEM)+" zu stark "
+      "zusetzen, "+kraut->QueryPronoun(WEN)+" gar zerstoeren."));
+  }
+  // Es ist schon eine Pflanze im Trockner? Dann nicht noch eine reintun.
+  else if ( first_inventory(ME) ) {
+    tell_object(PL, BS("Hier wird gerade schon etwas getrocknet."));
+  }
+  // Aus irgendeinem Grund schlaegt die Bewegung des Krautes in den Trockner
+  // fehl? Dann muss sich das ein Magier anschauen, denn das geht nicht mit
+  // rechten Dingen zu.
+  else if ( kraut->move(ME, M_PUT) != MOVE_OK ) {
+    tell_object(PL, BS("Aus einem Dir unerfindlichen Grund schaffst Du es "
+      "nicht, die Trocknung "+kraut->name(WESSEN,1)+" zufriedenstellend "
+      "durchzufuehren und brichst den Versuch wieder ab. Du solltest einem "
+      "Magier Bescheid sagen, dass hier etwas nicht stimmt."));
+  }
+  // Alles geklappt, alle Bedingungen erfuellt? Dann koennen wir jetzt
+  // tatsaechlich endlich das Kraut trocknen.
+  else {
+    int drying_delay = drying_data[0]; // nur lokal benoetigt
+    drying_quality = drying_data[1]; // globale Variable
+    string where = load_name(environment(ME));
+    string msg_self, msg_other;
+    string kr = kraut->name(WEN,1);
+    string* callout_msgs = ({
+        kraut->Name(WER,1)+" ist jetzt in einem zufriedenstellenden "
+        "Zustand. Besser wirst Du es an diesem Ort vermutlich nicht "
+        "hinbekommen, daher beendest Du die Trocknung und nimmst "+
+        kraut->QueryPronoun(WEN)+" wieder an Dich.",
+        PL->Name(WER)+" schaut "+kr+" pruefend an und "
+        "beendet dann die Trocknung, offenbar zufrieden mit dem Resultat."});
+    int blocker; // auf 1 setzen, falls das Trocknen verhindert werden soll
+    // Hier koennen jetzt abhaengig vom Raum passende Meldungen gesetzt
+    // werden. Die zulaessigen Standorte hier noch weiter zu obfuscaten
+    // waere zwar moeglich, aber zu unuebersichtlich geworden.
+    switch(where) {
+      /*
+       * GEBIRGE 
+       */
+      case "/d/gebirge/silvana/cronoertal/room/th7u":
+        msg_self = "Du legst "+kr+" vorsichtig und in gebuehrendem Abstand "
+          "zu den Flammen neben die Feuerstelle und wartest gespannt, ob "
+          "die Trocknung wohl gelingen wird.";
+        msg_other = PL->Name(WER)+" legt etwas neben die Feuerstelle, "
+          "vermutlich, um es zu trocknen.";
+        short_desc = kraut->Name(WER)+" liegt zum Trocknen am Feuer";
+        break;
+      /*
+       * EBENE 
+       */
+      case "/d/ebene/zardoz/burg/kueche":
+        msg_self = "Du legst "+kr+" vorsichtig an eine der kuehleren "
+          "Stellen des Bleches im Ofen, es soll ja trocknen, und nicht "
+          "backen.";
+        msg_other = PL->Name(WER)+" legt etwas Gruenzeug auf das Blech im "
+          "Ofen.";
+        short_desc = kraut->Name(WER)+" liegt zum Trocknen im Ofen";
+        break;
+      case "/d/ebene/esme/masinya/rooms/kueche":
+        msg_self = "Du haengst "+kr+" ueber den Herd, um "+
+          kraut->QueryPronoun(WEN)+" in der Abwaerme trocknen zu lassen.";
+        msg_other = PL->Name(WER)+" haengt ein Kraut zum Trocknen ueber "
+          "den Herd.";
+        short_desc = kraut->Name(WER)+" haengt zum Trocknen ueber dem Herd";
+        break;
+      case "/d/ebene/throin/brauerei/room/darre06":
+        msg_self = "Du legst "+kr+" zu dem Malz in den "
+          "Keimkasten in der Hoffnung, dass "+kraut->QueryPronoun(WER)+
+          " auf diese Weise getrocknet werden kann.";
+        msg_other = PL->Name(WER)+" legt etwas eigenes Gruenzeug zu dem "
+          "Malz in den Keimkasten.";
+        short_desc = kraut->Name(WER)+" liegt zum Trocknen im Keimkasten";
+        break;
+      case "/d/ebene/arathorn/orakel/room/zelt":
+        msg_self = "Du legst "+kr+" vorsichtig ans "
+          "Lagerfeuer. Du schaust unsicher zu Chinkuwaila, doch der "
+          "alte Schamane nickt zustimmend, Du hast wohl alles richtig "
+          "gemacht.";
+        msg_other = PL->Name(WER)+" legt eine Pflanze ans Lagerfeuer, wohl "
+          "um sie zu trocknen.";
+        short_desc = kraut->Name(WER)+" wird gerade am Lagerfeuer getrocknet";
+        break;
+      /*
+       * WUESTE
+       */
+      case "/d/wueste/tsunami/schule/rooms/kraeuter":
+        blocker = objectp(present_clone("/d/wueste/tsunami/schule/mon/hexe", 
+                    environment(ME)));
+        if ( !blocker ) {
+          msg_self = "Du steckst "+kr+" in Muetterchen "
+            "Isewinds Trockenofen, der wohlig zu knistern beginnt.";
+          msg_other = PL->Name(WER)+" legt vorsichtig ein Kraut ";
+          short_desc = kraut->Name(WER)+" wird gerade im Ofen getrocknet";
+        }
+        else {
+          msg_self = "Muetterchen Isewind haelt Dich auf, sie scheint "
+            "niemanden an ihren Trockenofen heranlassen zu wollen.";
+          msg_other = PL->Name(WER)+" wird von Muetterchen Isewind an der "
+            "Benutzung des Ofens gehindert.";
+        }
+        break;
+      /*
+       * WALD
+       */
+      case "/d/wald/feigling/quest/room/huette3":
+        msg_self = "Du legst "+kr+" so nah ans Feuer, "
+          "wie Du glaubst, dass es der Trocknung nicht schadet.";
+        msg_other = PL->Name()+" legt eine Pflanze an die Kochstelle.";
+        short_desc = kraut->Name(WER)+" liegt zum Trocknen an der "
+          "Feuerstelle";
+        break;
+      case "/d/wald/leusel/quest/rooms/laborsuedosten":
+        msg_self = "Du legst "+kr+" in eins der "
+          "Tonschiffchen und schiebst es in den mittelheissen Ofen hinein. "
+          "Hoffentlich geht das gut, es kommt Dir da drinnen schon fast zu "
+          "warm fuer eine ordnungsgemaesse Trocknung vor.";
+        msg_other = PL->Name(WER)+" schiebt ein Kraut in einem "
+          "Tonschiffchen in einen der Oefen hinein, um es zu trocknen.";
+        short_desc = kraut->Name(WER)+" dampft in einem Tonschiffchen im "
+          "ersten Ofen vor sich hin";
+        break;
+      /*
+       * INSELN
+       */
+      case "/d/inseln/zesstra/vulkanweg/room/r8":
+        msg_self = "Du legst "+kr+" vorsichtig auf die "
+          "heissen Felsen in der Naehe des Lavasees, auf dass die "
+          "heissen Winde "+kraut->QueryPronoun(WEN)+" trocknen moegen.";
+        msg_other = PL->Name(WER)+" legt ein Kraut auf den Felsen ab, um es "
+          "von der heissen Luft trocknen zu lassen.";
+        short_desc = kraut->Name(WER)+" liegt zum Trocknen auf dem Felsen";
+        break;
+      case "/d/inseln/miril/zyklopen/room/palast/insel1p6":
+        // QueryOven() liefert 2 fuer "Feuer", 1 fuer "Glut", 0 fuer "aus".
+        switch (environment(ME)->QueryOven()) {
+          case 2:
+            msg_self = "Du legst "+kr+" vorsichtig an das Feuer, das in "
+              "der Feuerstelle brennt, sorgsam bemueht, dass "+
+              kraut->QueryPronoun(WER)+" nicht zuviel Hitze abbekommt.";
+            msg_other = PL->Name(WER)+" legt sorgsam ein Kraut in die Naehe "
+              "des Feuers, das in der Feuerstelle brennt.";
+            break;
+          case 1:
+            msg_self = "Du legst "+kr+" an die Feuerstelle, pruefst die "
+              "Hitze und rueckst "+kraut->QueryPronoun(WEN)+" noch etwas "
+              "naeher an die Glut, dann trittst Du zufrieden einen Schritt "
+              "zurueck.";
+            msg_other = PL->Name(WER)+" legt "+kraut->name(WEN)+" an die "
+              "Feuerstelle, schubst noch ein wenig daran herum und tritt "
+              "dann von der Glut zurueck, "+PL->QueryPronoun(WER)+" scheint "
+              "recht zufrieden zu sein.";
+            break;
+          default:
+            blocker=1;
+            msg_self = "In dem Kamin findest Du nicht einmal etwas Glut, "
+              "geschweige denn offenes Feuer. So wird das mit dem Trocknen "
+              "nichts, und auf dem Herd ist Dir das Risiko zu gross, dass "
+              "Fett aus der Pfanne auf "+kr+" spritzt.";
+            break;
+        }
+        short_desc = kraut->Name(WER)+" liegt zum Trocknen an der "
+          "Feuerstelle";
+        break;
+      /*
+       * POLAR
+       */
+      case "/d/polar/tilly/llp/rentner/kueche":
+        msg_self = "Der Herd gibt eine infernalische Hitze ab. Zum Kochen "
+          "ist das toll, aber Du brauchst doch einen Moment, um ein "
+          "geeignetes Plaetzchen fuer "+kr+" zu finden.";
+        msg_other = PL->Name(WER)+" legt ein Kraut auf den heissen Herd "
+          "und schiebt es unruhig noch ein wenig hin und her, als waere "+
+          PL->QueryPronoun(WEM)+" die Hitze beinahe ein wenig zu gross.";
+        short_desc = kraut->Name(WER)+" liegt auf dem schmiedeeisernen "
+          "Herd";
+        break;
+      /*
+       * VLAND
+       */
+      case "/d/vland/morgoth/room/kata/ukat13":
+        object c = present_clone("/d/vland/morgoth/obj/kata/rfdcorpse", 
+              environment(ME));
+        // Bequemer, das auf 0 zu setzen, wenn's klappt, bei sovielen
+        // Hinderungsgruenden. ;-)
+        blocker = 1;
+        // Leiche liegt da.
+        if ( objectp(c) ) 
+        {
+          // Feuerdaemon anwesend: der blockt den Versuch
+          if ( present_clone("/d/vland/morgoth/npc/kata/firedemon3",
+                environment(ME)) ) 
+          {
+            msg_self = "Der Feuerdaemon droht Dir mit sengender Hoellenpein. "
+              "Beschwichtigend trittst Du einen Schritt zurueck.";
+            msg_other = PL->Name(WER)+" tritt auf den Aschehaufen zu, wird "
+              "aber von dem Feuerdaemon bedroht und weicht wieder zurueck.";
+          }
+          // Aschehaufen nicht mehr heiss genug?
+          else if ( c->QueryDecay()<2 ) {
+            msg_self = c->Name(WER,1)+" ist schon zu sehr abgekuehlt und "
+              "wuerde nicht mehr genug Hitze spenden, um "+kr+" zu trocknen.";
+          }
+          else 
+          {
+            blocker = 0;
+            msg_self = "Dich vorsichtig umschauend, legst Du "+kr+" in die "
+              "Naehe "+c->name(WESSEN,1)+". Hoffentlich gelingt das in "
+              "dieser unwirtlichen Umgebung!";
+            msg_other = PL->Name(WER)+" beugt sich zum Boden hinuntern und "
+              "legt vorsichtig ein Kraut in die Naehe "+c->name(WESSEN,1)+".";
+          }
+          short_desc = kraut->Name(WER)+" liegt neben "+c->name(WEM,1)+
+            ", offenbar soll "+kraut->QueryPronoun(WER)+" getrocknet werden";
+        }
+        // Keine Leiche da? Dann geht's nicht.
+        else {
+          msg_self = "Genausowenig, wie Dir die Flammen in diesem Inferno "
+            "etwas anhaben koennen, so wenig kannst Du sie nutzen, um "+
+            kr+" zu trocknen.";
+        }
+        break;
+      case "/d/vland/morgoth/room/kata/kata5":
+        object ob = present_clone("/d/vland/morgoth/npc/kata/ghost",
+            environment(ME));
+        blocker = 1;
+        // Geist anwesend? Keine Chance.
+        if ( objectp(ob) ) {
+          msg_self = ob->Name(WER,1)+" stoert Dich in Deiner Konzentration, "
+            "Du kannst so nicht arbeiten!";
+        }
+        // Umgebung noch neblig? Dann nicht trocknen.
+        else if ( environment(ME)->QueryFog() ) {
+          msg_self = "In diesem verdammten Nebel ist absolut nichts zu "
+            "sehen. Ausserdem ist es hier viel zu feucht, "+kr+" wuerde "
+            "ohnehin nur vor Deiner Nase wegschimmeln.";
+        }
+        // Feuer brennt nur noch 90 Sekunden? Dann lohnt sich das nicht.
+        else if ( query_next_reset(environment()) < time()+90 ) {
+          msg_self = "Ein pruefender Blick auf das Feuer zeigt Dir, dass "
+            "es wohl nicht mehr lange genug brennen wird, um "+kr+" noch "
+            "erfolgreich trocknen zu koennen.";
+        }
+        else {
+          blocker = 0;
+          msg_self = "Du legst "+kr+" in angemessenem Abstand zum Feuer "
+            "auf den Boden und wartest gespannt, ob Dir hier wohl eine "
+            "brauchbare Trocknung gelingen wird.";
+          msg_other = PL->Name(WER)+" bueckt sich zum Boden und legt etwas "
+            "ans Feuer, anscheinend ein Kraut, das "+PL->QueryPronoun(WER)+
+            " trocknen will.";
+          short_desc = kraut->Name(WER)+" liegt zum Trocknen am Feuer";
+        }
+        break;
+      case "/d/vland/alle/koomi_v/wschenke/room/waldschenke":
+        msg_self = "Dieser Kachelofen ist ungemein praktisch. Du legst "+
+          kr+" einfach oben drauf, und die kuschelige Waerme trocknet "+
+          kraut->QueryPronoun(WEN)+" beinahe von selbst.";
+        msg_other = PL->Name(WER)+" legt ein Kraut zum Trocknen oben auf "
+          "den Kachelofen, offenbar recht angetan von dessen kuscheliger "
+          "Waerme.";
+        short_desc = kraut->Name(WER)+" liegt zum Trocknen auf dem "
+          "Kachelofen";
+        break;
+      /*
+       * DEBUGZWECKE
+       */
+      case "/players/arathorn/workroom":
+        msg_self = "Du haeltst das Kraut vors Feuer und beginnst die "
+          "Trocknung.";
+        msg_other = PL->Name(WER)+" schickt sich an, ein Kraut am Feuer zu "
+          "trocknen.";
+        short_desc = kraut->Name(WER)+" liegt zum Trocknen am Feuer";
+        break;
+    }
+    // Raummeldungen entsprechend der eingestellten Texte ausgeben.
+    tell_object(PL, BS(msg_self));
+    if ( msg_other )
+      tell_room(environment(ME), BS(msg_other), ({PL}));
+    // Callout starten, wenn niemand das Trocknen verhindert.
+    if ( !blocker )
+      call_out(#'dry_plant, drying_delay, kraut, callout_msgs);
+    // Ansonsten das Kraut in den Spieler zurueck, das ja oben schon
+    // in den Trockner bewegt wurde.
+    else {
+      kraut->move(PL, M_GET);
+    }
+  }
+  return 1;
+}
+
+// Kraut wird getrocknet, sofern der Spieler noch im Raum ist, ...
+private void dry_plant(object kraut, string *msgs) {
+  if ( objectp(PL) && environment(PL) == environment(ME) ) {
+    tell_object(PL, BS(msgs[0]));
+    tell_room(environment(ME), BS(msgs[1]), ({PL}));
+    kraut->move(PL, M_GET);
+    kraut->DryPlant(drying_quality);
+  }
+  // ... ansonsten laeuft die Trocknung weiter, und das Kraut verbrennt.
+  else {
+    tell_room(ME, BS(kraut->Name(WER,1)+" wird extrem dunkel, bald wird "+
+      kraut->QueryPronoun(WER)+" zu nichts mehr zu gebrauchen sein!"));
+    // Das Delay fuer diesen zweiten Callout ist immer fix. Kommt hoffentlich
+    // selten genug vor und braucht daher eher nicht extra aus dem Master
+    // geholt zu werden.
+    call_out(#'destroy_herb, 20, kraut);
+  }
+}
+
+// Zerstoerung des Krautes. Da die Krautobjekte selbst eine Meldung 
+// ausgeben, wenn DryPlant(0) gerufen wird, wird erst das Kraut ins
+// Environment bewegt und erst danach die Funktion gerufen.
+private void destroy_herb(object kraut) {
+  kraut->move(environment(ME),M_PUT);
+  kraut->DryPlant(0);
+}
+
+// Nur die korrekten Krautobjekte koennen in den Trockner bewegt werden.
+// Schuetzt natuerlich nicht vor M_NOCHECK, aber wenn das vorkommen sollte,
+// muss vielleicht noch weiter abgesichert werden, oder der verursachende
+// Magier ausgeschimpft. ;-) 
+varargs int PreventInsert(object ob) {
+  if (load_name(ob) == PLANTITEM && clonep(ob)) 
+    return ::PreventInsert(ob);
+  return 1;
+}
diff --git a/items/kraeuter/virtual_compiler.c b/items/kraeuter/virtual_compiler.c
new file mode 100644
index 0000000..d48370e
--- /dev/null
+++ b/items/kraeuter/virtual_compiler.c
@@ -0,0 +1,300 @@
+// (c) by Padreic (Padreic@mg.mud.de)
+
+#pragma no_inherit,no_clone,strong_types,rtt_checks
+
+#include <defines.h>
+#include <properties.h>
+#include <v_compiler.h>
+#include <items/kraeuter/kraeuter.h>
+#include <wizlevels.h>
+
+inherit "/std/virtual/v_compiler";
+inherit "/std/thing/language";
+inherit "/std/thing/description";
+
+// mit Hilfe dieses mappings kommt man sowohl an die ID und die Eigenschaften,
+// als auch an die Liste der Raeume in denen das Kraut mit dem filenamen room
+// gefunden werden kann.
+// ([ "key": ({ ({eigenschaften}), ([raeume]) }) ])
+private mapping krautdaten;
+
+// AN: enthaelt die Liste der gueltigen Kraeuter-Dateinamen ohne .c
+// am Ende. Ich vermute, dass es deswegen ein Mapping ist, damit in 
+// Validate() einfach member() drauf gemacht werden kann und man nur 0/1
+// als Rueckgabewerte pruefen muss, statt -1 bei nem member() auf ein Array.
+private mapping validfiles;
+
+public void update(mapping data)
+{
+  if (previous_object() == find_object(PLANTMASTER))
+  {
+    krautdaten = data;
+    validfiles = mkmapping(m_indices(krautdaten));
+  }
+}
+
+// Wird benutzt, um kurze IDs von Kraeutern zu raten. Diese IDs werden
+// eingetragen, wenn der Krautname die ID als Teilstring enthaelt.
+#define IDLIST ({ "klee", "rebe", "hahnenfuss", "rettich", "kraut", "wurz",\
+                     "moos", "enzian", "rautenwicke", "pilz", "nelke",\
+                     "lichtnelke", "wicke", "zwiebel", "hanf", "kresse"})
+
+void create()
+{
+   seteuid(getuid());
+
+   v_compiler::create();
+   description::create();
+
+   SetProp(P_COMPILER_PATH, __DIR__);
+   SetProp(P_STD_OBJECT, PLANTITEM);
+   
+   PLANTMASTER->UpdateVC();
+}
+
+string Validate(string file)
+{
+   if (!stringp(file)) return 0;
+   file = ::Validate(explode(file, "#")[0]);
+#if MUDNAME == "MorgenGrauen"
+   return (member(validfiles, file) ? file : 0);
+#else
+   return file;
+#endif
+}
+
+private nosave object simul_efun;
+
+// fuer SIMUL_EFUN_FILE
+#include <config.h>
+
+// AN: Funktion liefert das clonende Objekt als dessen Blueprint-Namen,
+// indem es den Caller-Stack durchlaeuft und nach einem Objekt sucht,
+// das weder der Master, noch ein Simul-Efun-Objekt, noch dieser VC selbst
+// ist. Der Name des gefundenen Objekts wird zurueckgegeben, oder 0.
+nomask private string get_cloner()
+{
+   int i;
+   object po;
+
+   if (!simul_efun)
+   {
+      if (!(simul_efun=find_object(SIMUL_EFUN_FILE)))
+         simul_efun=find_object(SPARE_SIMUL_EFUN_FILE);
+   }
+   // wenn sie jetzt nicht existiert - auch gut, dann gibt es halt keine
+   // sefuns.
+
+   for (i=0; po=previous_object(i); i++)
+   {
+      if (po==master() || po==simul_efun || po==ME || po==previous_object())
+         continue;
+      return BLUE_NAME(po);
+   }
+   return 0;
+}
+
+// Konfiguriert das erzeugte Objekt entsprechend der dafuer im Kraeutermaster
+// bekannten Daten. Vergibt auf die Plant-ID.
+varargs string CustomizeObject(string file)
+{
+   if (previous_object()->QueryPlantId()) return 0; // bereits initialisiert
+   
+   if (stringp(file))
+      file=Validate(file);
+   else file=::CustomizeObject();
+   if (!file) return 0;
+
+   closure sp=symbol_function("SetProp", previous_object());
+   mixed arr=krautdaten[file];
+   if (pointerp(arr))
+   {
+      // Welches Objekt clont das File?
+      string cloner = get_cloner();
+      string rooms = arr[1];
+      mixed props = arr[0];
+      // Wird das Kraut legal von einem eingetragenen Cloner erzeugt? Nur dann
+      // bekommt es eine gueltige Plant-ID.
+     int legal=member(rooms, get_cloner()) || cloner==PLANTMASTER;
+     if (!legal && this_interactive() && IS_ARCH(this_interactive()))
+        legal=1;
+     
+      // Konfiguriert wird das Objekt dann, wenn es per VC erzeugt wird oder
+      // ein Clone einer per VC erzeugten BP ist - d.h. wenn es nicht aus
+      // einem real existierenden File auf der Platte existiert. Das ist dann
+      // der Fall, wenn der Loadname gleich dem Standardplantobjekt des VC
+      // ist.
+      if (load_name(previous_object())==PLANTITEM)
+      {
+        if ((props[INGREDIENT_NAME]=="Klee") ||
+            (props[INGREDIENT_NAME][<4..]=="klee")) {
+           funcall(sp, P_NAME, ({ props[INGREDIENT_NAME],
+                                  props[INGREDIENT_NAME]+"s",
+                                  props[INGREDIENT_NAME],
+                                  props[INGREDIENT_NAME]}));
+        }
+        else funcall(sp, P_NAME,     props[INGREDIENT_NAME]);
+        funcall(sp, P_NAME_ADJ, props[INGREDIENT_ADJ]);
+        funcall(sp, P_GENDER,   props[INGREDIENT_GENDER]);
+        funcall(sp, P_LONG,     props[INGREDIENT_LONG]);
+        funcall(sp, PLANT_ROOMDETAIL, props[INGREDIENT_ROOMDETAIL]);
+        if (props[INGREDIENT_DEMON]==RAW) {
+           funcall(sp, P_ARTICLE, 0);
+           funcall(sp, P_SHORT, previous_object()->Name(WER));
+           funcall(sp, P_ARTICLE, 1);
+        }
+        else funcall(sp, P_SHORT,
+             previous_object()->Name(WER,props[INGREDIENT_DEMON]));
+        previous_object()->AddId(lowerstring(props[INGREDIENT_NAME]));
+        // bei zusammengesetzten Namen, auch den hauptnamen akzeptieren
+        string str=lowerstring(props[INGREDIENT_NAME]);
+        string *names=explode(str, "-");
+        if (sizeof(names)>1) previous_object()->AddId(names[<1]);
+        names=explode(str, " ");
+        if (sizeof(names)>1) previous_object()->AddId(names[<1]);
+        foreach(string id: IDLIST)
+        {
+          if (strstr(str, id)==-1) continue;
+          previous_object()->AddId(id);
+          break;
+        }
+        // Adjective vorher deklinieren
+        str=props[INGREDIENT_ADJ];
+        if (stringp(str))
+        {
+          str=DeclAdj(lowerstring(str), WEN, 0);
+          previous_object()->AddAdjective(str);
+        }
+      }  // Ende Konfiguration eines VC-erzeugten Objekts
+      // Plant-ID wird fuer alle Objekte auf irgendwas gesetzt.
+      previous_object()->SetPlantId(legal ? props[INGREDIENT_ID] : -1);
+   }
+   // Keine Krautdaten bekannt...
+   else
+   {
+     funcall(sp, P_NAME,     "Kraut");
+     funcall(sp, P_GENDER,   NEUTER);
+     funcall(sp, P_SHORT,    "Ein Testkraut ("+capitalize(file)+")");
+     funcall(sp, P_LONG,     "Ein nicht naeher spezifiziertes Testkraut.\n");
+     funcall(sp, PLANT_ROOMDETAIL,
+         "Ein nicht naeher spezifiziertes Testkraut ("
+         +capitalize(file)+").\n");
+     previous_object()->AddId("kraut");
+     previous_object()->SetPlantId(-1);
+   }
+   return file;
+}
+
+int NoParaObjects()
+{   return 1;   }
+
+// AN: Funktion erzeugt aus den vorliegenden Daten der Kraeuterliste ein
+// physikalisch existierendes File in diesem Verzeichnis, zB wenn die Daten
+// erweitert werden sollen. Die Kraeuterliste stellt nur generische Objekte
+// zur Verfuegung, die keine Details haben. Wenn die Objekte ausgeschmueckt
+// werden sollen, koennen diese auch als Datei hier liegen.
+// Wird vom Plantmaster aus gerufen. Die Existenz von Klartext-
+// Fehlermeldungen laesst darauf schliessen, dass diese Funktion dafuer
+// vorgesehen war, vom Planttool aus gerufen zu werden. Dies wird dadurch
+// bestaetigt, dass dort wie hier alle von Magiern benutzbaren Kommando-
+// funktionen mit _ beginnen (_showplant(), _addroom() etc.), und die
+// Kommandofunktion im Planttool generell in der Lage ist, alle _*() 
+// im Plantmaster zu rufen, sofern existent und fuer den Magier freigegeben.
+// AN/TODO: ggf. sollte man hier noch pruefen, ob die VC-Blueprint des
+// angeforderten Krautes gerade existiert, denn sonst wuerde das auf der
+// Platte liegende, scheinbar (nicht) geladene Objekt nicht mit dem
+// VC-Objekt uebereinstimmen. Evtl. reicht es aus, die Blueprint einfach
+// zu zerstoeren und neuzuladen.
+int _createfile(string filename)
+{
+   int i;
+   string str, short, long, gender, *name, roomdetail;
+   string *ids;
+   string plantfile;
+
+/*   if (object_name(previous_object())!=PLANTMASTER) {
+      write("Illegal usage of _createfile()!\n");
+      return 1;   
+   }*/
+// ^^^ Zook, ggf.spaeter wieder Kommentar entfernen. 
+
+   mixed arr;
+   if (!pointerp(arr=krautdaten[filename])) {
+      write("Unknown plant '"+filename+"'.\n");
+      return 1;
+   }
+   if (file_size(PLANTDIR+filename+".c")>=0) {
+      write("error: file "+PLANTDIR+filename+".c already exists.\n");
+      return 1;
+   }
+   mixed props = arr[0];
+
+   // Kurzbeschreibung erzeugen
+   SetProp(P_NAME,     props[INGREDIENT_NAME]);
+   SetProp(P_NAME_ADJ, props[INGREDIENT_ADJ]);
+   SetProp(P_GENDER,   props[INGREDIENT_GENDER]);
+   if (props[INGREDIENT_DEMON]==RAW) {
+       SetProp(P_ARTICLE, 0);
+       short=Name(WER);
+       SetProp(P_ARTICLE, 1);
+   }
+   else short=Name(WER,props[INGREDIENT_DEMON]);
+   ids = ({ lowerstring(props[INGREDIENT_NAME]) });
+   // bei zusammengesetzten Namen, auch den hauptnamen akzeptieren
+   str=lowerstring(props[INGREDIENT_NAME]);
+   name=explode(str, "-");
+   if (sizeof(name)>1) ids += ({ name[<1] });
+   name=explode(str, " ");
+   if (sizeof(name)>1) ids += ({ name[<1] });
+   for (i=sizeof(IDLIST)-1; i>=0; i--) {
+       if (strstr(str, IDLIST[i], 0)==-1) continue;
+       ids += ({ IDLIST[i] });
+       break;
+   }
+   switch(props[INGREDIENT_GENDER]) {
+     case MALE:   gender="MALE"; break;
+     case FEMALE: gender="FEMALE"; break;
+     case NEUTER: gender="NEUTER"; break;
+     default: gender=props[INGREDIENT_GENDER];
+   }
+   long="    \""+implode(old_explode(props[INGREDIENT_LONG], "\n"), 
+                   "\\n\"\n   +\"")+"\\n\"";
+   roomdetail="    \""+implode(
+      old_explode(props[INGREDIENT_ROOMDETAIL], "\n"), "\\n\"\n   +\"")+
+      "\\n\"";
+   plantfile=
+    "#pragma strong_types,rtt_checks\n\n"
+    "#include <properties.h>\n"
+    "#include <items/kraeuter/kraueter.h>\n"
+    "#include <items/kraeuter/kraeuterliste.h>\n\n"
+    "inherit STDPLANT;\n\n"
+    "protected void create()\n"
+    "{\n"
+    "  ::create();\n";
+   plantfile+="  customizeMe("+upperstring(filename)+");\n";
+   plantfile+=
+    "  SetProp(P_NAME,     \""+props[INGREDIENT_NAME]+"\");\n"
+    "  SetProp(P_NAME_ADJ, \""+(props[INGREDIENT_ADJ]||"")+"\");\n"
+    "  SetProp(P_GENDER,   "+gender+");\n"
+    "  SetProp(P_LONG,     \n"+
+    long+");\n"
+    "  SetProp(PLANT_ROOMDETAIL, \n"+
+    roomdetail+");\n"
+    "  SetProp(P_SHORT,    \""+short+"\");\n";
+   plantfile+="  AddId(({";
+   for (i=sizeof(ids)-1; i>=0; i--)
+     plantfile+=" \""+ids[i]+"\",";
+   plantfile[<1]=' ';
+   plantfile+="}));\n";
+   // Adjective vorher deklinieren
+   if (stringp(short=props[INGREDIENT_ADJ])) {
+     short=DeclAdj(lowerstring(short), WEN, 0)[0..<2];
+     plantfile+="  AddAdjective(\""+short+"\");\n";
+   }
+   plantfile+="}\n";
+   write(plantfile);
+   //write_file(PLANTDIR+filename+".c", plantfile);
+   write("Filename: "+PLANTDIR+filename+".c\n");
+   return 1;
+}
+