Added public files

Roughly added all public files. Probably missed some, though.
diff --git a/obj/team.c b/obj/team.c
new file mode 100644
index 0000000..0319f5e
--- /dev/null
+++ b/obj/team.c
@@ -0,0 +1,1432 @@
+#pragma strong_types,save_types
+#pragma no_shadow
+// TODO: Morgoth erbt das Teamobjekt: klaeren warum und wieso eigentlich.
+//#pragma no_inherit
+#pragma pedantic
+
+#include <living/team.h>
+#include <properties.h>
+#include <language.h>
+#include <new_skills.h>
+#include <ansi.h>
+#include <wizlevels.h>
+#define ME this_object()
+#define PO previous_object()
+#define TP this_player()
+#define TI this_interactive()
+
+#define AUTOINF_HP_MINUS 0x01
+#define AUTOINF_HP_PLUS  0x02
+#define AUTOINF_SP_MINUS 0x04
+#define AUTOINF_SP_PLUS  0x08
+#define AUTOINF_INSTANT  0x10
+
+private nosave mapping is_member;   // Teammitglieder
+private nosave object leader;       // Teamleiter
+private nosave string tname;        // Teamname
+private nosave mapping wanted_row;  // Gewuenschte Reihe
+private nosave mapping wimpy_row;   // Fluchtreihe
+private nosave mapping act_row;     // Aktuelle Reihe
+private nosave mapping autofollow;  // Spieler folgt Teamleiter
+private nosave mapping attack_cmd;  // Spieler hat Angriffsbefehl
+private nosave mapping assoc_mem;   // Zugeordnete Mitglieder (Kampf NPCs)
+private nosave int *formin,*formax; // Formation
+private nosave mixed *rows;         // Die Reihen
+private nosave int last_reorder;    // Letzte Formationspruefung
+private nosave mapping h;           // Sortier-Score: 125*G.Reihe-HP
+private nosave object *att_exec;    // Mitglieder, die Attacke ausfuehren.
+private nosave object *mis_attacked;// (Ex-)Mitglieder ohne Begruessungsschlag
+private nosave mapping mis_init_att;// Fehlende Begruessungsschlaege
+private nosave mapping hp_info;     // HP, MAX_HP, SP, MAX_SP
+private nosave int autoinf_flags;
+private nosave mapping autoinf_hp;
+private nosave mapping autoinf_sp;
+private nosave int autoinf_time;
+private nosave string *hist;
+
+private nosave object debugger;
+
+void _set_debug() {
+  if (!debugger && IS_LEARNER(TI))
+    debugger=TI;
+  else if (debugger==TI)
+    debugger=0;
+}
+ 
+private void debug(string str) {
+  if (objectp(debugger) && stringp(str))
+    tell_object(debugger,"#TEAM: "+str);
+}
+
+void create() {
+  autoinf_flags=0;
+  autoinf_sp=([]);
+  autoinf_hp=([]);
+  is_member=([]);
+  leader=0;
+  wanted_row=([]);
+  act_row=([]);
+  wimpy_row=([]);
+  autofollow=([]);
+  attack_cmd=([]);
+  assoc_mem=([]);
+  formin=({1,0,0,0,0});
+  formax=({5,4,3,2,1});
+  rows=EMPTY_TEAMARRAY;
+  h=([]);
+  att_exec=({});
+  mis_init_att=([]);
+  mis_attacked=({});
+  hp_info=([]);
+  hist=({});
+  if (object_name(ME)==TEAM_OBJECT)
+    return;
+  if (!stringp(tname=TEAM_MASTER->RegisterTeam()))
+    tname="?";
+}
+
+object *Members() {
+  return (m_indices(is_member)-({0}));
+}
+
+object Leader() {
+  return leader;
+}
+
+varargs string name(int casus, int demon) {
+  if (!stringp(tname))
+    return "Team ?";
+  return "Team "+capitalize(tname);
+}
+
+varargs string Name(int casus, int demon) {
+  return name(casus,demon);
+}
+
+varargs int remove(int silent) {
+  if (mappingp(is_member) && sizeof(Members())) // Nur leere Teams removen
+    return 0;
+  TEAM_MASTER->UnregisterTeam(); // Teamnamen freigeben.
+  destruct(ME);
+  return 1;
+}
+
+private void TryRemove() {
+  if (clonep(ME)
+      && (!mappingp(is_member) || !sizeof(Members()))
+      && !first_inventory(ME)
+      && find_call_out("remove")<0)
+    call_out("remove",0);
+}
+
+int CmpFirstArrayElement(mixed *a, mixed *b) {
+  return(a[0]<b[0]);
+}
+
+varargs private void gtell(string str, string who, int tohist) {
+  int i;
+  object *tmembers,rochus;
+  string prefix,msg;
+
+  tmembers=Members();
+  prefix=sprintf("[%s:%s] ",name(),stringp(who)?who:"");
+  msg=break_string(str,78,prefix);
+  for (i=sizeof(tmembers)-1;i>=0;i--)
+    tell_object(tmembers[i],msg);
+  if (objectp(rochus=find_player("rochus"))
+      && rochus->QueryProp("debug_team"))
+    tell_object(rochus,msg);
+  if (tohist)
+    hist=(hist+({break_string(str+" <"+ctime()[11..15]+">",78,prefix)}))[-100..];
+}
+
+int IsMember(object ob) {
+  return (objectp(ob) && is_member[ob]);
+}
+
+int IsInteractiveMember(object ob) {
+  return (objectp(ob) && is_member[ob] && query_once_interactive(ob));
+}
+
+varargs private int *GetHpInfo(object ob, closure cl) {
+  int *res;
+
+  if (!closurep(cl)) cl=symbol_function("QueryProp",ob);
+  if (!pointerp(res=hp_info[ob]) || sizeof(res)<4)
+    res=({0,funcall(cl,P_MAX_HP),0,funcall(cl,P_MAX_SP)});
+  res[0]=funcall(cl,P_HP);
+  res[2]=funcall(cl,P_SP);
+  return hp_info[ob]=res;
+}
+
+int CompareHp(object a, object b) {
+  return h[a]>h[b];
+}
+
+// Aktualisiert act_row (->wer steht in welcher Reihe).
+private void UpdateActRow() {
+  int i,j,update_hp;
+  object *new;
+  mixed aso;
+
+  act_row=([]);
+  rows[0]+=Members();
+  update_hp=0;
+  if (!mappingp(h)) {
+    h=([]);
+    update_hp=1;
+  }
+  for (i=MAX_TEAMROWS-1;i>=0;i--) {
+    new=({});
+    foreach(object ob: rows[i]) {
+      if (objectp(ob) && is_member[ob] && !act_row[ob]) {
+        act_row[ob]=i+1;
+        new+=({ob});
+        if (update_hp) {
+          if (!objectp(aso=assoc_mem[ob]) || environment(aso)!=environment(ob))
+            aso=ob;
+          h[ob]=
+            1000*wanted_row[aso]
+            +40*act_row[aso]
+            -(query_once_interactive(aso)?8:2)*aso->QueryProp(P_HP)
+            -query_once_interactive(ob);
+          // NPCs bekommen fast gleichen Wert wie Caster,
+          // im Zweifelsfalle steht der Caster weiter vorne...
+        }
+      }
+    }
+    rows[i]=sort_array(new,"CompareHp",ME);
+  }
+}
+
+private void CheckFormation() {
+  int i,mincap,maxcap,d,num;
+
+  mincap=maxcap=0;
+  if (formin[0]<1)
+    formin[0]=1;
+  
+  // erstmal die Wuensche normalisieren/korrigieren auf sinnvolle Werte.
+  for (i=0;i<MAX_TEAMROWS;i++) {
+    if (formin[i]<0) formin[i]=0;
+    if (formax[i]<formin[i]) formax[i]=formin[i];
+    if (formax[i]>MAX_TEAM_ROWLEN) formax[i]=MAX_TEAM_ROWLEN;
+    if (formin[i]>formax[i]) formin[i]=formax[i];
+    mincap+=formin[i]; // Summe der min. je Reihe gewuenschten.
+    maxcap+=formax[i]; // Summe der max. je Reihe gewuenschten.
+  }
+
+  num=sizeof(Members());
+
+  // max. gewuenschte Reihenlaenge verlaengern, wenn die Summe der maximal je
+  // Reihe gewuenschten kleiner als die Anzahl der Spieler ist. Von vorne
+  // natuerlich.
+  d=num-maxcap;
+  for (i=0;i<MAX_TEAMROWS;i++) {
+    if (d<=0)
+      break;
+    d-=(MAX_TEAM_ROWLEN-formax[i]);
+    formax[i]=MAX_TEAM_ROWLEN;
+    if (d<0)
+      formax[i]+=d;  // doch noch was uebrig, wieder anhaengen. 
+  }
+  // min. gewuenschte Reihenlaenge auf 0 verkuerzen, wenn die Summe der
+  // minimal je Reihe gewuenschten groesser als die Anzahl der Spieler ist.
+  // Von hinten natuerlich.
+  d=mincap-num; // 
+  for (i=MAX_TEAMROWS-1;i>=0;i--) {
+    if (d<=0)
+      break;
+    d-=formin[i];
+    formin[i]=0;
+    if (d<0)
+      formin[i]-=d; // doch noch was uebrig, wieder anhaengen.
+  }
+}
+
+private void MakeFormation() {
+  // Verlegungsstrategie:
+  //    Richtung Test Verschieben
+  // 1. -----> a) MAX          <- X
+  //           b) MAX             X ->
+  //           c) MIN             X <- <- <- <-
+  // 2. <----- a) MIN -> -> -> -> X
+  //           b) MAX          <- X
+  int i,j,d;
+
+  last_reorder=time();
+  UpdateActRow();
+  CheckFormation();
+  for (i=0;i<MAX_TEAMROWS;i++) {
+    d=sizeof(rows[i]);
+    if (d<formin[i] || d>formax[i])
+      break;
+  }
+  if (i>=MAX_TEAMROWS)
+    return; // Formation ist noch in Ordnung
+
+  for (i=0;i<MAX_TEAMROWS;i++) {
+    if (sizeof(rows[i])>formax[i]) {     // Reihe ist zu voll
+      if (i>0) {
+        d=formax[i-1]-sizeof(rows[i-1]);
+        if (d>0) {                       // Reihe vorher hat d freie Plaetze
+          rows[i-1]+=rows[i][0..(d-1)];  // Also d Mitglieder abgeben
+          rows[i]=rows[i][d..];
+        }
+      }
+      if (i<MAX_TEAMROWS-1 && sizeof(rows[i])>formax[i]) {// Immer noch zu voll
+        rows[i+1]=rows[i][formax[i]..]+rows[i+1];         // Rest nach hinten.
+        rows[i]=rows[i][0..(formax[i]-1)];
+      }
+      continue; // War zu voll, kann nicht zu leer sein
+    }
+    for (j=i+1;j<MAX_TEAMROWS;j++) {
+      d=formin[i]-sizeof(rows[i]);
+      if (d<=0)                   // Ausreichende Anzahl
+        break;                    // kein weiteres j noetig
+      rows[i]+=rows[j][0..(d-1)]; // Sonst Nachschub von hinten holen
+      rows[j]=rows[j][d..];
+    }
+  }
+  for (i=MAX_TEAMROWS-1;i>0;i--) {
+    for (j=i-1;j>=0;j--) {
+      d=formin[i]-sizeof(rows[i]);
+      if (d<=0)                   // Ausreichende Anzahl
+        break;                    // kein weiteres j noetig
+      rows[i]+=rows[j][0..(d-1)]; // Sonst Nachschub von vorne holen
+      rows[j]=rows[j][d..];
+    }
+    d=sizeof(rows[i])-formax[i];
+    if (d>0) {
+      rows[i-1]+=rows[i][0..(d-1)]; // Ueberschuss nach vorne schieben
+      rows[i]=rows[i][d..];
+    }
+  }
+  UpdateActRow();
+}
+
+private void RemoveFromRow(object ob, int src) {
+  if (src<0 || src>=MAX_TEAMROWS)
+    return;
+  rows[src]-=({ob});
+  if (sizeof(rows[src])>=formin[src])
+    return;
+  // Falls hinten noch Ueberschuss da ist, her damit.
+  if (src<MAX_TEAMROWS-1 && sizeof(rows[src+1])-1>=formin[src+1]) {
+    rows[src]+=rows[src+1][0..0];
+    rows[src+1]=rows[src+1][1..];
+    return;
+  }
+  // Falls vorne noch Ueberschuss da ist, her damit.
+  if (src>0 && sizeof(rows[src-1])-1>=formin[src-1]) {
+    rows[src]=rows[src-1][<1..]+rows[src];
+    rows[src-1]=rows[src-1][0..<2];
+  }
+}
+
+private void AddToRow(object ob, int dest) {
+  if (dest<0 || dest>=MAX_TEAMROWS)
+    return;
+  rows[dest]+=({ob});
+  if (sizeof(rows[dest])<=formax[dest])
+    return;
+  // Falls vorne noch jemand hin kann, dorthin
+  if (dest>0 && sizeof(rows[dest-1])+1<=formax[dest-1]) {
+    rows[dest-1]+=rows[dest][0..];
+    rows[dest]=rows[dest][1..];
+    return;
+  }
+  // Falls hinten noch jemand hin kann, dorthin
+  if (dest<MAX_TEAMROWS-1 && sizeof(rows[dest+1])+1<=formax[dest+1]) {
+    // Dest: ({... <3, <2, ob});
+    rows[dest+1]=rows[dest][<2..<2]+rows[dest+1];
+    rows[dest]=rows[dest][0..<3]+({ob});
+  }
+}
+
+private void CycleRows(object ob, int src, int dest) {
+  int i;
+
+  if (src<0 || src>=MAX_TEAMROWS || dest<0 || dest>=MAX_TEAMROWS)
+    return;
+  rows[src]-=({ob});
+  if (sizeof(rows[src])<formin[src] || sizeof(rows[dest])>=formax[dest]) {
+    if (src<dest) {             // (<- -X) <- <- <- <- (+X <-)
+      for (i=src+1;i<=dest;i++) {
+        rows[i-1]+=rows[i][0..0];
+        rows[i]=rows[i][1..];
+      }
+    } else if (src>dest) {           // (-> +X) -> -> -> -> (-X ->)
+      for (i=src-1;i>=dest;i--) {
+        rows[i+1]=rows[i][<1..]+rows[i+1];
+        rows[i]=rows[i][0..<2];
+      }
+    }
+  }
+  if (src<=dest)
+    rows[dest]+=({ob});
+  else
+    rows[dest]=({ob})+rows[dest];
+}
+
+// tauscht zufaellig aus den ersten 4 Reihen aus einer Reihe den letzten mit
+// dem ersten aus der Folgereihe, wenn die der Score des vorderen Spieler mehr
+// als 10 groesser als der des hinteren Spielers ist.
+private void RandomChangeRow() {
+  int *nums,i;
+  object p1,p2;
+
+  if (!mappingp(h))
+    UpdateActRow();
+  for (nums=({0,1,2,3});sizeof(nums);nums-=({i})) {
+    i=nums[random(sizeof(nums))];
+    if (!sizeof(rows[i]) || !sizeof(rows[i+1])) continue;
+    if (!objectp(p1=rows[i][<1]) || !objectp(p2=rows[i+1][0])) continue;
+    if (wanted_row[p1]<wanted_row[p2]) continue;
+    if (h[p2]-h[p1]>=-10) continue;
+    rows[i][<1]=p2;
+    rows[i+1][0]=p1;
+    return;
+  }
+}
+
+varargs private string
+CountUpNames(object *obs, string zsing, string zplur) {
+  string res;
+  object ob;
+  int i,sz;
+
+  res="";
+  if (!pointerp(obs)) return res;
+  if (!stringp(zsing)) zsing="";
+  if (!stringp(zplur)) zplur="";
+  if (sz=sizeof(obs)) {
+    for (i=0;i<sz;i++) {
+      if (i)
+	      res+=((i<sz-1)?", ":" und ");
+      if (objectp(ob=obs[i]))
+        res+=ob->name(WER);
+    }
+    if (sz>1)
+      res+=zplur;
+    else
+      res+=zsing;
+  }
+  return res;
+}
+
+static void DoChangeRow(object pl, int dest) {
+  mapping old_row,ec1,ec2,ecb;
+  int i;
+  object *obs,*envs,env;
+  string *msg,str;
+
+  dest--;
+  CheckFormation();
+  h=0;  // damit HP-Liste geupdated wird.
+  UpdateActRow();
+  old_row=deep_copy(act_row);
+
+  // welche Objekte bewegen?
+  obs=({});
+  if (objectp(pl)) {
+    obs=({pl});
+    if (pointerp(assoc_mem[pl]))
+      obs+=assoc_mem[pl];
+  } else {
+    RandomChangeRow();
+  }
+
+  foreach (object ob:obs) {
+    if (!objectp(ob))
+      continue;
+    // Alle assoziierten NPC kriegen die gleiche gewuenschte Reihe wie der
+    // Spieler.
+    wanted_row[ob]=wanted_row[pl];
+    UpdateActRow();
+    // und dann in die gew. Reihe stopfen.
+    int src=act_row[ob]-1;
+    if (dest<0 || dest>=MAX_TEAMROWS)
+      RemoveFromRow(ob,src);
+    else if (src<0 || src>=MAX_TEAMROWS)
+      AddToRow(ob,dest);
+    else if (src!=dest)
+      CycleRows(ob,src,dest);
+  }
+
+  MakeFormation();
+
+  obs = Members(); // alle Members beruecksichtigen beim Abgleich!
+  object *changed = allocate(0);
+  foreach (object ob:obs)
+    if (objectp(ob) && old_row[ob]!=act_row[ob]) {
+      ob->InformRowChange(old_row[ob],act_row[ob]);
+      changed += ({ob});
+    }
+
+  // Ab jetzt nur noch Ausgabe.
+  if (get_eval_cost()<800000) return; // War schon teuer genug, Lagvermeidung
+  msg=({});ec1=([]);ec2=([]);ecb=([]);
+  foreach (object ob:changed) {
+    tell_object(ob,sprintf("Du bist jetzt in Reihe %d.\n",act_row[ob]));
+    msg+=({sprintf("%s->%d",ob->Name(WER),act_row[ob])});
+    if (query_once_interactive(ob) && !interactive(ob)) continue;
+    if (!objectp(env=environment(ob))) continue;
+    if (old_row[ob]<=1) {
+      if (!pointerp(envs=ec1[env])) envs=({});
+      ec1[env]=envs+({ob});ecb[env]|=1;
+    }
+    if (act_row[ob]<=1) {
+      if (!pointerp(envs=ec2[env])) envs=({});
+      ec2[env]=envs+({ob});ecb[env]|=2;
+    }
+  }
+  if (sizeof(msg))
+    gtell(implode(msg,", ")); // Ausgabe an alle Gruppenmitglieder.
+  m_delete(ecb,find_object("/room/netztot")); // Das gaebe Mega-Lag :-)
+  envs=m_indices(ecb);obs=Members();
+  for (i=sizeof(envs)-1;i>=0;i--) {
+    if (!objectp(env=envs[i])) continue;
+    str="";
+    str+=CountUpNames(ec1[env]," tritt zurueck"," treten zurueck");
+    if (ecb[env]==3) str+=", ";
+    str+=CountUpNames(ec2[env]," tritt vor"," treten vor");
+    str+=".\n";
+    tell_room(env,capitalize(break_string(str,78)),obs);
+  }
+}
+
+void UpdateFormation() {
+  DoChangeRow(0,0);
+}
+
+int SwapRows(object ob1, object ob2) {
+  int i,r1,r2,p1,p2;
+
+  if (!objectp(ob1) || !objectp(ob2) || ob1==ob2)
+    return 0;
+  r1=r2=-1;
+  for (i=0;i<MAX_TEAMROWS;i++) {
+    if (r1==-1 && (p1=member(rows[i],ob1))>=0)
+      r1=i;
+    if (r2==-1 && (p2=member(rows[i],ob2))>=0)
+      r2=i;
+  }
+  if (r1==-1 || r2==-1)
+    return 0;
+  if (r1==r2)
+    return 1;
+  if (r1<r2) { // Nicht Monster vor Spieler stellen
+    if (query_once_interactive(ob1) && !interactive(ob2))
+      return 0;
+  } else {
+    if (query_once_interactive(ob2) && !interactive(ob1))
+      return 0;
+  }
+  rows[r1][p1]=ob2;ob2->InformRowChange(r2,r1);
+  rows[r2][p2]=ob1;ob1->InformRowChange(r1,r2);
+  gtell(ob1->Name(WER)+" und "+ob2->name(WER)+" tauschen die Plaetze.\n");
+  return 1;
+}
+
+private int ChangeRow(string arg) {
+  int num;
+
+  if (!arg || sscanf(arg,"%d",num)!=1)
+    return notify_fail("In welche Reihe willst Du denn wechseln?\n"),0;
+  if (num<1 || num>MAX_TEAMROWS)
+    return notify_fail("Die Reihenangabe ist ungueltig.\n"),0;
+  TP->SetProp(P_TEAM_WANTED_ROW,wanted_row[TP]=num);
+  printf("Du versuchst in Reihe %d zu wechseln.\n",num);
+  DoChangeRow(TP,num);
+  return 1;
+}
+
+private int ChangeWimpyRow(string arg) {
+  int num;
+
+  if (!arg || sscanf(arg,"%d",num)!=1)
+    return notify_fail("In welche Reihe willst Du fliehen?\n"),0;
+  if (num<0 || num>MAX_TEAMROWS)
+    return notify_fail("Die Reihenangabe ist ungueltig.\n"),0;
+  TP->SetProp(P_TEAM_WIMPY_ROW,wimpy_row[TP]=num);
+  if (num>1)
+    printf("Bei der Flucht wirst Du in Reihe %d wechseln.\n",num);
+  else
+    write("Bei der Flucht wirst Du den Raum verlassen.\n");
+  return 1;
+}
+
+mixed *PresentRows(object env) {
+  int i,j,d,arbeit;
+  mixed *res;
+  object *nd,ob;
+
+  if (!objectp(env))
+    env=environment(TP);
+  if (last_reorder!=time() || !mappingp(h))
+    UpdateFormation();
+  res=EMPTY_TEAMARRAY;arbeit=0;nd=({});
+  for (i=0;i<MAX_TEAMROWS;i++) {
+    object *new=({});
+    foreach(ob: rows[i]) {
+      if (objectp(ob) && is_member[ob] && environment(ob)==env) {
+        if (query_once_interactive(ob) && !interactive(ob)) {
+          nd+=({ob});
+          arbeit=1;
+        } else {
+          new+=({ob});
+        }
+      } else {
+        arbeit=1;
+      }
+    }
+    res[i]=new;
+  }
+  if (!arbeit)
+    return res;
+  for (i=j=0;i<MAX_TEAMROWS;i++) {
+    if (j<=i) j=i+1;
+    for (;j<MAX_TEAMROWS;j++) {
+      d=formin[i]-sizeof(res[i]);
+      if (d<=0)                   // Ausreichende Anzahl
+        break;                    // kein weiteres j noetig
+      res[i]+=res[j][0..(d-1)];   // Sonst Nachschub von hinten holen
+      res[j]=res[j][d..];
+    }
+  }
+  res[MAX_TEAMROWS-1]+=nd; // Netztote bieten keine Deckung, nach hinten.
+  return res;
+}
+
+mapping PresentPositions(mixed pres_rows) {
+  mapping res=([]);
+  if (objectp(pres_rows))
+    pres_rows=PresentRows(pres_rows);
+  if (!pointerp(pres_rows))
+    return res;
+  for (int i=0;i<MAX_TEAMROWS;i++) {
+    foreach(object ob: pres_rows[i])
+      if (ob)
+        res[ob]=i+1;
+  }
+  return res;
+}
+
+
+varargs int FleeToRow(object ob) {
+  int num;
+
+  if (!objectp(ob))
+    ob=TP;
+  if (!IsMember(ob))
+    return 0;
+  h=0; // Reihen bei naechster Abfrage neu sortieren
+  num=wimpy_row[ob];
+  if (num<2 || num>MAX_TEAMROWS) // Flucht in 1. Reihe nicht sinnvoll.
+    return 0;
+  if (num==wanted_row[ob]) // Ist schonmal nach hinten geflohen.
+    return 0;
+  tell_object(ob,sprintf("Du versuchst in Reihe %d zu fliehen.\n",num));
+  ob->SetProp(P_TEAM_WANTED_ROW,wanted_row[ob]=num);
+  DoChangeRow(ob,num);
+  if (PresentPositions(environment(ob))[ob]<=1) // Flucht gescheitert?
+    return 0;
+  return 1;
+}
+
+static int ChangeFormation(string arg) {
+  string *words;
+  int i,min,max;
+  mapping old_row;
+
+  if (arg=="aus")
+    arg="1-6 0-6 0-6 0-6 0-6";
+  i=sizeof(words=old_explode(arg," "));
+  if (i>MAX_TEAMROWS)
+    i=MAX_TEAMROWS;
+  for (--i;i>=0;i--) {
+    if (sscanf(words[i],"%d-%d",min,max)==2) {
+      formin[i]=min;
+      formax[i]=max;
+    } else if (sscanf(words[i],"%d",min)==1) {
+      formin[i]=formax[i]=min;
+    }
+  }
+
+  UpdateFormation();
+
+  words=({});
+  for (i=0;i<MAX_TEAMROWS;i++)
+    words+=({sprintf("%d-%d",formin[i],formax[i])});
+  gtell("Die Formation ist jetzt "+implode(words," / ")+".\n");
+  return 1;
+}
+
+int Shout(string str) {
+  if (!str || str=="")
+    return notify_fail("Was willst Du den anderen Teammitgliedern sagen?\n"),0;
+  gtell(str,TP->Name(WER),1);
+  return 1;
+}
+
+int Hist(string str) {
+  int i,anz,maximal;
+
+  // non-interactive oder Nicht-Mitglieder sollten die Hist nicht abfragen.
+  if (!IsInteractiveMember(TP)) return -1;
+
+  maximal=sizeof(hist);
+  if (str && sscanf(str,"%d",anz)==1)
+    i=maximal-anz;
+  if (i<0)
+    i=0;
+
+  TP->More(sprintf("%@s",hist[i..maximal])||"");
+  return 1;
+}
+
+private void DoChangeLeader(object ob) {
+  if (objectp(leader) && leader->QueryProp(P_TEAM_LEADER)==ME)
+    leader->SetProp(P_TEAM_LEADER,0);
+  leader=ob;
+  leader->SetProp(P_TEAM_LEADER,ME);
+}
+
+private int ChangeLeader(string arg) {
+  object ob;
+
+  if (stringp(arg) && arg!="") {
+    if (!objectp(ob=find_player(arg))
+        && !objectp(ob=present(arg,environment(TP))))
+      return notify_fail(capitalize(arg)+" nicht gefunden.\n"),0;
+  } else {
+    ob=TP;
+  }
+  if (objectp(leader)
+      && TP!=leader
+      && (!interactive(TP) || (interactive(leader) && query_idle(leader)<180)))
+    return notify_fail("Der Teamleiter ist noch aktiv.\n"),0;
+  if (objectp(leader)
+      && query_once_interactive(leader)
+      && !query_once_interactive(leader))
+    return notify_fail("Nur ein Spieler kann das Team leiten.\n"),0;
+  if (!IsMember(ob))
+    return notify_fail(ob->Name(WER)+" ist kein Teammitglied.\n"),0;
+  DoChangeLeader(ob);
+  gtell(ob->Name(WER)+" leitet jetzt das Team.\n");
+  return 1;
+}
+
+varargs private int CheckSecond(object pl, object *obs) {
+  mixed x,ip;
+  mapping second;
+  object ob;
+  int i;
+
+  if (!objectp(pl))
+    pl=TP;
+  if (!query_once_interactive(pl))
+    return 0;
+  if (!pointerp(obs))
+    obs=Members();
+  obs-=({pl});
+  second=([]);
+  for (i=sizeof(obs)-1;i>=0;i--) {
+    if (!objectp(ob=obs[i]) || !query_once_interactive(ob)) continue;
+    second[getuid(ob)]=1;
+    if (stringp(x=ob->QueryProp(P_SECOND)))
+      second[lower_case(x)]=1;
+  }
+  if (second[getuid(pl)] ||
+      (stringp(x=pl->QueryProp(P_SECOND)) && second[lower_case(x)]))
+    return 1;
+
+  if (!stringp(ip=pl->QueryProp(P_CALLED_FROM_IP)) || ip=="")
+    return 0;
+  for (i=sizeof(obs)-1;i>=0;i--) {
+    if (!objectp(ob=obs[i]) || !query_once_interactive(ob)) continue;
+    if (ob->QueryProp(P_CALLED_FROM_IP)!=ip) continue;
+    log_file("rochus/zweitieverdacht",
+             sprintf("%s %s,%s\n",ctime()[4..15],getuid(pl),getuid(ob)));
+    break;
+  }
+  return 0;
+}
+
+static int Allowed(object pl) {
+  mixed x;
+  string nm;
+
+  if (!objectp(pl))
+    return notify_fail("WER soll ins Team aufgenommen werden?\n"),0;
+  if (pl==TP)
+    nm="Du b";
+  else
+    nm=pl->Name(WER)+" ";
+  if (objectp(x=pl->QueryProp(P_TEAM)) && x!=ME)
+    return notify_fail(nm+"ist schon in einem anderen Team.\n"),0;
+  if (pl->QueryGuest())
+    return notify_fail(nm+"ist ein Gast.\n"),0;
+  if (pl->QueryProp(P_GHOST))
+    return notify_fail(nm+"ist ein Geist.\n"),0;
+  if (CheckSecond(pl))
+    return notify_fail(nm+"ist Zweitspieler eines Teammitglieds.\n"),0;
+  if (sizeof(filter(Members(),"IsInteractiveMember",ME))
+      >=MAX_TEAM_MEMBERS)
+    return notify_fail("Es sind schon zuviele Spieler im Team.\n"),0;
+  return 1;
+}
+
+private void DoAddMember(object ob) {
+  closure cl;
+
+  if (!IsMember(leader))
+    DoChangeLeader(ob);
+  ob->SetProp(P_TEAM,ME);
+  if (IsMember(ob))
+    return;
+  is_member[ob]=1;
+  cl=symbol_function("QueryProp",ob);
+  attack_cmd[ob]=funcall(cl,P_TEAM_ATTACK_CMD);
+  autofollow[ob]=funcall(cl,P_TEAM_AUTOFOLLOW);
+  wimpy_row[ob]=funcall(cl,P_TEAM_WIMPY_ROW);
+  wanted_row[ob]=funcall(cl,P_TEAM_WANTED_ROW);
+  if (!wanted_row[ob]) wanted_row[ob]=1;;
+  ob->SetProp(P_TEAM_NEWMEMBER,0);
+  GetHpInfo(ob,cl);
+  if (query_once_interactive(ob)) ob->AddHpHook(ME);
+  if (!objectp(assoc_mem[ob]))
+    gtell(ob->Name(WER)+" wurde ins Team aufgenommen.\n");
+  DoChangeRow(ob,wanted_row[ob]);
+}
+
+int AddAssocMember(object caster, object npc) {
+  object *obs;
+
+  if (extern_call() && PO!=caster)
+    return 0;
+  if (!IsMember(caster)
+      || !objectp(npc)
+      || query_once_interactive(npc)
+      || IsMember(npc)
+      || objectp(assoc_mem[caster])
+      || assoc_mem[npc]
+      || caster==npc)
+    return 0;
+  assoc_mem[npc]=caster;
+  DoAddMember(npc);
+  if (!pointerp(obs=assoc_mem[caster]))
+    obs=({});
+  obs=(obs-({npc,0}))+({npc});
+  assoc_mem[caster]=obs;
+  return 1;
+}
+
+static void AddMemberAndAssocs(object caster) {
+  object *obs,ob;
+  int i;
+
+  DoAddMember(caster);
+  if (!pointerp(obs=caster->QueryProp(P_TEAM_ASSOC_MEMBERS)))
+    return;
+  for (i=sizeof(obs)-1;i>=0;i--)
+    if (objectp(ob=obs[i]) && !caster->IsEnemy(ob))
+      AddAssocMember(caster,ob);
+}
+
+int AddMember(object ob) {
+ 
+  if (!Allowed(ob))
+    return 0;
+  
+  // Wenn es einen TP gibt, unterliegt er einigen Einschraenkungen.
+  // Dafuer wird er aber via Aufnahme ins Team auch ggf. Teamleiter, wenn
+  // noetig.
+  // TODO: Dieser Code geht davon aus, dass er nur aus einem Kommando
+  // ausgefuehrt wird und TP der Veranlasser der Aufnahme ist. Dies scheint
+  // mir unrealistisch. (Zesstra)
+  if (TP && TP==previous_object()) {
+    if (!Allowed(TP))
+      return 0;
+    if (TP!=ob && CheckSecond(ob,({TP}))) {// Zweitiereglung bei Gruendung.
+      notify_fail(ob->Name(WER)+" ist Zweitspieler von Dir.\n");
+      return 0;
+    }
+    if (!IsMember(leader))
+      AddMemberAndAssocs(TP);
+    if (TP!=leader) {
+      notify_fail("Nur der Teamleiter kann Mitglieder aufnehmen.\n");
+      return 0;
+    }
+  }
+
+  AddMemberAndAssocs(ob);
+  return 1;
+}
+
+private void DoRemoveMember(object ob) {
+  object *tmembers,caster;
+  mixed asmem;
+  int i;
+
+  ob->SetProp(P_TEAM,0);
+  m_delete(is_member,ob);
+  m_delete(wanted_row,ob);
+  m_delete(act_row,ob);
+  m_delete(wimpy_row,ob);
+  m_delete(autofollow,ob);
+  m_delete(attack_cmd,ob);
+  if (objectp(caster=assoc_mem[ob]) && pointerp(asmem=assoc_mem[caster]))
+    assoc_mem[caster]=asmem-({ob,0});
+  if (query_once_interactive(ob)) ob->RemoveHpHook(ME);
+  m_delete(hp_info,ob);
+  m_delete(autoinf_hp,ob);
+  m_delete(autoinf_sp,ob);
+  DoChangeRow(ob,-1);
+
+  if (!objectp(assoc_mem[ob])) {
+    if (ob->QueryProp(P_GHOST)) {
+      gtell(upper_case(ob->name(WER))+" HAT DAS TEAM VERLASSEN.","Tod");
+    } else {
+      tell_object(ob,"Du verlaesst das Team.\n");
+      gtell(ob->Name(WER)+" hat das Team verlassen.\n");
+    }
+  }
+  m_delete(assoc_mem,ob);
+
+  if (IsMember(leader)) // Hat das Team noch einen Leiter?
+    return;
+  tmembers=Members();ob=0;
+  if (i=sizeof(tmembers)) {
+    ob=tmembers[0];
+    for (--i;i>=0;i--) {
+      if (interactive(tmembers[i])) {
+        ob=tmembers[i];
+        break;
+      }
+      if (query_once_interactive(tmembers[i]))
+        ob==tmembers[i];
+    }
+    DoChangeLeader(ob);
+    gtell(leader->Name(WER)+" hat die Teamleitung uebernommen.\n");
+    return;
+  }
+  TryRemove();
+}
+
+int RemoveAssocMember(object caster, object npc) {
+  object *obs;
+
+  if (extern_call() && PO!=caster)
+    return 0;
+  if (!IsMember(caster) || assoc_mem[npc]!=caster)
+    return 0;
+  DoRemoveMember(npc);
+  return 1;
+}
+
+static void RemoveMemberAndAssocs(object caster) {
+  object *obs,ob;
+  int i;
+
+  if (pointerp(obs=caster->QueryProp(P_TEAM_ASSOC_MEMBERS))) {
+    for (i=sizeof(obs)-1;i>=0;i--)
+      if (objectp(ob=obs[i]))
+        RemoveAssocMember(caster,ob);
+  }
+  DoRemoveMember(caster);
+}
+
+private void RemoveSingles() {
+  object *obs;
+  mixed aso;
+
+  if (!IsMember(leader)) return;
+  if (!query_once_interactive(leader)) return; // NPC Team
+  obs=Members()-({leader});
+  if (pointerp(aso=assoc_mem[leader]))
+    obs-=aso;
+  if (sizeof(obs)) return;
+  gtell("Das Team loest sich auf.\n");
+  RemoveMemberAndAssocs(leader);
+  TryRemove();
+}
+
+int RemoveMember(mixed arg) {
+  object *mems,mem,ob;
+  int i;
+  
+  if (objectp(arg)) {
+    ob=arg;
+  } else if (stringp(arg) && arg!="") {
+    mems=Members()-({leader});
+    for (i=sizeof(mems)-1;i>=0;i--) {
+      if (objectp(mem=mems[i]) && mem->id(arg))  {
+        ob=mem;
+        if (query_once_interactive(ob))
+          break;
+      }
+    }
+    if (!objectp(ob))
+      return notify_fail(capitalize(arg)+" nicht gefunden.\n"),0;
+  } else {
+    return 0;
+  }
+  if (TP!=leader && TP!=ob && ob!=PO)
+    return notify_fail("Nur der Teamleiter kann Mitglieder entlassen.\n"),0;
+  if (!IsMember(ob) && ob->QueryProp(P_TEAM)!=ME)
+    return notify_fail(ob->Name(WER)+" ist kein Teammitglied.\n"),0;
+  if (PO!=ob && objectp(assoc_mem[ob]))
+    return notify_fail(ob->Name(WER)+" gehoert zu "+
+                       assoc_mem[ob]->Name(WEM)+".\n"),0;
+
+  RemoveMemberAndAssocs(ob);
+  RemoveSingles();
+  return 1;
+}
+
+static int ChangeName(string str) {
+  if (leader && TP!=leader)
+    return notify_fail("Nur der Teamleiter kann den Namen aendern.\n"),0;
+  if (!str || str=="")
+    return 0;
+  str=str[0..19];
+  if (!stringp(str=(TEAM_MASTER->RegisterTeam(str))))
+    return notify_fail("Der Name ist schon vergeben.\n"),0;
+  tname=str;
+  if (objectp(TP))
+    gtell(TP->Name(WER)+" aendert den Teamnamen auf \""+tname+"\".\n");
+  return 1;
+}
+
+void TeamInitAttack() {
+  object *obs,*ens,*removed,ob,en;
+  int i,j;
+
+  debug(sprintf("mis_init_att: %O\n",mis_init_att));
+  ens=m_indices(mis_init_att);removed=({});
+  for (i=sizeof(ens)-1;i>=0;i--) {
+    if (!objectp(en=ens[i]))
+      continue;
+    if (!pointerp(obs=mis_init_att[en]))
+      continue;
+    for (j=sizeof(obs)-1;j>=0;j--)
+      if (!IsMember(ob=obs[j]) || environment(ob)!=environment(en))
+        obs[j]=0;
+    obs-=({0});
+    ob=en->SelectNearEnemy(obs,1);
+    debug(sprintf("Begruessungsschlag von %O fuer %O\n",en,ob));
+    if (!objectp(ob))
+      continue;
+    en->Attack2(ob); // Begruessungsschlag
+    removed+=({en}); // Kein Begruessungsschlag mehr von diesem Monster
+  }
+
+  for (i=sizeof(mis_attacked)-1;i>=0;i--)
+    if (objectp(ob=mis_attacked[i]))
+      ob->ExecuteMissingAttacks(removed);
+  // Begruessungsschlaege die ausgefuehrt wurden entfernen
+  // und nicht ausgefuehrte nachholen
+
+  mis_attacked=({});
+  mis_init_att=([]);
+  att_exec=({});
+}
+
+int InitAttack_Callback(object enemy) {
+  object *arr;
+
+  if (!IsMember(PO) || member(att_exec,PO)<0)
+    return 0;
+  if (!pointerp(arr=mis_init_att[enemy]))
+    arr=({});
+  mis_init_att[enemy]=arr+({PO});
+  if (member(mis_attacked,PO)<0)
+    mis_attacked+=({PO});
+  return 1;
+}
+
+void TeamAttackExecuted_Callback(int success) {
+  if (!IsMember(PO))
+    return;
+  att_exec-=({PO,0});
+  if (!sizeof(att_exec))
+    TeamInitAttack();
+}
+
+private int StartAttack() {
+  object *tmembers,ob,env;
+  int i;
+
+  if (TP!=leader || !objectp(env=environment(TP)))
+    return notify_fail("Nur der Teamleiter kann den Angriff starten.\n"),0;
+  tmembers=Members();
+  TeamInitAttack(); // Falls noch Schlaege fehlen....
+  for (i=sizeof(tmembers)-1;i>=0;i--)
+    if (objectp(ob=tmembers[i]) && stringp(attack_cmd[ob]))
+      if (ob->CallTeamAttack(env)) // Angriff wird ausgefuehrt?
+        att_exec+=({ob});          // Liste der Angreifer
+  gtell(TP->Name(WER)+" startet den Angriff.\n");
+  return 1;
+}
+
+void StartFollow(object env) {
+  object *tmembers,ob;
+  int i;
+  string cmd,args;
+
+  // printf("ld:%O PO:%O TP:%O qv:%O env:%O\n",leader,PO,TP,query_verb(),env);
+  if (TP!=leader
+      || !stringp(cmd=query_verb())
+      || !objectp(env)
+      || TP!=PO
+      || TP->QueryProp(P_GHOST) // Der Befehl war wohl ungesund...
+      || TP->IsTeamMove()) // Angriffsbefehl nicht durch verfolge 2 mal...
+    return;
+  cmd="\\"+cmd;
+  if (stringp(args=TP->_unparsed_args()) && args!="")
+    cmd+=(" "+args);
+  tmembers=Members()-({leader});
+  for (i=sizeof(tmembers)-1;i>=0;i--)
+    if (objectp(ob=tmembers[i]) && autofollow[ob])    
+    	if(!query_once_interactive(ob)  || env==environment(ob)) // autofolge nur bei anfuehrer im gleichen raum
+    	      ob->CallTeamFollow(env,cmd);
+}
+
+void ShowTeamInfo() {
+  int i;
+  object *tmembers,ob;
+  string form;
+
+  if (!TI || TP!=TI || !IS_LEARNER(TI))
+    return;
+
+  UpdateActRow();
+  form="";
+  for (i=0;i<MAX_TEAMROWS;i++)
+    form+=sprintf(" %d-%d",formin[i],formax[i]);
+  printf("%s [%O] L: %s F:%s\n",name(),ME,
+         (objectp(leader)?leader->Name(WER):"?"),form);
+  tmembers=Members();
+  for (i=sizeof(tmembers)-1;i>=0;i--) {
+    ob=tmembers[i];
+    printf("  %d(%d) %s [%O]\n",
+           act_row[ob],wanted_row[ob],ob->Name(WER),ob);
+  }
+}
+
+varargs int ShowTeamHP(string arg) {
+  object *tmembers,ob;
+  closure qp;
+  int i,longinf;
+  mixed *vals,*res,cols,fr,rr,termstr;
+  mapping real_row;
+  string nm,inf;
+
+  if (arg && arg[0..3]=="lang") {
+    if (TP!=leader)
+      return notify_fail("Nur der Teamleiter kann die lange "+
+                         "Uebersicht aufrufen.\n"),0;
+    longinf=1;
+    arg=arg[5..];
+  } else
+    longinf=0;
+  real_row=PresentPositions(environment(TP));
+  tmembers=({});
+  for (i=0;i<MAX_TEAMROWS;i++)
+    tmembers+=rows[i];
+  res=({});
+  switch(TP->QueryProp(P_TTY)) {
+  case "ansi":
+    termstr=({ANSI_RED+ANSI_BOLD,ANSI_YELLOW,ANSI_GREEN,ANSI_NORMAL,
+              ANSI_UNDERL});
+    break;
+  case "vt100":
+    termstr=({ANSI_BOLD,"","",ANSI_NORMAL,ANSI_UNDERL});
+    break;
+  default:
+    termstr=({"","","","",""});
+  }
+  vals=({"",0,0,termstr[3],"",0,0,termstr[3]});
+
+  printf("  Name        Gilde           LV GLV  LP (MLP)  KP (MKP) Vors. GR AR TR FR A V\n");
+  if (longinf)
+    printf("  (Zeile 2)   Angriffsbefehl                             Fluchtrichtung\n");
+
+  for (i=sizeof(tmembers)-1;i>=0;i--) {
+    if (!objectp(ob=tmembers[i])) continue;
+    qp=symbol_function("QueryProp",ob);
+    fr=GetHpInfo(ob,qp);
+    vals[1]=fr[0];vals[2]=fr[1];vals[5]=fr[2];vals[6]=fr[3];
+    /*
+    vals[1]=funcall(qp,P_HP);vals[2]=funcall(qp,P_MAX_HP);
+    vals[5]=funcall(qp,P_SP);vals[6]=funcall(qp,P_MAX_SP);
+    */
+    if (!pointerp(cols=funcall(qp,P_TEAM_COLORS)) || sizeof(cols)<4)
+      cols=({vals[2]/4,vals[2]/2,vals[6]/4,vals[6]/2});
+    if (vals[1]<cols[0])
+      vals[0]=termstr[0];
+    else if (vals[1]<cols[1])
+      vals[0]=termstr[1];
+    else
+      vals[0]=termstr[2];
+    if (vals[5]<cols[2])
+      vals[4]=termstr[0];
+    else if (vals[5]<cols[3])
+      vals[4]=termstr[1];
+    else
+      vals[4]=termstr[2];
+    if (intp(fr=wimpy_row[ob]) && fr>1) fr=sprintf("%2d",fr); else fr="--";
+    if (intp(rr=real_row[ob]) && rr>0)  rr=sprintf("%2d",rr); else rr="--";
+    nm=ob->Name(RAW);
+    inf=sprintf("%s %-11s%s %-14s %3d %3d %s%3d (%3d)%s %s%3d (%3d)%s %5d %2d %2d %2s %2s %1s %1s\n",
+               ((ob==leader)?(termstr[4]+"*"):" "),
+               nm[0..10],termstr[3],
+               capitalize(funcall(qp,P_GUILD)||"")[0..13],
+               funcall(qp,P_LEVEL),
+               funcall(qp,P_GUILD_LEVEL),
+               vals[0],vals[1],vals[2],vals[3],
+               vals[4],vals[5],vals[6],vals[7],
+               funcall(qp,P_WIMPY),
+               wanted_row[ob],act_row[ob],rr,fr,
+               (attack_cmd[ob]?"X":"-"),
+               (autofollow[ob]?"X":"-"));
+    if (longinf) {
+      if (!stringp(fr=funcall(qp,P_WIMPY_DIRECTION))) fr="";
+      if (!stringp(rr=attack_cmd[ob])) rr="";
+      if (fr!="" || rr!="")
+        inf+=sprintf("              %-42s %-21s\n",rr[0..41],fr[0..20]);
+    }
+
+    switch (arg) {
+    case "alphabetisch":
+      res+=({({(query_once_interactive(ob)?"0":"1")+nm,inf})});
+      break;
+    case "sortiert":
+      res+=({({sprintf("%2s %2d %2d %s",rr,act_row[ob],wanted_row[ob],nm),
+                 inf})});
+      break;
+    default:
+      res+=({({i,inf})});
+    }
+  }
+  if (arg && arg!="")
+    res=sort_array(res,"CmpFirstArrayElement",ME);
+  for (i=sizeof(res)-1;i>=0;i--)
+    write(res[i][1]);
+  return 1;
+}
+
+varargs int ShowTeamRooms(string arg) {
+  object *tmembers,ob;
+  string s1,s2;
+  mixed *res;
+  int i;
+
+  tmembers=Members();res=({});
+  for (i=sizeof(tmembers)-1;i>=0;i--) {
+    if (!objectp(ob=tmembers[i])) continue;
+    if (!query_once_interactive(ob) && arg!="alle") continue;
+    s1=ob->Name(RAW);
+    if (!objectp(ob=environment(ob))) continue;
+    if (!stringp(s2=ob->QueryProp(P_INT_SHORT))) s2="";
+    res+=({({s1,s2})});
+  }
+  res=sort_array(res,"CmpFirstArrayElement",ME);
+  for (i=sizeof(res)-1;i>=0;i--)
+    printf("%-11s %-66s\n",res[i][0][0..10],res[i][1][0..65]);
+  return 1;
+}
+
+int ChangeColors(string arg) {
+  int *col;
+
+  notify_fail("team farben lp_rot lp_gelb [kp_rot kp_gelb]\n");
+  if (!arg)
+    return 0;
+  col=({0,0,0,0});
+  if (sscanf(arg,"%d %d %d %d",col[0],col[1],col[2],col[3])!=4) {
+    if (sscanf(arg,"%d %d",col[0],col[1])!=2)
+      return 0;
+    col[2]=col[0];col[3]=col[1];
+  }
+  printf("Die Anzeige ist jetzt gelb unter %d LP, rot unter %d LP\n"+
+         "                 bzw. gelb unter %d KP, rot unter %d KP.\n",
+         col[1],col[0],col[3],col[2]);
+  TP->SetProp(P_TEAM_COLORS,col);
+  TP->Set(P_TEAM_COLORS,SAVE,F_MODE_AS);
+  return 1;
+}
+
+int ChangeAutoInfo(string arg) {
+  string *words,txt;
+  int i,fl;
+
+  if (TP!=leader)
+    return notify_fail("Nur der Teamleiter kann automatisch "+
+                       "informiert werden.\n"),0;
+  words=old_explode(arg?arg:""," ");fl=0;
+  for (i=sizeof(words)-1;i>=0;i--) {
+    switch(words[i]) {
+    case "aus":
+      write("Du wirst nicht mehr automatisch informiert.\n");
+      autoinf_flags=0;
+      return 1;
+    case "+kp":
+      fl|=AUTOINF_SP_PLUS;
+    case "kp":
+      fl|=AUTOINF_SP_MINUS;
+      break;
+    case "+lp":
+      fl|=AUTOINF_HP_PLUS;
+    case "lp":
+    case "ein":
+    case "an":
+      fl|=AUTOINF_HP_MINUS;
+      break;
+    case "sofort":
+      fl|=AUTOINF_INSTANT;
+      break;
+    default:
+      ;
+    }
+  }
+  if (!fl)
+    return notify_fail("WIE moechtest Du automatisch informiert werden?\n"),0;
+  if (fl==AUTOINF_INSTANT) fl|=AUTOINF_HP_MINUS;
+  autoinf_flags=fl;
+  txt="Du wirst"+((fl&AUTOINF_INSTANT)?" sofort ":" ")+"informiert, wenn";
+  if (fl&(AUTOINF_HP_PLUS|AUTOINF_HP_MINUS)) {
+    txt+=" die Lebenspunkte eines Teammitglieds";
+    if (fl&(AUTOINF_HP_PLUS)) txt+=" sich aendern"; else txt+=" fallen";
+    if (fl&(AUTOINF_SP_PLUS|AUTOINF_SP_MINUS)) txt+=" oder";
+  }
+  if (fl&(AUTOINF_SP_PLUS|AUTOINF_SP_MINUS)) {
+    txt+=" die Konzentrationspunkte";
+    if (fl&(AUTOINF_SP_PLUS)) txt+=" sich aendern"; else txt+=" fallen";
+  }
+  write(break_string(txt+".\n",78));
+  return 1;
+}
+
+private void DoNotifyHpChange() {
+  object *obs,ob;
+  string str;
+  int i;
+
+  autoinf_time=time();
+  str="";
+  obs=m_indices(autoinf_hp);
+  for (i=sizeof(obs)-1;i>=0;i--) {
+    if (!objectp(ob=obs[i])) continue;
+    if (str!="") str+=", ";
+    str+=sprintf("%s: %d LP",ob->name(WER),autoinf_hp[ob]);
+    if (member(autoinf_sp,ob))
+      str+=sprintf(" / %d KP",autoinf_sp[ob]);
+    m_delete(autoinf_sp,ob);
+  }
+  obs=m_indices(autoinf_sp);
+  for (i=sizeof(obs)-1;i>=0;i--) {
+    if (!objectp(ob=obs[i])) continue;
+    if (str!="") str+=", ";
+    str+=sprintf("%s: %d KP",ob->name(WER),autoinf_sp[ob]);
+  }
+
+  if (str!="" && IsMember(leader))
+    tell_object(leader,break_string(capitalize(str)+"\n",78));
+  autoinf_hp=([]);
+  autoinf_sp=([]);
+}
+
+void NotifyHpChange() {
+  mixed act,old;
+  int change;
+
+  if (!IsMember(PO) || !pointerp(act=hp_info[PO]))
+    return;
+  old=act[0..];
+  act=GetHpInfo(PO);change=0;
+  if (!autoinf_flags) return;
+  if (((autoinf_flags&AUTOINF_HP_MINUS) && act[0]<old[0]) ||
+      ((autoinf_flags&AUTOINF_HP_PLUS)  && act[0]>old[0]))
+    autoinf_hp[PO]=act[0],change=1;
+  if (((autoinf_flags&AUTOINF_SP_MINUS) && act[2]<old[2]) ||
+      ((autoinf_flags&AUTOINF_SP_PLUS)  && act[2]>old[2]))
+    autoinf_sp[PO]=act[2],change=1;
+
+  if (autoinf_time<time() || (change && (autoinf_flags&AUTOINF_INSTANT)))
+    DoNotifyHpChange();
+}
+
+int TeamCmd(string cmd, string arg) {
+  if (!IsMember(TP)) {
+    notify_fail("Du bist kein Teammitglied.\n");
+    if (TP->QueryProp(P_TEAM)==ME)
+      TP->SetProp(P_TEAM,0);
+    return 0;
+  }
+  switch(cmd) {
+  case "angriff":
+    return StartAttack();
+  case "angriffsbefehl":
+    if (stringp(arg))
+      attack_cmd[TP]=arg;
+    else
+      m_delete(attack_cmd,arg);
+    return 1;
+  case "autofolge":
+  case "autof":
+    if (arg=="ein" || arg=="an")
+      autofollow[TP]=1;
+    else
+      m_delete(autofollow,TP);
+    return 1;
+  case "autoi":
+  case "autoinf":
+  case "autoinfo":
+  case "autoinform":
+    return ChangeAutoInfo(arg);
+  case "entlasse":
+    return RemoveMember(arg);
+  case "farben":
+    return ChangeColors(arg);
+  case "fluchtreihe":
+  case "flucht":
+    return ChangeWimpyRow(arg);
+  case "formation":
+    if (TP!=leader)
+      return notify_fail("Nur der Teamleiter kann die Formation aendern.\n"),0;
+    return ChangeFormation(arg);
+  case "hist":
+  case "history":
+    return Hist(arg);
+  case "":
+  case "info":
+    return ShowTeamHP(arg);
+  case "leiter":
+  case "leiterin":
+  case "leitung":
+    return ChangeLeader(arg);
+  case "name":
+    return ChangeName(arg);
+  case "orte":
+    return ShowTeamRooms(arg);
+  case "kampfreihe":
+  case "reihe":
+    return ChangeRow(arg);
+  case "ruf":
+  case "rufe":
+    return Shout(arg);
+  case "verlasse":
+    TP->SetProp(P_TEAM,0);
+    return RemoveMember(TP);
+  default:;
+  }
+  return 0;
+}
+
+void reset() {
+  RemoveSingles();
+  TryRemove();
+}