/* $Header: db.c,v 2.0 90/05/05 12:45:30 lachesis Exp $
 * $Log:    db.c,v $
 * Revision 2.0  90/05/05  12:45:30  lachesis
 * Incorporated ABODE and HAVEN flags (remembering to convert FireFoot's
 * usage of those flags to ours).
 * Added Examine of objects that don't belong to you, added GOD_PRIV.
 *
 * Revision 1.3  90/04/21  17:20:40  lachesis
 * Added property lists.
 *
 * Revision 1.2  90/04/20  14:06:02  lachesis
 * Added @odrop && @drop.
 *
 * Revision 1.1  90/04/14  14:56:41  lachesis
 * Initial revision
 *
 */
#include "copyright.h"

#include "os.h"

#include "db.h"
#include "config.h"
#include "interface.h"
#include "externs.h"

struct object *db = 0;
dbref db_top = 0;
#ifdef RECYCLE
dbref recyclable = NOTHING;
#endif

#ifdef TEST_MALLOC
int malloc_count = 0;
#endif /* TEST_MALLOC */

#ifndef DB_INITIAL_SIZE
#define DB_INITIAL_SIZE 10000
#endif /* DB_INITIAL_SIZE */

#ifdef DB_DOUBLING

dbref db_size = DB_INITIAL_SIZE;
#endif /* DB_DOUBLING */

struct plist *new_prop (void);
void free_prop (struct plist *p);
int number (char *s);
void putproperties (FILE * f, struct plist *p);
void putprop (FILE * f, struct plist *p);
struct plist *getproperties (FILE * f);

char *alloc_string (const char *string)
{
  char *s;

  /* NULL, "" -> NULL */
  if (string == 0 || *string == '\0')
    return 0;

  if ((s = (char *) malloc (strlen (string) + 1)) == 0) {
    abort ();
  }
  (void) strcpy (s, string);
  return s;
}

#ifdef DB_DOUBLING

static void db_grow (dbref newtop)
{
  struct object *newdb;

  if (newtop > db_top) {
    db_top = newtop;
    if (!db) {
      /* make the initial one */
      db_size = DB_INITIAL_SIZE;
      if ((db = (struct object *)
          malloc (db_size * sizeof (struct object))) == 0) {
        abort ();
      }
    }

    /* maybe grow it */
    if (db_top > db_size) {
      /* make sure it's big enough */
      while (db_top > db_size)
        db_size *= 2;
      if ((newdb = (struct object *)
          realloc ((void *) db, db_size * sizeof (struct object))) == 0) {
        abort ();
      }
      db = newdb;
    }
  }
}

#else /* DB_DOUBLING */

static void db_grow (dbref newtop)
{
  struct object *newdb;

  if (newtop > db_top) {
    db_top = newtop;
    if (db) {
      if ((newdb = (struct object *)
          realloc ((void *) db, db_top * sizeof (struct object))) == 0) {
        abort ();
      }
      db = newdb;
    } else {
      /* make the initial one */
      if ((db = (struct object *)
          malloc (DB_INITIAL_SIZE * sizeof (struct object))) == 0) {
        abort ();
      }
    }
  }
}
#endif /* DB_DOUBLING */

dbref new_object (void)
{
  dbref newobj;
  struct object *o;

#ifdef RECYCLE
  if (recyclable != NOTHING) {
    newobj = recyclable;
    recyclable = db[newobj].next;
  } else
#endif
  {
    newobj = db_top;
    db_grow (db_top + 1);
  }

  /* clear it out */
  o = db + newobj;
  o->name = 0;
  o->description = 0;
  o->location = NOTHING;
  o->contents = NOTHING;
  o->next = NOTHING;
  o->key = TRUE_BOOLEXP;
  o->fail_message = 0;
  o->succ_message = 0;
  o->drop_message = 0;
  o->ofail = 0;
  o->osuccess = 0;
  o->odrop = 0;
  o->properties = 0;
  /* flags you must initialize yourself */
  /* type-specific fields you must also initialize */

  return newobj;
}

