game/bin/
game/data/
/*
 * object.c - low-level object manipulation routines 
 */
/*
 * $Id: object.c,v 1.4 1995/12/03 09:18:32 root Exp root $ 
 */

#include "copyright.h"
#include "autoconf.h"

#include "db.h"
#include "mudconf.h"
#include "command.h"
#include "externs.h"
#include "flags.h"
#include "powers.h"
#include "attrs.h"
#include "alloc.h"

#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); }

extern int NDECL(next_timer);

static int check_type;

#ifdef STANDALONE

/*
 * Functions needed in standalone mode 
 */

/*
 * move_object: taken from move.c with look and penny check extracted 
 */

void move_object(thing, dest)
dbref thing, dest;
{
	dbref src;

	/*
	 * Remove from the source location 
	 */

	src = Location(thing);
	if (src != NOTHING)
		s_Contents(src, remove_first(Contents(src), thing));

	/*
	 * Special check for HOME 
	 */

	if (dest == HOME)
		dest = Home(thing);

	/*
	 * Add to destination location 
	 */

	if (dest != NOTHING) {
		s_Contents(dest, insert_first(Contents(dest), thing));
	} else {
		s_Next(thing, NOTHING);
	}
	s_Location(thing, dest);
}

#define move_via_generic(obj,where,extra,hush) move_object(obj,where)

#endif

/*
 * ---------------------------------------------------------------------------
 * * 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_text((char *)" in ");
		log_type_and_name(loc);
	}
	log_text((char *)": ");
	if (prior == NOTHING) {
		log_text((char *)reftype);
	} else {
		log_text((char *)"Next pointer");
	}
	log_text((char *)" ");
	log_type_and_name(ref);
	log_text((char *)" ");
	log_text((char *)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_text((char *)" in ");
		log_type_and_name(loc);
	}
	log_text((char *)": ");
	log_text((char *)valtype);
	log_text((char *)" ");
	if (is_object)
		log_type_and_name(val);
	else
		log_number(val);
	log_text((char *)" ");
	log_text((char *)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_text((char *)" in ");
		log_type_and_name(loc);
	}
	log_text((char *)": ");
	log_text((char *)errtype);
	ENDLOG
}

/*
 * ---------------------------------------------------------------------------
 * * start_home, default_home, can_set_home, new_home, clone_home:
 * * Routines for validating and determining homes.
 */

dbref NDECL(start_home)
{
	if (mudconf.start_home != NOTHING)
		return mudconf.start_home;
	return mudconf.start_room;
}

dbref NDECL(default_home)
{
	if (mudconf.default_home != NOTHING)
		return mudconf.default_home;
	if (mudconf.start_home != NOTHING)
		return mudconf.start_home;
	return mudconf.start_room;
}

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))
			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 default_home();
}

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);
}

/*
 * ---------------------------------------------------------------------------
 * * create_obj: Create an object of the indicated type IF the player can
 * * afford it.
 */

#ifndef STANDALONE

dbref create_obj(player, objtype, name, cost)
dbref player;
int objtype, cost;
char *name;
{
	dbref obj, owner;
	int quota, okname, value, self_owned, require_inherit;
	FLAG f1, f2, f3;
	time_t tt;
	char *buff;
	const char *tname;

	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";
		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";
		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);
		tname = "an exit";
		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";
		}
		buff = munge_space(name);
		if (!badname_check(buff)) {
			notify(player, "That name is not allowed.");
			free_lbuf(buff);
			return NOTHING;
		}
		if (*buff) {
			okname = ok_player_name(buff);
			if (!okname) {
				notify(player, "That's a silly name for a player.");
				free_lbuf(buff);
				return NOTHING;
			}
		}
		if (okname) {
			okname = (lookup_player(NOTHING, buff, 0) == NOTHING);
			if (!okname) {
				notify(player, tprintf("The name %s is already taken.",
						       name));
				free_lbuf(buff);
				return NOTHING;
			}
		}
		free_lbuf(buff);
		break;
	default:
		LOG_SIMPLE(LOG_BUGS, "BUG", "OTYPE",
			   tprintf("Bad object type in create_obj: %d.",
				   objtype));
		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, "Permission denied.");
			return NOTHING;
		}
	}
	/*
	 * Make sure the creator can pay for the object. 
	 */

	if ((player != NOTHING) && !canpayfees(player, player, cost, quota))
		return NOTHING;

	/*
	 * 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_obj(obj) && IS_CLEAN(obj)) {
			mudstate.freelist = Link(obj);
		} else {
			LOG_SIMPLE(LOG_PROBLEMS, "FRL", "DAMAG",
				tprintf("Freelist damaged, bad object #%d.",
					obj));
			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);
	s_Parent(obj, NOTHING);

	s_Zone(obj, Zone(player));
	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);

	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", quota);
		atr_add_raw(obj, A_QUOTA, buff);
		atr_add_raw(obj, A_RQUOTA, buff);
		add_player_name(obj, Name(obj));
		free_sbuf(buff);
		s_Zone(obj, NOTHING);
	}
	return obj;
}

#endif

/*
 * ---------------------------------------------------------------------------
 * * 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;
	STACK *sp, *next;

#ifndef STANDALONE
	char *tname;

#endif

	if (!Good_obj(obj))
		return;


	/*
	 * Validate the owner 
	 */

	owner = Owner(obj);
	good_owner = Good_owner(owner);

	/*
	 * Halt any pending commands (waiting or semaphore) 
	 */

