Added public files

Roughly added all public files. Probably missed some, though.
diff --git a/secure/materialdb.c b/secure/materialdb.c
new file mode 100644
index 0000000..7f71bc4
--- /dev/null
+++ b/secure/materialdb.c
@@ -0,0 +1,936 @@
+// MorgenGrauen MUDlib
+//
+// - Prototypen und Properties in thing/material.h
+// - Liste in materials.h
+//
+// TODO: properties.h um materials.h erweitern
+//
+// - implizite Gruppenzuordnung entfernen, da jetzt explizit in
+//   Definitionsdateien vorhanden
+// - Materialdoku ueberarbeiten, dabei Hinweis auf nicht mehr implizite
+//   Gruppenzuordnung
+// /p/daemon/materialdb.c -- Materialdatenbank
+//
+// $Id: materialdb.c 8755 2014-04-26 13:13:40Z Zesstra $
+
+#pragma strong_types
+#pragma no_clone
+#pragma no_inherit
+#pragma no_shadow
+#pragma pedantic
+
+// Die Propertydefinition reinholen
+#include <language.h>
+#include <thing/description.h>
+// Materialliste nicht mit reinziehen
+#define _SKIP_MATERIALS_
+#include <thing/material.h>
+#include <player/description.h>
+#include <rtlimits.h>
+
+// Basisverzeichnis der Materialien
+#define MAT_DIR "/doc/materials"
+
+// Ausgabeverzeichnis fuer Doku
+#define DOC_DIR(x) ("/doc/materials/"+x)
+
+// Dateiname des Headers mit Materialdefinitionen
+#define HEADERFILE "/sys/materials.h"
+
+// Savefile.
+#define SAVEFILE DOC_DIR("materialdb")
+
+// Rein intern verwendete Namen
+#define P_RECOC "recognizability"
+#define P_ID    "id"
+#define P_DEFSTR "defstr"
+#define P_MEMBERS "members"
+#define P_MG_FRACTIONS "mg_fractions"
+
+#define LOG_ERROR(x) if(find_player("raschaua")&&find_player("raschaua")->QueryProp("mdb-debug"))tell_object(find_player("raschaua"),"MDB-Error:"+x)
+#define LOG_WARN(x) if(find_player("raschaua")&&find_player("raschaua")->QueryProp("mdb-debug"))tell_object(find_player("raschaua"),"MDB-Warn:"+x)
+
+// Prototypes:
+// (Liefert den Anteil der Materialgruppe grp an mats)
+int MaterialGroup(mapping mats, string grp);
+// Konvertiert eine Liste von Materialien zu ihren Namen, dabei wird die
+// Erkennungsfaehigkeit beruecksichtigt und evtl. falsch erkannt
+varargs string ConvMaterialList(mixed mats, int casus, mixed idinf);
+varargs string MaterialName(string mat, int casus, mixed idinf);
+// Gibt den Namen einer Materialgruppe zurueck
+string GroupName(string grp);
+// Gibt alle Materialien zurueck
+string *AllMaterials();
+// Gibt alle Gruppen zurueck
+string *AllGroups();
+// Gibt alle Gruppen zurueck, in denen mat enthalten ist
+string *GetMatMembership(string mat);
+// Gibt alle Materialien zurueck, die in grp enthalten sind
+string *GetGroupMembers(string grp);
+// Erneuert die Materialien durch Scannen des Materialverzeichnisses
+void Update();
+// generiert Headerfile aus den Daten
+varargs void GenHeaderFile(string fn);
+
+mapping materials;
+mapping material_groups;
+private status initialized;
+mapping old_mat_keys;     // Alter Materialkey -> neuer Key (Kompatibilitaet)
+nosave mapping new_materials;       // Materialien waehrend des Scannens
+nosave mapping new_material_groups; // Materialgruppen waehrend des Scannens
+private nosave status isScanning;
+private nosave int updateTicks;
+
+void create() {
+  seteuid(getuid());
+  // Savefile einlesen, falls moeglich, damit die DB direkt initialisert ist,
+  // wenn auch ggf. mit alten Daten.
+  restore_object(SAVEFILE);
+  if (initialized) {
+    // falls erfolgreich, direkt Header fuer die Mudlib schreiben
+    GenHeaderFile();
+  }
+  // jetzt Update der Daten durchfuehren.
+  Update();
+}
+
+//==================== Umwandeln der Strings aus der thing/material.h
+private string getMatId(string key) {
+  // Alte Bezeichner umwandeln
+  if (!member(materials, key))
+    key = old_mat_keys[key];
+  return key;
+}
+private string getMatGroupId(string key) {
+  // Alte Bezeichner umwandeln
+  if (!member(material_groups, key))
+    key = "MATGROUP_"+upperstring(key[3..]);
+  return key;
+}
+private string matKey2Defstr(string key, mapping mats) {
+  string id;
+  if (member(mats[key], P_DEFSTR))
+    id = mats[key][P_DEFSTR];
+  else
+    id = key;
+  return id;
+}
+private string groupKey2Defstr(string key) {
+  if (sizeof(key) > 9)
+    key = "mg_"+lowerstring(key[9..]);
+  else
+    key = "";
+  return key;
+}
+
+//==================== Schnittstellenfunktionen zur Verwendung der DB
+varargs string MaterialName(string mat, int casus, mixed idinf) {
+  if (initialized) {
+    string *names;
+    mapping props;
+    mixed *dif;
+    // Anpassen der Materialid
+    mat = getMatId(mat);
+
+    if (!mappingp(props=materials[mat]))
+      props=([]);
+
+    // Je nach Koennen des Spielers kann man das exakte Material
+    // mehr oder weniger gut erkennen:
+    if (pointerp(dif=props[P_RECOC])
+&& (!intp(idinf)||idinf<100) ) { // 100=exakte Erkennung
+      int i, n, recval;
+      mixed *grps, tmp, x;
+
+      recval=0;
+      grps=props[P_MG_FRACTIONS];
+      if (!pointerp(idinf))
+        idinf=({idinf});
+
+      // Zunaechst die Faehigkeit des Spielers (da koennen noch
+      // Gildenfaehigkeiten hinzu kommen) ermitteln, dieses
+      // Material zu erkennen:
+      i=sizeof(idinf);
+      while(i--) {
+        tmp=idinf[i];
+        if (objectp(tmp)) // Diese Property ist hauptsaechlich fuer Rassen:
+          tmp=tmp->QueryProp(P_MATERIAL_KNOWLEDGE);
+        if (intp(tmp)) {
+          recval+=tmp; // Allgemeine Erkennungsfaehigkeit
+          break;
+        }
+        if (closurep(tmp) && intp(x=funcall(tmp,mat,grps))) {
+          recval+=x;
+          break; // Closures koennen immer nuetzlich sein :)
+        }
+        if (mappingp(tmp)) {
+          int j;
+          if ((x=tmp[mat]) && intp(x)){
+            // Erkennung von speziell diesem Material
+            recval+=x;
+            break;
+          }
+          // Erkennung von Gruppen
+          j=sizeof(grps);
+          while(j--)
+            if((x=tmp[grps[j]]) && intp(x))
+              recval+=x;
+          if (pointerp(tmp=tmp[MATERIAL_SYMMETRIC_RECOGNIZABILITY])) {
+            for (j=sizeof(tmp)-2;j>=0;j-=2) {
+              if (!intp(x=tmp[j+1]))
+                raise_error("materialdb: illegal sym.recoc. format\n");
+              if (props[tmp[j]])
+                recval+=x;
+              else // bei passenden Gruppen +, bei anderen -
+                recval-=x;
+            }
+          }
+        }
+      }
+
+      // Jetzt wird ermittelt, ob vielleicht eine ungenauere
+      // Beschreibung gegeben werden soll:
+      x=dif[0];
+      n = sizeof(dif)-1;
+      for (i=2;i<=n;i+=2) {
+        if (recval>=dif[i-1])
+          x=dif[i];
+      }
+      // Wenn die Faehigkeiten des Spielers nicht fuer den echten Klarnamen
+      // ausreichen, gib die Alternative zurueck:
+      if (x!=mat)
+        return MaterialName(x, casus, 100);
+    }
+
+    if (!pointerp(names=props[P_NAME]) || sizeof(names)<4)
+      names=({"unbekanntes Material", "unbekannten Materials",
+              "unbekanntem Material", "unbekannten Material"});
+    if (casus<0 || casus>3)
+      casus=0;
+    return names[casus];
+  }
+}
+
+varargs string ConvMaterialList(mixed mats, int casus, mixed idinf) {
+  if (initialized) {
+    string *ms,ml;
+    int i;
+
+    ml="";
+    if (mappingp(mats))
+      ms=m_indices(mats);
+    else if (stringp(mats))
+      ms=({mats});
+    else if (pointerp(mats))
+      ms=mats;
+    else
+      ms=({});
+    i=sizeof(ms);
+    while(i) {
+      ml+=MaterialName(ms[--i],casus,idinf);
+      if (i)
+        ml+=((i>1)?", ":" und ");
+    }
+    return ml;
+  }
+}
+
+int MaterialGroup(mapping mats, string grp) {
+   if (initialized) {
+     string *ms;
+     int i,res;
+
+     res=0;
+     if (!mappingp(mats) || !stringp(grp))
+       return res;
+     ms=m_indices(mats);
+     i=sizeof(ms);
+     while(i--) {
+       string mat;
+       mapping props;
+       mat=ms[i];
+       if (mappingp(props=materials[getMatId(mat)]))
+         res+=(mats[mat]*props[P_MG_FRACTIONS][getMatGroupId(grp)])/100;
+     }
+     if (res<-100) // Vielleicht noch Antimaterie zulassen
+       res=-100;   // (noch nicht sicher ob das so bleiben wird oder 0 sein wird)
+     if (res>100)
+       res=100;
+     return res;
+   }
+}
+
+string *AllMaterials() {
+  if (initialized) {
+    // Aus Kompatibilitaetsgruenden die alten Schluessel (#define-String)
+    // zurueckgeben
+    return m_indices(old_mat_keys);
+  }
+  return 0;
+}
+
+string *AllGroups() {
+  if (initialized) {
+    // Aus Kompatibilitaetsgruenden die alten Schluessel (#define-String)
+    // zurueckgeben
+    return map(m_indices(material_groups), #'groupKey2Defstr);
+  }
+  return 0;
+}
+
+string *GetMatMembership(string mat) {
+  if (initialized) {
+    mapping props;
+    // Anpassen der Materialid
+    mat = getMatId(mat);
+
+    if (!mappingp(props=materials[mat]))
+      return ({});
+    return map(m_indices(props[P_MG_FRACTIONS]), #'groupKey2Defstr);
+  }
+  return 0;
+}
+
+string *GetGroupMembers(string grp) {
+  if (initialized) {
+    string *mats;
+    // Anpassen der Materialid
+    grp = getMatGroupId(grp);
+    if (!member(material_groups, grp) ||
+	!pointerp(mats=material_groups[grp][P_MEMBERS]))
+      return ({});
+    return map(mats, #'matKey2Defstr, materials);
+  }
+  return 0;
+}
+
+string GroupName(string grp) {
+  if (initialized) {
+    if (member(material_groups, getMatGroupId(grp)))
+      return material_groups[getMatGroupId(grp)][P_NAME];
+    else
+      return "Unbekanntes";
+  }
+}
+
+string GroupDescription(string grp) {
+  if (initialized) {
+    if (member(material_groups, getMatGroupId(grp)))
+      return material_groups[getMatGroupId(grp)][P_DESCRIPTION];
+    else
+      return "Gruppe unbekannt";
+  }
+}
+
+//==================== Generieren von Headerfile und Manpages
+private string *get_ordered_groups()
+{
+  return ({"MATGROUP_WOOD", "MATGROUP_JEWEL", "MATGROUP_STONE", "MATGROUP_MAGNETIC",
+           "MATGROUP_METAL", "MATGROUP_DRUG", "MATGROUP_HERBAL", "MATGROUP_FLEXIBLE",
+           "MATGROUP_BIO", "MATGROUP_ACIDIC", "MATGROUP_BASIC", "MATGROUP_POISONOUS",
+           "MATGROUP_EXPLOSIVE", "MATGROUP_INFLAMMABLE",
+           "MATGROUP_ELEMENTAL", "MATGROUP_ELECTRICAL", "MATGROUP_MAGIC",
+           "MATGROUP_HOLY", "MATGROUP_UNHOLY", "MATGROUP_INVIS",
+           "MATGROUP_SOLID", "MATGROUP_FLUID", "MATGROUP_GAS"});
+}
+private string gen_material_h_head()
+{
+  return
+    "// MorgenGrauen MUDlib\n//\n"
+    "// materials.h -- material definitions\n//\n"
+    "// This file is generated by /secure/materialdb.c\n//\n"
+    "// DO NOT EDIT!\n//\n"
+    "// $Id: materialdb.c 8755 2014-04-26 13:13:40Z Zesstra $\n\n"
+    "#ifndef __MATERIALS_H__\n"
+    "#define __MATERIALS_H__\n\n";
+}
+private string gen_material_h_material(string mat, string last_grp)
+{
+  mat = old_mat_keys[mat];
+  return sprintf("#define %-24s\"%-20s // %s\n", mat,
+                (member(materials[mat], P_DEFSTR)?materials[mat][P_DEFSTR]:mat)+"\"",
+                materials[mat][P_DESCRIPTION]||materials[mat][P_NAME][WER]);
+}
+private string gen_material_h_materials_grp(string grp, string *left)
+{
+  string txt, *mats;
+  txt = sprintf("\n// Gruppe: %s\n", GroupName(grp));
+  mats = GetGroupMembers(grp) - (GetGroupMembers(grp) - left);
+  txt += sprintf("%@s", map(sort_array(mats, #'>), #'gen_material_h_material));
+  left -= GetGroupMembers(grp);
+  return txt;
+}
+private string gen_material_h_materials()
+{
+  string txt, last_grp;
+  string *grps, *mats;
+  txt = "// ****************************** Materialien ******************************\n";
+  // Gruppenweise ordnen
+  grps = get_ordered_groups();
+  mats = AllMaterials();
+  txt += sprintf("%@s", map(grps, #'gen_material_h_materials_grp,
+                                  &mats));
+  // Übriggebliene Materialien ausgeben
+  txt += "// sonstige Materialien:\n";
+  txt += sprintf("%@s", map(mats, #'gen_material_h_material));
+  return txt;
+}
+private string gen_material_h_group(string grp)
+{
+  return sprintf("#define %-27s\"%-18s // %s\n",
+                 grp, groupKey2Defstr(grp)+"\"", GroupName(grp));
+}
+private string gen_material_h_groups()
+{
+  string txt;
+  txt = "\n// **************************** Materialgruppen ****************************\n\n"
+    "#ifndef _IS_MATERIALDB_\n";
+  txt += sprintf("%@s\n", map(sort_array(m_indices(material_groups), #'>),
+                                    #'gen_material_h_group));
+  txt += "\n#endif // _IS_MATERIALDB_\n";
+  return txt;
+}
+private string gen_material_h_foot()
+{
+  return
+    "#endif // __THING_MATERIAL_H__\n";
+}
+private int dump_material_h(string fn)
+{
+  return (write_file(fn, gen_material_h_head()) &&
+          write_file(fn, gen_material_h_materials()) &&
+          write_file(fn, gen_material_h_groups()) &&
+          write_file(fn, gen_material_h_foot()));
+}
+private string gen_material_list_material(string mat)
+{
+  mat = old_mat_keys[mat];
+  return sprintf("  %-28s%=-45s\n", mat,
+                 materials[mat][P_DESCRIPTION]||materials[mat][P_NAME][WER]);
+}
+private string gen_material_list_materials_grp(string grp, string *left)
+{
+  string txt, *mats;
+  txt = sprintf("%s:\n", capitalize(GroupName(grp)));
+  mats = sort_array(GetGroupMembers(grp) - (GetGroupMembers(grp) - left), #'>);
+  txt += sprintf("%@s\n", map(mats, #'gen_material_list_material));
+  left -= GetGroupMembers(grp);
+  return txt;
+}
+private void dump_material(string fn)
+{
+  string txt;
+  string *grps, *mats;
+  // Gruppenweise ordnen
+  grps = get_ordered_groups();
+  mats = AllMaterials();
+  txt = sprintf("%@s", map(grps, #'gen_material_list_materials_grp,
+                                 &mats));
+  // Übriggebliene Materialien ausgeben
+  txt += "sonstige Materialien:\n";
+  txt += sprintf("%@s", map(mats, #'gen_material_list_material));
+  write_file(fn, txt) ||
+    raise_error(sprintf("Konnte Liste nicht weiter in Datei %s schreiben,"
+                        " Abbruch\n", fn));
+}
+private void dump_group(string grp, string fn)
+{
+  // upperstring langsame simul_efun, warum?
+  write_file(fn, sprintf("  %-28s%=-48s\n", (grp),
+                         GroupName(grp))) ||
+    raise_error(sprintf("Konnte Liste nicht weiter in Datei %s schreiben,"
+                        " Abbruch\n", fn));
+}
+private string gen_doc_foot(string other)
+{
+  return sprintf("\nSIEHE AUCH:\n"
+                 "     Konzepte:    material, materialerkennung\n"
+                 "     Grundlegend: P_MATERIAL, /sys/materials.h, /sys/thing/material.h\n"
+                 "     Methoden:    QueryMaterial(), QueryMaterialGroup(), MaterialList(),\n"
+                 "     Listen:      AllMaterials(), AllGroups()\n"
+                 "                  %s\n"
+                 "     Master:      ConvMaterialList(), MaterialGroup(),\n"
+                 "                  GroupName(), MaterialName(),\n"
+                 "                  GetGroupMembers(), GetMatMembership()\n"
+                 "     Sonstiges:   P_MATERIAL_KNOWLEDGE\n\n"
+                 "%s generiert aus /secure/materialdb\n", other, dtime(time()));
+}
+
+/* GenMatList
+ *
+ * Generiert Datei mit registrierten Materialien fuer die Dokumentation,
+ */
+varargs void GenMatList(string fn)
+{
+  if (initialized) {
+    string txt;
+    if (!stringp(fn) || !sizeof(fn))
+      fn = DOC_DIR("materialliste");
+    if (file_size(fn) >= 0) {
+      printf("Datei %s existiert bereits, loesche sie\n", fn);
+      rm(fn);
+    }
+    if (write_file(fn, "Material Liste\n==============\n\n")) {
+      dump_material(fn);
+      write_file(fn, gen_doc_foot("materialgruppen"));
+      printf("Materialliste erfolgreich in Datei %s geschrieben\n", fn);
+    } else
+      printf("Konnte Liste nicht in Datei %s schreiben, Abbruch\n", fn);
+  }
+}
+
+/* GenMatGroupList
+ *
+ * Generiert Datei mit registrierten Materialgruppen fuer die Dokumentation,
+ */
+varargs void GenMatGroupList(string fn)
+{
+  if (initialized) {
+    string txt;
+    if (!stringp(fn) || !sizeof(fn))
+      fn = DOC_DIR("materialgruppen");
+    if (file_size(fn) >= 0) {
+      printf("Datei %s existiert bereits, loesche sie\n", fn);
+      rm(fn);
+    }
+    if (write_file(fn, "Materialgruppen\n===============\n")) {
+      map(sort_array(m_indices(material_groups), #'>), #'dump_group, fn);
+      write_file(fn, gen_doc_foot("materialliste"));
+      printf("Materialliste erfolgreich in Datei %s geschrieben\n", fn);
+    } else
+      printf("Konnte Liste nicht in Datei %s schreiben, Abbruch\n", fn);
+  }
+}
+
+/* GenHeaderFile
+ *
+ * Generiert Headerfile mit Definitionen der moeglichen Materialien und
+ * Gruppen
+ */
+varargs void GenHeaderFile(string fn)
+{
+  if (initialized) {
+    string txt;
+    if (!stringp(fn) || !sizeof(fn))
+      fn = HEADERFILE;
+    if (file_size(fn) >= 0) {
+      printf("Datei %s existiert bereits, loesche sie\n", fn);
+      rm(fn);
+    }
+    if (dump_material_h(fn))
+      printf("Headerdatei erfolgreich in %s geschrieben\n", fn);
+    else
+      printf("Konnte Headerdatei nicht in Datei %s schreiben, Abbruch\n", fn);
+  }
+}
+
+//==================== Pruef- und Hilfsfunktionen fuer Materialien
+private void updateGroupMembers(mapping groups, string mat_id, mapping mat) {
+  mixed *addgrps; // Array zum Ableiten von Gruppenzugehoerigkeiten
+  string *h;
+  int i, val;
+  mapping fractions; // Mapping mit Anteilen an Gruppen
+  fractions = mat[P_MG_FRACTIONS];
+  if (!mappingp(fractions))
+    fractions = ([]);
+  addgrps=({ // Reihenfolge wird rueckwaerts abgearbeitet
+    // Ableitungen sind z.T. abenteuerlich gewesen, mal ordentlich
+    // ausmisten. Die Zugehoerigkeit gehoert explizit in die
+    // Materialdefinition
+    // Gase sieht man normalerweise nicht:
+    ({"MATGROUP_INVIS", "MATGROUP_GAS"}),
+    // Mineralien sind auch Steine
+    ({"MATGROUP_STONE","MATGROUP_MINERAL"}),
+    // Edelmetalle sind Metalle:
+    ({"MATGROUP_METAL","MATGROUP_PRECIOUS_METAL"}),
+    // Lebewesen und deren Ueberreste, Paiere und Stoffe sind biologisch
+    ({"MATGROUP_BIO","MATGROUP_LIVING","MATGROUP_DEAD",
+      "MATGROUP_PAPER"}),
+    // Holz ist pflanzlich:
+    ({"MATGROUP_HERBAL", "MATGROUP_WOOD"}),
+    // Holz ist meistens tot:
+    ({"MATGROUP_DEAD","MATGROUP_WOOD"}),
+    // Holz, Papier und Stoffe brennen:
+    ({"MATGROUP_INFLAMMABLE","MATGROUP_WOOD","MATGROUP_PAPER"}),
+    // Laubhoelzer, Nadelhoelzer und Tropenhoelzer sind Holz
+    ({"MATGROUP_WOOD","MATGROUP_TROPICAL_WOOD","MATGROUP_DECIDUOUS_WOOD",
+      "MATGROUP_CONIFER_WOOD"}),
+    // Explosive Dinge sind immer entzuendlich:
+    ({"MATGROUP_INFLAMMABLE","MATGROUP_EXPLOSIVE"})
+  });
+  i=sizeof(addgrps);
+  while(i--) {
+    int j;
+    h=addgrps[i];
+    if (member(fractions,h[0])) // Existiert schon eigener Eintrag?
+      continue; // Automatische Eintragung unnoetig
+    val=0;
+    for (j=sizeof(h)-1;j>=1;j--)
+      val+=fractions[h[j]];
+    if (!val)
+      continue;
+    if (val>100)
+      val=100;
+    else if (val<-100)
+      val=-100;
+    fractions[h[0]]=val;
+  }
+  if (fractions["MATGROUP_LIVING"]) // Im Falle von lebendem Holz, tot loeschen
+    m_delete(fractions,"MATGROUP_DEAD");
+  // Alles, was nicht als gasfoerming, fluessig oder fest eingeordnet ist, ist
+  // sonstwas:
+  if (!member(fractions, "MATGROUP_FLUID")
+      && !member(fractions, "MATGROUP_GAS")
+      && !member(fractions, "MATGROUP_SOLID"))
+    fractions["MATGROUP_MISC"]=100;
+  // Materialien als Mitglieder in die Gruppen eintragen
+  addgrps=m_indices(fractions);
+  i=sizeof(addgrps);
+  while(i--) {
+    mixed ind;
+    ind=addgrps[i];
+    if (!fractions[ind] || !member(groups, ind)) {
+      // Unbekannte Gruppe und Gruppe ohne Anteil aus Mapping loeschen
+      m_delete(fractions,ind);
+      continue;
+    }
+    if (!pointerp(h=groups[ind][P_MEMBERS]))
+      h=({});
+    h+=({mat_id});
+    groups[ind][P_MEMBERS]=h;
+  }
+  mat[P_MG_FRACTIONS] = fractions;
+}
+
+//==================== Einlesen der Mappings aus Dateien
+
+#define MDESC_ERROR(x, y) LOG_ERROR(sprintf("Materialbeschreibung '%s': %s\n", x, y))
+#define MDESC_WARN(x, y) //LOG_WARN(sprintf("Materialbeschreibung '%s': %s\n", x, y))
+
+private mapping getDescParts(string s) {
+  string* lines;
+  string key, val;
+  int i, n;
+  mapping m;
+  m = ([]);
+  val = "";
+  lines = explode(s, "\n");
+  n = sizeof(lines);
+  if (n > 0) {
+    while (i < n) {
+      if (sscanf(lines[i], "%s:", key)) {
+        status multiline;
+        multiline = 0;
+        // Schluessel gefunden, Wert auslesen
+        while ( (++i < n) && sizeof(lines[i])) {
+          // Mehrzeilige Werte mit newline verketten
+          if (multiline) {
+            val += "\n";
+          }
+          val += lines[i];
+          multiline = 1;
+        }
+        m += ([key:val]);
+        val = "";
+      }
+      i++;
+    }
+  }
+  return m;
+}
+private varargs int isFile(string fn, string path) {
+  if (stringp(path) && sizeof(path))
+    fn = path+"/"+fn;
+  return (file_size(fn) >= 0);
+}
+
+private varargs mixed readGroupDesc(string id) {
+  mixed m;
+  string fn;
+  fn = MAT_DIR+"/groups/"+id;
+  if (file_size(fn) > 0) {
+    mapping parts;
+    string desc;
+    parts = getDescParts(read_file(fn));
+    m = ([P_NAME:parts["Name"],
+          P_MEMBERS:({})]);
+    if (member(parts,"Beschreibung"))
+      m += ([P_DESCRIPTION:parts["Beschreibung"]]);
+    if (parts["Gruppenid"] != id)
+      LOG_WARN(sprintf("Unstimmigkeit Gruppenid bei '%s'\n", id));
+  } else {
+    LOG_ERROR(sprintf("Kann Gruppenbeschreibung %s nicht laden\n", fn));
+  }
+  return m;
+}
+
+private mapping convMatId(string s) {
+  mapping m;
+  string* parts;
+  parts = explode(s, "\"");
+  if (sizeof(parts)) {
+    int ende;
+    ende = strstr(parts[0]," ")-1;
+    if (ende < 0)
+      ende = sizeof(parts[0]);
+    m = ([P_ID:parts[0][0..ende]]);
+    if (sizeof(parts) > 1)
+      m += ([P_DEFSTR:parts[1]]);
+  }
+  return m;
+}
+private string* convMatNames(string s) {
+  string* names;
+  names = filter(explode(s, "\""),
+                       lambda( ({'x}), ({#'>, ({#'sizeof, 'x}), 1}) ));
+  if (sizeof(names)<1)
+    names=0;
+  else {
+    if (sizeof(names)<2)
+      names+=({names[0]+"s"});
+    if (sizeof(names)<3)
+      names+=({names[0]});
+    if (sizeof(names)<4)
+      names+=({names[0]});
+  }
+  return names;
+}
+private int convMatGender(string s) {
+  int gender;
+  s = lowerstring(s);
+  // Ein Buchstabe reicht zur Bestimmung. Wenn nur weiblich|female
+  // bzw. maennlich|male verwendet wird. Dabei ist dann allerdings die
+  // Reihenfolge der Auswertung wichtig, damit das m bei MALE nicht mehr bei
+  // female passt.
+  if (sizeof(regexp( ({s}), "f|w"))) {
+    gender = FEMALE;
+  } else if (sizeof(regexp( ({s}), "m"))) {
+    gender = MALE;
+  } else {
+    gender = NEUTER;
+  }
+  return gender;
+}
+private string convMatDesc(string s) {
+  if (sizeof(regexp( ({s}), "- nicht vorhanden -"))) {
+    s = 0;
+  } else {
+    // Mehrzeilige Beschreibungen zu einer Zeile zusammenfassen
+    s = implode(explode(s, "\n"), " ");
+  }
+  return s;
+}
+private void addRecocLine(string s, mixed* r) {
+  // Die weitere Bewertung der Schwierigkeit kann erst vorgenommen werden,
+  // wenn alle Materialien bekannt sind und passiert spaeter. Zuerst werden
+  // nur die Elemente des Arrays konvertiert und eingetragen
+  string mat;
+  int val;
+  if (sscanf(s, "%s:%d", mat, val)) {
+    r += ({mat,val});
+  } else if (sscanf(s, "%d", val)) {
+    r += ({val});
+  } else {
+    r += ({s});
+  }
+}
+private mixed convMatRec(string s) {
+  mixed difficulties;
+  if (sizeof(regexp( ({s}), "- keine Einschraenkung -"))) {
+    difficulties = 0;
+  } else {
+    difficulties = ({});
+    // Jede Zeile enthaelt eine Bedingung
+    map(explode(s, "\n"), #'addRecocLine, &difficulties);
+  }
+  return difficulties;
+}
+private void addGroupLine(string s, mapping g) {
+  // Die weitere Bewertung der Zugehoerigkeit passiert spaeter.
+  string grp;
+  int val;
+  if (sscanf(s, "%s:%d", grp, val)) {
+    g += ([grp:val]);
+  } else {
+    g += ([grp:100]);
+  }
+}
+private mapping convMatGroups(string s) {
+  mapping groups;
+  if (!sizeof(regexp( ({s}), "- keine -"))) {
+    groups = ([]);
+    // Jede Zeile enthaelt eine Bedingung
+    map(explode(s, "\n"), #'addGroupLine, groups);
+  }
+  return groups;
+}
+private mapping convMaterialDesc(string id, mapping desc) {
+  /* Struktur Materialmapping:
+     P_GENDER,
+     P_NAME:({name_nom, name_gen, name_dativ, name_akkusativ}),
+     (P_RECOC:({mat1,faehigkeit1,mat2,faehigkeit2,...}),)
+     (P_DEFSTR: bei bedarf),
+     P_DESCRIPTION,
+     (grupp1:anteil1,
+     gruppe2:anteil2,
+     ...)
+  */
+  mapping m;
+  mixed val, val2;
+  m = ([]);
+  // Der string fuer das #define zuerst:
+  val = convMatId(desc["Materialid"]);
+  if (mappingp(val)) {
+    if (val[P_ID] != id)
+      LOG_WARN(sprintf("Unstimmigkeit Materialid bei '%s':%O\n", id, val[P_ID]));
+    if (member(val, P_DEFSTR)) {
+      m += ([P_DEFSTR:val[P_DEFSTR]]);
+    } else {
+      // Wenn kein String fuers #define angegeben wurde, dann direkt ID verwenden
+      //m += ([P_DEFSTR:lowerstring(id)[4..]]);
+    }
+  }
+  // Die Namen
+  if (val = convMatNames(desc["Name"])) {
+    m += ([P_NAME:val]);
+  } else {
+    MDESC_WARN(id, "keine Namen");
+    m += ([P_NAME:({"", "", "", ""})]);
+  }
+  // Das Geschlecht, standard ist NEUTER
+  m += ([P_GENDER:convMatGender(desc["Geschlecht"]) ]);
+  // Die Beschreibung
+  val = convMatDesc(desc["Beschreibung"]);
+  if (sizeof(val)) {
+    m += ([P_DESCRIPTION:val]);
+  } else {
+    MDESC_WARN(id, "keine Beschreibung");
+  }
+  // Die Erkennbarkeit
+  val = convMatRec(desc["Erkennbarkeit"]);
+  if (sizeof(val)) {
+    m += ([P_RECOC:val]);
+  }
+  // und zum Schluss die Gruppenzugehoerigkeit
+  val = convMatGroups(desc["Gruppenzugehoerigkeit"]);
+  if (mappingp(val) && sizeof(val)) {
+    m += ([P_MG_FRACTIONS:val]);
+  }
+  return m;
+}
+private varargs mixed readMaterialDesc(string id) {
+  mixed m;
+  string fn;
+  fn = MAT_DIR+"/materials/"+id;
+  if (file_size(fn) > 0) {
+    mapping parts;
+    string desc;
+    parts = getDescParts(read_file(fn));
+    m = convMaterialDesc(id, parts);
+  } else {
+    LOG_ERROR(sprintf("MDB:Kann Materialbeschreibung %s nicht laden\n", fn));
+  }
+  return m;
+}
+
+public int GetUpdateTicks() {
+  return updateTicks;
+}
+
+private void scanFinished() {
+  isScanning = 0;
+  initialized = 1;
+  // Mappings umkopieren
+  materials = new_materials;
+  material_groups = new_material_groups;
+  // Letzter Schritt: Mapping mit alten Schluesseln anlegen
+  old_mat_keys = mkmapping(map(m_indices(materials), #'matKey2Defstr, materials),
+                           m_indices(materials));
+  // Generieren der Doku und des Materialheaders
+  GenHeaderFile();
+  GenMatList();
+  GenMatGroupList();
+  // Savefile schreiben
+  save_object(SAVEFILE);
+}
+
+public int IsScanning() {
+  return isScanning;
+}
+
+private varargs void doScanMaterials(string* mats, int i, int step) {
+  int ticks, start;
+  string matid;
+  start = get_eval_cost();
+  if (step < 2) {
+    while ( (i < sizeof(mats)) &&
+            ((start - get_eval_cost()) < query_limits()[LIMIT_EVAL]/3 ) ) {
+      matid = mats[i];
+      switch (step) {
+      case 0:
+        // Erster Schritt: Einlesen der Dateien
+        new_materials[matid] = readMaterialDesc(matid);
+        break;
+      case 1:
+        // Zweiter Schritt: Bearbeiten der Erkennung und Gruppenzugehoerigkeit
+        updateGroupMembers(new_material_groups, matid, new_materials[matid]);
+        break;
+      default:
+        break;
+      }
+      i++;
+    }
+  }
+  if (i < sizeof(mats)) {
+    catch(raise_error(sprintf("MaterialDB: Initialisierung noch nicht beendet,"
+                              " fehlende Materialbeschreibungen moeglich"
+                              " (Phase %d:%d/%d)\n",
+                              step, i, sizeof(mats)));publish);
+    call_out(#'doScanMaterials, 2, mats, i, step);
+  } else {
+    // Zweite Stufe ausloesen oder beenden
+    if (step < 1) {
+      if ((start - get_eval_cost()) < query_limits()[LIMIT_EVAL]/2 )
+          doScanMaterials(mats, 0, step+1);
+      else
+          call_out(#'doScanMaterials, 2, mats, 0, step+1);
+    }
+    else
+      scanFinished();
+  }
+  updateTicks += start - get_eval_cost();
+}
+
+private mapping ScanGroups() {
+  mapping groups;
+  string* grpfiles;
+  groups = ([]);
+  grpfiles = filter(get_dir(MAT_DIR+"/groups/MATGROUP_*"),
+                          #'isFile, MAT_DIR+"/groups");
+  groups = mkmapping(grpfiles, map(grpfiles, #'readGroupDesc, 1));
+  return groups;
+}
+
+private void ScanMaterials() {
+  string *matfiles;
+  matfiles = filter(get_dir(MAT_DIR+"/materials/MAT_*"),
+                          #'isFile, MAT_DIR+"/materials");
+  doScanMaterials(matfiles);
+}
+
+void Update() {
+  int start;
+  updateTicks = 0;
+  start = get_eval_cost();
+  if (!isScanning) {
+    if (sizeof(get_dir(MAT_DIR))) {
+      isScanning = 1;
+      new_material_groups = ScanGroups();
+      new_materials = ([]);
+      updateTicks = start - get_eval_cost();
+      ScanMaterials();
+    } else {
+      LOG_ERROR("Kann Materialverzeichnis nicht finden, keine Materialien angelegt!\n");
+    }
+  }
+}