Added public files

Roughly added all public files. Probably missed some, though.
diff --git a/secure/krautmaster.c b/secure/krautmaster.c
new file mode 100644
index 0000000..0d0ab06
--- /dev/null
+++ b/secure/krautmaster.c
@@ -0,0 +1,1289 @@
+#pragma strong_types, save_types, rtt_checks
+#pragma no_inherit, no_clone, no_shadow
+
+#include <defines.h>
+#include <properties.h>
+#include <wizlevels.h>
+#include <items/kraeuter/kraeuter.h>
+#include <items/kraeuter/trankattribute.h>
+#include <living/comm.h>
+
+
+#ifndef BS
+#define BS(x)             break_string(x, 78)
+#endif
+
+// Weiter unten beim Parsen des Datenfiles werden SetProp() und Name()
+// verwendet, daher erben wir thing.
+inherit "/std/secure_thing";
+
+// Liste aller moeglichen Zutaten, gemappt ueber einen key
+// AN: irritierender Name, denn es sind nicht direkt die Zutaten enthalten
+// sondern die ueber einen Key identifizierten Kraeutergruppen, die die
+// Wirkungen/Funktionen festlegen.
+// Format:
+// ([ key : ({ attr_pos, attr_neg1, attr_neg2, Haltbark., Wirkdauer, 
+//             Unterstuetzung, Stoerung, Haeufigkeit  }) ])
+// Beispiel: [( "a7":({({2,400}),({11,150}),0,172800,0,({"n*"}),0,1}) ])
+// HU: War ein Mapping, bleibt ein Mapping, und neu: Die Eintraege werden 
+// auch Mappings!
+private mapping ingredients;
+
+// Mapping von id zum Key der Zutaten im ingredients-mapping: id2key[id]=key
+// Die beiden Listen werden benoetigt, um zu den Kraut-IDs die jeweiligen
+// Keys zu finden.
+// HU: KEIN MAPPING. Ein Array von Strings. Aber das geht natuerlich.
+private string *id2key;
+
+// Mapping von key zu Kraeuter-ID der Zutaten.
+private mapping key2id;
+
+// Eigenschaften der Kraeuter:
+// krautprops[id]=({ID, Demon, Gender, Name, Adjektiv(e), P_LONG, Raumdetail})
+// krautprops[0] = 0; ID 0 darf nicht existieren
+private mixed *krautprops;
+
+// hier wird gespeichert welche Version einer Zutat fuer einen Spieler ist.
+// AN/TODO: Wenn das fuer Nichtseher nutzbar sein soll, sollte hier besser
+// mit getuuid() gearbeitet werden, statt getuid() zu verwenden.
+// player enthaelt fuer jede Spieler-UID einen Bitstring. Gesetztes Bit
+// bedeutet, dass der Spieler ueber das Kraut mit dieser ID etwas ueber
+// die Verwendung und Wirkung weiss. Es gibt jedoch noch keine Lernmoeglich-
+// keit und INSBESONDERE keine Funktionalitaet im Master, ueber die man 
+// die Bits setzen koennte.
+private mapping player;
+
+// verstuemmeltes mapping fuer den VC in service (Save-Dummy)
+private mapping map_ldfied;
+
+// rooms enthaelt die Teileintraege des map_ldfied mapping vom VC
+private mapping rooms;
+
+// Enthaelt Daten zu den Raeumen, in denen das Trocknen von Kraeutern
+// moeglich ist.
+private mapping drying_data = ([:4]);
+
+string build_plantname(mixed *props);
+
+// struct-Templat fuer Trankattribute
+// Fuer das SQL-Log MUSS die Reihenfolge der Trankattribute hier genau die
+// sein, wie die Spalten in der Tabelle.
+/* Currently not used.
+struct trank_attrib_s {
+    int car;
+    int da;
+    int dm;
+    int du;
+    int dn;
+    int flt;
+    int fro;
+    int hI;
+    int hP;
+    int hK;
+    int hL;
+    int pa;
+    int pm;
+    int pu;
+    int ss;
+    int sp;
+    int sd;
+};
+*/
+
+#define allowed() (!process_call() && \
+        IS_ARCH(RPL) && IS_ARCH(this_interactive()) )
+
+#define POTIONFILES ({ TRANKITEM })
+
+// eigenschaften im krautprops-array
+#define PROP_ID           INGREDIENT_ID
+#define PROP_DEMON        INGREDIENT_DEMON
+#define PROP_GENDER       INGREDIENT_GENDER
+#define PROP_NAME         INGREDIENT_NAME
+#define PROP_ADJ          INGREDIENT_ADJ
+#define PROP_LONG         INGREDIENT_LONG
+#define PROP_ROOMDETAIL   INGREDIENT_ROOMDETAIL
+
+// ATTR_ is immer ein array ({ attribut, ability })
+#define ATTR_ARR_ATTRIB      0
+#define ATTR_ARR_ABILITY     1
+
+// maximal erlaubter Wert fuer eine Eigenschaft
+#define MAX_ATTR_ABILITY      999
+
+void debug(string str)
+{
+  //write("debug: "+str+"\n");
+  if (this_player() && IS_ARCH(this_player()))
+    this_player()->ReceiveMsg(str,536870912);
+}
+
+protected void create()
+{
+   seteuid(getuid());
+   // AN/TODO: Variablen oben direkt initialisieren. Pruefen, ob davon
+   // irgendwas nosave sein kann.
+   if (!restore_object(__DIR__"/ARCH/krautmaster"))
+   {
+      ingredients=([]);
+      id2key=({});
+      key2id=([]);
+      krautprops=({});
+      player=([]);
+      map_ldfied=([]);
+      rooms=([]);
+   }
+   if (sl_open("/log/ARCH/krauttrank.sqlite") != 1)
+   {
+     raise_error("Datenbank konnte nicht geoeffnet werden.\n");
+   }
+   sl_exec("CREATE TABLE IF NOT EXISTS traenke(id INTEGER PRIMARY KEY, "
+           "uid TEXT NOT NULL, rnd INTEGER, "
+           "time INTEGER DEFAULT CURRENT_TIMESTAMP, "
+           "receipe TEXT NOT NULL, "
+           "quality TEXT NOT NULL, "
+           "car INTEGER, "
+           "da INTEGER, "
+           "dm INTEGER, "
+           "du INTEGER, "
+           "dn INTEGER, "
+           "flt INTEGER, "
+           "fro INTEGER, "
+           "hI INTEGER, "
+           "hP INTEGER, "
+           "hK INTEGER, "
+           "hL INTEGER, "
+           "pa INTEGER, "
+           "pm INTEGER, "
+           "pu INTEGER, "
+           "ss INTEGER, "
+           "sp INTEGER, "
+           "sd INTEGER);"
+           );
+   sl_exec("CREATE INDEX IF NOT EXISTS idx_uid ON traenke(uid);");
+   sl_exec("CREATE INDEX IF NOT EXISTS idx_receipe ON traenke(receipe);");
+   sl_exec("CREATE TABLE IF NOT EXISTS rohdaten(id INTEGER PRIMARY KEY, "
+           "uid TEXT NOT NULL, rnd INTEGER, "
+           "time INTEGER DEFAULT CURRENT_TIMESTAMP, "
+           "receipe TEXT NOT NULL, "
+           "quality TEXT NOT NULL, "
+           "car INTEGER, "
+           "da INTEGER, "
+           "dm INTEGER, "
+           "du INTEGER, "
+           "dn INTEGER, "
+           "flt INTEGER, "
+           "fro INTEGER, "
+           "hI INTEGER, "
+           "hP INTEGER, "
+           "hK INTEGER, "
+           "hL INTEGER, "
+           "pa INTEGER, "
+           "pm INTEGER, "
+           "pu INTEGER, "
+           "ss INTEGER, "
+           "sp INTEGER, "
+           "sd INTEGER);"
+           );
+   sl_exec("CREATE INDEX IF NOT EXISTS idx_uid_r ON rohdaten(uid);");
+}
+
+public string QueryPlantFile(int id)
+{
+  if (member(krautprops, id))
+    return build_plantname(krautprops[id]);
+  return 0;
+}
+
+// AN: Funktion ermittelt, ob ein Spieler pl das Kraut mit der ID id
+// verwenden kann. Laut Kommentar unten muss man dafuer wohl was ueber das
+// Kraut gelernt haben, von wem ist mir gerade noch nicht klar.
+// AN/TODO: Es ist bisher keine Funktionalitaet vorhanden, um die IDs fuer
+// den Spieler freizuschalten. Die Funktionsweise muss aus dem Beispielcode
+// unten fuer das Learn-On-Demand abgelesen werden.
+nomask int CanUseIngredient(object pl, int id)
+{
+  // Ich mach mal den harten Weg. -- Humni
+  return 1;
+  // Bitstring des Spielers aus der Liste auslesen.
+   string str=player[getuid(pl)];
+   if (!stringp(str)) str="";
+   if (test_bit(str, id))
+      return 1; // make the common case fast
+   else {
+     // letztenendes habe ich mich entschlossen einzubauen, das man nur die
+     // Kraeuter verwenden kann, ueber die man schonmal etwas gelesen/gelernt
+     // hat, aus diesem Grund, ist folgender Block auskommentiert.
+     // Dieser Block bedeutet quasi ein "auto learning on demand" d.h.
+     // wird ein Kraut verwendet wird geprueft ob fuer diese Gruppe bereits
+     // eine ID freigeschaltet wurde - ansonsten wird eine freigeschaltet.
+     /*
+         // pruefen ob fuer den Key bereits ein Bit gesetzt ist, ggf. setzen
+         if (id>=sizeof(id2key)) return 0; // illegale Id
+         int idlist=key2id[id2key[id]];
+         int i;
+         for (i=sizeof(idlist)-1; i>=0; i--) {
+           if (test_bit(str, idlist[i])) return 0; // Key bereits vorhanden
+         }
+         // Zufaellig ein Bit fuer den Key setzen
+         i=random(sizeof(idlist));
+         player[getuid(pl)]=set_bit(str, idlist[i]);
+         save_object(object_name());
+         return (i==id);
+     */
+     return 0;
+   }
+}
+
+// Diese Funktion wird vom Metzelorakel aufgerufen, um die Belohnungen zu
+// erzeugen, die man dort fuer erfolgreich absolvierte Metzelauftraege erhaelt
+#define ALLOWED_CALLER ({ "/d/ebene/arathorn/orakel/secure/schamane" })
+object get_plant_by_category(int npc_class)
+{
+  if ( member(ALLOWED_CALLER, load_name(previous_object()))<0 )
+    raise_error("unauthorised call to get_plant_by_category()\n");
+
+  // Uebergebene NPC-Klasse in Kraut-Kategorie umsetzen.
+  // Kategorie 7 wird als 7-9 interpretiert (siehe unten).
+  int category;
+  switch(npc_class) {
+    case 1: case 2:         category=4; break;
+    case 3: case 4:         category=5; break;
+    case 5: case 6: case 7: category=6; break;
+    default:                category=7; break;
+  }
+
+  // Alle Kraeuter der ermittelten Kategorie raussuchen. Bei Kategorie 7
+  // werden auch alle aus 8 und 9 dazugenommen.
+  int *eligible_plant_ids=({});
+  foreach( string grp, mapping grpdata : ingredients ) {
+    if ( category == 7 && grpdata[T_ABUNDANCE]>=7 || 
+          category == grpdata[T_ABUNDANCE] )
+      eligible_plant_ids += key2id[grp];
+  }
+
+  // Krautnamen zu den Kraut-IDs ermitteln.
+  string *plantfiles=map(eligible_plant_ids, function string (int plantid) {
+    return build_plantname(krautprops[plantid]);});
+
+  // Ein Kraut zufaellig auswaehlen, clonen und das Objekt zurueckgeben.
+  object plant=clone_object(PLANTDIR+plantfiles[random(sizeof(plantfiles))]);
+  plant->DryPlant(80+random(11));
+  // Aufschreiben, wer welches Kraut mit welcher Qualitaet rausbekommt.
+  log_file("ARCH/plant_by_category",
+    sprintf("%s %-12s %-s Qual %d Kat %d Class %d\n", 
+      strftime("%x %X",time()), getuid(PL),
+      object_name(plant)[sizeof(PLANTDIR)..],
+      plant->QueryProp(P_QUALITY), category, npc_class));
+// sprintf("%24s: call from %O, player: %s (PL: %O), kategory: %d\n", ctime(),
+// previous_object(), getuid(player), PL, kategory))
+  return plant;
+}
+
+private nosave object simul_efun, plantvc;
+
+// fuer SIMUL_EFUN_FILE
+#include <config.h>
+
+// AN/TODO: Klaeren, warum hier eine eigene Funktion get_cloner() existiert,
+// wo doch der Kraeuter-VC schon eine hat.
+nomask private string get_cloner()
+{
+   int i;
+   object po;
+   if (!simul_efun) {
+      if (!(simul_efun=find_object(SIMUL_EFUN_FILE)))
+         simul_efun=find_object(SPARE_SIMUL_EFUN_FILE);
+   }
+   // wenn sie jetzt nicht existiert - auch gut, dann gibt es halt keine
+   // sefuns.
+
+   if (!plantvc) plantvc=find_object(KRAEUTERVC);
+   
+   for (i=0; po=previous_object(i); i++) {
+      if (po==master() || po==simul_efun || po==ME ||
+          po==previous_object() || po==plantvc)
+         continue;
+      else return object_name(po);
+   }
+   return 0;
+}
+
+// AN: 
+nomask string CheckPlant(int id)
+{
+   if (id<=0 || id>=sizeof(id2key)) return 0;
+   if (!stringp(id2key[id])) return 0;
+   return get_cloner();
+}
+
+// ueber diese Funktion kann die Liste der Id's updatet werden
+// es wird <filename> eingelesen und durchgeparst.
+// Diese Datei muss in jeder Zeile folgendes Format einhalten und darf keine
+// leerzeilen enthalten! > Id,key,Gender,Name,Adjektiv
+// AN: Das ist Quatsch. Das Format muss so aussehen:
+// String-ID;Kraut-ID;demon;gender;P_NAME;adjektiv;P_LONG;roomdetail;
+// HU: Diese Funktion lass ich so. Harhar.
+// Update: Na gut. Fast.
+nomask private int LoadIdList(string filename)
+{
+   int i, id, si, demon;
+   string *lines, file;
+   mixed *data;
+   file=read_file(filename);
+   if (!stringp(file)) raise_error("Missing File: "+filename+"\n");
+   lines=explode(read_file(filename), "\n");
+   si=sizeof(lines)-1;
+   // AN/TODO: Warum verwendet man hier nicht auch einfach explode()?
+   // Wenn "##ende##" als Trennzeichen enthalten ist, liefert explode()
+   // als letzten Eintrag einen Leerstring, darauf kann man pruefen.
+   // Allerdings muesste das vor dem explode() zur Zeilentrennung passieren.
+   for ( ;si>=0; si--) 
+   {
+     string lili=lines[si];
+     if (strstr(lines[si],"##ende##")>=0) break;
+   }
+   si--;
+   if (si<0) raise_error("##ende## not found in id-list.\n");
+   id2key=allocate(si+2);
+   krautprops=allocate(si+2);
+   // AN: Fuer das Auslesen der Werte aus dem Array "data" muessen die 
+   // Indizierungs-Defines jeweils +1 verwendet werden, weil im Datenfile
+   // schlauerweise ein Datenfeld *vorne* angefuegt wurde.
+   // AN/TODO: vielleicht sollte man besser Element 0 zuerst auslesen und
+   // dann das Array arr um ein Element kuerzen: arr = arr[1..];
+   for (i=si; i>=0; i--) {
+      data=explode(lines[i], ";");
+      if (sizeof(data)!=8) raise_error("Wrong id-list format in line "+(i+1)+"\n");
+      id=to_int(data[PROP_ID+1]);
+      data[PROP_ID+1]=id;
+      if (id>si+1) raise_error(sprintf("Line %d: id %d greater than array size %d.\n", i, id, si));
+      id2key[id]=data[0];
+      // AN: Ich sehe noch nicht so ganz, warum man von dem Datenfeld
+      // PROP_DEMON nur das letzte Zeichen als Literal pruefen sollte.
+      // Komplett geht doch genausogut?
+      switch(data[PROP_DEMON+1][<1]) {
+        case 'R': data[PROP_DEMON+1]=RAW; break;
+        case '0': data[PROP_DEMON+1]=0;   break;
+        case '1': data[PROP_DEMON+1]=1;   break;
+        case '2': data[PROP_DEMON+1]=2;   break;
+        default: raise_error("Unknown demonformat '"+data[PROP_DEMON+1]+
+                   "' in idlist line "+(i+1)+"\n");
+      }
+      switch(data[PROP_GENDER+1][<1]) {
+        case 'N': data[PROP_GENDER+1]=NEUTER; break;
+        case 'F': data[PROP_GENDER+1]=FEMALE; break;
+        case 'M': data[PROP_GENDER+1]=MALE;   break;
+        default: raise_error("Unknown genderformat '"+data[PROP_GENDER+1]+
+                   "' in idlist line "+(i+1)+"\n");
+      }
+      SetProp(P_GENDER, data[PROP_GENDER+1]);
+      SetProp(P_NAME, data[PROP_NAME+1]);
+      // AN/TODO: data[PROP_ADJ] muss man nicht unbedingt vorher auf 0 setzen
+      if (!sizeof(data[PROP_ADJ+1])) data[PROP_ADJ+1]=0;
+      if (data[PROP_ADJ+1])
+         SetProp(P_NAME_ADJ, ({"ganz normal", data[PROP_ADJ+1]}));
+      else SetProp(P_NAME_ADJ, "ganz normal");
+      SetProp(P_ARTICLE, data[PROP_DEMON+1]!=RAW);
+      demon=(data[PROP_DEMON+1]==RAW ? 0 : data[PROP_DEMON+1]);
+      // AN: Wenn keine Langbeschreibung hinterlegt wurde, wird der Name
+      // des Krautes als solche verwendet. Ebenso fuer das Raumdetail, das
+      // beim Betrachten des Krautes im Raum ausgegeben wird.
+      if (!sizeof(data[PROP_LONG+1])) {
+         data[PROP_LONG+1] = Name(WER, demon)+".\n";
+      }
+      else data[PROP_LONG+1] = BS(data[PROP_LONG+1]);
+// Humni: Offenbar kommen am Zeilenende manchmal Zeichen dazu. Ich gehe davon
+// aus, dass keine Beschreibung kuerzer als 2 Zeichen ist.
+      if (sizeof(data[PROP_ROOMDETAIL+1])<2) {
+         data[PROP_ROOMDETAIL+1] = Name(WER, demon)+".\n";
+      }
+      else data[PROP_ROOMDETAIL+1] = BS(data[PROP_ROOMDETAIL+1]);
+      krautprops[id]=data[1..];
+   }
+   SetProp(P_NAME, 0);
+   SetProp(P_NAME_ADJ, "");
+   
+   // key2id-Cache neu aufbauen.
+   key2id=([]);
+   for (i=sizeof(id2key)-1; i>=0; i--) {
+      if (member(key2id, id2key[i]))
+         key2id[id2key[i]]+=({ i });
+      else key2id[id2key[i]]=({ i });
+   }
+   return 1;
+}
+
+// Hilfsfunktion wird zum einparsen benötigt
+// wandelt z.B. den string von "h 500" in ({ 3, 500 }) um
+private nomask <string|int>* buildAttrArr(string attr)
+{
+   if (!attr) return 0;
+   attr=trim(attr);
+   <string|int>* res=explode(attr, " ")-({""});
+   //debug(sprintf(" build Attr %O",res));
+   if (sizeof(res)!=2) raise_error("Wrong attrib format!\n");
+   //debug(sprintf("%O",T_KRAUT_MAP));
+   return ({T_KRAUT_MAP[res[0]],(int)res[1]});
+}
+
+// AN: Hilfsfunktion zum Einlesen der Traenkeliste.
+nomask private void LoadIndex(string filename)
+{
+   int i, j, si;
+   string *lines, file;
+   mixed  *data;
+
+   file=read_file(filename);
+   if (!stringp(file)) raise_error("Missing File: "+filename+"\n");
+   
+   // AN/TODO: Auch hier waere wieder die Verwendung von explode() zu
+   // erwaegen.
+   lines=explode(file, "\n")-({""});
+   si=sizeof(lines);
+   for (i=0; i<si; i++) if (lines[i]=="##start##") break;
+   i++;
+   if (i>=si) raise_error("missing ##start## in Index.\n");
+   ingredients=([]);
+   for (; i<si; i++) { // alle zeilen durchlaufen...
+     // AN/TODO: Tabulatoren als Trennzeichen finde ich irgendwie bloed.
+     // HU: Darum nun Semikolons
+     // debug("Zeile "+lines[i]);
+     data=old_explode(lines[i], ";"); // an Semikolons trennen...
+     // debug(sprintf("%O",data));
+     if (sizeof(data)!=9) 
+       {
+         //debug(sprintf("%O",data));
+         raise_error("Wrong indexlist format in line "+(i+1)+"\n");
+       }
+     for (j=8; j>=0; j--) {
+       // AN/TODO: if(data[j]=="" || data[j]=="-") data[j]=0;
+       // es sind ohnehin ueberall Strings enthalten.
+       // Wir machen aus "-" oder "0" eine echte int-Null.
+       if (sizeof(data[j])==0 || data[j][0]=='-') data[j]=0;
+     }
+     // HU: Ab hier bau ich mal neu. Den Rest pack ich auskommentiert darunter, wenn jemand den alten Code
+     // nachschauen will.
+     // Ich will ein Mapping von dieser Kraeutergruppe.
+     mapping mk=([]);
+     // Dieses Mapping soll die Eintraege nun enthalten.
+     mk[T_EFFECT_DURATION]=to_int(data[5]);
+     mk[T_ABUNDANCE]=to_int(data[8]);
+     // positive Effekte aufteilen
+     //debug(sprintf("Vorposis %O - %O",mk,data));
+     if (stringp(data[1]))
+       {
+         string* posis=explode(data[1],",");
+         //debug(sprintf("%O - %O",mk,posis));
+         foreach (string q:posis) {
+           //debug(q);
+           <string|int>* arr=buildAttrArr(q);
+           //debug(sprintf("%O",arr));
+           mk[arr[0]]=mk[arr[0]]+arr[1];
+         }
+       }
+     //debug(sprintf("%O",mk));
+     // Erster Negativer Effekt
+     if (data[2]!=0)
+       {
+         <string|int>* arr=buildAttrArr(data[2]);
+         mk[arr[0]]=mk[arr[0]]-arr[1];
+       }
+     //debug(sprintf("vorneg %O",mk));
+     // Zeiter negativer Effekt
+     if (data[3]!=0)
+       {
+         <string|int>* arr=buildAttrArr(data[3]);
+         mk[arr[0]]=mk[arr[0]]-arr[1];
+       }
+     // Haltbarkeit wird umgerechnet
+     string* sti=explode(data[4]," ")-({""});
+     //debug("haltbar "+sti[0]);
+     string stt=trim(sti[0]);
+     int dur;
+     if (stt[<1..]=="d") // Tage
+       {
+         //debug("Tage");
+         // Der erste Teil ist die Dauer in Tagen.
+         dur=to_int(sti[0][..<2]);
+         dur=dur*24*60*60; // Sekunden
+         mk[T_EXPIRE]=dur;
+       }
+     if (stt[<1..]=="h") // Stunden
+       {
+         //debug("Stunden");
+         // Sonst ist es halt die Dauer in Stunden.
+         dur=to_int(sti[0][..<2]);
+         dur=dur*60*60;
+         mk[T_EXPIRE]=dur;
+       }
+     //debug("ergibt "+dur);
+     // Nun die lustigen Unterstuetzungen. Dazu benutzen wir unseren lustigen Glueckshasen.
+     // Und ein Reh.
+     string* glueckshase;
+     string reh;
+     // Alle Leerzeichen raus!
+     //debug("Rehe");
+     //debug(sprintf("Data: %O",data));
+     if (stringp(data[6]))
+       {
+         reh=(explode(data[6]," ")-({""}))[0];
+     
+         glueckshase=explode(reh,",")-({});
+         mk[T_SUPPORTER]=glueckshase;
+       }
+     else
+       {
+         mk[T_SUPPORTER]=0;
+       }
+     // Nun machen wir genauso die Blockaden.
+     // Das tolle ist: Reh und Glueckshase koennen wir wiederverwenden! Das freut.
+     if (stringp(data[7]))
+       {
+         reh=(explode(data[7]," ")-({""}))[0];
+         glueckshase=explode(reh,",")-({});
+         mk[T_BLOCKING]=glueckshase;
+       }
+     else
+       {
+         mk[T_BLOCKING]=0;
+       }
+     ingredients[trim(data[0])]=mk;
+   }
+   //debug("Wuff");
+   //debug(sprintf("%O",ingredients));
+}
+
+nomask private void save()
+{
+  save_object(__DIR__"/ARCH/krautmaster");
+}
+
+// AN: erzeugt aus Namen + Adjektiv der Pflanzendaten den Objektnamen,
+// z.B. waldrebe_gemein oder ackerklee_gelb, wobei Bindestriche auch
+// durch Unterstriche ersetzt werden (acker_rettich).
+string build_plantname(mixed *props)
+{
+   string key;
+   // AN/TODO: erst PROP_NAME in key schreiben, dann ggf. PROP_ADJ dazu
+   if (sizeof(props[PROP_ADJ])>0)
+      key=lowerstring(props[PROP_NAME]+"_"+props[PROP_ADJ]);
+   else key=lowerstring(props[PROP_NAME]);
+   // AN/TODO: ersetzen durch regreplace();
+   key=implode(old_explode(key, " "), "_");
+   key=implode(old_explode(key, "-"), "_");
+   return key;
+}
+
+public void UpdateVC()
+{
+  KRAEUTERVC->update(map_ldfied);
+}
+
+// AN: Daten neu parsen
+// Nach dem Schreiben des Savefiles mittels save() wird auch das
+// Kraeuter-Headerfile vom Kraeuter-VC neu geschrieben.
+int _refresh()
+{
+   int i;
+   string key;
+   if (extern_call() && !allowed())
+     return 0;
+   
+   LoadIdList(__DIR__"ARCH/kraeuterliste.dump");
+   LoadIndex(__DIR__"ARCH/kraeuterindex.dump");
+   map_ldfied=([]);
+   for (i=sizeof(krautprops)-1; i>=0; i--)
+   {
+      if (sizeof(krautprops[i])<=PROP_ROOMDETAIL) continue;
+      key = build_plantname(krautprops[i]);
+      map_ldfied[key]=({ krautprops[i], rooms[key]||([]) });
+   }
+   save();
+   UpdateVC();
+   
+   // Update Headerfile mit Kraeuterliste
+   string *keys = sort_array(m_indices(map_ldfied), #'<);
+   string headerfile =
+     "// Automatisch generiertes File, nicht von Hand editieren!\n"
+     "// Erzeugendes File: "+object_name()+"\n\n"
+     "#define PLANTCOUNT "+to_string(sizeof(keys))+"\n\n"
+     +"#define PLANT(x) \"/items/kraeuter/\"+x\n\n";
+   foreach(key: keys)
+   {
+     headerfile += sprintf("#define %-30s PLANT(\"%s\")\n",
+                     upperstring(key), key);
+   }
+   write_file(KRAEUTERLISTE, headerfile, 1);
+
+   write("Inputfiles parsed. Save & Headerfiles updated!\n");
+   return 1;
+}
+
+int _cloneplant(string str)
+{
+  if (allowed())
+  {
+    if (to_string(to_int(str)) == str)
+      str = QueryPlantFile(to_int(str));
+    clone_object(PLANTDIR+str)->move(this_player(), M_NOCHECK);
+    write("Kraut " + str + " geclont.\n");
+    return 1;
+  }
+  return 0;
+}
+
+#define MAX_ROOMS 10  /* kein Kraut ist in mehr als 10 Raeumen */
+// AN: Ausgabe der Kategorienliste ueber das Planttool.
+int _showplant(string str)
+{
+   int i, si, kat, secure;
+   string *list, key;
+   mixed *res, *arr;
+   mapping props;
+
+   secure=allowed();
+   notify_fail("Es gibt folgende Kraeuterkategorien:\n"
+    +" 0 - haeufig und an vielen Stellen im Mud anzufinden\n"
+    +" 1 - etwas seltener zu finden, aber immer noch leicht\n"
+    +" 2 - an wenigen gut versteckten Stellen in abgelegenen Gebieten\n"
+    +" 3 - dito, jedoch muss das Kraut durch einen NPC (XP >= 500000) bewacht sein.\n"
+    +" 4 - aeusserst selten und XP >= 1 mio\n"
+    +" 5 - aeusserst selten und XP >= 2 mio\n"
+    +" 6 - aeusserst selten und NPC bringt >= 5  Stupse\n"
+    +" 7 - aeusserst selten und NPC bringt >= 10 Stupse\n"
+    +" 8 - aeusserst selten und NPC bringt >= 20 Stupse\n"
+    +" 9 - aeusserst selten und NPC bringt >= 50 Stupse\n"
+    +"\nSyntax: showplant <kategorie>.\n");
+   kat=to_int(str);
+   if (kat<0 || kat>9) return 0;
+   if (to_string(kat)!=str) return 0;
+   list=m_indices(map_ldfied);
+   // AN: *grummel*
+   // res = allocate(MAX_ROOMS, ({}));
+   res=map(allocate(MAX_ROOMS), #'allocate); // ({ ({}) ... ({}) })
+   for (i=sizeof(list)-1; i>=0; i--) {
+      arr=map_ldfied[list[i]];
+      if (sizeof(arr)!=2) raise_error("Wrong map_ldfied-Format by "+list[i]+"\n");
+      key=id2key[arr[0][PROP_ID]];
+      if (!key) raise_error("Missing Key for id "+arr[0][PROP_ID]+"\n");
+      props=ingredients[key];
+      //if (!pointerp(props)) continue; // noch nicht eingetragen
+      //if (sizeof(props)!=8)
+      // printf("Warning: Wrong ingredient-content by "+key+"\n");
+      //debug(sprintf("%O",props));
+      if (props==0)
+      {
+        debug("Falscher Key: "+key);
+      }
+      else
+      {
+        if (props[T_ABUNDANCE]==kat)
+        {
+          si=sizeof(arr[1]);
+          if (si<MAX_ROOMS) {
+            if (stringp(arr[0][PROP_ADJ])) {
+              SetProp(P_ARTICLE, 0);
+              SetProp(P_NAME, arr[0][PROP_NAME]);
+              SetProp(P_NAME_ADJ, arr[0][PROP_ADJ]);
+              SetProp(P_GENDER, arr[0][PROP_GENDER]);
+              key=Name(WER);
+            }
+            else key=arr[0][PROP_NAME];
+            if (secure)
+              res[si]+=({ sprintf("%3d %-40s: %d\n", arr[0][PROP_ID], key, si) });
+            else res[si]+=({ sprintf("%-40s: %d\n", key, si) });
+          }
+        }
+      }
+   }
+   for (i=0; i<MAX_ROOMS; i++) {
+      sort_array(res[i], #'>);
+      filter(res[i], #'write);
+   }
+   return 1;
+}
+
+// AN: Ausgabe der Raeume, in denen die Kraeuter zu finden sind.
+// angesteuert ueber das Planttool.
+int _showrooms(string str)
+{
+   int i, j, id;
+   string *list, dummy;
+   mixed *arr;
+   if (!allowed()) return 0;
+   notify_fail("Syntax: showrooms <id> oder showrooms all\n");
+   if (str!="all") {
+     id=to_int(str);
+     if (to_string(id)!=str) return 0;
+   }
+   else id=-1;
+   list=m_indices(map_ldfied);
+   for (i=sizeof(list)-1; i>=0; i--) {
+      arr=map_ldfied[list[i]];
+      if (sizeof(arr)!=2) raise_error("Wrong map_ldfied-Format by "+list[i]+"\n");
+      if (arr[0][PROP_ID]==id || id<0) {
+         if (!sizeof(m_indices(arr[1]))) {
+            if (id>=0) write("Fuer diese Id sind keine Raeume eingetragen.\n");
+         }
+         else if (id>=0) {
+            write("Folgende Raeume sind fuer "+arr[0][PROP_ID]+" eingetragen.\n");
+            filter(map(m_indices(arr[1]), #'+, "\n"), #'write);
+            return 1;
+         }
+         else filter(map(m_indices(arr[1]), #'+, ": "+arr[0][PROP_ID]+", "+arr[0][PROP_NAME]+"\n"), #'write);
+         if (id>=0) return 1;
+      }
+   }
+   write("Fuer diese Id sind bisher keine Kraeuter eingetragen.\n");
+   return 1;
+}
+
+// Nutzung der Kraeuter in  Gebieten liefert nur dann gueltige Kraeuter,
+// wenn der Raum eingetragen ist.
+int _addroom(string str)
+{
+   int id, i;
+   string *list, vc;
+
+   if (!allowed()) {
+      write("Fuer das Eintragen der Raeume wende Dich doch bitte "
+            "an einen EM.\n");
+      return 1;
+   }
+   notify_fail("Syntax: addroom <krautnummer> <filename>\n");
+   str=PL->_unparsed_args();
+   if (!str || sscanf(str, "%d %s", id, str)!=2) return 0;
+   if (str=="hier" || str=="here")
+   {
+     if (!this_player())
+     {
+       notify_fail("Kein Spielerobjekt, kann "
+           "Raum nicht identifizieren.\n");
+       return 0;
+     }
+     str=to_string(environment(this_player()));
+   }
+   if (str[<2..]==".c") str=str[0..<3]; // .c abschneiden
+   if (file_size(str+".c")<=0) {
+      list=explode(str, "/");
+      vc=implode(list[0..<2], "/")+"/virtual_compiler.c";
+      if (file_size(vc)<=0 || !call_other(vc, "Validate", list[<1])) {
+         write("No such file \""+str+"\".\n");
+         return 1;
+      }
+   }
+   if (id<=0 || id>=sizeof(id2key)) {
+      write("Illegal plantid "+id+".\n");
+      return 1;
+   }
+   list=m_indices(map_ldfied);
+   for (i=sizeof(list)-1; i>=0; i--) {
+      if (map_ldfied[list[i]][0][PROP_ID]==id) {
+         if (!member(map_ldfied[list[i]][1], str)) {
+            map_ldfied[list[i]][1]+=([ str ]);
+            rooms[list[i]]=(rooms[list[i]]||([]))+([ str ]);
+            write("Raum Erfolgreich eingetragen!\n");
+         }
+         else write("Raum bereits eingetragen.\n");
+         save();
+         _refresh();
+         return 1;
+      }
+   }
+   write("Kraut mit id "+id+" nicht gefunden.\n");
+   return 1;
+}
+
+int _delroom(string str)
+{
+   int i, done;
+   string *list;
+
+   if (!allowed()) {
+      write("Fuer das Loeschen von Raeumen wende Dich doch bitte "
+            "an einen EM.\n");
+      return 1;
+   }
+   notify_fail("Syntax: delroom <filename>.\n");
+   str=PL->_unparsed_args();
+   if (!str) return 0;
+   if (str[<2..]==".c") str=str[0..<3];
+   list=m_indices(map_ldfied); done=0;
+   for (i=sizeof(list)-1; i>=0; i--)
+   {
+      if (member(map_ldfied[list[i]][1], str)) {
+         m_delete(map_ldfied[list[i]][1], str);
+         m_delete(rooms[list[i]], str);
+         write("Raum bei id "+map_ldfied[list[i]][0][PROP_ID]
+               +" ausgetragen.\n");
+         done=1;
+      }
+   }
+   if (!done) {
+      if (file_size(str+".c")<0)
+         write("No such file \""+str+"\".\n");
+      else write("Fuer "+str+" sind keine Kraeuter eingetragen!\n");
+   }
+   else {
+      save();
+      _refresh();
+   }
+   return 1;
+}
+
+// Veranlasst den Kraeuter-VC, eine phys. Datei aus den Kraeuterdaten eines
+// Krautes zu erzeugen, falls man diese ausbeschreiben will.
+int _createfile(string str)
+{
+  int id;
+  if (!allowed()) {
+    write("Diese Funktion wurde fuer Dich nicht freigegeben!\n");
+    return 1;
+  }
+  id=to_int(str);
+  if (to_string(id)!=str || id<=0 || id>=sizeof(id2key)) {
+    write("Illegal plantid '"+str+"'.\n");
+    return 1;
+  }
+  notify_fail("Unknown Function im kraeuterVC: _createfile()\n");
+  return call_other(KRAEUTERVC, "_createfile", build_plantname(krautprops[id]));
+}
+
+// AN: Hilfsfunktionen, derzeit ebenfalls deaktiviert.
+// i = 0..7, Position des Krautes, fuer das der Aufruf erfolgt, in der 
+// Liste der in den Kessel einfuellten Kraeuter.
+// keyLst ist die Liste der Kraeutergruppen, der die Kraeuter zugeordnet
+// sind.
+// An dieser Stelle kann also die Wirkung von Kraeutergruppen abgehaengt
+// werden. Unklar ist mir aktuell nur, warum diese Funktion den Parameter
+// "i" benoetigen wuerde.
+// Idee: Es soll die Entscheidung davon abhaengig gemacht werden koennen,
+// wie die Gesamtkombination aussieht, und zusaetzlich davon, aus welcher
+// Gruppe das einzelne Kraut stammt.
+nomask private int IsBlocked(int i, string *keyLst)
+{
+  return 0;
+}
+
+// AN: Diese Funktion muesste nach dem Code in make_potion() zu urteilen
+// 0 oder 1 zurueckgeben, dann wird der Eigenschaftswert der Kraeutergruppe
+// um den Faktor 1.5 verstaerkt.
+nomask private int IsBoosted(int i, string *keyLst)
+{
+  return 0;
+}
+
+#define PRNG "/std/util/rand-glfsr"
+// Individuelle Boni/Mali fuer Spieler. ploffset soll via Referenz uebergeben
+// werden und wird von der Funktion gesetzt.
+int calculate_mod(int krautindex, string plname, int ploffset)
+{
+  // Startoffset zufaellig ermittelt, aber immer gleich
+  // fuer jeden Spielernamen
+  PRNG->InitWithUUID(plname);
+  ploffset = PRNG->random(16);
+  // Jedes Kraut hat auch einen iOffset (der konstant bleibt und sich nicht
+  // aendert). Der wird auch addiert. Bei Ueberschreiten von 30 wird nach 0
+  // gewrappt.
+  // Der Offset ist dann (spieleroffset + krautindex) % 16, d.h. alle Modifikatoren werden
+  // der Reihe nach durchlaufen. So kriegt jeder Spieler - fast egal, bei welchem
+  // Offset er startet - auch der Reihe nach alle Boni+Mali.
+  int offset = ((ploffset + krautindex) % 16) * 2;
+  // Am Ende wird das ganze noch nach 85 bis 115 verlegt.
+  return offset + 85;
+}
+
+#define ZWEITIES "/secure/zweities"
+
+mapping calculate_potion(int* plantids, int* qualities, string familie)
+{
+  // Man sollte ohne die Kraeuter nicht so einfach Wirkungen beliebig
+  // berechnen koennen.
+  if (extern_call() && !ARCH_SECURITY)
+    return 0;
+
+  // Hier speichern wir unser Ergebnis bzw. unser Zwischenergebnis.
+  mapping attrib;
+  // Hier speichern wir die Wirkgruppen, die aus den Plants gezogen werden.
+  mapping* wirkungen=({});
+  // Hier speichern wir gleich schon beim Erzeugen die wichtigsten Blockaden.
+  string* unterstuetzungen=({});
+  string* blockaden=({});
+  int zufall;
+  // Die Sortierung nach PlantID ist nur fuer das Tranklog wichtig.
+  plantids = sort_array(plantids, #'<=);
+
+  // PASS 1: Pflanzen durch Wirkungen ersetzen, dabei Unterstuetzer
+  // und Blocker merken.
+  foreach (int id, int qual : mkmapping(plantids,qualities))
+    {
+      //debug(sprintf("Gehe durch Plant: %d",id));
+      string key=id2key[id];
+      // Wirkungen dieses Krauts kopieren
+      mapping ing=copy(ingredients[key]);
+      //debug(sprintf("%O",ing));
+      // Zu den Wirkungen noch den Key hinzufuegen.
+      ing["key"]=key;
+      // Die Qualitaet des Krautes wird noch mit dem spielerindividuellen
+      // Modifikator skaliert.
+      ing["quality"]=(qual * calculate_mod(id, familie, &zufall)) / 100;
+      wirkungen+=({ing});
+      if (pointerp(ing[T_SUPPORTER]))
+        {
+          foreach (string pi:ing[T_SUPPORTER])
+            {
+              unterstuetzungen+=({pi});
+            }
+        }
+      if (pointerp(ing[T_BLOCKING]))
+        {
+          foreach (string pi:ing[T_BLOCKING])
+            {
+              blockaden+=({pi});
+            }
+        }
+      debug(sprintf("Kraut %s ergibt Werte %O.",key,wirkungen));
+    }
+  // PASS 2: Jetzt die Unterstuetzungen auswerten
+  foreach (mapping mar:wirkungen)
+    {
+      foreach (string pi:unterstuetzungen)
+        {
+          // Haben wir eine Unterstuetzung?
+          if (mar["key"]==pi || ((pi[1]=='*') && (pi[0]==mar["key"][0])))
+            {
+              debug (sprintf("mar=%O, pi=%O",mar,pi));
+              // ALLE ZAHLEN mal 1.5 nehmen. Mir ist klar, dass das nun auch
+              // mit irgendwelchen Haeufigkeiten passiert, aber mal ehrlich,
+              // das ist zur Berechnung egal.
+              foreach (string kk, mixed val : &mar)
+                {
+                  if (intp(val))
+                    {
+                      val=15*val/10;
+                    }
+                }
+            }
+        }
+    }
+  // PASS 3: Jetzt die Blockaden auswerten
+  foreach (mapping mar:wirkungen)
+    {
+      foreach (string pi:blockaden)
+        {
+          // Haben wir eine Blockade?
+          if (mar["key"]==pi || ((pi[1]=='*') && (pi[0]==mar["key"][0])))
+            {
+              debug (sprintf("mar=%O, pi=%O",mar,pi));
+              // Hier werden alle Zahlen auf Null gesetzt.
+              foreach (string kk, mixed val : &mar)
+                {
+                  if (intp(val))
+                    {
+                      val=0;
+                    }
+                }
+            }
+        }
+    }
+
+  // PASS 3.5: Qualitaet der Kraeuter skalieren.
+  foreach (mapping mar:wirkungen)
+    {
+      foreach (string kk, mixed val : &mar)
+        {
+          if (intp(val) && kk!="quality")
+            {
+              val=val*mar["quality"]/100;
+            }
+        }
+    }
+
+  // PASS 4: Nun addieren wir alles auf in das Mapping attrib.
+  attrib=([]);
+  foreach (mapping mar:wirkungen)
+    {
+      foreach (string kk:mar)
+        {
+          if (intp(mar[kk]))
+            {
+              attrib[kk]=attrib[kk]+mar[kk];
+            }
+        }
+    }
+
+  // Die Wirkungsdauer ist der Durchschnitt der Wirkungsdauern
+  attrib[T_EFFECT_DURATION] /= sizeof(plantids);
+  debug(sprintf("Duration: %d\n",attrib[T_EFFECT_DURATION]));
+
+  // Die Haltbarkeit des Tranks ist die Haltbarkeit des kleinsten Krautes.
+  int dur=10000000;
+  foreach (mapping mar:wirkungen)
+    {
+      if (mar[T_EXPIRE]>0 && dur>mar[T_EXPIRE])
+        {
+          dur=mar[T_EXPIRE];
+        }
+    }
+  if (dur==10000000)
+    {
+      dur=0;
+    }
+  attrib[T_EXPIRE]=dur;
+  debug(sprintf("Expire: %d\n",dur));
+
+  int maximum=0;
+  // Effekte rausrechnen, die nicht maximal sind
+  foreach (string kk, mixed val:attrib)
+  {
+    if (member(T_KRAUT_EFFECTS,kk)>=0)
+    {
+      if (val>0 && maximum<val)
+      {
+        maximum=val;
+      }
+    }
+  }
+  // Logeintrag erstellen.
+  sl_exec("INSERT INTO rohdaten(uid, rnd, receipe, quality, car, da, dm, du, "
+          "dn, flt, fro, hI, hP, hK, hL, pa, pm, pu, ss, sp, sd) "
+          "VALUES(?1,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11, ?12, ?13, ?14, "
+          "?15, ?16, ?17, ?18, ?19, ?20, ?21);",
+          this_player() ? getuid(this_player()) : "<unknown>",
+          zufall,
+          implode(map(plantids, #'to_string), ", "),
+          implode(map(qualities, #'to_string), ", "),
+          attrib[T_CARRY], attrib[T_DAMAGE_ANIMALS],
+          attrib[T_DAMAGE_MAGIC], attrib[T_DAMAGE_UNDEAD],
+          attrib[T_EFFECT_DURATION], attrib[T_FLEE_TPORT],
+          attrib[T_FROG], attrib[T_HEAL_DISEASE],
+          attrib[T_HEAL_POISON], attrib[T_HEAL_SP],
+          attrib[T_HEAL_LP], attrib[T_PROTECTION_ANIMALS],
+          attrib[T_PROTECTION_MAGIC], attrib[T_PROTECTION_UNDEAD],
+          attrib[T_SA_SPEED], attrib[T_SA_SPELL_PENETRATION],
+          attrib[T_SA_DURATION]);
+
+  // Maximal zwei positive Effekte.
+  int cteff=0;
+  foreach (string kk, mixed val : &attrib)
+  {
+      if (member(T_KRAUT_EFFECTS,kk)>=0)
+      {
+        // Nur die 2 staerksten positiven Wirkungen bleiben ueber (dazu
+        // muessen sie wirklich den gleichen Zahlenwert haben, kann bei
+        // Heilungen vorkommen, sonst eher unwahrscheinlich).
+        if (val>0 && maximum>val)
+        {
+          val=0;
+        }
+        // Thresholds. Zu zu grosse Wirkungen haben die Grenze als
+        // Auswirkung. Negative Wirkungen, die -T_MINIMUM_THRESHOLD nicht
+        // ueberschreiben, fallen weg. Bei den positiven bleibt ja ohnehin nur
+        // die staerkste Wirkung ueber, da gibt es erstmal keine
+        // Mindestgroesse mehr.
+        if (val>T_MAXIMUM_THRESHOLD)
+        {
+          val=T_MAXIMUM_THRESHOLD;
+        }
+        if (val < 0 && val > -T_MINIMUM_THRESHOLD)
+        {
+          val=0;
+        }
+        if (maximum==val && val>0)
+        {
+          cteff++;
+          // Voellig willkuerlich, was hier getroffen wird. Ob reproduzierbar,
+          // vermutlich ja, aber haengt mit der Mappingstruktur zusammen.
+          // Harhar.
+          if (cteff>2)
+          {
+            val=0;
+          }
+        }
+      }
+  }
+  debug(sprintf(" TRANKERGEBNIS: %O",attrib));
+  // Logeintrag erstellen.
+  sl_exec("INSERT INTO traenke(uid, rnd, receipe, quality, car, da, dm, du, "
+          "dn, flt, fro, hI, hP, hK, hL, pa, pm, pu, ss, sp, sd) "
+          "VALUES(?1,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11, ?12, ?13, ?14, "
+          "?15, ?16, ?17, ?18, ?19, ?20, ?21);",
+          this_player() ? getuid(this_player()) : "<unknown>",
+          zufall,
+          implode(map(plantids, #'to_string), ", "),
+          implode(map(qualities, #'to_string), ", "),
+          attrib[T_CARRY], attrib[T_DAMAGE_ANIMALS],
+          attrib[T_DAMAGE_MAGIC], attrib[T_DAMAGE_UNDEAD],
+          attrib[T_EFFECT_DURATION], attrib[T_FLEE_TPORT],
+          attrib[T_FROG], attrib[T_HEAL_DISEASE],
+          attrib[T_HEAL_POISON], attrib[T_HEAL_SP],
+          attrib[T_HEAL_LP], attrib[T_PROTECTION_ANIMALS],
+          attrib[T_PROTECTION_MAGIC], attrib[T_PROTECTION_UNDEAD],
+          attrib[T_SA_SPEED], attrib[T_SA_SPELL_PENETRATION],
+          attrib[T_SA_DURATION]);
+
+  return attrib;
+}
+
+mapping make_potion(object* plants)
+{
+  // -> mappt nicht-Objekt zu 0, aber 0 ist auch ne gueltige PlantID. Daher
+  // müssen zerstoerten Objekte vorher raus.
+  // TODO: drauf verlassen, dass nur intakte Objekt enthalten sind?
+  if (member(plants, 0) >= 0)
+    raise_error(sprintf("make_potion() got invalid object in plant array "
+                        "%.50O\n",plants));
+
+  int* plantids = (int*)plants->QueryPlantId();
+  int* qualities = (int*)plants->QueryProp(P_QUALITY);
+
+  return calculate_potion(plantids, qualities,
+                          ZWEITIES->QueryFamilie(this_player()));
+}
+
+// AN: Sucht alle Pflanzen raus, in deren Namen der Suchbegriff "str"
+// vorkommt und listet diese auf. Laeuft allerdings momentan noch in einen
+// Fehler "index out of bounds", aber man muesste hier (TODO) sowieso mal
+// von explode() auf strstr() umbauen, denke ich.
+string _findplant(string str) {
+  int i, k;
+  string *ind, *tmp;
+
+  if(!str) return "";
+  write("Suche nach '"+str+"':\n\n");
+  ind = m_indices(map_ldfied);
+  for(i=0;i<sizeof(ind);i++) {
+    tmp = map_ldfied[ind[i]][0];
+    if( stringp(tmp[3]) && 
+        old_explode(lower_case(tmp[3]),str)[0] != lower_case(tmp[3]) 
+        ||
+        stringp(tmp[4]) && 
+        old_explode(lower_case(tmp[4]),str)[0] != lower_case(tmp[4]))
+      write(" -  "+tmp[3]+
+          (stringp(tmp[4])?" ("+tmp[4]+")":"")+"       - "+tmp[0]+"\n");
+  }
+
+  return "";
+}
+
+// AN: Funktion liefert das Ergebnisarray aus make_potion() fuer eine Liste
+// von Kraeutern, die als ", "-getrennte Kraut-IDs uebergeben werden muessen.
+mixed _checkTrank(string str)
+{
+  if (extern_call() && !allowed())
+    return 0;
+
+  string *ind, *args, name;
+  object *objs;
+  int k, l;
+
+  objs = ({});
+  if(!str) return "Keine Kraeuter uebergeben!";
+  ind = old_explode(str,",");
+//  ind = ({"180","11","53"});
+  for(int i=0;i<sizeof(ind);i++)
+  {
+    name = build_plantname(krautprops[to_int(ind[i])]);
+    write("Input: '"+name+"' ("+ind[i]+")\n");
+    objs += ({clone_object(PLANTDIR+name)});
+  }
+  mapping ragtest = make_potion(objs);
+  objs->remove();
+/*  name="";
+  for(int i=0;i<sizeof(ragtest);i++)
+       name = name + ragtest[i]+",";
+  write("Result: ({ "+name+" })\n");*/
+  return sprintf("%O\n",ragtest);
+}
+
+#define QUAL_BASE  0
+#define QUAL_RND   1
+#define DELAY_BASE 2
+#define DELAY_RND  3
+
+#define ALLOWED_DRIER "/items/kraeuter/trockner"
+
+// Liefert die Trocknungsdaten eines Raumes aus, mit denen der Kraeuter-
+// trockner dann das Kraut bearbeiten kann.
+int *QueryDryingData() {
+  // Es muss allerdings das aufrufende Objekt ein Trockner-Clone sein, 
+  // der in einem der zugelassenen Raeume stehen muss.
+  // Wenn das nicht der Fall ist, wird der Trockner das leere Array, das 
+  // zurueckgegeben wird, als Indiz werten, dass er im falschen Raum steht.
+  if ( objectp(previous_object()) &&
+       load_name(previous_object()) == ALLOWED_DRIER &&
+       member(drying_data, load_name(environment(previous_object()))) &&
+       clonep(previous_object()) )
+  {
+    // Raum ermitteln, Delay/Quali errechnen, Ergebnisarray zurueckgeben.
+    string where = load_name(environment(previous_object()));
+    int delay = drying_data[where,DELAY_BASE]+
+                random(drying_data[where,DELAY_RND]);
+    int qual  = drying_data[where,QUAL_BASE]+
+                random(drying_data[where,QUAL_RND]);
+    return ({ delay, qual });
+  }
+  return ({});
+}
+
+// Modifizieren der Trocknungsdaten.
+// <room> muss der volle Dateiname des Raumes sein, ohne .c am Ende.
+// <values> enthaelt die vier Parameter zu dem Raum in folgender Reihenfolge:
+// ({ Quali-Basis, Quali-Zufallsanteil, Delay-Basis, Delay-Zufallsanteil })
+// Wenn <values> nicht angeben wird oder 0 ist, werden die Daten zu <room>
+// geloescht.
+int|mapping SetDryingData(string room, int* values) 
+{
+  // keine Zugriffsrechte
+  if ( !allowed() )
+    return -1;
+  
+  // <values> wurde nicht uebergeben? Dann Daten loeschen.
+  if ( !values ) 
+  {
+    m_delete(drying_data, room);
+    return 1;
+  }
+
+  // Ansonsten muessen 4 Integer-Werte als <values> uebergeben werden.
+  if ( sizeof(values) != 4 ) 
+    return -2;
+
+  if ( room[<2..<1] == ".c" )
+    room = room[..<3];
+
+  // Uebergebene Daten aendern direkt das Mapping der Trocknungsdaten.
+  m_add(drying_data, room, values...);
+  save();
+  return ([ room : drying_data[room,0]; drying_data[room,1]; 
+                   drying_data[room,2]; drying_data[room,3]]);
+}
+
+varargs mapping QueryDrying()
+{
+  return (allowed() ? drying_data : ([]) );
+}
+
+varargs int remove(int silent) 
+{
+  save();
+  return ::remove(silent);
+}
+
+/*
+#define DRYINGDATA "/secure/ARCH/kraeutertrocknungsdaten"
+
+private void ReadDryingData() 
+{
+  mixed data = explode(read_file(DRYINGDATA), "\n")-({""});
+  foreach(string line : data) 
+  {
+    if ( line[0] == '#' )
+      continue;
+    string *fields = explode(line,";");
+    fields[1..] = map(fields[1..], #'to_int);
+    m_add(tmp_drying_data, fields...);
+  }
+}*/
+