#define DB_MSGLEN 512

void putref (FILE * f, dbref ref)
{
  fprintf (f, "%d\n", ref);
}

static void putstring (FILE * f, const char *s)
{
  if (s) {
    fputs (s, f);
  }
  putc ('\n', f);
}

static void putbool_subexp (FILE * f, struct boolexp *b)
{
  switch (b->type) {
  case BOOLEXP_AND:
    putc ('(', f);
    putbool_subexp (f, b->sub1);
    putc (AND_TOKEN, f);
    putbool_subexp (f, b->sub2);
    putc (')', f);
    break;
  case BOOLEXP_OR:
    putc ('(', f);
    putbool_subexp (f, b->sub1);
    putc (OR_TOKEN, f);
    putbool_subexp (f, b->sub2);
    putc (')', f);
    break;
  case BOOLEXP_NOT:
    putc ('(', f);
    putc (NOT_TOKEN, f);
    putbool_subexp (f, b->sub1);
    putc (')', f);
    break;
  case BOOLEXP_CONST:
    fprintf (f, "%d", b->thing);
    break;
  case BOOLEXP_PROP:
    putc ('[', f);
    putprop (f, b->prop_check);
    putc (']', f);
    break;
  default:
    break;
  }
}

void putprop (FILE * f, struct plist *prop)
{
  fputs (prop->type, f);
  putc (PROP_DELIMITER, f);
  if (prop->class)
    fputs (prop->class, f);
#ifdef NUMBER_PROPS
  else
    fprintf (f, "%d", prop->value);
#endif
}

void putboolexp (FILE * f, struct boolexp *b)
{
  if (b != TRUE_BOOLEXP) {
    putbool_subexp (f, b);
  }
  putc ('\n', f);
}

void putproperties (FILE * f, struct plist *p)
{
  while (p) {
    putprop (f, p);
    putc ('\n', f);
    p = p->next;
  }
}

int db_write_object (FILE * f, dbref i)
{
  struct object *o;
  int j;

  o = db + i;
  putstring (f, o->name);
  putstring (f, o->description);
  putref (f, o->location);
  putref (f, o->contents);
  putref (f, o->next);
  putboolexp (f, o->key);
  putstring (f, o->fail_message);
  putstring (f, o->succ_message);
  putstring (f, o->drop_message);
  putstring (f, o->ofail);
  putstring (f, o->osuccess);
  putstring (f, o->odrop);
  putref (f, o->flags);
/*    putstring(f, "***Property list start ***"); */
  putproperties (f, o->properties);
/*    putstring(f, "***Property list end ***"); */
  putstring (f, ":");

  switch (Typeof (i)) {
  case TYPE_THING:
    putref (f, o->sp.thing.home);
    putref (f, o->sp.thing.actions);
    putref (f, o->sp.thing.owner);
    putref (f, o->sp.thing.value);
    break;

  case TYPE_ROOM:
    putref (f, o->sp.room.dropto);
    putref (f, o->sp.room.exits);
    putref (f, o->sp.room.owner);
    break;

  case TYPE_EXIT:
    putref (f, o->sp.exit.ndest);
    for (j = 0; j < o->sp.exit.ndest; j++) {
      putref (f, (o->sp.exit.dest)[j]);
    }
    putref (f, o->sp.exit.owner);
    break;

  case TYPE_PLAYER:
    putref (f, o->sp.player.home);
    putref (f, o->sp.player.actions);
    putref (f, o->sp.player.pennies);
    putstring (f, o->sp.player.password);
    break;
  }

  return 0;
}

dbref db_write (FILE * f)
{
  dbref i;

  fputs ("***Christina Applegate TinyMUCK DUMP Format***\n", f);
  for (i = 0; i < db_top; i++) {
    fprintf (f, "#%d\n", i);
    db_write_object (f, i);
  }
  fputs ("***END OF DUMP***\n", f);
  fflush (f);
  return (db_top);
}

