/* $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 <stdio.h>
#include <ctype.h>

#include "db.h"
#include "config.h"
#include "interface.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);

const 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 < 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 */
}