/* object.c - low-level object manipulation routines */ /* $Id: object.c,v 1.66 2003/04/14 03:23:21 rmg Exp $ */ #include "copyright.h" #include "autoconf.h" #include "config.h" #include "alloc.h" /* required by mudconf */ #include "flags.h" /* required by mudconf */ #include "htab.h" /* required by mudconf */ #include "mudconf.h" /* required by code */ #include "db.h" /* required by externs */ #include "externs.h" /* required by code */ #include "powers.h" /* required by code */ #include "attrs.h" /* required by code */ #include "match.h" /* required by code */ #define IS_CLEAN(i) (IS(i, TYPE_GARBAGE, GOING) && \ (Location(i) == NOTHING) && \ (Contents(i) == NOTHING) && (Exits(i) == NOTHING) && \ (Next(i) == NOTHING) && (Owner(i) == GOD)) #define ZAP_LOC(i) { s_Location(i, NOTHING); s_Next(i, NOTHING); } static int check_type; extern int FDECL(boot_off, (dbref, char *)); extern void NDECL(cf_verify); extern void FDECL(fwdlist_clr, (dbref)); extern void FDECL(stack_clr, (dbref)); extern void FDECL(xvars_clr, (dbref)); extern int FDECL(structure_clr, (dbref)); /* --------------------------------------------------------------------------- * Log_pointer_err, Log_header_err, Log_simple_damage: Write errors to the * log file. */ static void Log_pointer_err(prior, obj, loc, ref, reftype, errtype) dbref prior, obj, loc, ref; const char *reftype, *errtype; { STARTLOG(LOG_PROBLEMS, "OBJ", "DAMAG") log_type_and_name(obj); if (loc != NOTHING) { log_printf(" in "); log_type_and_name(loc); } log_printf(": %s ", ((prior == NOTHING) ? reftype : "Next pointer")); log_type_and_name(ref); log_printf(" %s", errtype); ENDLOG } static void Log_header_err(obj, loc, val, is_object, valtype, errtype) dbref obj, loc, val; int is_object; const char *valtype, *errtype; { STARTLOG(LOG_PROBLEMS, "OBJ", "DAMAG") log_type_and_name(obj); if (loc != NOTHING) { log_printf(" in "); log_type_and_name(loc); } log_printf(": %s ", valtype); if (is_object) log_type_and_name(val); else log_printf("%d", val); log_printf(" %s", errtype); ENDLOG } static void Log_simple_err(obj, loc, errtype) dbref obj, loc; const char *errtype; { STARTLOG(LOG_PROBLEMS, "OBJ", "DAMAG") log_type_and_name(obj); if (loc != NOTHING) { log_printf(" in "); log_type_and_name(loc); } log_printf(": %s", errtype); ENDLOG } /* --------------------------------------------------------------------------- * can_set_home, new_home, clone_home: * Routines for validating and determining homes. */ int can_set_home(player, thing, home) dbref player, thing, home; { if (!Good_obj(player) || !Good_obj(home) || (thing == home)) return 0; switch (Typeof(home)) { case TYPE_PLAYER: case TYPE_ROOM: case TYPE_THING: if (Going(home)) return 0; if (Controls(player, home) || Abode(home) || LinkAnyHome(player)) return 1; } return 0; } dbref new_home(player) dbref player; { dbref loc; loc = Location(player); if (can_set_home(Owner(player), player, loc)) return loc; loc = Home(Owner(player)); if (can_set_home(Owner(player), player, loc)) return loc; return (Good_home(mudconf.default_home) ? mudconf.default_home : (Good_home(mudconf.start_home) ? mudconf.start_home : (Good_home(mudconf.start_room) ? mudconf.start_room : 0))); } dbref clone_home(player, thing) dbref player, thing; { dbref loc; loc = Home(thing); if (can_set_home(Owner(player), player, loc)) return loc; return new_home(player); } /* --------------------------------------------------------------------------- * update_newobjs: Update a player's most-recently-created objects. */ static void update_newobjs(player, obj_num, obj_type) dbref player; dbref obj_num; int obj_type; { int i, aowner, aflags, alen; char *newobj_str, *p, tbuf[SBUF_SIZE], *tokst; int obj_list[4]; newobj_str = atr_get(player, A_NEWOBJS, &aowner, &aflags, &alen); if (!*newobj_str) { for (i = 0; i < 4; i++) obj_list[i] = -1; } else { for (p = strtok_r(newobj_str, " ", &tokst), i = 0; p && (i < 4); p = strtok_r(NULL, " ", &tokst), i++) { obj_list[i] = atoi(p); } } free_lbuf(newobj_str); switch (obj_type) { case TYPE_ROOM: obj_list[0] = obj_num; break; case TYPE_EXIT: obj_list[1] = obj_num; break; case TYPE_THING: obj_list[2] = obj_num; break; case TYPE_PLAYER: obj_list[3] = obj_num; break; } sprintf(tbuf, "%d %d %d %d", obj_list[0], obj_list[1], obj_list[2], obj_list[3]); atr_add_raw(player, A_NEWOBJS, tbuf); } /* --------------------------------------------------------------------------- * ok_exit_name: Make sure an exit name contains no blank components. */ static int ok_exit_name(name) char *name; { char *p, *lastp, *s; char buff[LBUF_SIZE]; strcpy(buff, name); /* munchable buffer */ /* walk down the string, checking lengths. skip leading spaces. */ for (p = buff, lastp = buff; (p = strchr(lastp, ';')) != NULL; lastp = p) { *p++ = '\0'; s = lastp; while (isspace(*s)) s++; if (strlen(s) < 1) return 0; } /* check last component */ while (isspace(*lastp)) lastp++; if (strlen(lastp) < 1) return 0; return 1; } /* --------------------------------------------------------------------------- * create_obj: Create an object of the indicated type IF the player can * afford it. */ dbref create_obj(player, objtype, name, cost) dbref player; int objtype, cost; char *name; { dbref obj, owner; dbref parent = NOTHING; dbref proto = NOTHING; int quota, okname = 0, value, self_owned, require_inherit; FLAG f1, f2, f3; time_t tt; char *buff; const char *tname; struct timeval obj_time; /* First check to see whether or not we're allowed to grow the * database any further (we must either have an object in the * freelist, or we have to be under the limit). */ if ((mudstate.db_top + 1 >= mudconf.building_limit) && (mudstate.freelist == NOTHING)) { notify(player, "The database building limit has been reached."); return NOTHING; } value = 0; quota = 0; self_owned = 0; require_inherit = 0; switch (objtype) { case TYPE_ROOM: cost = mudconf.digcost; quota = mudconf.room_quota; f1 = mudconf.room_flags.word1; f2 = mudconf.room_flags.word2; f3 = mudconf.room_flags.word3; okname = ok_name(name); tname = "a room"; if (Good_obj(mudconf.room_parent)) parent = mudconf.room_parent; if (Good_obj(mudconf.room_proto)) proto = mudconf.room_proto; break; case TYPE_THING: if (cost < mudconf.createmin) cost = mudconf.createmin; if (cost > mudconf.createmax) cost = mudconf.createmax; quota = mudconf.thing_quota; f1 = mudconf.thing_flags.word1; f2 = mudconf.thing_flags.word2; f3 = mudconf.thing_flags.word3; value = OBJECT_ENDOWMENT(cost); okname = ok_name(name); tname = "a thing"; if (Good_obj(mudconf.thing_parent)) parent = mudconf.thing_parent; if (Good_obj(mudconf.thing_proto)) proto = mudconf.thing_proto; break; case TYPE_EXIT: cost = mudconf.opencost; quota = mudconf.exit_quota; f1 = mudconf.exit_flags.word1; f2 = mudconf.exit_flags.word2; f3 = mudconf.exit_flags.word3; okname = ok_name(name) && ok_exit_name(name); tname = "an exit"; if (Good_obj(mudconf.exit_parent)) parent = mudconf.exit_parent; if (Good_obj(mudconf.exit_proto)) proto = mudconf.exit_proto; break; case TYPE_PLAYER: if (cost) { cost = mudconf.robotcost; quota = mudconf.player_quota; f1 = mudconf.robot_flags.word1; f2 = mudconf.robot_flags.word2; f3 = mudconf.robot_flags.word3; value = 0; tname = "a robot"; require_inherit = 1; } else { cost = 0; quota = 0; f1 = mudconf.player_flags.word1; f2 = mudconf.player_flags.word2; f3 = mudconf.player_flags.word3; value = mudconf.paystart; quota = mudconf.start_quota; self_owned = 1; tname = "a player"; } if (Good_obj(mudconf.player_parent)) parent = mudconf.player_parent; if (Good_obj(mudconf.player_proto)) proto = mudconf.player_proto; buff = munge_space(name); if (!badname_check(buff)) { notify(player, "That name is not allowed."); free_lbuf(buff); return NOTHING; } if (!ok_player_name(buff)) { free_lbuf(buff); break; } if (lookup_player(NOTHING, buff, 0) != NOTHING) { notify(player, tprintf("The name %s is already taken.", name)); free_lbuf(buff); return NOTHING; } ++okname; free_lbuf(buff); break; default: STARTLOG(LOG_BUGS, "BUG", "OTYPE") log_printf("Bad object type in create_obj: %d.", objtype); ENDLOG return NOTHING; } if (!okname) { notify(player, tprintf("That's a silly name for %s!", tname)); return NOTHING; } if (!self_owned) { if (!Good_obj(player)) return NOTHING; owner = Owner(player); if (!Good_obj(owner)) return NOTHING; } else { owner = NOTHING; } if (require_inherit) { if (!Inherits(player)) { notify(player, NOPERM_MESSAGE); return NOTHING; } } /* Make sure the creator can pay for the object. */ if ((player != NOTHING) && !canpayfees(player, player, cost, quota, objtype)) return NOTHING; else if (player != NOTHING) payfees(player, cost, quota, objtype); /* Get the first object from the freelist. If the object is not * clean, discard the remainder of the freelist and go get a * completely new object. */ obj = NOTHING; if (mudstate.freelist != NOTHING) { obj = mudstate.freelist; if (Good_dbref(obj) && IS_CLEAN(obj)) { mudstate.freelist = Link(obj); } else { STARTLOG(LOG_PROBLEMS, "FRL", "DAMAG") log_printf("Freelist damaged, bad object #%d.", obj); ENDLOG obj = NOTHING; mudstate.freelist = NOTHING; } } if (obj == NOTHING) { obj = mudstate.db_top; db_grow(mudstate.db_top + 1); } atr_free(obj); /* just in case... */ /* Set things up according to the object type */ s_Location(obj, NOTHING); s_Contents(obj, NOTHING); s_Exits(obj, NOTHING); s_Next(obj, NOTHING); s_Link(obj, NOTHING); /* We do not autozone players to their creators. */ if (mudconf.autozone && (player != NOTHING) && (objtype != TYPE_PLAYER)) { s_Zone(obj, Zone(player)); } else { if (proto != NOTHING) { s_Zone(obj, Zone(proto)); } else { s_Zone(obj, NOTHING); } } if (proto != NOTHING) { s_Parent(obj, Parent(proto)); s_Flags(obj, objtype | (Flags(proto) & ~TYPE_MASK)); s_Flags2(obj, Flags2(proto)); s_Flags3(obj, Flags3(proto)); } else { s_Parent(obj, parent); s_Flags(obj, objtype | f1); s_Flags2(obj, f2); s_Flags3(obj, f3); } s_Owner(obj, (self_owned ? obj : owner)); s_Pennies(obj, value); Unmark(obj); buff = munge_space((char *)name); s_Name(obj, buff); free_lbuf(buff); #ifndef NO_TIMECHECKING obj_time.tv_sec = obj_time.tv_usec = 0; s_Time_Used(obj, obj_time); #endif s_Accessed(obj); s_Modified(obj); s_StackCount(obj, 0); s_VarsCount(obj, 0); s_StructCount(obj, 0); s_InstanceCount(obj, 0); if (proto != NOTHING) atr_cpy(GOD, obj, proto); if (objtype == TYPE_PLAYER) { time(&tt); buff = (char *)ctime(&tt); buff[strlen(buff) - 1] = '\0'; atr_add_raw(obj, A_LAST, buff); buff = alloc_sbuf("create_obj.quota"); sprintf(buff, "%d %d %d %d %d", quota, mudconf.start_room_quota, mudconf.start_exit_quota, mudconf.start_thing_quota, mudconf.start_player_quota); atr_add_raw(obj, A_QUOTA, buff); atr_add_raw(obj, A_RQUOTA, buff); add_player_name(obj, Name(obj)); free_sbuf(buff); if (!cost) payfees(obj, 0, mudconf.player_quota, TYPE_PLAYER); } if (player != NOTHING) { update_newobjs(player, obj, objtype); } CALL_ALL_MODULES(create_obj, (player, obj)); return obj; } /* --------------------------------------------------------------------------- * destroy_obj: Destroy an object. Assumes it has already been removed from * all lists and has no contents or exits. */ void destroy_obj(player, obj) dbref player, obj; { dbref owner; int good_owner, val, quota; char *tname; if (!Good_obj(obj)) return; /* Validate the owner */ owner = Owner(obj); good_owner = Good_owner(owner); /* Halt any pending commands (waiting or semaphore) */ if (halt_que(NOTHING, obj) > 0) { if (good_owner && !Quiet(obj) && !Quiet(owner)) { notify(owner, "Halted."); } } nfy_que(GOD, obj, 0, NFY_DRAIN, 0); cron_clr(obj, NOTHING); /* Remove forwardlists, stacks, etc. from the hash tables. */ fwdlist_clr(obj); stack_clr(obj); xvars_clr(obj); structure_clr(obj); CALL_ALL_MODULES(destroy_obj, (player, obj)); /* Compensate the owner for the object */ val = 1; quota = 1; if (good_owner && (owner != obj)) { switch (Typeof(obj)) { case TYPE_ROOM: val = mudconf.digcost; quota = mudconf.room_quota; break; case TYPE_THING: val = OBJECT_DEPOSIT(Pennies(obj)); quota = mudconf.thing_quota; break; case TYPE_EXIT: val = mudconf.opencost; quota = mudconf.exit_quota; break; case TYPE_PLAYER: if (Robot(obj)) val = mudconf.robotcost; else val = 0; quota = mudconf.player_quota; } payfees(owner, -val, -quota, Typeof(obj)); if (!Quiet(owner) && !Quiet(obj)) notify(owner, tprintf("You get back your %d %s deposit for %s(#%d).", val, mudconf.one_coin, Name(obj), obj)); } if ((player != NOTHING) && !Quiet(player)) { if (good_owner && Owner(player) != owner) { if (owner == obj) { notify(player, tprintf("Destroyed. %s(#%d)", Name(obj), obj)); } else { tname = alloc_sbuf("destroy_obj"); strcpy(tname, Name(owner)); notify(player, tprintf("Destroyed. %s's %s(#%d)", tname, Name(obj), obj)); free_sbuf(tname); } } else if (!Quiet(obj)) { notify(player, "Destroyed."); } } atr_free(obj); s_Name(obj, NULL); s_Flags(obj, (TYPE_GARBAGE | GOING)); s_Flags2(obj, 0); s_Flags3(obj, 0); s_Powers(obj, 0); s_Powers2(obj, 0); s_Location(obj, NOTHING); s_Contents(obj, NOTHING); s_Exits(obj, NOTHING); s_Next(obj, NOTHING); s_Link(obj, NOTHING); s_Owner(obj, GOD); s_Pennies(obj, 0); s_Parent(obj, NOTHING); s_Zone(obj, NOTHING); } /* --------------------------------------------------------------------------- * do_freelist: Grab a garbage object, and move it to the top of the freelist. */ void do_freelist(player, cause, key, str) dbref player, cause; int key; char *str; { dbref i, thing; /* We can only take a dbref; don't bother calling match_absolute() even, * since we're dealing with the garbage pile anyway. */ if (*str != '#') { notify(player, NOMATCH_MESSAGE); return; } str++; if (!*str) { notify(player, NOMATCH_MESSAGE); return; } thing = atoi(str); if (!Good_dbref(thing)) { notify(player, NOMATCH_MESSAGE); return; } /* The freelist is a linked list going from the lowest-numbered * objects to the highest-numbered objects. We need to make sure an * object is clean before we muck with it. */ if (IS_CLEAN(thing)) { if (mudstate.freelist == thing) { notify(player, "That object is already at the head of the freelist."); return; } /* We've got to find this thing's predecessor so we avoid * circular chaining. */ DO_WHOLE_DB(i) { if (Link(i) == thing) { if (IS_CLEAN(i)) { s_Link(i, Link(thing)); break; /* shouldn't have more than one linkage */ } else { notify(player, "Unable to relink freelist at this time."); return; } } } s_Link(thing, mudstate.freelist); mudstate.freelist = thing; notify(player, "Object placed at the head of the freelist."); } else { notify(player, "That object is not clean garbage."); } } /* --------------------------------------------------------------------------- * make_freelist: Build a freelist */ static void NDECL(make_freelist) { dbref i; mudstate.freelist = NOTHING; DO_WHOLE_DB_BACKWARDS(i) { if (IS_CLEAN(i)) { /* If there's clean garbage at the end of the db, just * trim it off. Memory will be reused if new objects are * needed, but can be eliminated by restarting. */ mudstate.db_top--; } else { break; } } DO_WHOLE_DB_BACKWARDS(i) { if (IS_CLEAN(i)) { s_Link(i, mudstate.freelist); mudstate.freelist = i; } } } /* --------------------------------------------------------------------------- * divest_object: Get rid of KEY contents of object. */ void divest_object(thing) dbref thing; { dbref curr, temp; SAFE_DOLIST(curr, temp, Contents(thing)) { if (!Controls(thing, curr) && Has_location(curr) && Key(curr)) { move_via_generic(curr, HOME, NOTHING, 0); } } } /* --------------------------------------------------------------------------- * empty_obj, purge_going: Get rid of GOING objects in the db. */ void empty_obj(obj) dbref obj; { dbref targ, next; /* Send the contents home */ SAFE_DOLIST(targ, next, Contents(obj)) { if (!Has_location(targ)) { Log_simple_err(targ, obj, "Funny object type in contents list of GOING location. Flush terminated."); break; } else if (Location(targ) != obj) { Log_header_err(targ, obj, Location(targ), 1, "Location", "indicates object really in another location during cleanup of GOING location. Flush terminated."); break; } else { ZAP_LOC(targ); if (Home(targ) == obj) { s_Home(targ, new_home(targ)); } move_via_generic(targ, HOME, NOTHING, 0); divest_object(targ); } } /* Destroy the exits */ SAFE_DOLIST(targ, next, Exits(obj)) { if (!isExit(targ)) { Log_simple_err(targ, obj, "Funny object type in exit list of GOING location. Flush terminated."); break; } else if (Exits(targ) != obj) { Log_header_err(targ, obj, Exits(targ), 1, "Location", "indicates exit really in another location during cleanup of GOING location. Flush terminated."); break; } else { destroy_obj(NOTHING, targ); } } } /* --------------------------------------------------------------------------- * destroy_exit, destroy_thing, destroy_player */ void destroy_exit(exit) dbref exit; { dbref loc; loc = Exits(exit); s_Exits(loc, remove_first(Exits(loc), exit)); destroy_obj(NOTHING, exit); } void destroy_thing(thing) dbref thing; { move_via_generic(thing, NOTHING, Owner(thing), 0); empty_obj(thing); destroy_obj(NOTHING, thing); } void destroy_player(victim) dbref victim; { dbref aowner, player; int count, aflags, alen; char *buf, *a_dest; /* Bye bye... */ a_dest = atr_get_raw(victim, A_DESTROYER); if (a_dest) { player = atoi(a_dest); if (!Good_owner(player)) player = GOD; } else { player = GOD; } boot_off(victim, (char *)"You have been destroyed!"); halt_que(victim, NOTHING); count = chown_all(victim, player, player, 0); /* Remove the name from the name hash table */ delete_player_name(victim, Name(victim)); buf = atr_pget(victim, A_ALIAS, &aowner, &aflags, &alen); Clear_Player_Aliases(victim, buf); free_lbuf(buf); move_via_generic(victim, NOTHING, player, 0); CALL_ALL_MODULES(destroy_player, (player, victim)); destroy_obj(NOTHING, victim); notify_quiet(player, tprintf("(%d objects @chowned to you)", count)); } static void NDECL(purge_going) { dbref i; DO_WHOLE_DB(i) { if (!Going(i)) continue; switch (Typeof(i)) { case TYPE_PLAYER: destroy_player(i); break; case TYPE_ROOM: /* Room scheduled for destruction... do it */ empty_obj(i); destroy_obj(NOTHING, i); break; case TYPE_THING: destroy_thing(i); break; case TYPE_EXIT: destroy_exit(i); break; case TYPE_GARBAGE: break; default: /* Something else... How did this happen? */ Log_simple_err(i, NOTHING, "GOING object with unexpected type. Destroyed."); destroy_obj(NOTHING, i); } } } /* --------------------------------------------------------------------------- * check_dead_refs: Look for references to GOING or illegal objects. */ static void check_pennies(thing, limit, qual) dbref thing; int limit; const char *qual; { int j; if (Going(thing)) return; j = Pennies(thing); if (isRoom(thing) || isExit(thing)) { if (j) { Log_header_err(thing, NOTHING, j, 0, qual, "is strange. Reset."); s_Pennies(thing, 0); } } else if (j == 0) { Log_header_err(thing, NOTHING, j, 0, qual, "is zero."); } else if (j < 0) { Log_header_err(thing, NOTHING, j, 0, qual, "is negative."); } else if (j > limit) { Log_header_err(thing, NOTHING, j, 0, qual, "is excessive."); } } #define check_ref_targ(crt__label, crt__setref, crt__newref) \ do { \ if (Good_obj(targ)) { \ if (Going(targ)) { \ crt__setref(i, crt__newref); \ if (!mudstate.standalone) { \ owner = Owner(i); \ if (Good_owner(owner) && \ !Quiet(i) && !Quiet(owner)) { \ notify(owner, \ tprintf("%s cleared on %s(#%d)", \ crt__label, Name(i), i)); \ } \ } else { \ Log_header_err(i, Location(i), targ, 1, \ crt__label, "is invalid. Cleared."); \ } \ } \ } else if (targ != NOTHING) { \ Log_header_err(i, Location(i), targ, 1, \ crt__label, "is invalid. Cleared."); \ crt__setref(i, crt__newref); \ } \ } while (0) static NDECL(void check_dead_refs) { dbref targ, owner, i, j; int aflags, dirty; char *str; FWDLIST *fp; DO_WHOLE_DB(i) { /* Check the parent */ targ = Parent(i); check_ref_targ("Parent", s_Parent, NOTHING); /* Check the zone */ targ = Zone(i); check_ref_targ("Zone", s_Zone, NOTHING); switch (Typeof(i)) { case TYPE_PLAYER: case TYPE_THING: if (Going(i)) break; /* Check the home */ targ = Home(i); check_ref_targ("Home", s_Home, new_home(i)); /* Check the location */ targ = Location(i); if (!Good_obj(targ)) { Log_pointer_err(NOTHING, i, NOTHING, targ, "Location", "is invalid. Moved to home."); ZAP_LOC(i); move_object(i, HOME); } /* Check for self-referential Next() */ if (Next(i) == i) { Log_simple_err(i, NOTHING, "Next points to self. Next cleared."); s_Next(i, NOTHING); } if (check_type & DBCK_FULL) { /* Check wealth or value */ targ = OBJECT_ENDOWMENT(mudconf.createmax); if (OwnsOthers(i)) { targ += mudconf.paylimit; check_pennies(i, targ, "Wealth"); } else { check_pennies(i, targ, "Value"); } } break; case TYPE_ROOM: /* Check the dropto */ targ = Dropto(i); if (targ != HOME) { check_ref_targ("Dropto", s_Dropto, NOTHING); } if (check_type & DBCK_FULL) { /* NEXT should be null */ if (Next(i) != NOTHING) { Log_header_err(i, NOTHING, Next(i), 1, "Next pointer", "should be NOTHING. Reset."); s_Next(i, NOTHING); } /* LINK should be null */ if (Link(i) != NOTHING) { Log_header_err(i, NOTHING, Link(i), 1, "Link pointer ", "should be NOTHING. Reset."); s_Link(i, NOTHING); } /* Check value */ check_pennies(i, 1, "Value"); } break; case TYPE_EXIT: /* If it points to something GOING, set it going */ targ = Location(i); if (Good_obj(targ)) { if (Going(targ)) { s_Going(i); } } else if ((targ == HOME) || (targ == AMBIGUOUS)) { /* null case, always valid */ } else if (targ != NOTHING) { Log_header_err(i, Exits(i), targ, 1, "Destination", "is invalid. Exit destroyed."); s_Going(i); } else { if (!Has_contents(targ)) { Log_header_err(i, Exits(i), targ, 1, "Destination", "is not a valid type. Exit destroyed."); s_Going(i); } } /* Check for self-referential Next() */ if (Next(i) == i) { Log_simple_err(i, NOTHING, "Next points to self. Next cleared."); s_Next(i, NOTHING); } if (check_type & DBCK_FULL) { /* CONTENTS should be null */ if (Contents(i) != NOTHING) { Log_header_err(i, Exits(i), Contents(i), 1, "Contents", "should be NOTHING. Reset."); s_Contents(i, NOTHING); } /* LINK should be null */ if (Link(i) != NOTHING) { Log_header_err(i, Exits(i), Link(i), 1, "Link", "should be NOTHING. Reset."); s_Link(i, NOTHING); } /* Check value */ check_pennies(i, 1, "Value"); } break; case TYPE_GARBAGE: break; default: /* Funny object type, destroy it */ Log_simple_err(i, NOTHING, "Funny object type. Destroyed."); destroy_obj(NOTHING, i); } /* Check forwardlist */ dirty = 0; if (H_Fwdlist(i) && ((fp = fwdlist_get(i)) != NULL)) { for (j = 0; j < fp->count; j++) { targ = fp->data[j]; if (Good_obj(targ) && Going(targ)) { fp->data[j] = NOTHING; dirty = 1; } else if (!Good_obj(targ) && (targ != NOTHING)) { fp->data[j] = NOTHING; dirty = 1; } } } if (dirty) { str = alloc_lbuf("purge_going"); (void)fwdlist_rewrite(fp, str); atr_get_info(i, A_FORWARDLIST, &owner, &aflags); atr_add(i, A_FORWARDLIST, str, owner, aflags); free_lbuf(str); } /* Check owner */ owner = Owner(i); if (!Good_obj(owner)) { Log_header_err(i, NOTHING, owner, 1, "Owner", "is invalid. Set to GOD."); owner = GOD; s_Owner(i, owner); if (!mudstate.standalone) halt_que(NOTHING, i); s_Halted(i); } else if (check_type & DBCK_FULL) { if (Going(owner)) { Log_header_err(i, NOTHING, owner, 1, "Owner", "is set GOING. Set to GOD."); s_Owner(i, owner); if (!mudstate.standalone) halt_que(NOTHING, i); s_Halted(i); } else if (!OwnsOthers(owner)) { Log_header_err(i, NOTHING, owner, 1, "Owner", "is not a valid owner type."); } else if (isPlayer(i) && (owner != i)) { Log_header_err(i, NOTHING, owner, 1, "Player", "is the owner instead of the player."); } } if (check_type & DBCK_FULL) { /* Check for wizards */ if (Wizard(i)) { if (isPlayer(i)) { Log_simple_err(i, NOTHING, "Player is a WIZARD."); } if (!Wizard(Owner(i))) { Log_header_err(i, NOTHING, Owner(i), 1, "Owner", "of a WIZARD object is not a wizard"); } } } } } #undef check_ref_targ /* --------------------------------------------------------------------------- * check_loc_exits, check_exit_chains: Validate the exits chains * of objects and attempt to correct problems. The following errors are * found and corrected: * Location not in database - skip it. * Location GOING - skip it. * Location not a PLAYER, ROOM, or THING - skip it. * Location already visited - skip it. * Exit/next pointer not in database - NULL it. * Member is not an EXIT - terminate chain. * Member is GOING - destroy exit. * Member already checked (is in another list) - terminate chain. * Member in another chain (recursive check) - terminate chain. * Location of member is not specified location - reset it. */ static void check_loc_exits(loc) dbref loc; { dbref exit, back, temp, exitloc, dest; if (!Good_obj(loc)) return; /* Only check players, rooms, and things that aren't GOING */ if (isExit(loc) || Going(loc)) return; /* If marked, we've checked here already */ if (Marked(loc)) return; Mark(loc); /* Check all the exits */ back = NOTHING; exit = Exits(loc); while (exit != NOTHING) { exitloc = NOTHING; dest = NOTHING; if (Good_obj(exit)) { exitloc = Exits(exit); dest = Location(exit); } if (!Good_obj(exit)) { /* A bad pointer - terminate chain */ Log_pointer_err(back, loc, NOTHING, exit, "Exit list", "is invalid. List nulled."); if (back != NOTHING) { s_Next(back, NOTHING); } else { s_Exits(loc, NOTHING); } exit = NOTHING; } else if (!isExit(exit)) { /* Not an exit - terminate chain */ Log_pointer_err(back, loc, NOTHING, exit, "Exitlist member", "is not an exit. List terminated."); if (back != NOTHING) { s_Next(back, NOTHING); } else { s_Exits(loc, NOTHING); } exit = NOTHING; } else if (Going(exit)) { /* Going - silently filter out */ temp = Next(exit); if (back != NOTHING) { s_Next(back, temp); } else { s_Exits(loc, temp); } destroy_obj(NOTHING, exit); exit = temp; continue; } else if (Marked(exit)) { /* Already in another list - terminate chain */ Log_pointer_err(back, loc, NOTHING, exit, "Exitlist member", "is in another exitlist. Cleared."); if (back != NOTHING) { s_Next(back, NOTHING); } else { s_Exits(loc, NOTHING); } exit = NOTHING; } else if (!Good_obj(dest) && (dest != HOME) && (dest != AMBIGUOUS) && (dest != NOTHING)) { /* Destination is not in the db. Null it. */ Log_pointer_err(back, loc, NOTHING, exit, "Destination", "is invalid. Cleared."); s_Location(exit, NOTHING); } else if (exitloc != loc) { /* Exit thinks it's in another place. Check the * exitlist there and see if it contains this * exit. If it does, then our exitlist * somehow pointed into the middle of their * exitlist. If not, assume we own the exit. */ check_loc_exits(exitloc); if (Marked(exit)) { /* It's in the other list, give it up */ Log_pointer_err(back, loc, NOTHING, exit, "", "is in another exitlist. List terminated."); if (back != NOTHING) { s_Next(back, NOTHING); } else { s_Exits(loc, NOTHING); } exit = NOTHING; } else { /* Not in the other list, assume in ours */ Log_header_err(exit, loc, exitloc, 1, "Not on chain for location", "Reset."); s_Exits(exit, loc); } } if (exit != NOTHING) { /* All OK (or all was made OK) */ if (check_type & DBCK_FULL) { /* Make sure exit owner owns at least one of * the source or destination. Just * warn if he doesn't. */ temp = Owner(exit); if ((temp != Owner(loc)) && (temp != Owner(Location(exit)))) { Log_header_err(exit, loc, temp, 1, "Owner", "does not own either the source or destination."); } } Mark(exit); back = exit; exit = Next(exit); } } return; } static void NDECL(check_exit_chains) { dbref i; Unmark_all(i); DO_WHOLE_DB(i) check_loc_exits(i); DO_WHOLE_DB(i) { if (isExit(i) && !Marked(i)) { Log_simple_err(i, NOTHING, "Disconnected exit. Destroyed."); destroy_obj(NOTHING, i); } } } /* --------------------------------------------------------------------------- * check_misplaced_obj, check_loc_contents, check_contents_chains: Validate * the contents chains of objects and attempt to correct problems. The * following errors are found and corrected: * Location not in database - skip it. * Location GOING - skip it. * Location not a PLAYER, ROOM, or THING - skip it. * Location already visited - skip it. * Contents/next pointer not in database - NULL it. * Member is not an PLAYER or THING - terminate chain. * Member is GOING - destroy exit. * Member already checked (is in another list) - terminate chain. * Member in another chain (recursive check) - terminate chain. * Location of member is not specified location - reset it. */ static void FDECL(check_loc_contents, (dbref)); static void check_misplaced_obj(obj, back, loc) dbref *obj, back, loc; { /* Object thinks it's in another place. Check the contents list * there and see if it contains this object. If it does, then * our contents list somehow pointed into the middle of their * contents list and we should truncate our list. If not, * assume we own the object. */ if (!Good_obj(*obj)) return; loc = Location(*obj); Unmark(*obj); if (Good_obj(loc)) { check_loc_contents(loc); } if (Marked(*obj)) { /* It's in the other list, give it up */ Log_pointer_err(back, loc, NOTHING, *obj, "", "is in another contents list. Cleared."); if (back != NOTHING) { s_Next(back, NOTHING); } else { s_Contents(loc, NOTHING); } *obj = NOTHING; } else { /* Not in the other list, assume in ours */ Log_header_err(*obj, loc, Contents(*obj), 1, "Location", "is invalid. Reset."); s_Contents(*obj, loc); } return; } static void check_loc_contents(loc) dbref loc; { dbref obj, back, temp; if (!Good_obj(loc)) return; /* Only check players, rooms, and things that aren't GOING */ if (isExit(loc) || Going(loc)) return; /* Check all the exits */ back = NOTHING; obj = Contents(loc); while (obj != NOTHING) { if (!Good_obj(obj)) { /* A bad pointer - terminate chain */ Log_pointer_err(back, loc, NOTHING, obj, "Contents list", "is invalid. Cleared."); if (back != NOTHING) { s_Next(back, NOTHING); } else { s_Contents(loc, NOTHING); } obj = NOTHING; } else if (!Has_location(obj)) { /* Not a player or thing - terminate chain */ Log_pointer_err(back, loc, NOTHING, obj, "Contents list member", "is not a player or thing. Cleared."); if (back != NOTHING) { s_Next(back, NOTHING); } else { s_Contents(loc, NOTHING); } obj = NOTHING; } else if (Going(obj) && (Typeof(obj) == TYPE_GARBAGE)) { /* Going - silently filter out */ temp = Next(obj); if (back != NOTHING) { s_Next(back, temp); } else { s_Contents(loc, temp); } destroy_obj(NOTHING, obj); obj = temp; continue; } else if (Marked(obj)) { /* Already visited - either truncate or ignore */ if (Location(obj) != loc) { /* Location wrong - either truncate or fix */ check_misplaced_obj(&obj, back, loc); } else { /* Location right - recursive contents */ } } else if (Location(obj) != loc) { /* Location wrong - either truncate or fix */ check_misplaced_obj(&obj, back, loc); } if (obj != NOTHING) { /* All OK (or all was made OK) */ if (check_type & DBCK_FULL) { /* Check for wizard command-handlers inside * nonwiz. Just warn if we find one. */ if (Wizard(obj) && !Wizard(loc)) { if (Commer(obj)) { Log_simple_err(obj, loc, "Wizard command handling object inside nonwizard."); } } /* Check for nonwizard objects inside wizard * objects. */ if (Wizard(loc) && !Wizard(obj) && !Wizard(Owner(obj))) { Log_simple_err(obj, loc, "Nonwizard object inside wizard."); } } Mark(obj); back = obj; obj = Next(obj); } } return; } static void NDECL(check_contents_chains) { dbref i; Unmark_all(i); DO_WHOLE_DB(i) check_loc_contents(i); DO_WHOLE_DB(i) if (!Going(i) && !Marked(i) && Has_location(i)) { Log_simple_err(i, Location(i), "Orphaned object, moved home."); ZAP_LOC(i); move_via_generic(i, HOME, NOTHING, 0); } } /* --------------------------------------------------------------------------- * do_dbck: Perform a database consistency check and clean up damage. */ void do_dbck(player, cause, key) dbref player, cause; int key; { check_type = key; make_freelist(); if (!mudstate.standalone) cf_verify(); check_dead_refs(); check_exit_chains(); check_contents_chains(); purge_going(); if (!mudstate.standalone && (player != NOTHING)) { alarm(1); if (!Quiet(player)) notify(player, "Done."); } }