#ifndef STANDALONE
	if (halt_que(NOTHING, obj) > 0) {
		if (good_owner && !Quiet(obj) && !Quiet(owner)) {
			notify(owner, "Halted.");
		}
	}
	nfy_que(obj, NFY_DRAIN, 0);
#endif

	/*
	 * 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;
		}
		giveto(owner, val);
		if (mudconf.quotas)
			add_quota(owner, quota);

#ifndef STANDALONE
		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));
#endif
	}
#ifndef STANDALONE
	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");
				StringCopy(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.");
		}
	}
#endif
	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_Zone(obj, NOTHING);

	/*
	 * Clear the stack 
	 */
	for (sp = Stack(obj); sp != NULL; sp = next) {
		next = sp->next;
		free_lbuf(sp->data);
		free(sp);
	}

	s_Stack(obj, NULL);
	s_StackSize(obj, 0);

#ifndef STANDALONE
	if (mudconf.have_comsys)
		toast_player(obj);
#endif
	return;
}

/*
 * ---------------------------------------------------------------------------
 * * make_freelist: Build a freelist
 */

static void NDECL(make_freelist)
{
	dbref i;

	mudstate.freelist = NOTHING;
	DO_WHOLE_DB(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;
{
#ifndef STANDALONE
	dbref aowner, player;
	int count, aflags;
	char *buf;

	/*
	 * Bye bye... 
	 */
	player = (dbref) atoi(atr_get_raw(victim, A_DESTROYER));
	toast_player(victim);
	boot_off(victim, (char *)"You have been destroyed!");
	halt_que(victim, NOTHING);
	count = chown_all(victim, player);

	/*
	 * Remove the name from the name hash table 
	 */

	delete_player_name(victim, Name(victim));
	buf = atr_pget(victim, A_ALIAS, &aowner, &aflags);
	delete_player_name(victim, buf);
	free_lbuf(buf);

	move_via_generic(victim, NOTHING, player, 0);
	do_mail_clear(victim, NULL);
	do_mail_purge(victim);
	destroy_obj(NOTHING, victim);
	notify_quiet(player, tprintf("(%d objects @chowned to you)", count));
#endif
}

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(j, 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.");
	}
}

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);
		if (Good_obj(targ)) {
			if (Going(targ)) {
				s_Parent(i, NOTHING);
#ifndef STANDALONE
				owner = Owner(i);
				if (Good_owner(owner) &&
				    !Quiet(i) && !Quiet(owner)) {
					notify(owner,
					tprintf("Parent cleared on %s(#%d)",
						Name(i), i));
				}
#else
				Log_header_err(i, Location(i), targ, 1,
					 "Parent", "is invalid.  Cleared.");
#endif
			}
		} else if (targ != NOTHING) {
			Log_header_err(i, Location(i), targ, 1,
				       "Parent", "is invalid.  Cleared.");
			s_Parent(i, NOTHING);
		}
		/*
		 * Check the zone 
		 */

		targ = Zone(i);
		if (Good_obj(targ)) {
			if (Going(targ)) {
				s_Zone(i, NOTHING);
#ifndef STANDALONE
				owner = Owner(i);
				if (Good_owner(owner) &&
				    !Quiet(i) && !Quiet(owner)) {
					notify(owner,
					  tprintf("Zone cleared on %s(#%d)",
						  Name(i), i));
				}
#else
				Log_header_err(i, Location(i), targ, 1,
					    "Zone", "is invalid. Cleared.");
#endif
			}
		} else if (targ != NOTHING) {
			Log_header_err(i, Location(i), targ, 1,
				       "Zone", "is invalid. Cleared.");
			s_Zone(i, NOTHING);
		}
		switch (Typeof(i)) {
		case TYPE_PLAYER:
		case TYPE_THING:

			if (Going(i))
				break;

			/*
			 * Check the home 
			 */

			targ = Home(i);
			if (Good_obj(targ)) {
				if (Going(targ)) {
					s_Home(i, new_home(i));
#ifndef STANDALONE
					owner = Owner(i);
					if (Good_owner(owner) &&
					    !Quiet(i) && !Quiet(owner)) {
						notify(owner,
						       tprintf("Home reset on %s(#%d)",
							       Name(i), i));
					}
#else
					Log_header_err(i, Location(i), targ, 1,
						       "Home",
						     "is invalid.  Reset.");
#endif
				}
			} else if (targ != NOTHING) {
				Log_header_err(i, Location(i), targ, 1,
					   "Home", "is invalid.  Cleared.");
				s_Home(i, 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 (Good_obj(targ)) {
				if (Going(targ)) {
					s_Dropto(i, NOTHING);
#ifndef STANDALONE
					owner = Owner(i);
					if (Good_owner(owner) &&
					    !Quiet(i) && !Quiet(owner)) {
						notify(owner,
						       tprintf("Dropto removed from %s(#%d)",
							       Name(i), i));
					}
#else
					Log_header_err(i, NOTHING, targ, 1,
						       "Dropto",
						   "is invalid.  Removed.");
#endif
				}
			} else if ((targ != NOTHING) && (targ != HOME)) {
				Log_header_err(i, NOTHING, targ, 1,
					 "Dropto", "is invalid.  Cleared.");
				s_Dropto(i, 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) {
				/*
				 * null case, HOME is 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;
		fp = fwdlist_get(i);
		if (fp) {
			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);
#ifndef STANDALONE
			halt_que(NOTHING, i);
#endif
			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);
#ifndef STANDALONE
				halt_que(NOTHING, i);
#endif
				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");
				}
			}
		}
	}
}

/*
 * ---------------------------------------------------------------------------
 * * 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 != 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, "",
				     "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);
	}
}

/*
 * ---------------------------------------------------------------------------
 * * mark_place, check_floating: Look for floating rooms not set FLOATING.
 */

static void mark_place(loc)
dbref loc;
{
	dbref exit;

	/*
	 * If already marked, exit.  Otherwise set marked. 
	 */

	if (!Good_obj(loc))
		return;
	if (Marked(loc))
		return;
	Mark(loc);

	/*
	 * Visit all places you can get to via exits from here. 
	 */

	for (exit = Exits(loc); exit != NOTHING; exit = Next(exit)) {
		if (Good_obj(Location(exit)))
			mark_place(Location(exit));
	}
}

static NDECL(void check_floating)
{
	dbref owner, i;

	/*
	 * Mark everyplace you can get to via exits from the starting room 
	 */

	Unmark_all(i);
	mark_place(mudconf.start_room);

	/*
	 * Look for rooms not marked and not set FLOATING 
	 */

	DO_WHOLE_DB(i) {
		if (isRoom(i) && !Floating(i) && !Going(i) && !Marked(i)) {
			owner = Owner(i);
#ifndef STANDALONE
			if (Good_owner(owner)) {
				notify(owner, tprintf(
					 "You own a floating room: %s(#%d)",
							     Name(i), i));
			}
#else
			Log_simple_err(i, NOTHING, "Disconnected room.");
#endif
		}
	}
}

/*
 * ---------------------------------------------------------------------------
 * * 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();
	check_dead_refs();
	check_exit_chains();
	check_contents_chains();
	check_floating();
	purge_going();
#ifndef STANDALONE
	if (player != NOTHING) {
		alarm(next_timer());
		if (!Quiet(player))
			notify(player, "Done.");
	}
#endif
}