dbref parse_dbref (const char *s)
{
  const char *p;
  long x;

  x = atol (s);
  if (x > 0) {
    return x;
  } else if (x == 0) {
    /* check for 0 */
    for (p = s; *p; p++) {
      if (*p == '0')
        return 0;
      if (!isspace (*p))
        break;
    }
  }

  /* else x < 0 or s != 0 */
  return NOTHING;
}

dbref getref (FILE * f)
{
  static char buf[DB_MSGLEN];

  fgets (buf, sizeof (buf), f);
  return (atol (buf));
}

static const char *getstring_noalloc (FILE * f)
{
  static char buf[DB_MSGLEN];
  char *p;

  fgets (buf, sizeof (buf), f);
  for (p = buf; *p; p++) {
    if (*p == '\n') {
      *p = '\0';
      break;
    }
  }

  return buf;
}

#define getstring(x) alloc_string(getstring_noalloc(x))

#ifdef COMPRESS
extern const char *compress (const char *);
#define getstring_compress(x) alloc_string(compress(getstring_noalloc(x)));
#else
#define getstring_compress(x) getstring(x)
#endif /* COMPRESS */

static struct boolexp *negate_boolexp (struct boolexp *b)
{
  struct boolexp *n;

  /* Obscure fact: !NOTHING == NOTHING in old-format databases! */
  if (b == TRUE_BOOLEXP)
    return TRUE_BOOLEXP;

  n = (struct boolexp *) malloc (sizeof (struct boolexp));
  n->type = BOOLEXP_NOT;
  n->sub1 = b;

  return n;
}

/* returns true for numbers of form [ + | - ] <series of digits> */
int number (char *s)
{
  while (isspace (*s))
    s++;
  if (*s == '+' || *s == '-')
    s++;
  for (; s; s++)
    if (*s < '0' || *s > '9')
      return 0;
  return 1;
}

static struct boolexp *getboolexp1 (FILE * f)
{
  struct boolexp *b;
  struct plist *p;
  char buf[BUFFER_LEN];         /* holds string for reading in property */
  int c;
  int i;                        /* index into buf */

  c = getc (f);
  switch (c) {
  case '\n':
    ungetc (c, f);
    return TRUE_BOOLEXP;
    /* break; */
  case EOF:
    abort ();                   /* unexpected EOF in boolexp */
    break;
  case '(':
    b = (struct boolexp *) malloc (sizeof (struct boolexp));
    if ((c = getc (f)) == '!') {
      b->type = BOOLEXP_NOT;
      b->sub1 = getboolexp1 (f);
      if (getc (f) != ')')
        goto error;
      return b;
    } else {
      ungetc (c, f);
      b->sub1 = getboolexp1 (f);
      switch (c = getc (f)) {
      case AND_TOKEN:
        b->type = BOOLEXP_AND;
        break;
      case OR_TOKEN:
        b->type = BOOLEXP_OR;
        break;
      default:
        goto error;
        /* break */
      }
      b->sub2 = getboolexp1 (f);
      if (getc (f) != ')')
        goto error;
      return b;
    }
    /* break; */
  case '-':
    /* obsolete NOTHING key */
    /* eat it */
    while ((c = getc (f)) != '\n')
      if (c == EOF)
        abort ();               /* unexp EOF */
    ungetc (c, f);
    return TRUE_BOOLEXP;
    /* break */
  case '[':
    /* property type */
    b = (struct boolexp *) malloc (sizeof (struct boolexp));
    b->type = BOOLEXP_PROP;
    b->sub1 = b->sub2 = 0;
    p = b->prop_check = new_prop ();
    i = 0;
    while ((c = getc (f)) != PROP_DELIMITER && i < BUFFER_LEN) {
      buf[i] = c;
      i++;
    }
    if (i >= BUFFER_LEN && c != PROP_DELIMITER)
      goto error;
    buf[i] = '\0';
    p->type = alloc_string (buf);
    i = 0;
    while ((c = getc (f)) != ']') {
      buf[i] = c;
      i++;
    }
    buf[i] = '\0';
    if (i >= BUFFER_LEN && c != ']')
      goto error;
#ifdef NUMBER_PROPS
    if (number (buf))
      p->value = atol (buf);
    else
#endif
      p->class = alloc_string (buf);
    return b;
  default:
    /* better be a dbref */
    ungetc (c, f);
    b = (struct boolexp *) malloc (sizeof (struct boolexp));
    b->type = BOOLEXP_CONST;
    b->thing = 0;

    /* NOTE possibly non-portable code */
    /* Will need to be changed if putref/getref change */
    while (isdigit (c = getc (f))) {
      b->thing = b->thing * 10 + c - '0';
    }
    ungetc (c, f);
    return b;
  }

error:
  abort ();                     /* bomb out */
  return TRUE_BOOLEXP;
}

