Added public files

Roughly added all public files. Probably missed some, though.
diff --git a/std/items/fishing/angel.c b/std/items/fishing/angel.c
new file mode 100644
index 0000000..1b5709f
--- /dev/null
+++ b/std/items/fishing/angel.c
@@ -0,0 +1,510 @@
+/*
+  Zusammenfassung Bedingungen:
+  - Angel auswerfen
+      - Haken muss dran sein
+      - Haken muss Koeder haben
+      - darf nur ein Haken drin sein, sonst nix, und genau ein Haken
+      - Angel im Inventar
+      - Angel darf nicht schon ausgeworfen sein
+      - passendes Gewaesser im Raum
+      - Move-Hook muss korrekt registrierbar sein
+  - Angel einholen
+      - Angel-Callout muss ueberhaupt erstmal laufen
+  - Waehrend des Angelns
+      - bewegen beendet das Angeln
+      - Ausloggen beendet das Angeln
+      - Ende beendet das Angeln
+      - nichts rausnehmen
+      - nichts reintun, ausser Fisch am Ende der Wartezeit
+      - Langbeschreibung zeigt "ausgeworfen", Koeder und gefangenen Fisch an
+  - Fisch fangen (call_out zuende)
+      - Gewaesser passt (immer noch)
+      - Fisch passt noch in die Angel rein
+  - Haken dranhaengen
+      - Haken im Inv
+      - Haken muss "echter" Angelhaken sein
+      - Angel im Inv
+      - Koeder am Haken
+      - noch kein anderer Koeder dran
+      - Angel ist nicht ausgeworfen
+  - generell
+      - nur Haken dranstecken, nix anderes reintun
+      - reintun nur mit Syntax "haenge" etc. (damit sind Fische aussen vor,
+        weil die Syntax auf Haken prueft)
+        (ggf. ist dann das PreventInsert() zu vereinfachen)
+ */
+
+#pragma strong_types, save_types, rtt_checks
+#pragma no_clone, no_shadow
+
+inherit "/std/container";
+inherit "/std/hook_consumer";
+
+#include <new_skills.h>
+#include <language.h>
+#include <properties.h>
+#include <moving.h>
+#include <hook.h>
+#include <wizlevels.h>
+#include <items/fishing/fishing.h>
+#include <items/fishing/aquarium.h>
+
+#define TP this_player()
+#define ME this_object()
+#define BS(x) break_string(x, 78)
+
+#define MIN_DELAY       80   // Mindestdelay bis Fisch beisst!
+#define STD_DELAY_PLUS  60   //  +/- 60 sekunden Tolerant
+#define MAX_FISH_BONUS  60   // max. Wartezeitverkuerzung durch P_FISH
+#define WRONGWATER      60   // 1-2 min, falls Angel nicht geeignet
+#define WRONGWORM       60   // 1-2 min, falls Koeder nicht geeignet
+
+#define KOEDER_LOST     10
+// Verlustwahrsch. fuer Koeder falls Fisch zu schwer
+
+private object room, current_user;
+private int active, actime;
+private mapping aquarium = STDFISHLIST;
+
+nomask varargs void StopFishing(string msg_me, string msg_room);
+int IsFishing();
+
+protected void create() {
+  ::create();
+  
+  SetProp(P_SHORT, "Eine Angel");
+  SetProp(P_LONG, "Eine nagelneue Angel.\n");
+  SetProp(P_NAME, "Angel");
+  AddId(({"angel", "rute", ANGEL_ID}));
+
+  SetProp(P_GENDER, FEMALE);
+  SetProp(P_MATERIAL,([MAT_MISC_WOOD:90,MAT_WOOL:10]));
+  SetProp(P_TRANSPARENT,1);
+  SetProp(P_WEIGHT,200);
+  SetProp(P_MAX_WEIGHT, 10000);
+  SetProp(P_WATER, W_USER);     //muss (mehrere) Flags gesetzt haben!
+  SetProp(P_SOURCE_PREPOSITION, "von");
+  SetProp(P_NR_HANDS, 2); 
+
+  Set(P_NODROP, function string () {
+    return (IsFishing()?"Du solltest die Angel nicht fallenlassen, solange "
+      "Du angelst.\n":0);
+  }, F_QUERY_METHOD);
+
+  AddCmd(({"angel","angle"}),"angel");
+  AddCmd("hole|hol&@ID&ein", "stopit", "Was willst Du (ein)holen?|"
+    "Willst Du @WEN2 einholen?");
+  AddCmd("haenge|haeng|befestige&@PRESENT&@ID", "move_in",
+    "Was willst Du woran @verben?|"
+    "Woran willst Du @WEN2 denn @verben?");
+
+  AddCmd("atell","tell_stat");
+  AddCmd("angeltest","qangel");
+}
+ 
+protected void create_super() {
+  set_next_reset(-1);
+}
+     
+static int stopit(string str) {
+  if( find_call_out("do_angel")==-1 )
+    tell_object(TP, "Du angelst nicht!\nTP, ");
+  else 
+    StopFishing();
+  return 1;
+}
+
+varargs string long(int mode) {
+  string descr = QueryProp(P_LONG);
+  if(!QueryProp(P_TRANSPARENT)) 
+    return descr;
+
+  if ( find_call_out("do_angel")!=-1 )
+    descr += capitalize(QueryPronoun(WER))  + " ist ausgeworfen.\n";
+
+  object *inv = all_inventory(ME);
+  string inv_descr = make_invlist(TP, inv);
+  if ( inv_descr != "" )
+    descr += "An "+QueryPronoun(WEM) + " haeng"+(sizeof(inv)>1?"en":"t")+
+    ":\n" + inv_descr;
+  
+  return descr;
+}
+
+/*int tell_stat()
+{
+  int n;
+  
+  if(!IS_WIZARD(TP))
+    return 0;
+  if(!active){ 
+    write("Du angelst nicht\n"); return 1; 
+  }
+  n=find_call_out("do_angel");
+  if(active)
+    write(
+     "----------------------------------------------------------------------\n"
+     +"Der Fisch beisst in "+n+" sec!\n"
+     +"Bestandswert des Raumes: "+room->QueryProp(P_FISH)
+     +" = "+(room->QueryProp(P_FISH)+100)+"%.\n"
+     +"Bestandswert des Koeders: "+koeder->QueryProp(P_FISH)
+     +" = "+(100+koeder->QueryProp(P_FISH))+"%.\n"
+     +"Gesamt:"+bestand+" = "+(bestand+100)+"%.\n");
+  if(!(QueryProp(P_WATER)&room->QueryProp(P_WATER)))
+    write("Die Angel passt nicht zum Gewaessertypen.\n");
+  else
+    write("Die Angel passt zum Gewaessertypen.\n");
+  if(!(room->QueryProp(P_WATER)&koeder->QueryProp(P_WATER)))
+    write("Der Koeder passt nicht zum Gewaessertypen.\n");
+  else
+    write("Der Koeder passt zum Gewaessertypen.\n");
+  write(" => Delay="+delay+".\n"
+    +"----------------------------------------------------------------------\n"
+);
+  return 1;
+}*/
+
+static int move_in(string str, mixed *param) {
+  object haken = param[0];
+  // param[1] sind wir selbst, daher ab hier ME verwendet.
+
+  if ( find_call_out("do_angel")!=-1 ) { // angelt noch nicht?
+    tell_object(TP, BS(Name(WER,1)+" ist bereits ausgeworfen, da haengst "
+      "Du jetzt besser nichts mehr dran, das vertreibt nur die Fische."));
+  }
+  else if ( !haken->id(HAKEN_ID) ) { // echter Angelhaken?
+    tell_object(TP, BS(haken->Name(WER,1)+" ist kein Angelhaken, "+
+      haken->QueryArticle(WEN, 1, 1)+"kannst Du nicht an "+name(WEN,1)+
+      " haengen."));
+  }
+  else if ( environment(haken) != TP ) { // Haken im Inv?
+    tell_object(TP, BS("Wie er da so auf dem Boden liegt, schaffst Du es "
+      "einfach nicht, "+haken->name(WEN,1)+" vernuenftig an "+name(WEM,1)+
+      " zu befestigen."));
+  }
+  else if ( environment(ME) != TP ) { // Angel im Inv?
+    tell_object(TP, BS("Es stellt sich viel fummeliger heraus als gedacht, "+
+      haken->name(WEN,1)+" an "+name(WEM,1)+" zu befestigen, solange "+
+      QueryPronoun(WER)+" auf dem Boden herumliegt."));
+  }
+  else if ( present(HAKEN_ID, ME) ) { // anderer Haken schon dran?
+    tell_object(TP, BS("An "+name(WEM,1)+" haengt bereits ein Angelhaken."));
+  }
+  else if ( !haken->QueryKoeder() ) { // Koeder am Haken?
+    tell_object(TP, BS("Du willst "+haken->name(WEN,1)+" gerade an "+
+      name(WEN,1)+" haengen, als Dir auffaellt, dass Du beinahe den Koeder "
+      "vergessen haettest."));
+  }
+  else { // dann darf der Haken rangehaengt werden.
+    if ( haken->move(ME, M_PUT) == MOVE_OK ) {
+      tell_object(TP, BS("Du haengst "+haken->name(WEN,1)+" an "+
+        name(WEN,1)+"."));
+      tell_room(environment(TP), BS(TP->Name(WER)+" haengt etwas metallisch "
+        "Glaenzendes an "+name(WEN)+", vermutlich einen Angelhaken."), 
+        ({TP}));
+    }
+    else {
+      tell_object(TP, BS(haken->Name(WER,1)+" laesst sich nicht an "+
+        name(WEM,1)+" befestigen."));
+    }
+  }
+  return 1;
+}
+
+static int qangel() {
+  if(IS_WIZARD(TP)) {
+    call_out("do_angel",1);
+    return 1;
+  }
+  return 0;
+}
+
+static int angel(string str) {
+  object haken = present(HAKEN_ID,ME);
+  if ( environment(ME) != TP ) {
+    tell_object(TP, BS("Dafuer musst Du die Angel in die Hand nehmen."));
+  }
+  else if ( find_call_out("do_angel")!=-1 ) {
+    tell_object(TP, "Das tust Du doch schon.\n");
+  }
+  else if(!objectp(haken)) {
+    tell_object(TP, "Wie soll das gehen? Du hast ja nichtmal einen Haken "
+      "an der Angel!\n");
+  }
+  else if(!haken->QueryKoeder()) {
+    tell_object(TP, break_string("Ohne Koeder am Haken wird sich kein "
+      "Fisch der Welt fuer Deine Angel interessieren.",78));
+  }
+  else if(present(FISCH_ID,ME)) {
+    tell_object(TP, "Nimm erst mal die Beute von der Angel, die noch daran "
+      "zappelt!\n");
+  }
+  else if ( !TP->UseHands(ME, QueryProp(P_NR_HANDS)) ) { // freie Haende?
+    tell_object(TP, BS("Du musst schon beide Haende frei haben, um die "
+      "Angel sicher festhalten zu koennen."));
+  }
+  else {
+    // Raum festhalten, in dem geangelt wird.
+    room = environment(TP);
+    // Gewaessertyp abfragen
+    active = room->QueryProp(P_WATER);
+    // Kein Wasser vorhanden, oder nicht-befischbarer Sondertyp?
+    if( (!active) || (active & W_OTHER) ) {
+      tell_object(TP, "Du kannst hier nicht angeln.\n");
+      TP->FreeHands(ME);
+    }
+    // totes Gewaesser erwischt
+    else if ( active & W_DEAD ) {
+      write("In dem Gewaesser hier gibt es keine Fische.\n");
+      TP->FreeHands(ME);
+    }
+    // Jetzt aber: es gibt Fisch, Baby. ;-)
+    else {
+      int delay = MIN_DELAY; // 80 Sekunden.
+      // Fischbestand ermitteln aus Raum- und Koederparametern
+      int bonus = room->QueryProp(P_FISH) + haken->QueryProp(P_FISH);
+      // Ist kein Bonus, sondern ein Malus rausgekommen?
+      if ( bonus < 0 ) {
+        // Dann begrenzen auf 5 Min.
+        if ( bonus < -300 )
+          bonus = -300;
+        // Wartezeit erhoehen.
+        delay += random(-bonus);
+      }
+      else {
+        // Bonus deckeln auf Max-Wert (60 Sekunden)
+        if ( bonus > MAX_FISH_BONUS ) {
+          bonus = MAX_FISH_BONUS;
+        }
+        // Delay reduzieren (minimal 20 Sekunden)
+        delay -= random(bonus);
+      }
+  
+      // passt das Gewaesser zur Angel/zum Koeder ?
+      if( !(QueryProp(P_WATER) & room->QueryProp(P_WATER)) )
+        delay += WRONGWATER + random(WRONGWATER);
+      if( !(room->QueryProp(P_WATER) & haken->QueryProp(P_WATER)) &&
+          room->QueryProp(P_WATER) != W_USER )
+        delay += WRONGWORM + random(WRONGWORM);
+
+      int hook = TP->HRegisterToHook(H_HOOK_MOVE, ME, H_HOOK_OTHERPRIO(1),
+                                     H_LISTENER, 0);
+      if ( hook != 1 ) {
+        active = 0;
+        room = 0;
+        TP->FreeHands(ME);
+        tell_object(TP, BS(
+          "Du schleuderst die Angel mit Schwung in Richtung Wasser, aber der "
+          "Haken verfaengt sich in Deinen Sachen und piekst Dir in den "
+          "Allerwertesten. Das versuchst Du besser noch einmal anders."));
+        tell_room(environment(TP), BS(
+          TP->Name()+" schleudert eine Angel in Richtung Wasser, ist dabei "
+          "aber so ungeschickt, dass sich der Haken verfaengt."), ({TP}));
+      }
+      else {
+        // Alle Bedingungen erfuellt: 
+        // - Angel in der Hand.
+        // - Gewaesser stimmt.
+        // - Haken mit Koeder ist dran.
+        // - alte Beute wurde abgenommen.
+        // - Move-Hook gesetzt, um Abbruch ausloesen zu koennen.
+        actime = time();
+        current_user = TP;
+        tell_object(TP, "Du wirfst die Angel aus.\n");
+        tell_room(environment(TP), TP->Name()+" wirft eine Angel aus.\n", 
+          ({TP}));
+        call_out("do_angel", delay);
+      }
+    }
+  }
+  return 1;
+}
+
+private object GetFish(object room) {
+  int wtype = room->QueryProp(P_WATER);
+  string *fische;
+  
+  // Wenn GetAquarium() bei W_USER nichts zurueckliefert, geht der Angler
+  // leer aus. Es kann kein Fisch aus der Std-Liste genommen werden, weil
+  // der Gewaessertyp ja per Definition benutzerdefiniert ist.
+  if ( wtype == W_USER )
+    fische = room->GetAquarium(ME)||({});
+  else 
+    fische = aquarium[wtype];
+
+  string beute = fische[random(sizeof(fische))];
+
+  if ( !beute )
+    return 0;
+  // GetAquarium() liefert volle Pfade, STDFISHLIST nur Dateinamen relativ
+  // zu FISH()
+  else if ( wtype == W_USER )
+    return clone_object(beute);
+  else
+    return clone_object(FISH(beute));
+}
+
+static void do_angel() {
+  object haken = present(HAKEN_ID, ME);
+  object room  = environment(TP);
+  object fisch;
+
+  string msg_self, msg_other;
+  if ( member(TP->QueryProp(P_HANDS_USED_BY), ME)==-1 ) {
+    tell_object(TP, BS("Waehrend Du vertraeumt vor Dich hingeangelt hast, "
+      "hat sich Dein Griff an der Angel wohl gelockert, so dass der "
+      "Fisch, der gerade anbeissen wollte, sie Dir beinahe entrissen "
+      "haette."));
+    tell_room(environment(TP), BS(TP->Name(WER)+" hat sich gerade "
+      "ziemlich erschreckt, als "+TP->QueryPronoun(WEM)+" beinahe "+
+      TP->QPP(ME, WEN, SINGULAR)+" "+Name(RAW)+" entrissen worden waere."),
+      ({TP}));
+    if ( !random(3) && haken->KoederGefressen() )
+      tell_object(TP, BS("Der Koeder ist jedenfalls futsch."));
+    return;
+  }
+
+  if ( !objectp(haken) ) {
+    msg_self = "Huch, anscheinend hat sich in einem unbeobachteten "
+      "Moment der Angelhaken von der Angelschnur geloest. War der Knoten "
+      "doch nicht gut genug gesichert?";
+    msg_other = TP->Name()+" zieht unglaeubig eine leere Angelschnur aus "
+      "dem Wasser.";
+  }
+  else if ( !objectp(fisch=GetFish(room)) || 
+            active != room->QueryProp(P_WATER) )
+  {
+    msg_self = "Anscheinend gibt es hier doch keine Fische. Du gibst "
+      "auf und holst "+name(WEN,1)+" wieder ein.";
+    // Leaken von Fischobjekten verhindern.
+    if (objectp(fisch))
+      fisch->remove();
+  }
+  else if ( fisch->move(ME) != MOVE_OK ) {
+    msg_self = fisch->Name(WER)+" biss an, war aber zu schwer fuer "
+      "Dich (oder Deine Angel). "+capitalize(fisch->QueryPronoun(WER))+
+      " entgleitet Dir und plumpst zurueck ins Wasser!";
+    
+    if( !(fisch->QueryProp(P_FISH) & F_NOTHUNGRY) ) {
+      haken->KoederGefressen();
+      if(!random(KOEDER_LOST)) {
+        msg_self += " Dein Haken ist dabei leider abgerissen!";
+        tell_room(environment(TP), BS(Name(WER,1)+" von "+TP->Name(WEM)+
+          " wackelte sehr stark."), ({TP}));
+        haken->remove();
+      }
+    }
+    fisch->remove(1);
+  }
+  else {
+    haken->KoederGefressen();
+  }
+  StopFishing(msg_self, msg_other);
+}
+
+varargs int remove(int silent) {
+  if ( find_call_out("do_angel")!=-1 )
+    StopFishing(Name(WER,1)+" loest sich ploetzlich im Nichts auf.");
+  return ::remove(silent);
+}
+
+// Jemand macht "ende" waehrend er angelt?
+protected void NotifyMove(object dest, object oldenv, int method) {
+  if ( find_call_out("do_angel")!=-1 && oldenv == current_user && 
+       dest->IsRoom() ) {
+    StopFishing("Du holst die Angel ein und legst sie beiseite.");
+  }
+  return ::NotifyMove(dest, oldenv, method);
+}
+
+// Spieler loggt waehrend des Angelns aus? Dann angeln einstellen.
+void BecomesNetDead(object pl) {
+  if ( find_call_out("do_angel")!=-1 && pl == current_user ) {
+    StopFishing("Angeln ist so eine beruhigende Freizeitbeschaeftigung! "
+      "Bevor Du mit der Angel in der Hand einschlaeft, holst Du sie "
+      "lieber schnell ein.", pl->Name()+" holt schnell noch die Angel ein, "
+      "bevor "+pl->QueryPronoun(WER)+" einschlaeft.");
+  }
+}
+
+int PreventInsert(object ob) {
+  // Funktion wird aus einer Spielereingabe heraus gerufen
+  // Ich nehme hier nicht TP, weil der call_out("do_angel") die
+  // gefangenen Fische per move() in die Angel bewegt: Bei call_out()s wird
+  // TP durch die Kette weitergereicht, so dass dann hier keine 
+  // Unterscheidung zwischen Spielereingabe und call_out() moeglich waere.
+  SetProp(P_NOINSERT_MSG, 0);
+  if ( stringp(query_verb()) &&
+       member(({"haenge","haeng","befestige"}), query_verb()) == -1 ) {
+    SetProp(P_NOINSERT_MSG, BS(Name(WER,1)+" ist nur dafuer geeignet, "
+      "Angelhaken daranzuhaengen."));
+    return 1;
+  }
+  if( ob->id(FISCH_ID) ) {
+    write("Etwas zappelt an Deiner Angel.\n");
+  }
+  return ::PreventInsert(ob);
+}
+
+// Wenn geangelt wird, nimmt hier niemand was raus.
+public int PreventLeave(object ob, mixed dest) {
+  SetProp(P_NOLEAVE_MSG, 0);
+  if ( find_call_out("do_angel")!=-1 ) {
+    if ( objectp(TP) && ob->id(HAKEN_ID) )
+      SetProp(P_NOLEAVE_MSG, BS("Der Haken ist gerade unter Wasser. Es "
+        "waere kontraproduktiv, ihn ausgerechnet jetzt abzunehmen."));
+    return 1;
+  }
+  return ::PreventLeave(ob, dest);
+}
+
+// Beendet das Angeln, indem Aktiv-Flag geloescht, Hook deregistriert
+// und call_out() geloescht wird. Zusaetzlich werden die als Argument ueber-
+// gebenen Meldungen ausgegeben, oder passende generische verwendet.
+nomask varargs void StopFishing(string msg_me, string msg_room) {
+  active = 0;
+  while(remove_call_out("do_angel")!=-1);
+  object env = environment(ME);
+  if ( objectp(env) && env == current_user ) {
+    env->FreeHands(ME);
+    current_user = 0;
+    env->HUnregisterFromHook(H_HOOK_MOVE, ME);
+    tell_object(env, BS(msg_me||"Du holst Deine Angel wieder ein."));
+    tell_room(environment(env), BS(msg_room||
+      env->Name(WER)+" holt "+env->QueryPossPronoun(ME,WEN,SINGULAR)+" "+
+      name(RAW)+" ein."), ({env}));
+  }
+}
+
+// Diese Methode wird in jedem Hook-Konsumenten eines Hook-Providers
+// aufgerufen, solange die Verarbeitung nicht vorher abgebrochen wurde.
+// Dann jedoch wurde auch die Aktion (Bewegung) nicht ausgefuehrt, d.h.
+// solange hier kein Event ankommt, wurde keine Bewegung ausgefuehrt und 
+// das Angeln kann weiterlaufen.
+mixed HookCallback(object hookSource, int hookid, mixed hookData) {
+  if ( hookid == H_HOOK_MOVE && hookSource == current_user ) {
+    StopFishing("Deine Angelschnur ist zu kurz, um damit rumlaufen zu "
+      "koennen. Du holst die Angel wieder ein.");
+  }
+  return ({H_NO_MOD, hookData});
+}
+
+// Wird gerufen, wenn der Konsument von einem anderen mit hoeherer Prioritaet
+// verdraengt wurde.
+void superseededHook(int hookid, object hookSource) {
+  if ( hookid == H_HOOK_MOVE && hookSource == current_user ) {
+    StopFishing("Irgendetwas ist gerade passiert, das alle Fische "
+      "vertrieben hat. Du siehst sie unter der Wasseroberflaeche davon"
+      "flitzen. Ernuechtert holst Du Deine Angel wieder ein.");
+  }
+}
+
+// Angelzustand abfragen.
+int IsFishing() {
+  return (find_call_out("do_angel")!=-1);
+}
+
+// Gewaessertyp abfragen, in dem gerade geangelt wird.
+int query_active() {
+  return active;
+}
diff --git a/std/items/fishing/fish.c b/std/items/fishing/fish.c
new file mode 100644
index 0000000..4209b35
--- /dev/null
+++ b/std/items/fishing/fish.c
@@ -0,0 +1,222 @@
+//Revision 1.1: QueryQuality() added, sollte aber noch verbessert werden!
+/*
+Letzte Aenderung: Vanion, 26.05.02
+                  - Die Fische haben jetzt wieder einen Wert. Der Preis wird 
+                    von einem Master kontrolliert. Je mehr Fisch ein Spieler
+                    angelt, desto billiger wird der Fisch.
+                  - Gross-Kleinschreibung beim essen korrigiert.
+*/
+
+#pragma strong_types, save_types, rtt_checks
+#pragma no_clone, no_shadow
+
+inherit "/std/thing";
+
+#include <language.h>
+#include <properties.h>
+#include <defines.h>
+#include <items/fishing/fishing.h>
+
+#define TP this_player()
+#define BS(x) break_string(x, 78)
+
+private string eatmessage;
+string corpseobject = "/items/fishing/graeten";
+static int price_modifier;
+
+#define DECAYSTART   600  // beginnt nach 10 min zu faulen
+#define DECAYSTEP    120  // alle 2 Minuten wird er schlechter
+#define MAXQUALITY     4  // nach 4 Decays ist er giftig
+#define KILLFACTOR     5  // reduce_hit_points(STEPS*KILLFACTOR)
+#define HEALFACTOR    32  // 800 g = 25 LP
+#define MAXHEAL       40  // Kein Fisch bringt mehr als 40 LP
+#define MINHEAL        0  // oder weniger als 0
+#define FOODHEALFACTOR 3  // LP=LP + 1/3 des Gewichts
+
+void SetEatMessage(string str);
+void SetCorpseObject(string str);
+int QueryQuality();
+
+protected void create() {
+  ::create();
+  // Reset kurz nach Erreichen des max. Gammelzustands, dann zerstoeren.
+  set_next_reset(DECAYSTART+(MAXQUALITY+1)*DECAYSTEP);
+  AddId(({FISCH_ID ,"fisch"}));
+  SetProp(P_NAME, "Fisch" );
+  SetProp(P_GENDER , MALE);
+  SetProp(P_SHORT, "Ein Fisch" );
+  SetProp(P_LONG, "Ein stinknormaler Fisch.\n");
+  SetProp(P_MATERIAL, MAT_MISC_FOOD);
+  SetProp(P_NOBUY,1);
+  SetProp(P_WEIGHT, 500);
+  AddCmd("iss&@ID", "eatit", "Was willst Du essen?");
+
+  // Vanion, 26.05.02: Der Preis wird erst nach dem create() geholt, 
+  // da das Gewicht dann schon gesetzt werden muss.
+  call_out("GetPriceModifier",0);
+}
+
+protected void create_super() {
+  set_next_reset(-1);
+}
+
+// Wert des Fisches ist abhaengig vom price_modifier und von P_VALUE
+static int _query_value() {
+  int value;
+
+  // Minimalpreis ist immer 1 Muenze. Damit man ihn loswird.
+  if(!price_modifier) 
+    return 1;
+
+  value = Query(P_VALUE, F_VALUE);
+
+  if(!value)
+    return 0; 
+
+  return (value*price_modifier)/100 ;
+}
+
+/*Eingebaut von Vanion, 26.05.02
+  Die Funktion holt den price_modifier aus dem Fish-Master.
+  Hierbei wird ein Wert zwischen 0 und 100 zurueckgegeben, der 
+  den Prozentsatz des Wertes von P_VALUE angibt. 50 bedeutet, dass
+  der Fisch 50% seines Maximalwertes hat. */
+
+#define FISHMASTER "p/daemon/fishmaster"
+
+void GetPriceModifier() {
+  if (!this_player()) return;
+
+  // Jetzt wird der Preis fuer den Fisch bestimmt.
+  price_modifier = FISHMASTER->PriceOfFish(TP, ME);
+  return;
+}
+
+void SetEatMessage(string str) {
+  eatmessage=str;
+}
+
+void SetCorpseObject(string str) {
+  corpseobject=str;
+}
+
+int _set_weight(int gramm) {
+  int tmp = gramm/2 + random(gramm/2); // "nat. Schwankungen" nachbilden
+  Set(P_WEIGHT, tmp, F_VALUE);
+  SetProp(P_VALUE, tmp/4); // Im normalen Laden nur 1/4 Gewicht in Muenzen
+  return tmp;
+}
+
+void init() {
+  ::init();
+  if( QueryProp(P_FISH)&F_REPLACE ) {
+    if(query_once_interactive(environment(ME))) {
+      call_out("ReplaceFish",0);
+    }
+  }
+  return;
+}
+ 
+varargs string long(int mode) {
+  string pron = capitalize(QueryPronoun(WER));
+  string txt = QueryProp(P_LONG);
+ 
+  switch(QueryQuality()) {
+    case 4:  txt += pron+" ist fangfrisch.\n";            break;
+    case 3:  txt += pron+" ist noch recht frisch.\n";     break;
+    case 2:  txt += pron+" ist schon etwas runzlig.\n";   break;
+    case 1:  txt += pron+" sieht schon recht alt aus.\n"; break;
+    case 0:  txt += pron+" ist nicht mehr geniessbar!\n"; break;
+    case -1: txt += pron+" ist vergammelt.\n";            break;
+    default:
+      txt += (pron+" stinkt widerlich und ist bereits voller Maden!\n"+pron+
+        " zerfaellt noch waehrend Du "+QueryPronoun(WEN)+" betrachtest.\n");
+      remove();
+      break;
+  }
+  return(txt);
+}
+
+int _query_fish_age() {
+  return (time()-QueryProp(P_CLONE_TIME));
+}
+
+static int eatit(string str) {
+  string msg_other = TP->Name()+" isst "+name(WEN)+".";
+
+  if ( !eatmessage )
+    eatmessage = "Du isst "+name(WEN,1)+" auf.";
+
+  if ( stringp(corpseobject) ) {
+    object muell=clone_object(corpseobject);
+    if ( muell->move(TP, M_GET) != MOVE_OK ) {
+      muell->move(environment(TP), M_GET);
+    }
+  }
+  
+  // Heilung berechnen.
+  // Heilwert runterskalieren mit Qualitaet, wenn das Mindestalter erreicht
+  // ist, aber nur, wenn der Fisch auch vergammeln kann.
+  int healing = QueryProp(P_WEIGHT)/HEALFACTOR;
+  int age = QueryProp(P_FISH_AGE);
+  if ( age > DECAYSTART && !(QueryProp(P_FISH)&F_NOROTTEN)) {
+    healing = healing * (QueryQuality()*25)/100;
+  }
+  if ( healing > MAXHEAL )
+    healing = MAXHEAL;
+  else if ( healing < MINHEAL )
+    healing = MINHEAL;
+
+  if( age > DECAYSTART + MAXQUALITY*DECAYSTEP) {
+    tell_object(TP, BS(eatmessage + " Dir wird speiuebel, der Fisch war "
+      "zu alt!"));
+    tell_room(environment(TP), BS(msg_other + TP->QueryPronoun(WER)+
+      " verdreht angeekelt die Augen."), ({TP}));
+    TP->reduce_hit_points( ((age-DECAYSTART)/DECAYSTEP)*KILLFACTOR );
+    remove();
+  }
+  else {
+    if ( TP->eat_food(healing*FOODHEALFACTOR) ) {
+      tell_object(TP, BS(eatmessage));
+      tell_room(environment(TP), BS(msg_other), ({TP}));
+      if ( !(QueryProp(P_FISH)&F_NOHEAL) )
+        TP->buffer_hp(healing,5);
+      remove();
+    }
+  }
+  return 1;
+}
+
+// Qualitaet des Fisches ermitteln. Rueckgabewert: 4, 3, 2, 1, 0, -1 ...
+int QueryQuality() {
+  // Alter in Sekunden ueber der Grenze, ab der der Fisch anfaengt zu gammeln
+  int age = QueryProp(P_FISH_AGE)-DECAYSTART;
+  // Wenn die Grenze noch nicht erreicht ist, oder der Fisch generell nicht
+  // gammelt (F_NOROTTEN), dann ist das Alter 0 und die Qualitaet max.
+  if ( age<0 || QueryProp(P_FISH) & F_NOROTTEN ) 
+    age = 0;
+
+  // Qualitaet in DECAYSTEP-Stufen reduzieren (60 s pro Gammelstufe)
+  return MAXQUALITY-(age/DECAYSTEP);
+}
+
+void reset() {
+  if (clonep(ME)) {
+    string msg = BS(Name()+" ist vergammelt und zerfaellt.") ;
+    if ( interactive(environment()) ) {
+      tell_object(environment(), msg);
+    }
+    else if ( environment()->IsRoom() ) {
+      tell_room(environment(), msg);
+    }
+    if ( stringp(corpseobject) ) {
+      object muell=clone_object(corpseobject);
+      if ( muell->move(environment(), M_GET) != MOVE_OK ) {
+        muell->move(environment(environment()), M_GET);
+      }
+    }
+    call_out("remove",0);
+    return; 
+  }
+  return ::reset();
+}
diff --git a/std/items/fishing/haken.c b/std/items/fishing/haken.c
new file mode 100644
index 0000000..66b91f4
--- /dev/null
+++ b/std/items/fishing/haken.c
@@ -0,0 +1,157 @@
+#pragma strong_types, save_types, rtt_checks
+#pragma no_shadow, no_clone
+
+inherit "/std/thing";
+
+#include <language.h>
+#include <properties.h>
+#include <items/fishing/fishing.h>
+#include <items/flasche.h>
+#include <unit.h>
+
+#define TP this_player()
+#define ME this_object()
+
+int QueryKoeder();
+
+protected void create() {
+  ::create();
+
+  AddId(({HAKEN_ID,"haken","angelhaken"}));
+  SetProp(P_NAME, "Haken");
+  SetProp(P_GENDER, MALE);
+  SetProp(P_ARTICLE, 1);
+  SetProp(P_WEIGHT, 5);
+  SetProp(P_SHORT, "Ein Angelhaken");
+  SetProp(P_LONG, "Ein Angelhaken aus Metall.\n");
+  SetProp(P_LONG_EMPTY,"Vielleicht kannst Du etwas damit aufspiessen?\n");
+
+  // Lang- und Kurzbeschreibungen reagieren auf einen evtl. vorhandenen 
+  // Koeder, allerdings nur auf ein tatsaechlich existierendes Koeder-Objekt,
+  // nicht auf einen per ueberschriebenem QueryKoeder() vorgetaeuschten
+  // Koeder, wie es der Spezialhaken tut.
+  Set(P_SHORT, function string () {
+    object koeder = present(WURM_ID, ME);
+    return Query(P_SHORT,F_VALUE)+
+      (objectp(koeder)?" mit "+koeder->name(WEM):"");
+  }, F_QUERY_METHOD);
+  Set(P_LONG, function string () { 
+    object koeder = present(WURM_ID, ME);
+    return Query(P_LONG,F_VALUE) + (objectp(koeder)?
+      koeder->Name(WER)+" haengt daran.\n":QueryProp(P_LONG_EMPTY));
+  }, F_QUERY_METHOD);
+
+  // P_FISH und P_WATER liefern die Daten eines evtl. vorhandenen Koeders
+  // Wenn kein Koeder dranhaengt, werden P_WATER und P_FISH des Hakens
+  // zurueckgegeben, falls es sich um einen Fake-Koeder handelt wie beim
+  // Spezialhaken; in diesem Fall hat der Haken selbst die Properties 
+  // gesetzt.
+  Set(P_FISH, function int () {
+    object koeder = present(WURM_ID, ME);
+    return (objectp(koeder)?koeder->QueryProp(P_FISH):Query(P_FISH));
+  }, F_QUERY_METHOD);
+  Set(P_WATER, function int () {
+    object koeder = present(WURM_ID, ME);
+    return (objectp(koeder)?koeder->QueryProp(P_WATER):Query(P_WATER));
+  }, F_QUERY_METHOD);
+
+  SetProp(P_MATERIAL,([MAT_STEEL:100]));
+
+  AddCmd(({"spiess","spiesse"}),"spiessauf");
+}
+
+protected void create_super() {
+  set_next_reset(-1);
+}
+
+static int spiessauf(string str) {
+  string haken,wurmname,*substr;
+  object wurm;
+  int amount;
+
+  notify_fail("Was willst Du denn aufspiessen?\n");
+  if (!stringp(str) || !sizeof(str)) 
+    return 0;
+  
+  if( sscanf(str, "%s auf%s", wurmname, haken) != 2 )
+    return 0;
+
+  haken = trim(haken);
+
+  notify_fail("So etwas hast Du nicht bei Dir.\n");
+  if ( !objectp(wurm=present(wurmname,TP)) )
+    return 0;
+
+  notify_fail("Das kannst Du nicht aufspiessen.\n");
+  if( !wurm->id(WURM_ID) )
+    return 0;
+
+  notify_fail("Worauf willst Du "+wurm->name(WEN,1)+" denn spiessen?\n");
+  if ( haken!="" && !id(haken) )
+    return 0;
+
+  notify_fail(break_string("Dazu solltest Du "+name(WEN,1)+" erst einmal "
+    "bei Dir haben.",78));
+  if ( environment(ME) != TP )
+    return 0;
+
+  notify_fail("An dem Haken haengt schon ein Koeder.\n");
+  if ( QueryKoeder() )
+    return 0;
+
+  // Haken und Koeder sind im Inventar und fuehlen sich von der Syntax
+  // angesprochen.
+  if( wurm->IsUnit() ) {
+    // Das QueryProp() ist nicht unnoetig. Bei der Abfrage von U_REQ wird
+    // U_REQ genullt, wenn das aktuelle query_verb() != dem letzten ist.
+    // Bei der ersten Abfrage wuerde also das hier gesetzte U_REQ wieder
+    // geloescht. Daher muss das jetzt hier als erstes einmal abgefragt
+    // werden...
+    wurm->QueryProp(U_REQ);
+    wurm->SetProp(U_REQ,1);
+  }
+  tell_object(TP, break_string(
+    "Du spiesst "+wurm->name(WEN,1)+" auf "+name(WEN,1)+".",78));
+  tell_room(environment(TP), break_string(
+    TP->Name(WER)+" spiesst "+wurm->name(WEN)+" auf "+name(WEN)+".",78),
+    ({TP}));
+  // M_GET, damit auch P_NODROP-Koeder aufgespiesst werden koennen.
+  if ( wurm->move(ME,M_GET) != MOVE_OK) {
+    tell_object(TP, break_string(
+      "Verdammt! Jetzt hast Du Dir fast in den Finger gestochen! "+
+      wurm->Name(WEN,1)+" hast Du dabei natuerlich verfehlt.", 78));
+  }
+  return 1;
+}
+
+int QueryKoeder(){ 
+  return objectp(present(WURM_ID, ME));
+}
+
+object QueryKoederObject() {
+  return present(WURM_ID,ME);
+}
+
+int MayAddObject(object ob) {
+  return (objectp(ob) && ob->id(WURM_ID) && sizeof(all_inventory(ME))<=1); 
+}
+
+int _query_total_weight() {
+  int tw;
+  foreach(int w: all_inventory(ME)+({ME}))
+    tw += w->QueryProp(P_WEIGHT);
+  return tw;
+}
+
+varargs int remove(int sil) {
+  all_inventory(ME)->remove(); // funktioniert auch mit leeren Arrays
+  return ::remove(sil);
+}
+
+int KoederGefressen() {
+  // Nicht QueryKoeder() pruefen, da das bei Spezialhaken immer 1 ist.
+  object koeder = present(WURM_ID, ME);
+  if ( objectp(koeder) )
+    return koeder->remove();
+  return -1;
+}
diff --git a/std/items/fishing/koeder.c b/std/items/fishing/koeder.c
new file mode 100644
index 0000000..c63317b
--- /dev/null
+++ b/std/items/fishing/koeder.c
@@ -0,0 +1,28 @@
+#pragma strong_types, save_types, rtt_checks
+#pragma no_clone, no_shadow
+
+inherit "/std/thing";
+
+#include <language.h>
+#include <properties.h>
+#include <items/fishing/fishing.h>
+
+protected void create(){
+  ::create();
+  AddId(({WURM_ID,"koeder", "wurm","regenwurm"}));
+  SetProp(P_NAME, "Wurm");
+  SetProp(P_GENDER , MALE);
+  SetProp(P_ARTICLE, 1);
+  SetProp(P_FISH, 0);
+  SetProp(P_WATER, 0);
+  SetProp(P_SHORT, "Ein kleiner Wurm");
+  SetProp(P_LONG, "Ein kleiner Regenwurm.\n");
+  SetProp(P_MATERIAL, MAT_MISC_LIVING);
+  SetProp(P_VALUE, 1);
+  SetProp(P_WEIGHT, 5);
+}
+
+protected void create_super() {
+  set_next_reset(-1);
+}
+
diff --git a/std/items/flasche.c b/std/items/flasche.c
new file mode 100644
index 0000000..43735fe
--- /dev/null
+++ b/std/items/flasche.c
@@ -0,0 +1,333 @@
+/*OBJ*/
+/* 2013-Mai-23, Arathorn
+   Umfassend ueberarbeitet; setup(), init() und ReplaceWasser() entsorgt,
+   P_WEIGHT und P_LONG dynamisch gebaut, so dass das staendige Umsetzen
+   der Properties mittels setup() nach jedem Fuellen/Leeren entfaellt.
+ */
+
+/* Letzte Aenderung: 4.5.2004, Arathorn.
+   Zeile 149: cont_obj->remove() eingefuegt, damit das geclonte Objekt
+   nach dem Abfragen seines Namens wieder entfernt wird.
+   Update 6.6.2004, Arathorn. Habe um ein destruct() ergaenzt, weil das
+   trotz remove() hartnaeckig weitergebuggt hat. *fluch*
+ */
+
+/* Version 1.3 by Fraggle (17.1.1995) */
+
+ // PreventInsert: verhindert Fuellen wie std/container
+/* Version 1.2 by Fraggle (17.1.1995) */
+
+ // Flasche kann nun mittels environment(TP)->GetLiquid()
+ // etwas anderes als Wasser enthalten, sofern
+ // environment(TP)->QueryProp(P_WATER)==W_OTHER
+ // BUG: auch 1l Methan wiegt 1 Kg, aendere ich spaeter
+
+/* Version 1.1 by Fraggle (17.1.1995) */
+#pragma strong_types,rtt_checks
+#pragma no_clone
+
+inherit "/std/thing";
+#include <language.h>
+#include <properties.h>
+#include <items/flasche.h>
+#include <items/fishing/fishing.h>
+
+#define DEFAULT_LIQ "Wasser"
+#define TP this_player()
+
+private string co_filename;
+public string liquid_name=DEFAULT_LIQ;
+
+string dynamic_long();
+
+protected void create() {
+  ::create();
+
+  AddId(({"wasserbehaelter","behaelter","\nwater_source"}));
+  SetProp(P_NAME, "Wasserbehaelter");
+  SetProp(P_GENDER , MALE);
+  SetProp(P_ARTICLE, 1);
+  SetProp(P_SHORT, "Ein Standard-Wasserbehaelter");
+  SetProp(P_LONG,"none");
+  Set(P_LONG, #'dynamic_long, F_QUERY_METHOD);
+  SetProp(P_LONG_EMPTY,""); // Beschreibung fuer leeren Zustand
+  SetProp(P_LONG_FULL,"");  // Beschreibung fuer gefuellten Zusand
+  SetProp(P_LIQUID,1000);   // Fuellmenge = 1 Liter
+  SetProp(P_WATER,0);       // Flasche ist defaultmaessig leer!
+  SetProp(P_VALUE,10);
+
+  // P_WEIGHT auf Leergewicht der Flasche setzen, QueryMethode liefert
+  // die Summe aus Leergewicht+Fuellung zurueck (Dichte = 1).
+  SetProp(P_WEIGHT,20);
+  Set(P_WEIGHT, function int () {
+     return ( Query(P_WEIGHT,F_VALUE)+
+              (QueryProp(P_WATER)?QueryProp(P_LIQUID):0) );
+     }, F_QUERY_METHOD);
+
+  AddCmd(({"fuell","fuelle"}),"cmd_fuelle");
+  AddCmd(({"leere", "entleere"}),"cmd_leere");
+}
+
+protected void create_super() {
+  set_next_reset(-1);
+}
+
+// Behaelter ist gefuellt, dann ist die Langbeschreibung entweder die 
+// P_LONG_FULL, falls angegben, oder die P_LONG + Beschreibung der Fuellung.
+// Genauso, wenn sie leer ist.
+string dynamic_long() {
+  string l=Query(P_LONG,F_VALUE);
+  if ( QueryProp(P_WATER) ) {
+    string lf = QueryProp(P_LONG_FULL);
+    if ( stringp(lf) && sizeof(lf) )
+      l=lf;
+    else
+      l+=capitalize(QueryPronoun(WER))+" enthaelt "+liquid_name+".\n";
+    // Falls die Flasche mit etwas anderem als Wasser gefuellt wird, die
+    // Langbeschreibung fuer "volle Flasche" (P_LONG_FULL) aber nur fuer
+    // Wasser ausgelegt ist, wird "Wasser" durch den Inhalt von liquid_name
+    // ersetzt.
+    if ( liquid_name != DEFAULT_LIQ )
+      l=regreplace(l, DEFAULT_LIQ, liquid_name, 1);
+  } else {
+    string le = QueryProp(P_LONG_EMPTY);
+    if ( stringp(le) && sizeof(le) )
+      l=le;
+    else
+      l+=capitalize(QueryPronoun(WER))+" ist leer.\n";
+  }
+  return l;
+}
+
+// Zum Ueberschreiben! PreventInsert(object ob){return 0;} z.B.
+// macht die Flasche gasdicht.
+// Oder man kann die Flasche verschliessbar machen.
+int PreventInsert(object obj)
+{
+  if(obj->id("gas")) { //default: NICHT Gasdicht!
+    write(obj->Name(WER,1)+" entweicht sofort wieder!\n");
+    return 1;
+  }
+  return 0;
+}
+
+// Transferiert den Inhalt der Flasche an <dest>
+protected int transfer_to(object dest)
+{
+  int water=QueryProp(P_WATER);
+  if (!water)
+  {
+    write(Name(WER,1) + " ist schon leer!\n");
+    return 0;   // War schon leer!
+  }
+  int contents=QueryProp(P_LIQUID);
+
+  if ( water&W_OTHER )
+  {
+    dest->PutLiquid(co_filename);
+  }
+  else
+  {
+    dest->AddWater(contents);
+  }
+  SetProp(P_WATER,0);
+  RemoveId(lower_case(liquid_name));
+  liquid_name=DEFAULT_LIQ;
+  return contents; //gib die ml an Umgebung ab :)
+}
+
+// Entleert die Flasche ins Environment der Flasche, allerdings nicht, wenn
+// dies ein Lebewesen ist, dann wird das Environment von dem genommen.
+// TODO: Eine Flasche in einem Paket leeren wurde dann in das paket entleeren.
+// Das waere an sich sogar richtig... Nur: gewollt? Alternative koennen wir
+// auch das aeusserste Environment nehmen, was nicht lebt.
+public int empty()
+{
+  if (environment())
+  {
+    // Environment des Benutzers finden.
+    object env = environment();
+    while (living(env))
+      env=environment(env);
+    return transfer_to(env);
+  }
+  return 0;
+}
+
+// Fuellt die Flasche aus <src>
+protected int fill_bottle(object src)
+{
+  int liquidtype = src->QueryProp(P_WATER);
+  if(liquidtype)
+  {
+    if(QueryProp(P_WATER)) {
+      write(Name(WER,1)+" ist bereits voll!\n");
+      return 1;
+    }
+    // Wasser von Umgebung abziehen!
+    // Man kann als Magier die Funktion AddWater(int n) dazu benuetzten,
+    // beispielsweise eine Pfuetze zu leeren, ...
+    src->AddWater(-QueryProp(P_LIQUID));
+    object cont_obj;
+    if(liquidtype&W_OTHER)
+    {
+      // Mittels GetLiquid() kann die Flasche mit was anderem als Wasser
+      // gefuellt werden.
+      co_filename=src->GetLiquid();
+      if (co_filename)
+      {
+        cont_obj=clone_object(co_filename);
+        if(PreventInsert(cont_obj))
+        {
+          // Hier passiert eigentlich das gleiche wie nach dem ifblock, aber
+          // auch noch Funktion beenden.
+          // TODO: Rueckgaenig machen von AddWater()?
+          // TODO: Die Meldung aus dem PreventInsert() muesste eigentlich
+          // _vorher_ noch mit einer Befuellmeldung begleitet werden.
+          cont_obj->remove(1);
+          if ( objectp(cont_obj) )
+            cont_obj->move("/room/muellraum",M_PUT);
+          cont_obj=0;
+          return 0;
+        }
+        else
+          liquid_name=cont_obj->name(WEN);
+        // In jedem Fall wird das Objekt wieder zerstoert - es wurde nur fuer
+        // das Ermitteln des liquid_name benutzt... Weia.
+        if ( cont_obj ) cont_obj->remove();
+        if ( cont_obj ) cont_obj->move("/room/muellraum",M_PUT);
+      }
+    }
+    SetProp(P_WATER,liquidtype);
+    AddId(lower_case(liquid_name));
+    //wie praktisch, 1 ml == 1 g :) // Aber nur fuer Wasser, du VOGEL! :-|
+    return 1;
+  }
+  else {
+    write("Du findest hier nichts, was Du in "+name(WEN,1)+
+      " fuellen koenntest!\n");
+    return 0;
+  }
+  return 0;
+}
+
+static int cmd_leere(string str)
+{
+  object dest;
+  notify_fail("Was willst Du denn (wo hinein) leeren?\n");
+  if (!str)
+    return 0;
+
+  string strbottle,strobj;
+  // leere flasche
+  if (id(str))
+  {
+    //NOOP
+  }
+  // leere flasche in xxx
+  else if (sscanf(str,"%s in %s",strbottle,strobj)==2)
+  {
+    if (!id(strbottle))
+      return 0;
+    dest = present(strobj, environment(this_player()))
+           || present(strobj, this_player());
+    if (!dest)
+      return 0;
+  }
+  else
+    return 0;
+  // Syntaxpruefung fertig.
+
+  if(!QueryProp(P_WATER))
+  {
+    write("Da ist kein "+liquid_name+" drin!\n");
+    return 1;
+  }
+
+  if (dest)
+  {
+    write(break_string("Du leerst "+name(WEN,1)+ " in "
+          + dest->name(WEN) + ".",78));
+    say(break_string(TP->name()+" leert "+name(WEN,0)
+          + " in " + dest->name(WEN) + ".",78),TP);
+    transfer_to(dest);
+    return 1;
+  }
+  write(break_string("Du leerst "+name(WEN,1)+".",78));
+  say(break_string(TP->name()+" leert "+name(WEN,0)+".",78),TP);
+  empty();
+  return 1;
+}
+
+public int cmd_fuelle(string str)
+{
+  string strbottle,strobj;
+
+  notify_fail("Was willst Du denn (womit) fuellen?\n");
+  if(!str)
+    return 0;
+
+  // fuelle flasche
+  if (id(str))
+  {
+    if (fill_bottle(environment(this_player())))
+    {
+      write(break_string("Du fuellst etwas "+liquid_name+" in "
+            +name(WEN,1)+".",78));
+      say(break_string(TP->Name(WER)+" fuellt etwas "
+            +liquid_name+" in "+name(WEN)+".",78), TP);
+    }
+    return 1;
+  }
+  // fuelle flasche aus xxx
+  // fuelle flasche mit xxx
+  // fuelle xxx in flasche
+  // fuelle flasche in xxx
+  // fuelle xxx aus flasche
+  // fuelle xxx mit flasche
+  else if (sscanf(str,"%s in %s",strobj,strbottle)==2
+           || sscanf(str,"%s mit %s",strbottle,strobj)==2
+           || sscanf(str,"%s aus %s",strbottle,strobj)==2)
+  {
+    object obj;
+    // Flasche befuellen?
+    if (id(strbottle)
+        && ( obj=present(strobj, environment(this_player())) 
+                 || present(strobj, this_player()) )
+       )
+    {
+      if (fill_bottle(obj))
+      {
+        write(break_string(
+              "Du fuellst etwas "+liquid_name+" aus " + obj->name(WEM,1) 
+              + " in "+name(WEN,1)+".",78));
+        say(break_string(TP->Name(WER)+" fuellt etwas "+liquid_name+ " aus " 
+              + obj->name(WEM,1) + " in "+name(WEN)+".",78), TP);
+      }
+      return 1;
+    }
+    // anderes Gefaess befuellen?
+    else if (id(strobj)
+        && ( obj=present(strbottle, environment(this_player())) 
+                 || present(strbottle, this_player()) )
+       )
+    {
+      if (transfer_to(obj))
+      {
+        write(break_string(
+              "Du fuellst etwas "+liquid_name+" aus " + name(WEM,1)
+              + " in "+obj->name(WEN,1)+".",78));
+        say(break_string(TP->Name(WER)+" fuellt etwas "+liquid_name+ " aus " 
+              + name(WEM,1) + " in "+obj->name(WEN)+".",78), TP);
+      }
+      return 1;
+    }
+  }
+  // Syntax passt nicht.
+  return 0;
+}
+
+int IsBottle() {
+   return 1;
+}
+
diff --git a/std/items/kraeuter/kraut.c b/std/items/kraeuter/kraut.c
new file mode 100644
index 0000000..2ce5045
--- /dev/null
+++ b/std/items/kraeuter/kraut.c
@@ -0,0 +1,195 @@
+// (c) September 2000 by Padreic (Padreic@mg.mud.de)
+
+#pragma strong_types, save_types, rtt_checks
+
+inherit "/std/thing";
+
+#include <properties.h>
+#include <items/kraeuter/kraeuter.h>
+
+#define PLANT_LIFETIME  (24*3600)
+#define FRESH_TIME      (6*3600)
+// Die plantID wird fuer ungueltig erschaffene Kraeuter auf -1 gesetzt.
+#define DRIED_PLANT     -1
+
+private int age=time();
+// enthaelt die Nummer des Krauts
+private int plantId;
+// enthaelt den Pfad des clonenden Objekts
+private string cloner;
+// Qualitaet des Krautes.
+private int quality=100;
+
+// File kann ein name sein, dessen Eigenschaften der VC konfigurieren sein
+// oder 0, wenn er selber ermitteln soll, was fuer ein File er gerade erzeugt
+// hat.
+// Sprich: real existierende Files auf der Platte muessen da ihren Namen
+// angeben, Standardfall ist aber, dass man 0 angibt und der VC weiss, was er
+// gerade erzeugt hat.
+void customizeMe(string file)
+{
+  if (stringp(file)) 
+    file=explode(file, "/")[<1];
+  KRAEUTERVC->CustomizeObject(file);
+}
+
+protected void create()
+{
+  if (object_name(this_object()) == __FILE__[0..<3])
+  {
+    set_next_reset(-1);
+    return;
+  }
+  ::create();
+ 
+  Set(P_QUALITY, function int () { return quality; }, F_QUERY_METHOD);
+  SetProp(P_WEIGHT, 120);
+  SetProp(P_VALUE, 70);
+  SetProp(P_MATERIAL, MAT_MISC_PLANT);
+}
+
+protected void create_super()
+{
+  set_next_reset(-1);
+}
+
+public string short()
+{
+  string str=QueryProp(P_SHORT);
+  if (!stringp(str)) 
+      return 0;
+  if (plantId==-1)
+      return str+" (unwirksam).\n";
+  else if (age==DRIED_PLANT)
+     return str+" (getrocknet).\n";
+  else if (age+FRESH_TIME+PLANT_LIFETIME<time())
+     return str+" (verfault).\n";
+  return str+".\n";
+}
+
+// Liefert einen Skalierungsfaktor zurueck, der mit dem aktuellen Wert von
+// P_QUALITY verrechnet wird. Rueckgabewerte:
+// Pflanze getrocknet oder nicht aelter als 6 h : 100
+// Pflanze aelter als 6, aber juenger als 30 h : 99...1
+// Pflanze aelter als 30 h: 0.
+// DryPlant() zerstoert das Kraut, wenn P_QUALITY unter 1 faellt.
+public int PlantQuality()
+{
+  int factor;
+  // schon getrocknet oder nicht aelter als 6 h? 
+  // Dann keine weitere Reduktion.
+  if ( age == DRIED_PLANT || age+FRESH_TIME > time()) 
+    factor = 100;
+  // >30 Stunden nach dem Pfluecken ist das Kraut verschimmelt.
+  else if ( age + FRESH_TIME + PLANT_LIFETIME < time() ) 
+    factor = 1;
+  // Zeit zwischen 6 und 30 Stunden nach dem Pfluecken in 99 gleichmaessige
+  // Abschnitte unterteilen. 24 h sind 86400 s, 86400/99 = 873.
+  else
+    factor=(time()-age-FRESH_TIME)/873;
+
+  return QueryProp(P_QUALITY)*factor/100;
+}
+
+// Wie lange (in Sekunden) ist das Kraut noch haltbar?
+public int TimeToLive()
+{
+  if ( age == DRIED_PLANT )
+    return __INT_MAX__;
+  return age-time()+PLANT_LIFETIME;
+}
+
+//TODO: vielleicht etwas zufall? Sonst Kraeuterqualitaet hieran ermittelbar.
+static int _query_value()
+{
+  int val = Query(P_VALUE,F_VALUE)*PlantQuality()/100;
+  if (plantId<=0 || val = 0)
+    return 0;
+  return val-val%10;
+}
+
+static string _query_nosell() 
+{
+  if (age != DRIED_PLANT)
+    return "Mit ungetrockneten Kraeutern handele ich nicht. Die verderben "
+      "immer so schnell im Lager, und dann werde ich sie nicht wieder los.";
+  return 0;
+}
+
+// Mit DryPlant() wird die Pflanze getrocknet. Als Argument wird der Prozent-
+// wert uebergeben, auf den die Qualitaet sinken soll. Als Ausgangswert
+// dieser Berechnung wird der Rueckgabewert von PlantQuality() verwendet.
+// Hintergrund: Die Qualitaet des Krauts sinkt im ungetrockneten Zustand
+// ueber einen Zeitraum von 24 h kontinuierlich von 100 auf 1 ab, sobald
+// es aelter als 6 Stunden ist. Danach ist es verschimmelt, was aber seiner
+// verbleibenden "Wirkung" keinen Abbruch tut.
+// Es wird die zum Zeitpunkt des Trocknungsvorganges gueltige Qualitaet
+// also sozusagen "eingefroren" und entsprechend dem Rueckgabewert von
+// PlantQuality() heruntergerechnet.
+
+// Diese Funktion kann natuerlich auch ueberschrieben werden, wenn bestimmte
+// Kraeuter erst durch trocknen in der Qualitaet steigen.
+
+// TODO: Ist das Argument "qual" dabei prozentual aufzufassen, oder 
+// soll nur ein noch zu bestimmender Festwert abgezogen werden?
+// Arathorn: Es bleibt jetzt erstmal prozentual.
+
+#define DRYING_ALLOWED ({PLANTMASTER, "/items/kraeuter/trockner"})
+
+public void DryPlant(int qual) 
+{
+  // Keine mehrfache Trocknung zulassen.
+  if ( age == DRIED_PLANT )
+    return;
+ 
+  // Nur bestimmte Objekte duerfen Trocknungen ausloesen.
+  if ( member(DRYING_ALLOWED, load_name(previous_object())) == -1 )
+    return;
+
+  // Qualitaet auf 100 deckeln.
+  if ( qual>100 )
+    qual = 100;
+  
+  // Qualitaet mittels PlantQuality() runterskalieren.
+  qual = PlantQuality()*qual/100;
+  
+  if ( qual < 1 ) {
+    if(objectp(this_player()))
+      tell_object(this_player(), 
+        Name(WER,1)+" zerfaellt in unzaehlige feine Kruemel.\n");
+    remove();
+    return;
+  }
+  // Kraut als getrocknet kennzeichnen.
+  age=DRIED_PLANT;
+  quality = qual;
+}
+
+/* Funktionen zum Initialisieren der Pflanze */
+// Der Kraeutermaster prueft den Clone auf Gueltigkeit. Eine einmal gesetzte
+// ID kann auf diesem Weg nicht mehr geaendert werden.
+nomask int SetPlantId(int new_id)
+{
+  if (plantId != 0)
+    return -1;
+  cloner=0;
+  age=time();
+  if (catch(cloner=call_other(PLANTMASTER, "CheckPlant", new_id)) || !cloner)
+    new_id = -1;
+  return (plantId=new_id);
+}
+
+nomask int QueryPlantId()
+{
+  return plantId;
+}
+
+nomask string QueryCloner()
+{
+  return cloner;
+}
+
+nomask int QueryDried() 
+{
+  return (age == DRIED_PLANT);
+}
diff --git a/std/items/kraeuter/trank.c b/std/items/kraeuter/trank.c
new file mode 100644
index 0000000..f3c0efe
--- /dev/null
+++ b/std/items/kraeuter/trank.c
@@ -0,0 +1,1147 @@
+//TODO:
+
+#pragma strong_types,rtt_checks
+
+inherit "/std/thing";
+
+#include <properties.h>
+#include <defines.h>
+#include <items/kraeuter/kraeuter.h>
+#include <items/kraeuter/trankattribute.h>
+#include <hook.h>
+#include <class.h>
+#include <new_skills.h>
+#include <wizlevels.h>
+
+#ifndef BS
+#  define BS(x)             break_string(x, 78)
+#endif
+
+#define allowed(x) (object_name(x)==PLANTMASTER)
+#define DRINK_POTION "lib_kraeutertrank_trinken"
+// for debug
+#define private public
+
+// Ablaufzeit des Tranks, ab dann keine Wirkung mehr
+private nosave int expiry;
+// Ablaufzeit der wirkungen des Trankes (0, wenn Trank nicht getrunken)
+private nosave int duration;
+// Trankattribute, vom Krautmaster schon skaliert, gekappt, beschraenkt auf
+// die jeweils max. positiven Effekte
+private nosave mapping data;
+// Klassen, die bei Schutz oder verstaerktem Schaden betroffen sind. Werte
+// ist der Schutz- oder Attackebonus/-malus.
+private nosave mapping att_classes;
+private nosave mapping prot_classes;
+// max. Summe von Giftstufen (P_POISON und P_LEVEL/10 bei CL_POISON).
+private nosave int prot_poison;
+// max. geheilte Summe von Krankheitslevel (P_LEVEL/5 in CL_DISEASE).
+private nosave int prot_disease;
+// nach Wirkung absteigend sortierte Liste der Effekte, wird beim ersten
+// Aufruf von DetermineStrongesEffect() befuellt.
+private nosave string* sorted_effects;
+
+mixed _query_data() {return data;}
+int _set_data(mixed d) {data=d; expiry = __INT_MAX__; return data!=0;}
+private string effect2colour();
+
+protected void create()
+{
+  if (object_name(this_object()) == __FILE__[0..<3])
+  {
+    set_next_reset(-1);
+    return;
+  }
+  ::create();
+  SetProp(P_GENDER, FEMALE);
+  SetProp(P_NAME, "Glasflasche");
+  SetProp(P_NAME_ADJ, ({"klein"}));
+  SetProp(P_VALUE, 10);
+  SetProp(P_WEIGHT, 100);
+  SetProp(P_KILL_NAME, "Ein Kraeutertrank");
+  SetProp(P_KILL_MSG, "%s hat sich wohl beim Anruehren vertan.");
+  AddId(({"glasflasche", "flasche"}));
+  AddAdjective(({"klein", "kleine"}));
+  AddCmd("trink|trinke&@ID", "cmd_trinken",
+    "Was willst Du trinken?");
+}
+
+protected void create_super()
+{
+  set_next_reset(-1);
+}
+
+static string _query_short()
+{
+  if (!clonep(ME))
+    return "Eine kleine Glasflasche";
+  return Name(WEN, 0)+(data==0 ? "" : " (gefuellt)");
+}
+
+static string _query_long()
+{
+  if (data==0)
+    return break_string(
+      "Eine kleine leere Glasflasche, die mit einem Korken verschlossen "
+      "ist.\n"
+      "Flaschen wie diese werden ueblicherweise verwendet, um darin "
+      "magische Traenke abzufuellen.", 78, 0, BS_LEAVE_MY_LFS);
+
+  return break_string(
+    "Eine kleine Glasflasche, die mit einer "+effect2colour()+"en "
+    "Fluessigkeit gefuellt ist.\n"
+    "Sie ist mit einem Korken verschlossen, um den Inhalt der "
+    "Flasche zu schuetzen.",78, 0, BS_LEAVE_MY_LFS);
+}
+
+private string num2desc(int bumms)
+{
+  switch(abs(bumms))
+  {
+    case 0..499:
+      return "ein wenig";
+    case 500..999:
+      return "so einiges";
+    case 1000..1499:
+      return "erheblich";
+    case 1500..2000:
+      return "unglaublich";
+  }
+  return "ungenehmigt viel"; // kommt hoffentlich nicht vor.
+}
+
+varargs private string DetermineStrongestEffect(int pos)
+{
+  // globale Werteliste befuellen, wenn da noch nichts drinsteht.
+  if ( !pointerp(sorted_effects) ) {
+    sorted_effects = sort_array(m_indices(data) & T_KRAUT_EFFECTS,
+      function int (string a, string b) {
+        return (abs(data[a])<=abs(data[b]));
+      });
+  }
+
+  // Zur Indizierung des Arrays muss <pos> bei Null starten, es wird
+  // aber mit der Bedeutung einer Ordinalzahl (erste, zweite, dritte, ...)
+  // uebergeben. Daher um 1 reduzieren.
+  --pos;
+
+  string ret;
+
+  // Im Array muss mindestens ein Eintrag stehen, sonst gibt's gar keinen
+  // Effekt.
+  if ( sizeof(sorted_effects) )
+  {
+    // Wenn der angefragte Index ausserhalb der Arraygrenzen liegt, wird
+    // angenommen, dass der erste bzw. letzte Eintrag gesucht waren.
+    if ( pos < 0 )
+      ret = sorted_effects[0];
+    else if ( pos >= sizeof(sorted_effects) )
+      ret = sorted_effects[<1];
+    else
+      ret = sorted_effects[pos];
+  }
+  return ret;
+}
+
+// Liefert zu dem maximal wirksamen Effekt die Farbe des Trankes zurueck.
+private string effect2colour()
+{
+  // Ist die Haltbarkeit schon abgelaufen, wird der Trank farblos.
+  if ( time() > expiry )
+    return "farblos";
+
+  // Namen des staerksten Effekts holen.
+  string effect = DetermineStrongestEffect(1);
+  mapping colours = ([
+    T_CARRY:              "trueb braun",
+    T_DAMAGE_ANIMALS:     "blutrot",
+    T_DAMAGE_MAGIC:       "oktarinfarben",
+    T_DAMAGE_UNDEAD:      "anthrazitfarben",
+    T_FLEE_TPORT:         "schwefelgelb",
+    T_FROG:               "schlammgruen",
+    T_HEAL_DISEASE:       "perlmuttfarben",
+    T_HEAL_POISON:        "gruen",
+    T_HEAL_SP:            "blau",
+    T_HEAL_LP:            "scharlachrot",
+    T_PROTECTION_ANIMALS: "metallisch grau",
+    T_PROTECTION_MAGIC:   "violett",
+    T_PROTECTION_UNDEAD:  "strahlend weiss",
+    T_SA_SPEED:           "orangefarben",
+    T_SA_SPELL_PENETRATION: "stahlblau",
+    T_SA_DURATION:        "pinkfarben",
+  ]);
+  string ret = colours[effect];
+  return stringp(ret) ? ret : "farblos";
+}
+
+// Wird gerufen, wenn die Wirkung des Trankes ablaufen soll.
+private void terminate_effects()
+{
+  tell_object(environment(),
+      "Die letzten Wirkungen des Kraeutertrankes klingen ab.\n");
+  remove(1);
+}
+
+// Von den Hooks H_HOOK_ATTACK_MOD und H_HOOK_DEFEND gerufen, erhoeht oder
+// verringert den Schaden gegen Lebenwesen bestimmter Klassen (Keys in
+// <classes>). Der Malus/Bonus steht als Wert zum jeweiligen Key in dem
+// Mapping.
+mixed hcallback(object hookSource, int hookid, mixed hookData)
+{
+    if (hookSource != environment())
+        return ({H_NO_MOD,hookData});
+    switch(hookid)
+    {
+      case H_HOOK_ATTACK_MOD:
+        foreach(string class, int modval : att_classes)
+        {
+          if (hookData[SI_ENEMY]->is_class_member(class))
+          {
+            // Yeah. Treffer. Schaden erhoehen oder verringern... ;)
+            hookData[SI_SKILLDAMAGE] += modval;
+            // Ende. keine weiteren Klassen pruefen.
+            return ({H_ALTERED, hookData});
+          }
+        }
+        break;
+      case H_HOOK_DEFEND:
+        // hookData: ({dam,dam_type,spell,enemy})
+        foreach(string class, int modval : prot_classes)
+        {
+          if (hookData[3]->is_class_member(class))
+          {
+            // Yeah. Treffer. Schaden erhoehen oder verringern... ;)
+            hookData[0] += modval;
+            // Ende. keine weiteren Klassen pruefen.
+            return ({H_ALTERED, hookData});
+          }
+        }
+        break;
+      case H_HOOK_INSERT:
+        // Wenn die Giftschutzkapazitaet noch ausreicht, wird das reinkommende
+        // Objekt zerstoert (und die Kapazitaet reduziert).
+        // hookData: neues object
+        if (prot_poison > 0
+            && hookData->is_class_member(CL_POISON))
+        {
+          // kostet ein Zehntel des Levels, aber min. 1.
+          int p=hookData->QueryProp(P_LEVEL) / 10 + 1;
+          if (p < prot_poison)
+          {
+            hookData->remove(1);
+            prot_poison-=p;
+            return ({H_CANCELLED, hookData});
+          }
+        }
+        // Wenn die Krankheitsschutzkapazitaet noch ausreicht, wird das reinkommende
+        // Objekt zerstoert (und die Kapazitaet reduziert).
+        // hookData: neues object
+        if (prot_disease > 0
+            && hookData->is_class_member(CL_DISEASE))
+        {
+          // kostet ein Fuenftel des Levels, aber min. 1.
+          int lvl = hookData->QueryProp(P_LEVEL) / 5 + 1;
+          if (lvl < prot_disease)
+          {
+            hookData->remove(1);
+            prot_disease-=lvl;
+            return ({H_CANCELLED, hookData});
+          }
+        }
+        break;
+      case H_HOOK_POISON:
+        // hookData: poisonval
+        // Alle Giftlevel werden reduziert auf 0 und von prot_poison
+        // abgezogen. Wenn das 0 ist, endet der Giftschutz.
+        if (prot_poison>0)
+        {
+          if (hookData < prot_poison)
+          {
+            prot_poison-=hookData;
+            hookData = 0;
+          }
+          else
+          {
+            hookData -= prot_poison;
+            prot_poison=0;
+          }
+          return ({H_ALTERED, hookData});
+        }
+        break;
+    }
+    return ({H_NO_MOD, hookData});
+}
+
+private int reg_hook(int hook, int hooktype)
+{
+  // Wenn schon registriert, zaehlt das auch als Erfolg.
+  if (environment()->HIsHookConsumer(hook, #'hcallback))
+    return 1;
+  int res = environment()->HRegisterToHook(hook, #'hcallback,
+                 H_HOOK_OTHERPRIO(0), hooktype, duration);
+  if (res <= 0)
+  {
+    // wenn andere Fehler als -7 (zuviele Hooks registriert) vorkommen:
+    // Fehlermeldung ausgeben
+    if (res != -7)
+      tell_object(environment(),break_string(sprintf(
+          "Technischer Hinweis, den Du an einen Magier weitergeben "
+          "solltest: Beim Registrieren des Hooks %d gab es Fehler: %d\n",
+          hook, res),78));
+    return 0;
+  }
+  return 1;
+}
+
+// effekt: Wirkungswert des Tranks (muss negativ sein)
+// type: 1 fuer Gift, 0 fuer Krankheit
+private void ticktack(int effekt, int type)
+{
+  // Schaden tickt alle 5 Sekunden
+  int delay = 5;
+  // Der halbe Betrag des negativen Effekts wird als Schaden am
+  // Spieler verursacht.
+  // Berechnung: (Schaden pro Sekunde) * (Delay in Sekunden)
+  // in float rechnen ist hier sinnvoll, inkl. aufrunden (durch die +0.5)
+//  int dam = to_int(((-0.5*effekt)/duration)*delay + 0.5);
+  int dam = to_int(0.5*abs(effekt)/data[T_EFFECT_DURATION]*delay + 0.5);
+
+  if (type)
+    tell_object(environment(),
+      break_string("Gift pulsiert brennend durch Deine Adern.",78));
+  else
+    tell_object(environment(),
+      break_string("Du fuehlst Dich schwach und elend, eine Erkrankung "
+      "zehrt an Deinen Kraeften.",78));
+
+  environment()->do_damage(dam, this_object());
+  call_out(#'ticktack, delay, effekt, type);
+}
+
+private int act_attr_heal_poison(int effekt)
+{
+  int erfolgreich;
+  tell_object(environment(), break_string(
+     "Du fuehlst, wie der Trank wie Feuer durch Deinen Koerper schiesst "
+     "und kruemmst Dich vor Schmerzen. Doch Momente spaeter laesst die "
+     "Tortur nach.",78));
+
+  // max. 40 Giftlevel heilen...
+  prot_poison = effekt / 50;
+
+  if (prot_poison < 0)
+  {
+    tell_object(environment(), BS(
+      "Bah! Der Trank schmeckt widerlich bitter. Wenn der mal nicht "
+      "giftig war."));
+    call_out(#'ticktack, 5, effekt, 1); // 1 => Gift
+    return 1;
+  }
+  
+  // ab jetzt nur noch positive Wirkungen.
+
+  // P_POISON zuerst.
+  int poison = environment()->QueryProp(P_POISON);
+  if (poison)
+  {
+    if (poison <= prot_poison)
+    {
+      prot_poison -= poison;
+      environment()->SetProp(P_POISON,0);
+      if (!environment()->QueryProp(P_POISON))
+        ++erfolgreich;
+    }
+    else
+    {
+      poison -= prot_poison;
+      prot_poison=0;
+      environment()->SetProp(P_POISON, poison);
+      // Wenn erfolgreich, direkt Meldung und raus.
+      if (environment()->QueryProp(P_POISON) == poison)
+      {
+        tell_object(environment(), break_string(
+           "Ueberrascht stellst Du fest, dass Du Dich "
+           "besser fuehlst - der Trank hat Deine Vergiftung offenbar "
+           "gelindert.",78));
+        return 1;
+      }
+    }
+  }
+
+  // wenn Trank immer noch positiv (also noch WIrkung uebrig)
+  if (prot_poison > 0)
+  {
+    // Als naechstes Objekte suchen.
+    object *ob = filter_objects(all_inventory(environment()),
+                                "is_class_member", CL_POISON);
+    foreach(object o: ob)
+    {
+      // Entgiften kostet ein Zehntel des Levels, aber min. 1.
+      poison = o->QueryProp(P_LEVEL);
+      if (poison <= prot_poison*10)
+      {
+        prot_poison -= poison/10 + 1;
+        o->SetProp(P_LEVEL, 0);
+        if (o->remove())
+          ++erfolgreich;
+      }
+      else
+      {
+        poison -= prot_poison * 10;
+        prot_poison = 0;
+        o->SetProp(P_LEVEL, poison);
+        if (o->QueryProp(P_LEVEL) == poison)
+          ++erfolgreich;
+      }
+      if (prot_poison <= 0)
+        break;
+    }
+  }
+
+  if (erfolgreich)
+  {
+    tell_object(environment(), break_string(
+      "Ueberrascht stellst Du fest, dass Du Dich viel besser fuehlst - der "
+      "Trank wirkt offenbar gegen Vergiftungen.",78));
+  }
+  else
+  {
+    tell_object(environment(), break_string(
+      "Eine Ahnung sagt Dir, dass der Trank irgendeine positive "
+      "Wirkung hat."));
+  }
+
+  // ggf. an die Hooks registrieren, wenn noch Schutzwirkung uebrig ist.
+  if (prot_poison > 0)
+  {
+    // Rueckgabewerte von HRegisterToHook speichern...
+    int *res = ({ reg_hook(H_HOOK_POISON, H_DATA_MODIFICATOR) });
+    res += ({ reg_hook(H_HOOK_INSERT, H_HOOK_MODIFICATOR) });
+    // Wenn alle versuchten Registrierungen erfolgreich waren...? Ansonsten
+    // andere Meldung... Ich bin noch nicht gluecklich, das hier so explizit
+    // auszugeben, aber ich weiss gerade sonst nicht, wie man drauf kommen
+    // soll, dass es ein Problem gibt.
+    if (sizeof(res) == sizeof(res & ({1})))
+      tell_object(environment(),
+         "Vielleicht haelt diese Wirkung ja sogar noch etwas an?\n");
+    else
+    {
+      // zumindest ein erfolg?
+      if (member(res, 1) > -1)
+        tell_object(environment(),
+            "Vielleicht haelt ein Teil dieser Wirkung ja sogar noch etwas an?\n");
+    }
+  }
+  return 1;
+}
+
+private int act_attr_heal_disease(int effekt)
+{
+  int erfolgreich;
+
+  // max. 40 Krankheitslevel heilen...
+  prot_disease = effekt / 50;
+
+  if (prot_disease > 0)
+  {
+     tell_object(environment(), break_string(
+       "Du fuehlst, wie der Trank in Deinem Bauch eine wohlige Waerme "
+       "verbreitet und laechelst unwillkuerlich.",78));
+   
+    // Objekte suchen.
+    object *ob = filter_objects(all_inventory(environment()),
+                                "is_class_member", CL_DISEASE);
+    foreach(object o: ob)
+    {
+      // Heilen kostet ein Fuenftel des Levels, aber min. 1.
+      int disease = o->QueryProp(P_LEVEL);
+      if (disease <= prot_disease*5)
+      {
+        prot_disease -= disease/5 + 1;
+        o->SetProp(P_LEVEL, 0);
+        if (o->remove())
+          ++erfolgreich;
+      }
+      else
+      {
+        disease -= prot_disease * 5;
+        prot_disease = 0;
+        o->SetProp(P_LEVEL, disease);
+        if (o->QueryProp(P_LEVEL) == disease)
+          ++erfolgreich;
+      }
+      if (prot_disease <= 0)
+        break;
+    }
+  }
+  else
+  {
+    tell_object(environment(), BS(
+      "Der Trank schmeckt eklig faulig. Dein Magen rebelliert umgehend. "
+      "Du kannst Deinen Brechreiz gerade noch unterdruecken, fuehlst "
+      "Dich aber krank."));
+    call_out(#'ticktack, 5, effekt, 0); // 0 => Krankheit
+    return 1;
+  }
+
+  if (erfolgreich)
+  {
+    tell_object(environment(), break_string(
+      "Entspannt durchatmend stellst Du fest, dass Du Dich viel besser fuehlst - der "
+      "Trank wirkt offenbar gegen Krankheiten.",78));
+  }
+  else
+  {
+    tell_object(environment(), break_string(
+      "Eine Ahnung sagt Dir, dass der Trank irgendeine positive "
+      "Wirkung hat."));
+  }
+
+  // ggf. an die Hooks registrieren.
+  if (prot_disease > 0)
+  {
+    // Registrierung erfolgreich...? Ansonsten andere Meldung... Ich bin
+    // noch nicht gluecklich, das hier so explizit auszugeben, aber ich
+    // weiss gerade sonst nicht, wie man drauf kommen soll, dass es ein
+    // Problem gibt.
+    if (reg_hook(H_HOOK_INSERT, H_HOOK_MODIFICATOR)==1)
+      tell_object(environment(),
+         "Vielleicht haelt diese Wirkung ja sogar noch etwas an?\n");
+
+  }
+  return 1;
+}
+
+private string num2desc_fight(int bumms)
+{
+  switch(abs(bumms))
+  {
+    case 0..499:
+      return "ein wenig";
+    case 500..999:
+      return "spuerbar";
+    case 1000..1499:
+      return "deutlich";
+    case 1500..2000:
+      return "erheblich";
+  }
+  return "ungenehmigt viel"; // kommt hoffentlich nicht vor.
+}
+
+// AN: Tiere sind: CL_ANIMAL, CL_FISH, CL_FROG, CL_INSECT, CL_MAMMAL,
+// CL_MAMMAL_LAND, CL_MAMMAL_WATER, CL_REPTILE, CL_ARACHNID, CL_BIRD
+private int act_attr_dam_animals(int effekt)
+{
+  if (reg_hook(H_HOOK_ATTACK_MOD, H_DATA_MODIFICATOR) == 1)
+  {
+    if (!mappingp(att_classes)) att_classes=m_allocate(1);
+    att_classes[CL_ANIMAL] = effekt/20;
+    tell_object(environment(), break_string(
+        "Du spuerst in Dir ein seltsames Verlangen aufsteigen, auf die Jagd "
+        "zu gehen - als wuerde Artemis persoenlich Deine Angriffe "
+        + num2desc_fight(effekt)
+        + " verbessern.",78));
+    return 1;
+  }
+  return 0;
+}
+
+// AN: Magische Wesen sollen sein: CL_ELEMENTAL, CL_ILLUSION, CL_SHADOW
+// CL_DRAGON, CL_DEMON, CL_SHAPECHANGER, CL_HARPY
+private int act_attr_dam_magical(int effekt)
+{
+  if (reg_hook(H_HOOK_ATTACK_MOD, H_DATA_MODIFICATOR) == 1)
+  {
+    if (!mappingp(att_classes)) att_classes=m_allocate(4);
+    att_classes[CL_DRAGON] = att_classes[CL_ELEMENTAL]
+                            = att_classes[CL_SHADOW]
+                            = att_classes[CL_ILLUSION]
+                            = effekt/20;
+    tell_object(environment(), break_string(
+        "Merkwuerdig. Du hast gerade das Gefuehl, als fiele Dir der "
+        "Kampf gegen von Hekate beschenkte Wesen "
+        + num2desc_fight(effekt)
+        + " leichter.",78));
+    return 1;
+  }
+  return 0;
+}
+
+// AN: Untote sollen sein: CL_SKELETON, CL_GHOUL, CL_GHOST, CL_VAMPIRE
+// CL_ZOMBIE, CL_UNDEAD
+// Bloed ist nur, dass CL_UNDEAD alle anderen enthaelt bis auf CL_GHOST.
+// Also lassen wir die Unterscheidung und schrittweise Freischaltung
+// erst einmal sein.
+private int act_attr_dam_undead(int effekt)
+{
+  if (reg_hook(H_HOOK_ATTACK_MOD, H_DATA_MODIFICATOR) == 1)
+  {
+    // Zombies, Skelette, Ghule...
+    if (!mappingp(att_classes)) att_classes=m_allocate(1);
+    att_classes[CL_UNDEAD] =  effekt/20;
+    tell_object(environment(), break_string(
+        "Auf einmal hast Du den Eindruck, dass die Kreaturen des "
+        "Hades Deinen Angriffen "
+        + num2desc_fight(effekt)
+        + " weniger entgegen zu setzen haben.",78));
+    return 1;
+  }
+  return 0;
+}
+
+private int act_attr_prot_animals(int effekt)
+{
+  if (reg_hook(H_HOOK_DEFEND, H_DATA_MODIFICATOR) == 1)
+  {
+    if (!mappingp(prot_classes)) prot_classes=m_allocate(1);
+    prot_classes[CL_ANIMAL] = effekt/20;
+    tell_object(environment(), break_string(
+        "Du hast das Gefuehl, dass Artemis ihre schuetzende Hand "
+        + num2desc_fight(effekt)
+        + " ueber Dich haelt.",78));
+    return 1;
+  }
+  return 0;
+}
+
+private int act_attr_prot_magical(int effekt)
+{
+  if (reg_hook(H_HOOK_DEFEND, H_DATA_MODIFICATOR) == 1)
+  {
+    if (!mappingp(prot_classes)) prot_classes=m_allocate(4);
+    prot_classes[CL_DRAGON] = prot_classes[CL_ELEMENTAL]
+                            = prot_classes[CL_SHADOW]
+                            = prot_classes[CL_ILLUSION]
+                            = effekt/20;
+    tell_object(environment(), break_string(
+        "Du hast das Gefuehl, dass von Hekate beschenkte Wesenheiten Dir "
+        +num2desc_fight(effekt)
+        + " weniger anhaben koennen.",78));
+    return 1;
+  }
+  return 0;
+}
+
+private int act_attr_prot_undead(int effekt)
+{
+  if (reg_hook(H_HOOK_DEFEND, H_DATA_MODIFICATOR) == 1)
+  {
+    // Zombies, Skelette, Ghule...
+    if (!mappingp(prot_classes)) prot_classes=m_allocate(1);
+    prot_classes[CL_UNDEAD] =  effekt/20;
+    tell_object(environment(), break_string(
+        "Du bist ploetzlich zuversichtlich, Angriffen der Kreaturen "
+        "des Hades "
+        + num2desc_fight(effekt)
+        + " besser widerstehen zu koennen.",78));
+    return 1;
+  }
+  return 0;
+}
+
+
+private int act_attr_tragen(int effekt)
+{
+  if ( IS_LEARNER(environment()) )
+    tell_object(environment(), sprintf("effekt: %d\n",effekt));
+  SetProp(P_WEIGHT, QueryProp(P_WEIGHT) - effekt*4);
+  tell_object(environment(),
+      BS("Du fuehlst Dich ploetzlich "+num2desc(effekt)
+        + (effekt>0 ? " entlastet" : " belastet") + "."));
+  return 1;
+}
+
+// Berechnet eine max. Verzoegerung der Wirkung abhaengig von der Wirkung und
+// einer Wirkschwelle. Der rel. Abstand von der Wirkschwelle (relativ zum max.
+// moeglichen Abstand) wird hierbei genutzt. Ausserdem ist die max.
+// Verzoegerung natuerlich die Wirkungsdauer des Trankes.
+// <duration> muss im Trank schon gesetzt sein.
+private int calc_max_delay(int effekt, int wirkschwelle)
+{
+  int abstand = abs(effekt - wirkschwelle);
+  if (!duration) duration = time()+600;
+  if ( IS_LEARNER(environment()) )
+    printf("calc_max_delay: %d\n",((duration-time()) * abstand) /
+      (2000-abs(wirkschwelle)));
+  return ((duration-time()) * abstand) / (2000-abs(wirkschwelle));
+}
+
+//TODO: die Zeitverzoegerung ist nen netter Effekt, aber zeitvergzoegerte
+//Tports sind oefter keine gute Idee -> noch pruefen
+//TODO: Die Zeitverzoegerung bei NO_TPORT_OUT auch nochmal pruefen
+private int act_attr_flee_tport(int effekt)
+{
+  // effekt > 0 teleportiert sofort zu halbwegs sicheren Orten
+  // -750 <= effekt < 0 teleportiert auch sofort zu nicht so dollen Orten
+  // -2000 <= effekt < -750 teleportiert spaeter zu bloeden Orten
+  if (effekt < -750 && effekt >= -2000)
+  {
+    // Verzoegerung ausrechnen und dies hier per call_out nochmal mit einem
+    // effekt rufen, der eine instantane Reaktion ausloest.
+    // effekt - 2000 ist ein Hack, damit nicht nochmal verzoegert wird.
+    call_out(#'act_attr_flee_tport,
+        random(calc_max_delay(effekt, -750))+1, effekt - 2000);
+    tell_object(environment(),
+        "Deine Umgebung fuehlt sich nach und nach unwirklicher an.\n");
+    return 1;
+  }
+
+  // Effekte instantan ausloesen.
+
+  // wenn hier kein P_NO_TPORT erlaubt ist, mal gucken, ob wir spaeter noch
+  // zeit haben.
+  if (environment(environment())->QueryProp(P_NO_TPORT)==NO_TPORT_OUT)
+  {
+    tell_object(environment(),
+        BS("Irgendetwas haelt Dich an diesem Ort fest."));
+    int delay = duration - time();
+    // wenn noch genug Restzeit, nochmal versuchen, sonst nix machen.
+    if (delay>10)
+    {
+      // AN/TODO: Kann das nicht auch ziemlich lang sein?
+      call_out(#'act_attr_flee_tport, random(delay), effekt);
+    }
+    return 0;
+  }
+
+  // den Hack von oben rueckgaengig machen, wir brauchen den originalen
+  // Effektwert zurueck.
+  if (effekt < -2000)
+    effekt += 2000;
+
+  string* dests;
+  if ( effekt > 0 )
+  {
+    switch(effekt)
+    {
+      case 0..499:
+        dests = ({"/d/inseln/zesstra/vulkanweg/room/r1",
+                  "/d/fernwest/li/xian/lab2/grab2",
+                  "/d/ebene/miril/schloss/heide11",
+                  "/d/polar/alle/room/weg4_15",
+                  "/d/dschungel/arathorn/tempel/room/t4-5",
+                  "/d/anfaenger/arathorn/minitut/room/huette_"+
+                    environment()->query_real_name(),
+                 });
+        break;
+      case 500..749:
+        dests = ({"/d/ebene/bertram/ebene/wasser8",
+                  "/d/ebene/room/gebuesch2_3",
+                  "/d/polar/alle/room/eiswueste/eiswueste[4,6]",
+                 });
+        if (environment()->QueryProp(P_REAL_RACE)!="Dunkelelf")
+          dests += ({"/d/unterwelt/padreic/kneipe/kneipe"});
+        break;
+      case 750..999:
+        dests = ({"/d/gebirge/silvana/cronoertal/room/tf4",
+                  "/d/inseln/schiffe/floss",
+                  "/d/polar/humni/hexen/room/leuchtkammer",
+                  "/d/polar/gabylon/temong/rooms/anlegestelle",
+                 });
+        break;
+      case 1000..1249:
+        dests = ({"/d/fernwest/shinobi/konfu_quest/room/insel4",
+                  "/d/fernwest/ulo/mura/tokoro/haus4",
+                  "/d/ebene/room/Halle/shalle14",
+                 });
+        break;
+      case 1250..1499:
+        dests = ({"/d/gebirge/silvana/cronoertal/room/tf4",
+                  "/gilden/zauberer",
+                  "/d/gebirge/georg/room/w11"
+                 });
+        break;
+      case 1500..1749:
+        dests = ({"/gilden/bierschuettler",
+                  "/gilden/kaempfer",
+                  "/d/wald/gundur/hobbitdorf/schrein",
+                  "/d/vland/morgoth/room/city/rathalle",
+                 });
+        break;
+      default:
+        dests = ({environment()->QueryProp(P_START_HOME)||
+                    environment()->QueryDefaultHome(),
+                  environment()->QueryDefaultHome(),
+                  "/d/ebene/room/PortVain/po_haf2",
+                  "/d/gebirge/room/he3x3",
+                  "/d/ebene/room/huette",
+                 });
+        break;
+    }
+  }
+  else if ( effekt < 0 )
+  {
+    switch(effekt)
+    {
+      case -499..0:
+        dests = ({"/d/polar/bendeigid/rooms/neskaya/neskay12",
+                  "/players/ketos/gl/room/gl1x1",
+                  "/d/inseln/zesstra/vulkanweg/room/r10",
+                  "/d/vland/morgoth/room/kata/ktanke",
+                  "/d/ebene/zardoz/burg/feuerrau",
+                 });
+        break;
+      case -999..-500:
+        dests = ({"/d/ebene/room/Hoehle/hg6",
+                  "/d/gebirge/silvana/warok/room/l3/wa_3x7",
+                  "/d/vland/morgoth/room/kata/xkat03",
+                  "/d/vland/morgoth/room/kata/kata5",
+                  "/d/wald/yoru/ort/dravi/weg5",
+                  "/d/wald/atamur/room/e83",
+                 });
+        break;
+      case -1499..-1000:
+        dests = ({"/d/polar/bendeigid/rooms/pass/pass_e1",
+                  "/d/ebene/room/gebuesch",
+                  "/d/gebirge/silvana/warok/room/l1/wa_1x6",
+                  "/d/vland/morgoth/room/kata/gkat17",
+                  "/d/wald/atamur/room/e12",
+                 });
+        if (environment()->QueryProp(P_REAL_RACE)=="Dunkelelf")
+          dests += ({"/d/unterwelt/padreic/kneipe/kneipe"});
+        break;
+      case -1749..-1500:
+        dests = ({"/d/dschungel/wurzel/t2",
+                  "/d/vland/morgoth/room/kata/ukat26",
+                  "/d/vland/morgoth/room/kata/h12",
+                  "/d/gebirge/boing/sm/l5/m5x2",
+                 });
+        if (environment()->QueryProp(P_GUILD)=="chaos")
+          dests+=({"/gilden/klerus"});
+        break;
+      default:
+        dests = ({"/d/ebene/rochus/room/sp10",
+                  "/d/ebene/rochus/quest_3player/room/schacht10",
+                 });
+        if ( IS_SEER(environment()) )
+          dests += ({"/d/wald/paracelsus/froom/sch_6e",
+                     "/d/wald/paracelsus/froom/sch_9x",
+                     "/d/wald/paracelsus/froom/sch2_6d",
+                    });
+        break;
+    }
+  }
+  tell_object(environment(),
+      BS("Eine Kraft zerrt an Dir, die Welt verschwimmt..."));
+  if (environment()->move(dests[random(sizeof(dests))],M_TPORT) == MOVE_OK)
+    tell_object(environment(),
+        "Einen Moment spaeter bist Du ganz woanders.\n");
+  else
+    tell_object(environment(),
+        "Aber sonst passiert nichts.\n");
+  return 1;
+}
+
+private int act_attr_change_dimension(int effekt)
+{
+  // nur effekt >= 1000 aendern die Dimension instantan. ;-)
+  if (effekt > 0 && effekt < 1000)
+  {
+    // Verzoegerung ausrechnen und dies hier per call_out nochmal mit einem
+    // effekt rufen, der eine instantane Reaktion ausloest.
+    call_out(#'act_attr_change_dimension,
+        random(calc_max_delay(effekt,1000))+1, 1000);
+    tell_object(environment(),BS("Um Dich herum wird alles "
+        "langsam grauer.\n"));
+    return 1;
+  }
+  // nur -600 < effekt < 0 aendern die Dimension instantan ;-)
+  if (effekt < -600)
+  {
+    // Verzoegerung ausrechnen und dies hier per call_out nochmal mit einem
+    // effekt rufen, der eine instantane Reaktion ausloest.
+    call_out(#'act_attr_change_dimension,
+        random(calc_max_delay(effekt, -600))+1, -601);
+    tell_object(environment(),BS("Um Dich herum wird alles "
+        "langsam grauer.\n"));
+    return 1;
+  }
+  // Effekte instantan ausloesen.
+  // wenn hier kein Para-Trans erlaubt ist, mal gucken, ob wir spaeter noch
+  // zeit haben.
+  if (environment(environment())->QueryProp(P_NO_PARA_TRANS))
+  {
+    int delay = duration - time();
+    // wenn noch genug Restzeit, nochmal versuchen, sonst nix machen.
+    if (delay>10)
+    {
+      call_out(#'act_attr_change_dimension, random(delay), effekt);
+      tell_object(environment(), BS("Die Welt um Dich wird ein "
+            "wenig grauer."));
+    }
+    return 0;
+  }
+  if ( effekt > 0 )
+  {
+    if ( environment()->QueryProp(P_PARA) > 0 )
+    {
+      environment()->SetProp(P_PARA, 0);
+      tell_object(environment(), BS("Fuer einen kurzen Moment siehst Du nur "
+          "ein graues Wabern um Dich herum, bevor die Welt wieder "
+          "normal aussieht.\n"));
+    }
+    else
+    {
+      tell_object(environment(), BS("Fuer einen kurzen Moment sieht alles um "
+            "Dich herum grau aus."));
+    }
+  }
+  else if ( effekt < 0 )
+  {
+    if ( !environment()->QueryProp(P_PARA) )
+    {
+      environment()->SetProp(P_PARA, 1);
+       tell_object(environment(), BS("Fuer einen kurzen Moment siehst Du nur "
+          "ein graues Wabern um Dich herum, bevor die Welt wieder "
+          "normal aussieht. Aber es bleibt ein ungutes Gefuehl.\n"));
+    }
+    else {
+      tell_object(environment(), BS("Fuer einen kurzen Moment sieht alles um "
+            "Dich herum grau aus."));
+    }
+  }
+  return 1;
+}
+
+private int act_attr_defrog(int effekt)
+{
+  // nur effekt > 1000 entfroscht instantan. ;-)
+  if (effekt > 0 && effekt < 1000)
+  {
+    // Verzoegerung ausrechnen und dies hier per call_out nochmal mit einem
+    // effekt rufen, der eine instantane Reaktion ausloest.
+    call_out(#'act_attr_defrog, random(calc_max_delay(effekt,1000))+1, 1000);
+    tell_object(environment(),BS(
+        "Du hoerst ploetzlich lautes Gequake, was langsam immer leiser "
+        "wird.\n"));
+    return 1;
+  }
+  // nur -500 < effekt < 0 froscht instantan ;-)
+  if (effekt < -500)
+  {
+    // Verzoegerung ausrechnen und dies hier per call_out nochmal mit einem
+    // effekt rufen, der eine instantane Reaktion ausloest.
+    call_out(#'act_attr_defrog, random(calc_max_delay(effekt, -500))+1, -501);
+    tell_object(environment(),BS(
+        "Du hoerst ploetzlich ganz leisess Gequake, was langsam immer lauter "
+        "wird.\n"));
+    return 1;
+  }
+
+  // Effekte instantan ausloesen.
+  if ( effekt > 0 )
+  {
+    if ( environment()->QueryProp(P_FROG) )
+    {
+      environment()->SetProp(P_FROG,0);
+      tell_object(PL, "Du fuehlst Dich deutlich weniger gruen.\n");
+    }
+    else
+    {
+      tell_object(environment(), break_string("Die Welt um Dich herum verliert "
+        "ploetzlich alle gruenen Farbtoene, die bald darauf allmaehlich "
+        "zurueckkehren.",78));
+    }
+  }
+  else if ( effekt < 0 ) {
+    if ( !environment()->QueryProp(P_FROG) ) {
+      environment()->SetProp(P_FROG, 1);
+      tell_object(environment(), "Quak!\n");
+    }
+    else {
+      tell_object(environment(), break_string("Deine Sicht verschwimmt, alles wird "
+        "intensiv gruen. Merkwuerdig. Zum Glueck ist das schnell wieder "
+        "vorbei.",78));
+    }
+  }
+  return 1;
+}
+
+private int act_attr_heal_lp(int effekt)
+{
+  if (effekt > 0)
+  {
+    tell_object(environment(),
+        BS("Du fuehlst Dich schlagartig "+num2desc(effekt)
+          + " besser."));
+    environment()->restore_hit_points(effekt/10);
+  }
+  else
+  {
+    tell_object(environment(),
+        BS("Du fuehlst Dich schlagartig "+num2desc(effekt)
+          + " schlechter."));
+    environment()->do_damage(-effekt/10);
+  }
+  return 1;
+}
+
+private int act_attr_heal_sp(int effekt)
+{
+  if (effekt > 0)
+  {
+    tell_object(environment(),
+        BS("Du fuehlst Dich schlagartig "+num2desc(effekt)
+          + " konzentrierter."));
+    environment()->restore_spell_points(effekt/10);
+  }
+  else
+  {
+    tell_object(environment(),
+        BS("Du fuehlst Dich schlagartig "+num2desc(effekt)
+          + " unkonzentrierter."));
+    environment()->reduce_spell_points(-effekt/10);
+  }
+  return 1;
+}
+
+
+private int modify_sa(string sa, int effekt)
+{
+  string msg;
+  switch (sa)
+  {
+    case SA_SPEED:
+      msg = "Du wirst gerade " + num2desc(effekt)
+            +(effekt>0 ? " schneller." : " langsamer.");
+      effekt = (effekt * 30) / 2000;
+      break;
+    case SA_DURATION:
+      msg = "Du wirst gerade " + num2desc(effekt)
+            +(effekt>0 ? " ausdauernder."
+                       : " weniger ausdauernd.");
+      effekt = (effekt * 25) / 2000;
+      break;
+    case SA_SPELL_PENETRATION:
+      msg = "Deine geistige Durchsetzungskraft hat sich gerade "
+            + num2desc(effekt)
+            +(effekt>0 ? " verbessert." : " verschlechtert.");
+      effekt = (effekt * 30) / 2000;
+      break;
+  }
+  if (environment()->ModifySkillAttribute(sa, effekt, duration-time()) ==
+        SA_MOD_OK)
+  {
+    tell_object(environment(),BS(msg));
+    return 1;
+  }
+  return 0;
+}
+
+private int act_attr_change_sa_speed(int effekt)
+{
+  return modify_sa(SA_SPEED, effekt);
+}
+private int act_attr_change_sa_duration(int effekt)
+{
+  return modify_sa(SA_DURATION, effekt);
+}
+private int act_attr_change_sa_spell_penetration(int effekt)
+{
+  return modify_sa(SA_SPELL_PENETRATION, effekt);
+}
+
+
+// Spieler MUSS das environment() sein!
+private void effekt()
+{
+  // Als erstes die Wirkungsdauer verwursten, einige Effekte brauchen
+  // <duration>.
+  // Wann laufen die Effekte denn spaetenstens ab?
+  duration = time() + data[T_EFFECT_DURATION];
+  call_out(#'terminate_effects, data[T_EFFECT_DURATION]);
+
+  // nur echte wirkungen beruecksichtigen, keine "Metadaten"
+  mapping effects = data & T_KRAUT_EFFECTS;
+  // fuer Spieler wird der Paratrans nicht stattfinden.
+  if (!IS_SEER(environment()))
+    m_delete(data, T_CHANGE_DIMENSION);
+
+  // Waehrend der Wirkung ist dieses Objekt schonmal unsichtbar.
+  SetProp(P_INVIS, 1);
+  // Gewicht nullen, bevor die Wirkungen aktiviert werden, da die
+  // Wirkung von T_CARRY ansonsten wieder deaktiviert wuerde. 
+  SetProp(P_WEIGHT, 0);
+  // neue, leere Flasche ins Inventar des Spielers bewegen.
+  clone_object(TRANKITEM)->move(environment(),M_NOCHECK|M_SILENT);
+
+  // auftrennen in positive und negative Effekte. Keys mit Wert 0 werden
+  // ignoriert.
+  mapping peff = filter(effects, function int (string k, int val)
+                        {return val > 0;});
+  mapping neff = filter(effects, function int (string k, int val)
+                        {return val < 0;});
+  // erst die positiven, dann die negativen Wirkungen aktivieren
+  // fuer jede Wirkung wird eine lfun act_<trankattribut>() gerufen, z.B.
+  // act_attr_tragen() (-> act_T_CARRY() )
+  mapping notactivated =
+          filter(peff, function int (string k, int val)
+              {return !funcall(symbol_function("act_"+k,this_object()), val);})
+         +
+          filter(neff, function int (string k, int val)
+              {return !funcall(symbol_function("act_"+k,this_object()), val);});
+  // Meldungen ausgeben ueber nicht aktivierte Wirkungen?
+  // TODO
+}
+
+static int cmd_trinken(string str, mixed *param)
+{
+  if (environment(this_object()) != PL)
+  {
+    write("Aus auf dem Boden liegende Flaschen trinkt es sich so "
+       "schlecht!\n");
+  }
+  else if (data==0)
+  {
+    write("Die Flasche ist leer, Du muesstest erst etwas hineinfuellen.\n");
+  }
+  else if (time()>expiry) 
+  {
+    write(BS("Irgendwie passiert nichts. Hattest Du vielleicht doch nur "
+      "klares Wasser abgefuellt?"));
+    data=0;
+  }
+  else {
+    // Die Sperrzeit muss hier separat berechnet werden, weil eine
+    // Anpassung der Wirkungsdauer im Krautmaster dazu fuehren wuerde,
+    // dass es keine instantan wirkenden Traenke mehr gaebe.
+    int blocktime = max(60+random(60), data[T_EFFECT_DURATION]);
+    if(environment()->check_and_update_timed_key(blocktime, DRINK_POTION)==-1)
+    {
+      write(BS("Du oeffnest die Flasche und trinkst ihren Inhalt aus."));
+      say(BS(PL->Name(WER, 0)+" oeffnet eine Flasche und trinkt sie in einem "
+        "Schluck aus."));
+      effekt();
+      // TODO: reicht das hier aus, oder muss das noch an anderen Stellen 
+      // eingebaut werden?
+      RemoveId(({"trank","kraeutertrank"}));
+    }
+    else {
+      tell_object(environment(), BS(
+        "Der letzte Trank wirkt noch ein wenig nach. Du kannst Deinem "
+        "Magen nicht so bald schon den naechsten zumuten."));
+    }
+  }
+  return 1;
+}
+
+public nomask int Fill(object *plants)
+{
+  if (!pointerp(plants)) return -1;
+  if (data) return -2;  // schon voll
+  data = PLANTMASTER->make_potion(plants);
+  AddId(({"trank","kraeutertrank"}));
+  if (mappingp(data))
+  {
+    // Pflanzen zerstoert der Master, wenns geklappt hat.
+    expiry=time()+data[T_EXPIRE];
+    SetProp(P_WEIGHT, 300);
+    return 1;
+  }
+  return -3;
+}
+
+void NotifyPlayerDeath(object vic, object killer, int exp)
+{
+  call_out("remove",1);
+}