/* $Header: /belch_a/users/rearl/tinymuck/src/RCS/db.c,v 1.11 90/09/28 12:19:46 rearl Exp $ */ /* * $Log: db.c,v $ * Revision 1.11 90/09/28 12:19:46 rearl * Took out alloc_string(), moved it to stringutil.c * * Revision 1.10 90/09/18 07:55:13 rearl * Added hash tables, changed program `locked' field to INTERNAL flag. * * Revision 1.9 90/09/16 04:41:54 rearl * Preparation code added for disk-based MUCK. * * Revision 1.8 90/09/10 02:19:46 rearl * Introduced string compression of properties, for the * COMPRESS compiler option. * * Revision 1.7 90/09/01 05:57:03 rearl * Took out TEST_MALLOC references. * * Revision 1.6 90/08/27 03:22:33 rearl * Disk-based MUF source code, added neccesary locking. * * Revision 1.5 90/08/15 03:00:59 rearl * Cleaned up some stuff, took out #ifdef GENDER. * * Revision 1.4 90/08/02 18:47:46 rearl * Fixed calls to logging functions. * * Revision 1.3 90/07/29 17:31:20 rearl * One picky pointer cast fixed. * * Revision 1.2 90/07/23 03:12:27 casie * Cleaned up various gcc warnings. * * Revision 1.1 90/07/19 23:03:24 casie * Initial revision * * */ #include "copyright.h" #include "config.h" #include <stdio.h> #include <ctype.h> #include "db.h" #include "params.h" #include "interface.h" #include "externs.h" struct object *db = 0; dbref db_top = 0; #ifdef RECYCLE dbref recyclable = NOTHING; #endif #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 macrotable *macrotop; struct plist *new_prop(void); void free_prop(struct plist *p); int number(const char *s); void putproperties(FILE *f, struct plist *p); void putprop(FILE *f, struct plist *p); void getproperties(FILE *f, dbref objno); void getpropertieslach(FILE *f, dbref objno); void free_line(struct line *l) { if (l -> this_line) free((void *) l -> this_line); free((void *) l); } void free_prog_text(struct line *l) { struct line *next; while (l) { next = l -> next; free_line(l); l = next; } } #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 */ void db_clear_object(dbref i) { struct object *o = DBFETCH(i); NAME(i) = 0; o->location = NOTHING; o->contents = NOTHING; o->next = NOTHING; o->key = TRUE_BOOLEXP; o->attributes = 0; o->properties = 0; DBDIRTY(i); o->exits=NOTHING; o->contents=NOTHING; o->curr_prog=NOTHING; o->insert_mode=0; o->run=0; /* flags you must initialize yourself */ /* type-specific fields you must also initialize */ } dbref new_object(void) { dbref newobj; #ifdef RECYCLE if(recyclable != NOTHING) { newobj = recyclable; recyclable = DBFETCH(newobj)->next; } else #endif { newobj = db_top; db_grow(db_top + 1); } /* clear it out */ db_clear_object(newobj); return newobj; } #define DB_MSGLEN 1024 void putref(FILE *f, dbref ref) { fprintf(f, "%d\n", ref); } static void putstring(FILE *f, const char *s) { if(s) { fputs((char *)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((char *)prop -> type, f); putc(PROP_DELIMITER, f); fputs(prop -> class, f); } 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; } } void macrodump(struct macrotable *node, FILE *f) { if (!node) return; macrodump(node -> left, f); putstring(f, node -> name); putstring(f, node -> definition); putref(f, node -> implementor); macrodump(node -> right, f); } char * file_line(FILE *f) { char buf[BUFFER_LEN]; if (!fgets(buf, BUFFER_LEN, f)) return NULL; buf[strlen(buf) - 1] = '\0'; return alloc_string(buf); } void foldtree (struct macrotable *center) { int count = 0; struct macrotable *nextcent = center; for (; nextcent; nextcent = nextcent -> left) count++; if (count > 1) { for (nextcent = center, count /= 2; count--; nextcent = nextcent -> left); if (center -> left) center -> left -> right = NULL; center -> left = nextcent; foldtree(center -> left); } for (count = 0, nextcent = center; nextcent; nextcent = nextcent -> right) count++; if (count > 1) { for (nextcent = center, count /= 2; count--; nextcent = nextcent -> right); if (center -> right) center -> right -> left = NULL; foldtree(center -> right); } } int macrochain (struct macrotable *lastnode, FILE *f) { char *line, *line2; struct macrotable *newmacro; if (!(line = file_line(f))) return 0; line2 = file_line(f); newmacro = (struct macrotable *)new_macro(line, line2, getref(f)); if (!macrotop) macrotop = (struct macrotable *)newmacro; else { newmacro -> left = lastnode; lastnode -> right = newmacro; } return (1 + macrochain(newmacro, f)); } void macroload(FILE *f) { int count = 0; macrotop = NULL; count = macrochain(macrotop, f); fclose(f); for (count /= 2; count--; macrotop = macrotop -> right); foldtree(macrotop); return; } void write_program(struct line *first, dbref i) { FILE *f; char fname[BUFFER_LEN]; sprintf(fname, "muf/%d.m", (int) i); f = fopen(fname, "w"); if (!f) { log_status("Couldn't open file %s!\n", fname); return; } while (first) { if (!first -> this_line) continue; fputs((char *)first -> this_line, f); fputc('\n', f); first = first -> next; } fclose(f); } int db_write_object(FILE *f, dbref i) { struct object *o = DBFETCH(i); int j; putstring(f, NAME(i)); putref(f, o->location); putref(f, o->contents); putref(f, o->next); putboolexp(f, o->key); putref(f, (FLAGS(i) & ~INTERACTIVE)); putstring(f, "***Property list start ***"); putproperties(f, o->attributes); putproperties(f, o->properties); putstring(f, "***Property list end ***"); switch (Typeof(i)) { case TYPE_THING: putref(f, o->link); putref(f, o->exits); putref(f, OWNER(i)); putref(f, o->sp.thing.value); break; case TYPE_ROOM: putref(f, o->link); putref(f, o->exits); putref(f, OWNER(i)); 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, OWNER(i)); break; case TYPE_PLAYER: putref(f, o->link); putref(f, o->exits); putref(f, o->sp.player.pennies); putstring(f, o->sp.player.password); break; case TYPE_PROGRAM: putref(f, OWNER(i)); break; } return 0; } dbref db_write(FILE *f) { dbref i; fputs("***MAGE 1.1 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; char c; fgets(buf, sizeof(buf), f); if (strlen(buf) == DB_MSGLEN - 1) { /* ignore whatever comes after */ if (buf[DB_MSGLEN - 2] != '\n') while ((c = fgetc(f)) != '\n') ; } for(p = buf; *p; p++) { if(*p == '\n') { *p = '\0'; break; } } return buf; } 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(const char *s) { if (!s) return 0; 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; 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; } } } void getproperties(FILE *f, dbref object) { char buf[BUFFER_LEN], s[BUFFER_LEN]; int i, j; char *type, *class; /* get rid of first line */ fgets(buf, sizeof(buf), f); /* initialize first line stuff */ fgets(buf, sizeof(buf), f); while (strcmp(buf, "***Property list end ***\n")) /* fgets reads in \n too! */ { for (i = 0; buf[i] != PROP_DELIMITER; i++) s[i] = buf[i]; s[i] = '\0'; 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'; class = alloc_compressed(s); add_property(object, type, class); free((void *) class); free((void *) type); fgets(buf, sizeof(buf), f); } } void getpropertieslach(FILE *f, dbref object) { char buf[BUFFER_LEN], s[BUFFER_LEN]; int i, j; int flag; char *type, *class; const static char *toattr[11] = { "Ahear", "Aahear", "Amhear", "Adesc", "Adrop", "Afail", "Asucc", "Listen", "Sex", "Idesc", "Odesc" }; /* get rid of first line */ fgets(buf, sizeof(buf), f); /* initialize first line stuff */ fgets(buf, sizeof(buf), f); while (strcmp(buf, "***Property list end ***\n")) /* fgets reads in \n too! */ { for (i = 0; buf[i] != PROP_DELIMITER; i++) s[i] = buf[i]; s[i] = '\0'; 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'; if(s[0] == '^') class = alloc_compressed(s + 1); else if(!strncmp(s, "*s*", 3)) /* for MAGE 1.0 db */ class = alloc_compressed(s + 3); else if(!strncmp(s, "*i*", 3)) class = alloc_compressed(s + 3); else class = alloc_compressed(s); /* convert Va - Vz properties to attributes.. */ if((tolower(type[0]) == 'v') && (strlen(type) == 2)) { if(tolower(type[1]) < 'a' || tolower(type[1]) > 'z') { add_property(object, type, class); /* normal property */ } else { type[0] = 'V'; type[1] = tolower(type[1]); add_attr(object, type, class); } } else { /* convert special properties to attributes */ flag = 1; for(i = 0; (i < 11) && flag; i++) { if(!string_compare(toattr[i], type)) { add_attr(object, toattr[i], class); flag = 0; } } if(flag) /* type was not was of the attributes */ add_property(object, type, class); } free((void *) class); free((void *) type); fgets(buf, sizeof(buf), f); } } 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_object(dbref i) { struct object *o; o = DBFETCH(i); if(NAME(i)) free((void *) NAME(i)); if(o->key) free_boolexp(o->key); if (o->attributes) { struct plist *p, *next; for(p = o->attributes; p; p = next) { next = p -> next; free_prop(p); } } 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); } if (Typeof(i) == TYPE_PROGRAM) { int j; struct inst *code; code = o->sp.program.code; if (code) { for (j = 0; j < o->sp.program.siz; j++) if (code[j].type == PROG_STRING && code[j].data.string) free((void *) code[j].data.string); free((void *) code); } } DBDIRTY(i); } void db_free(void) { dbref i; if(db) { for(i = 0; i < db_top; i++) db_free_object(i); free((void *) db); db = 0; db_top = 0; } clear_players(); clear_primitives(); #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; p -> next = 0; return p; } struct line * get_new_line() { struct line *new; new = (struct line *) malloc(sizeof(struct line)); new -> this_line = NULL; new -> next = NULL; new -> prev = NULL; return new; } struct line * read_program(dbref i) { char buf[BUFFER_LEN]; struct line *first; struct line *prev; struct line *new; FILE *f; first = NULL; sprintf(buf, "muf/%d.m", (int) i); f = fopen(buf, "r"); if (!f) return 0; while (fgets(buf, BUFFER_LEN, f)) { new = get_new_line(); buf[strlen(buf) - 1] = '\0'; new -> this_line = alloc_string(buf); if (!first) first = prev = new; else { prev -> next = new; new -> prev = prev; prev = new; } } fclose(f); return first; } void db_read_object_mage(FILE *f, register struct object *o, dbref objno) { int j, c, prop_flag = 0; NAME(objno) = getstring(f); o->location = getref(f); o->contents = getref(f); o->next = getref(f); o->key = getboolexp(f); o->flags = getref(f); c = getc(f); if (c == '*') { getproperties(f, objno); prop_flag++; } else { /* do our own getref */ int sign = 0; char buf[BUFFER_LEN]; int i = 0; if (c == '-') sign = 1; else if (c != '+') { buf[i] = c; i++; } while ((c = getc(f)) != '\n') { buf[i] = c; i++; } buf[i] = '\0'; j = atol(buf); if (sign) j = -j; } switch (FLAGS(objno) & TYPE_MASK) { case TYPE_THING: o->link = prop_flag ? getref(f) : j; o->exits = getref(f); OWNER(objno) = getref(f); o->sp.thing.value = getref(f); break; case TYPE_ROOM: o->link = prop_flag ? getref(f) : j; o->exits = getref(f); OWNER(objno) = getref(f); break; case TYPE_EXIT: o->sp.exit.ndest = prop_flag ? getref(f) : j; 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); } OWNER(objno) = getref(f); break; case TYPE_PLAYER: o->link = prop_flag ? getref(f) : j; o->exits = getref(f); o->sp.player.pennies = getref(f); o->sp.player.password = getstring(f); o->curr_prog = NOTHING; o->insert_mode = 0; o->run = 0; break; case TYPE_PROGRAM: OWNER(objno) = getref(f); FLAGS(objno) &= ~INTERNAL; o -> sp.program.curr_line = 0; /* o -> sp.program.first = read_program(objno); */ o -> sp.program.code = 0; o -> sp.program.siz = 0; o -> sp.program.start = 0; /* do_compile(GOD, objno); free_prog_text(o -> sp.program.first); */ break; #ifdef RECYCLE case TYPE_GARBAGE: o->next = recyclable; recyclable = objno; free((void *)NAME(objno)); NAME(objno) = "<garbage>"; add_attr(objno, "Desc", "<recyclable>"); break; #endif } } void db_read_object_lachesis(FILE *f,register struct object *o, dbref objno) { int j, c, prop_flag = 0; NAME(objno) = getstring(f); add_attr(objno, "Desc", getstring_compress(f)); o->location = getref(f); o->contents = getref(f); o->next = getref(f); o->key = getboolexp(f); add_attr(objno, "Fail", getstring_compress(f)); add_attr(objno, "Succ", getstring_compress(f)); add_attr(objno, "Drop", getstring_compress(f)); add_attr(objno, "Ofail", getstring_compress(f)); add_attr(objno, "Osucc", getstring_compress(f)); add_attr(objno, "Odrop", getstring_compress(f)); /* OWNER(objno) = getref(f); */ /* o->pennies = getref(f); */ o->flags = getref(f); /* FLAGS(objno) = getref(f);*/ c = getc(f); if (c == '*') { getpropertieslach(f, objno); prop_flag++; } else { /* do our own getref */ int sign = 0; char buf[BUFFER_LEN]; int i = 0; if (c == '-') sign = 1; else if (c != '+') { buf[i] = c; i++; } while ((c = getc(f)) != '\n') { buf[i] = c; i++; } buf[i] = '\0'; j = atol(buf); if (sign) j = -j; /* set gender stuff */ /* convert GENDER flag to property */ switch((FLAGS(objno) & GENDER_MASK) >> GENDER_SHIFT) { case GENDER_NEUTER: add_attr(objno, "sex", "neuter"); break; case GENDER_FEMALE: add_attr(objno, "sex", "female"); break; case GENDER_MALE: add_attr(objno, "sex", "male"); break; default: add_attr(objno, "sex", "unassigned"); break; } } /* 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 (FLAGS(objno) & TYPE_MASK) { case TYPE_THING: o->link = prop_flag ? getref(f) : j; o->exits = getref(f); OWNER(objno) = getref(f); o->sp.thing.value = getref(f); break; case TYPE_ROOM: o->link = prop_flag ? getref(f) : j; o->exits = getref(f); OWNER(objno) = getref(f); break; case TYPE_EXIT: o->sp.exit.ndest = prop_flag ? getref(f) : j; 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); } OWNER(objno) = getref(f); break; case TYPE_PLAYER: o->link = prop_flag ? getref(f) : j; o->exits = getref(f); o->sp.player.pennies = getref(f); o->sp.player.password = getstring(f); o->curr_prog = NOTHING; o->insert_mode = 0; o->run = 0; break; case TYPE_PROGRAM: OWNER(objno) = getref(f); FLAGS(objno) &= ~INTERNAL; o -> sp.program.curr_line = 0; o -> sp.program.first = read_program(objno); o -> sp.program.code = 0; o -> sp.program.siz = 0; o -> sp.program.start = 0; do_compile(GOD, objno); free_prog_text(o -> sp.program.first); break; #ifdef RECYCLE case TYPE_GARBAGE: o->next = recyclable; recyclable = objno; free((void *)NAME(objno)); NAME(objno) = "<garbage>"; add_attr(objno, "Desc", "<recyclable>"); break; #endif } } dbref db_read(FILE *f) { dbref i; struct object *o; const char *special; int newformat; char c; newformat = 0; if ( (c = getc(f)) == '*') { special = getstring(f); if(!strcmp(special, "**MAGE 1.1 DUMP Format***")) { newformat = 1; } else if (!strcmp(special, "**Lachesis TinyMUCK DUMP Format***")) { newformat = 2; } free((void *)special); c = getc(f); /* get next char */ } db_free(); init_primitives(); 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 = DBFETCH(i); switch (newformat) { /* case 0: db_read_object_old(f, o, i); break; */ case 1: db_read_object_mage(f, o, i); break; case 2: db_read_object_lachesis(f, o, i); break; } if (Typeof(i) == TYPE_PLAYER) { OWNER(i) = i; add_player(i); } break; case '*': special = getstring(f); if(strcmp(special, "**END OF DUMP***")) { free((void *) special); return -1; } else { free((void *) special); return db_top; } default: return -1; /* break; */ } c = getc(f); } /* for */ } /* db_read */