struct boolexp *getboolexp (FILE * f)
{
  struct boolexp *b;

  b = getboolexp1 (f);
  if (getc (f) != '\n')
    abort ();                   /* parse error, we lose */
  return b;
}

void free_boolexp (struct boolexp *b)
{
  if (b != TRUE_BOOLEXP) {
    switch (b->type) {
    case BOOLEXP_AND:
    case BOOLEXP_OR:
      free_boolexp (b->sub1);
      free_boolexp (b->sub2);
      free ((void *) b);
      break;
    case BOOLEXP_NOT:
      free_boolexp (b->sub1);
      free ((void *) b);
      break;
    case BOOLEXP_CONST:
      free ((void *) b);
      break;
    case BOOLEXP_PROP:
      free_prop (b->prop_check);
      free ((void *) b);
      break;
    }
  }
}

struct plist *getproperties (FILE * f)
{
  char buf[BUFFER_LEN], s[BUFFER_LEN];
  int i, j;
  struct plist *head, *curr;

  head = curr = 0;

  /* initialize first line stuff */
  if (fgets (buf, sizeof (buf), f) == NULL)
    return NULL;
  while (strcmp (buf, "***Property list end ***\n") && strcmp (buf, ":\n")) {
    if (!head)
      head = curr = new_prop ();
    else {
      curr->next = new_prop ();
      curr = curr->next;
    }

    for (i = 0; buf[i] != PROP_DELIMITER; i++)
      s[i] = buf[i];
    s[i] = '\0';
    curr->type = alloc_string (s);
    for (i++, j = 0; i < (int) strlen (buf) - 1; i++, j++)    /* don't include \n */
      s[j] = buf[i];
    s[j] = '\0';
#ifdef NUMBER_PROPS
    if (number (s))
      curr->value = atol (s);
    else
#endif
      curr->class = alloc_string (s);

    if (fgets (buf, sizeof (buf), f) == NULL)
      return head;
    if (curr->next)
      curr = curr->next;
  }
  return head;
}

void free_prop (struct plist *p)
{
  if (p->class)
    free ((void *) p->class);
  if (p->type)
    free ((void *) p->type);
  free ((void *) p);
}

void db_free (void)
{
  dbref i;
  struct object *o;

  if (db) {
    for (i = 0; i < db_top; i++) {
      o = &db[i];
      if (o->name)
        free ((void *) o->name);
      if (o->description)
        free ((void *) o->description);
      if (o->succ_message)
        free ((void *) o->succ_message);
      if (o->fail_message)
        free ((void *) o->fail_message);
      if (o->drop_message)
        free ((void *) o->drop_message);
      if (o->ofail)
        free ((void *) o->ofail);
      if (o->osuccess)
        free ((void *) o->osuccess);
      if (o->odrop)
        free ((void *) o->odrop);
      if (o->key)
        free_boolexp (o->key);
      if (o->properties) {
        struct plist *p, *next;

        for (p = o->properties; p; p = next) {
          next = p->next;
          free_prop (p);
        }
      }
      if (Typeof (i) == TYPE_EXIT && o->sp.exit.dest) {
        free ((void *) o->sp.exit.dest);
      } else if (Typeof (i) == TYPE_PLAYER && o->sp.player.password) {
        free ((void *) o->sp.player.password);
      }

    }
    free ((void *) db);
    db = 0;
    db_top = 0;
  }
#ifdef RECYCLE
  recyclable = NOTHING;
#endif
}

struct plist *new_prop (void)
{
  struct plist *p;

  p = (struct plist *) malloc (sizeof (struct plist));
  p->type = 0;
  p->class = 0;
#ifdef NUMBER_PROPS
  p->value = 0;
#endif
  p->next = 0;
  return p;
}

void db_read_object_old (FILE * f, struct object *o)
{
  dbref exits, owner;
  int pennies;
  const char *password;

  o->name = getstring (f);
  o->description = getstring_compress (f);
  o->location = getref (f);
  o->contents = getref (f);
  exits = getref (f);
  o->next = getref (f);
  o->key = getboolexp (f);
  o->fail_message = getstring_compress (f);
  o->succ_message = getstring_compress (f);
  o->drop_message = NULL;
  o->ofail = getstring_compress (f);
  o->osuccess = getstring_compress (f);
  o->odrop = NULL;
  owner = getref (f);
  pennies = getref (f);
  o->flags = getref (f);
  /* flags have to be checked for conflict --- if they happen to coincide
     with chown_ok flags and jump_ok flags, we bump them up to
     the corresponding HAVEN and ABODE flags                           */
  if (o->flags & CHOWN_OK) {
    o->flags &= ~CHOWN_OK;
#ifdef HAVEN
    o->flags |= HAVEN;
#endif /* HAVEN */
  }
  if (o->flags & JUMP_OK) {
    o->flags &= ~JUMP_OK;
#ifdef ABODE
    o->flags |= ABODE;
#endif /* ABODE */
  }
  password = getstring (f);
#ifdef GENDER
  if ((o->flags & TYPE_MASK) == TYPE_PLAYER) {
    o->properties = new_prop ();
    o->properties->type = alloc_string ("sex");
    /* convert GENDER flag to property */
    switch ((o->flags & GENDER_MASK) >> GENDER_SHIFT) {
    case GENDER_NEUTER:
      o->properties->class = alloc_string ("neuter");
      break;
    case GENDER_FEMALE:
      o->properties->class = alloc_string ("female");
      break;
    case GENDER_MALE:
      o->properties->class = alloc_string ("male");
      break;
    default:
      o->properties->class = alloc_string ("unassigned");
      break;
    }
  }
#endif /* GENDER */
  /* For downward compatibility with databases using the */
  /* obsolete ANTILOCK flag. */
  if (o->flags & ANTILOCK) {
    o->key = negate_boolexp (o->key);
    o->flags &= ~ANTILOCK;
  }
  switch (o->flags & TYPE_MASK) {
  case TYPE_THING:
    o->sp.thing.home = exits;
    o->sp.thing.actions = NOTHING;
    o->sp.thing.owner = owner;
    o->sp.thing.value = pennies;
    break;
  case TYPE_ROOM:
    o->sp.room.dropto = o->location;
    o->location = NOTHING;
    o->sp.room.exits = exits;
    o->sp.room.owner = owner;
    break;
  case TYPE_EXIT:
    if (o->location == NOTHING) {
      o->sp.exit.ndest = 0;
      o->sp.exit.dest = NULL;
    } else {
      o->sp.exit.ndest = 1;
      o->sp.exit.dest = (dbref *) malloc (sizeof (dbref));
      (o->sp.exit.dest)[0] = o->location;
    }
    o->location = NOTHING;
    o->sp.exit.owner = owner;
    break;
  case TYPE_PLAYER:
    o->sp.player.home = exits;
    o->sp.player.actions = NOTHING;
    o->sp.player.pennies = pennies;
    o->sp.player.password = password;
    break;
#ifdef RECYCLE
  case TYPE_GARBAGE:
    o->next = recyclable;
    recyclable = o - db;
    free ((void *) o->name);
    free ((void *) o->description);
    o->name = "<garbage>";
    o->description = "<recyclable>";
    break;
#endif
  }
}

void db_read_object_new (FILE * f, struct object *o)
{
  int j;

  o->name = getstring (f);
  o->description = getstring_compress (f);
  o->location = getref (f);
  o->contents = getref (f);
  /* o->exits = getref(f); */
  o->next = getref (f);
  o->key = getboolexp (f);
  o->fail_message = getstring_compress (f);
  o->succ_message = getstring_compress (f);
  o->drop_message = NULL;
  o->ofail = getstring_compress (f);
  o->osuccess = getstring_compress (f);
  o->odrop = NULL;
  /* o->owner = getref(f); */
  /* o->pennies = getref(f); */
  o->flags = getref (f);
  /* flags have to be checked for conflict --- if they happen to coincide
     with chown_ok flags and jump_ok flags, we bump them up to
     the corresponding HAVEN and ABODE flags                           */
  if (o->flags & CHOWN_OK) {
    o->flags &= ~CHOWN_OK;
#ifdef HAVEN
    o->flags |= HAVEN;
#endif /* HAVEN */
  }
  if (o->flags & JUMP_OK) {
    o->flags &= ~JUMP_OK;
#ifdef ABODE
    o->flags |= ABODE;
#endif /* ABODE */
  }
#ifdef GENDER
  if ((o->flags & TYPE_MASK) == TYPE_PLAYER) {
    o->properties = new_prop ();
    o->properties->type = alloc_string ("sex");
    /* convert GENDER flag to property */
    switch ((o->flags & GENDER_MASK) >> GENDER_SHIFT) {
    case GENDER_NEUTER:
      o->properties->class = alloc_string ("neuter");
      break;
    case GENDER_FEMALE:
      o->properties->class = alloc_string ("female");
      break;
    case GENDER_MALE:
      o->properties->class = alloc_string ("male");
      break;
    default:
      o->properties->class = alloc_string ("unassigned");
      break;
    }
  }
#endif /* GENDER */

  /* o->password = getstring(f); */
  /* For downward compatibility with databases using the */
  /* obsolete ANTILOCK flag. */
  if (o->flags & ANTILOCK) {
    o->key = negate_boolexp (o->key);
    o->flags &= ~ANTILOCK;
  }
  switch (o->flags & TYPE_MASK) {
  case TYPE_THING:
    o->sp.thing.home = getref (f);
    o->sp.thing.actions = getref (f);
    o->sp.thing.owner = getref (f);
    o->sp.thing.value = getref (f);
    break;
  case TYPE_ROOM:
    o->sp.room.dropto = getref (f);
    o->sp.room.exits = getref (f);
    o->sp.room.owner = getref (f);
    break;
  case TYPE_EXIT:
    o->sp.exit.ndest = getref (f);
    o->sp.exit.dest = (dbref *) malloc (sizeof (dbref)
      * o->sp.exit.ndest);
    for (j = 0; j < o->sp.exit.ndest; j++) {
      (o->sp.exit.dest)[j] = getref (f);
    }
    o->sp.exit.owner = getref (f);
    break;
  case TYPE_PLAYER:
    o->sp.player.home = getref (f);
    o->sp.player.actions = getref (f);
    o->sp.player.pennies = getref (f);
    o->sp.player.password = getstring (f);
    break;
#ifdef RECYCLE
  case TYPE_GARBAGE:
    o->next = recyclable;
    recyclable = o - db;
    free ((void *) o->name);
    free ((void *) o->description);
    o->name = "<garbage>";
    o->description = "<recyclable>";
    break;
#endif
  }
}

void db_read_object_lachesis (FILE * f, struct object *o, int old)
{
  dbref j;
  struct plist *newp;
  char buf[BUFFER_LEN];

  o->name = getstring (f);
  o->description = getstring_compress (f);
  o->location = getref (f);
  o->contents = getref (f);
  /* o->exits = getref(f); */
  o->next = getref (f);
  o->key = getboolexp (f);
  o->fail_message = getstring_compress (f);
  o->succ_message = getstring_compress (f);
  o->drop_message = getstring_compress (f);
  o->ofail = getstring_compress (f);
  o->osuccess = getstring_compress (f);
  o->odrop = getstring_compress (f);
  /* o->owner = getref(f); */
  /* o->pennies = getref(f); */
  o->flags = getref (f);
  if (old)
    (void) fgets (buf, sizeof (buf), f);
  o->properties = getproperties (f);

#if 0
#ifdef GENDER
  /* set gender stuff */
  if (old && ((o->flags & TYPE_MASK) == TYPE_PLAYER)) {
    newp = new_prop ();
    if (!o->properties)
      o->properties = newp;
    else {
      newp->next = o->properties;
      o->properties = newp;
    }
    newp->type = alloc_string ("sex");
    /* convert GENDER flag to property */
    switch ((o->flags & GENDER_MASK) >> GENDER_SHIFT) {
    case GENDER_NEUTER:
      newp->class = alloc_string ("neuter");
      break;
    case GENDER_FEMALE:
      newp->class = alloc_string ("female");
      break;
    case GENDER_MALE:
      newp->class = alloc_string ("male");
      break;
    default:
      newp->class = alloc_string ("unassigned");
      break;
    }
  }
#endif /* GENDER */
#endif

  /* o->password = getstring(f); */
  /* For downward compatibility with databases using the */
  /* obsolete ANTILOCK flag. */
  if (o->flags & ANTILOCK) {
    o->key = negate_boolexp (o->key);
    o->flags &= ~ANTILOCK;
  }
  switch (o->flags & TYPE_MASK) {
  case TYPE_THING:
    o->sp.thing.home = getref (f);
    o->sp.thing.actions = getref (f);
    o->sp.thing.owner = getref (f);
    o->sp.thing.value = getref (f);
    break;
  case TYPE_ROOM:
    o->sp.room.dropto = getref (f);
    o->sp.room.exits = getref (f);
    o->sp.room.owner = getref (f);
    break;
  case TYPE_EXIT:
    o->sp.exit.ndest = getref (f);
    o->sp.exit.dest = (dbref *) malloc (sizeof (dbref)
      * o->sp.exit.ndest);
    for (j = 0; j < o->sp.exit.ndest; j++) {
      (o->sp.exit.dest)[j] = getref (f);
    }
    o->sp.exit.owner = getref (f);
    break;
  case TYPE_PLAYER:
    o->sp.player.home = getref (f);
    o->sp.player.actions = getref (f);
    o->sp.player.pennies = getref (f);
    o->sp.player.password = getstring (f);
    break;
#ifdef RECYCLE
  case TYPE_GARBAGE:
    o->next = recyclable;
    recyclable = o - db;
    free ((void *) o->name);
    free ((void *) o->description);
    o->name = "<garbage>";
    o->description = "<recyclable>";
    break;
#endif
  }
}

dbref db_read (FILE * f)
{
  dbref i, j, next;
  struct object *o;
  const char *special;
  int newformat;
  char c;

  newformat = 0;

  if ((c = getc (f)) == '*') {
    special = getstring (f);
    if (!strcmp (special, "**TinyMUCK DUMP Format***")) {
      newformat = 1;
    } else if (!strcmp (special, "**Lachesis TinyMUCK DUMP Format***")) {
      newformat = 2;
    } else if (!strcmp (special,
        "**Christina Applegate TinyMUCK DUMP Format***")) {
      newformat = 3;
    }
    free ((void *) special);
    c = getc (f);               /* get next char */
  }

  db_free ();
  clear_players ();
  for (i = 0;; i++) {
    switch (c) {
    case '#':
      /* another entry, yawn */
      if (i != getref (f)) {
        /* we blew it */
        return -1;
      }
      /* make space */
      db_grow (i + 1);

      /* read it in */
      o = db + i;
      switch (newformat) {
      case 0:
        db_read_object_old (f, o);
        break;
      case 1:
        db_read_object_new (f, o);
        break;
      case 2:
        db_read_object_lachesis (f, o, 1);
        break;
      case 3:
        db_read_object_lachesis (f, o, 0);
        break;
      }
      if (Typeof (i) == TYPE_PLAYER)
        add_player (i);
      break;
    case '*':
      special = getstring_noalloc (f);
      if (strcmp (special, "**END OF DUMP***")) {
        return -1;
      } else {
        if (newformat < 2) {
          for (i = 0; i < db_top; i++) {
            if (Typeof (i) == TYPE_ROOM) {
              DOLIST (j, db[i].sp.room.exits) {
                db[j].location = i;
              }
            } else if (Typeof (i) == TYPE_PLAYER) {
              for (j = db[i].contents; j != NOTHING; j = next) {
                next = db[j].next;
                if (Typeof (j) == TYPE_EXIT) {
                  fprintf (stderr, "Exit %d found on %s(%d)\n",
                    j, db[i].name, i);
                  db[i].contents = remove_first (db[i].contents, j);
                  db[j].location = i;
                  PUSH (j, db[i].sp.player.actions);
                  if (db[j].sp.exit.ndest) {
                    free ((void *) db[j].sp.exit.dest);
                    db[j].sp.exit.dest = NULL;
                  }
                  db[j].sp.exit.ndest = 0;
                }
              }
            }
          }
        }
        return db_top;
      }
    default:
      return -1;
      /* break; */
    }
    c = getc (f);
  }                             /* for */
}                               /* db_read */

void db_free_object (dbref i)
{
  struct object *o;

  o = &db[i];
  if (o->name)
    free ((void *) o->name);
  if (o->description)
    free ((void *) o->description);
  if (o->succ_message)
    free ((void *) o->succ_message);
  if (o->fail_message)
    free ((void *) o->fail_message);
  if (o->drop_message)
    free ((void *) o->drop_message);
  if (o->ofail)
    free ((void *) o->ofail);
  if (o->osuccess)
    free ((void *) o->osuccess);
  if (o->odrop)
    free ((void *) o->odrop);
  if (o->key)
    free_boolexp (o->key);
  if (o->properties) {
    struct plist *p, *next;

    for (p = o->properties; p; p = next) {
      next = p->next;
      free_prop (p);
    }
  }
  if (Typeof (i) == TYPE_EXIT && o->sp.exit.dest) {
    free ((void *) o->sp.exit.dest);
  } else if (Typeof (i) == TYPE_PLAYER && o->sp.player.password) {
    free ((void *) o->sp.player.password);
  }
}

void db_clear_object (dbref i)
{
  struct object *o = db + i;

  o->name = 0;
  o->description = 0;
  o->location = NOTHING;
  o->contents = NOTHING;
  o->next = NOTHING;
  o->key = TRUE_BOOLEXP;
  o->fail_message = 0;
  o->succ_message = 0;
  o->drop_message = 0;
  o->ofail = 0;
  o->osuccess = 0;
  o->odrop = 0;
  o->properties = 0;
  /* flags you must initialize yourself */
  /* type-specific fields you must also initialize */
}