pennmush/game/
pennmush/game/data/
pennmush/game/log/
pennmush/game/save/
pennmush/game/txt/evt/
pennmush/game/txt/nws/
pennmush/os2/
/* destroy.c */
#include "config.h"

#include <ctype.h>
#include <assert.h>
#include <stdlib.h>
#ifdef I_STRING
#include <string.h>
#else
#include <strings.h>
#endif

#include "copyrite.h"
#include "conf.h"
#include "mushdb.h"
#include "match.h"
#include "externs.h"
#include "globals.h"
#include "confmagic.h"

/* These lengthy comments are by Ralph Melton, December 1995. */
/*
 * First, a discourse on the theory of how we handle destruction.
 *
 * We want to maintain the following invariants:
 * 1. All destroyed objects are on the free list. (linked through the next
 *    fields.)
 * 2. All objects on the free list are destroyed objects.
 * 3. No undestroyed object has its next, contents, location, or home
 *    field pointing to a destroyed object.
 * 4. No object's zone or parent is a destroyed object.
 * 5. No object's owner is a destroyed object.
 * 
 * For the sake of efficiency, we allow indirect locks and other locks to
 * refer to destroyed objects; boolexp.c had better be able to cope with
 * these.
 *
 *
 * There are three logically distinct parts to destroying an object:
 *
 * Part 1: we do all the permissions checks, check for the SAFE flag
 * and the override switch, and decide that yes, we are going to destroy
 * this object.
 *
 * Part 2: we eliminate all the links from other objects in the database
 * to this object. This processing may depend on the object's type.
 *
 * Part 3: (logically concurrent with part 2, and must happen together
 * with part 2) Remove any commands the object may have in the queue,
 * free all the storage associated with the object, set the name to
 * 'Garbage', and set this object to be a destroyed object, and put it
 * on the free list. This process is independent of object type.
 *
 * Note that phases 2 and 3 do not have to happen immediately after Phase 1.
 * To allow some delay, we set the object GOING, and then process it on
 * the check that happens every ten minutes. 
 */

/*
 * This file has two main parts. One part is the functions for destroying
 * objects and getting objects off of the free list. The major public
 * functions here are do_destroy(), free_get(), and purge().
 *
 * The other part is functions for checking the consistency of the
 * database, and repairing any inconsistencies that are found. The
 * major function in this group is dbck().
 */

dbref first_free = NOTHING;

void do_destroy _((dbref player, char *name, int confirm));
void do_undestroy _((dbref player, char *name));
dbref free_get _((void));
void fix_free_list _((void));
void purge _((void));
void do_purge _((dbref player));

void dbck _((void));
void do_dbck _((dbref player));

static dbref what_to_destroy _((dbref player, char *name, int confirm));

static void pre_destroy _((dbref player, dbref thing));
void undestroy _((dbref player, dbref thing));
static void free_object _((dbref thing));
static void empty_contents _((dbref thing));
static void clear_thing _((dbref thing));
static void clear_player _((dbref thing));
static void clear_room _((dbref thing));
static void clear_exit _((dbref thing));

static void check_fields _((void));
static void check_connected_rooms _((void));
static void mark_connected _((dbref loc));
static void check_connected_marks _((void));
static void mark_contents _((dbref loc));
static void check_contents _((void));
static void check_locations _((void));

extern void remove_all_obj_chan _((dbref thing));
extern void chan_chownall _((dbref old, dbref new));

/* Section I: do_destroy() and related functions. This section is where
 * the human interface of do_destroy() should largely be determined.
 */

/* Methinks it's time to consider human interfaces criteria for destruction
 * as well as to consider the invariants that need to be maintained.
 *
 * My major criteria are these (with no implied ranking, since I haven't
 * decided how they balance out):
 * 
 * 1) It's easy to destroy things you intend to destroy.
 * 
 * 2) It's easy to correct from destroying things that you don't intend
 * to destroy. This includes both typos and realizing that you didn't mean
 * to destroy that. This principle requires two sub-principles:
 *      a) The player gets notified when something 'important' gets
 *         marked to be destroyed--and gets told .what. is marked.
 *      b) The actual destruction of the important thing is delayed
 *         long enough that you can recover from it.
 *
 * 3) You can't destroy something you don't have the proper privileges to
 * destroy. (Obvious, but still worth writing down.)
 *
 * To try to achieve a reasonable balance between items 1) and 2), we
 * have the following design:
 * Everything is finally destroyed on the second purge after the @destroy
 * command is done, unless it is set !GOING in the meantime.
 * @destroying an object while it is set GOING destroys it immediately.
 *
 * Let me introduce a little jargon for this discussion:
 *      pre-destroying an object == setting it GOING, running the @adestroy.
 *              (Pre-destroying corresponds to phase 1 above.)
 *      purging an object == actually irrevocably making it vanish.
 *              (This corresponds to phases 2 and 3 above.)
 *      undestroying an object == setting it !GOING, etc.
 *
 * We would also like to have an @adestroy attribute that contains
 * code to be executed when the object is destroyed. This is
 * complicated by the fact that the object is going to be
 * destroyed. To work around this, we run the @adestroy when the
 * object is pre-destroyed, not when it's actually purged. This
 * introduces the possibility that the adestroy may be invoked for
 * something that is then undestroyed. To compensate for that, we run
 * the @startup attribute when something is undestroyed.
 *
 * Another issue is how to run the @adestroy for objects that are
 * destroyed as a consequence of other objects being destroyed. For
 * example, when rooms are destroyed, any exits leading from those
 * rooms are also destroyed, and when a player is destroyed, !SAFE
 * objects they own may also be destroyed.
 * 
 * To handle this, we do the following:
 * pre-destroying a room pre-destroys all its exits.
 * pre-destroying a player pre-destroys all the objects that will be purged
 * when that player is purged.
 *
 * This requires the following about undestroys:
 * undestroying an exit undestroys its source room.
 * undestroying any object requires undestroying its owner.
 *
 * But it also seems to require the following in order to make '@destroy
 * foo; @undestroy foo' a no-op for all foo:
 * undestroying a room undestroys all its exits.
 * undestroying a player undestroys all its GOING things.
 * 
 * Now, consider this scenario:
 * Player A owns room #1. Player B owns exit #2, whose source is room #1.
 * Player B owns thing #3. Player A and player B are both pre-destroyed;
 * none of the objects are set SAFE. Thing #3 is then undestroyed.
 * 
 * If you trace through the dependencies, you find that this involves
 * undestroying all the objects, including both players! Is that what
 * we want? It seems to me that it would be very surprising in practice.
 * 
 * To reconcile this, we introduce the following compromise.
 * undestroying a room undestroys all exits in the room that are not owned
 *      by a GOING player or set SAFE..
 * undestroying a player undestroys all objects he owns that are not exits
 *      in a GOING room that he does not own.
 * 
 * In this way, the propagation of previous scenario would die out at exit
 * #2, which would stay GOING. Metaphorically, there are two 'votes' for
 * its destruction: the destruction of room #1, and the destruction of
 * player B. Undestroying player B by undestroying thing #3 removes one
 * of the 'votes' for exit #2's destruction, but there would still be
 * the vote from room #1.  
 */


static dbref
what_to_destroy(player, name, confirm)
    dbref player;
    char *name;
    int confirm;

/* Do all matching and permissions checking. Returns the object to be
 * destroyed if all the permissions checks are successful, otherwise
 * return NOTHING.
 */
{
  dbref thing;

  thing = noisy_match_result(player, name, NOTYPE, MAT_EVERYTHING);
  if (thing == NOTHING)
    return NOTHING;

  if (Destroyed(thing)) {
    notify(player, "Destroying that again is hardly necessary.");
    return NOTHING;
  }
  /* To destroy, you must either:
   * 1. Own it (and control it)
   * 2. Be a Wizard
   * 3. Control its source or destination if it's an exit
   * 4. Be dealing with a dest-ok thing
   */
  if (!(Owns(player, thing) && controls(player, thing)) &&
      !Wizard(player) &&
      !((Typeof(thing) == TYPE_EXIT) &&
	(controls(player, Destination(thing)) ||
	 controls(player, Source(thing)))) &&
      !DestOk(thing)) {
    notify(player, "Permission denied.");
    return NOTHING;
  }
  if (thing == PLAYER_START || thing == MASTER_ROOM || God(thing)) {
    notify(player, "That is too special to be destroyed.");
    return NOTHING;
  }
  if (REALLY_SAFE) {
    if (Safe(thing) && !DestOk(thing)) {
      notify(player, "That object is set SAFE. You must set it !SAFE before destroying it.");
      return NOTHING;
    }
  } else {			/* REALLY_SAFE */
    if (Safe(thing) && !DestOk(thing) && !confirm) {
      notify(player, "That object is marked SAFE. Use @nuke to destroy it.");
      return NOTHING;
    }
  }
  /* check to make sure there's no accidental destruction */
  if (!confirm && Wizard(player) && !Owns(player, thing) &&
      !DestOk(thing)) {
    notify(player,
	 "That object does not belong to you. Use @nuke to destroy it.");
    return NOTHING;
  }
  /* what kind of thing we are destroying? */
  switch (Typeof(thing)) {
  case TYPE_PLAYER:
    if (Typeof(player) != TYPE_PLAYER) {
      notify(player, "Programs don't kill people; people kill people!");
      return NOTHING;
    }
    if (!Wizard(player) && (Owner(thing) == thing)) {
      notify(player, "Sorry, no suicide allowed.");
      return NOTHING;
    }
    if (Wizard(thing) && !God(player)) {
      notify(player, "Even you can't do that!");
      return NOTHING;
    }
    if (God(player) && player == thing) {
      notify(player, "Sorry, God cannot destroy himself.");
      return NOTHING;
    }
    if (Connected(thing)) {
      notify(player, "How gruesome. You may not destroy players who are connected.");
      return NOTHING;
    }
    if (!confirm) {
      notify(player, "You must use @nuke to destroy a player.");
      return NOTHING;
    }
    break;
  case TYPE_THING:
    if (!confirm && Wizard(thing)) {
      notify(player,
	 "That object is set WIZARD. You must use @nuke to destroy it.");
      return NOTHING;
    }
    if (thing == Home(player) && (Typeof(player) != TYPE_EXIT)) {
      notify(player, "No home-wrecking allowed! Relink yourself.");
      return NOTHING;
    }
    break;
  case TYPE_ROOM:
    if (thing == Home(player) && (Typeof(player) != TYPE_EXIT)) {
      notify(player, "No home-wrecking allowed! Relink first.");
      return NOTHING;
    }
    break;
  case TYPE_EXIT:
    break;
  }
  return thing;

}


void
do_destroy(player, name, confirm)
    dbref player;
    char *name;
    int confirm;
{
  dbref thing;
  thing = what_to_destroy(player, name, confirm);
  if (!GoodObject(thing))
    return;

  /* If thing has already been marked for destruction, go ahead and
   * destroy immediately.
   */
  if (Going(thing)) {
    free_object(thing);
    notify(player, "Destroyed.");
    return;
  }
  /* Present informative messages. */
  switch (Typeof(thing)) {
  case TYPE_ROOM:
    /* wait until dbck */
    notify_except(Contents(thing), NOTHING,
		  "The room shakes and begins to crumble.");
    if (Owns(player, thing))
      notify(player,
	     tprintf("You will be rewarded shortly for %s.",
		     object_header(player, thing)));
    else {
      notify(player, tprintf("The wrecking ball is on its way for %s's %s and its exits.",
		      Name(Owner(thing)), object_header(player, thing)));
      notify(Owner(thing),
	     tprintf("%s has scheduled your room %s to be destroyed.",
		     Name(player), object_header(Owner(thing), thing)));
    }
    break;
  case TYPE_PLAYER:
    /* wait until dbck */
    notify(thing, tprintf("%s has scheduled your character for destruction.",
			  Name(player)));
    notify(player, tprintf("%s and all their objects are scheduled to be destroyed.",
			   object_header(player, thing)));
    break;
  case TYPE_THING:
    if (!Owns(player, thing) && !DestOk(thing)) {
      notify(Owner(thing), tprintf("%s has scheduled your %s for destruction.",
				   Name(player),
				   object_header(Owner(thing), thing)));
      notify(player, tprintf("%s's %s is scheduled to be destroyed.",
			     Name(Owner(thing)),
			     object_header(player, thing)));
    } else if (!Owns(player, thing)) {
      notify(player, tprintf("%s's %s is scheduled to be destroyed.",
			     Name(Owner(thing)),
			     object_header(player, thing)));
    } else {
      notify(player, tprintf("%s is scheduled to be destroyed.",
			     object_header(player, thing)));
    }
    break;
  case TYPE_EXIT:
    if (!Owns(player, thing)) {
      notify(Owner(thing), tprintf("%s has scheduled your %s for destruction.",
				   Name(player),
				   object_header(Owner(thing), thing)));
      notify(player, tprintf("%s's %s is scheduled to be destroyed.",
			     Name(Owner(thing)),
			     object_header(player, thing)));
    } else
      notify(player, tprintf("%s is scheduled to be destroyed.",
			     object_header(player, thing)));
    break;
  default:
    do_log(LT_ERR, NOTHING, NOTHING, "Surprising type in do_destroy.");
    return;
  }

  pre_destroy(player, thing);
  return;
}


/* Not undestroy, quite--it's actually 'remove it from its status as about
 * to be destroyed.'
 */
void
do_undestroy(player, name)
    dbref player;
    char *name;
{
  dbref thing;
  thing = noisy_match_result(player, name, NOTYPE, MAT_EVERYTHING);
  if (!GoodObject(thing)) {
    return;
  }
  if (!controls(player, thing)) {
    notify(player, "Alas, your efforts of mercy are in vain.");
    return;
  }
  undestroy(player, thing);
  notify(Owner(thing),
	 tprintf("Your %s has been spared from destruction.",
		 object_header(Owner(thing), thing)));
  if (player != Owner(thing)) {
    notify(player,
	   tprintf("%s has been spared from destruction.",
		   object_header(player, thing)));
  }
}



/* Section II: Functions that manage the actual work of destroying
 * Objects.
 */

/* Schedule something to be destroyed, run @adestroy, etc. */
static void
pre_destroy(player, thing)
    dbref player;
    dbref thing;
{
  dbref tmp;
  if (Going(thing) || Destroyed(thing)) {
    /* we've already covered this thing. No need to do so again. */
    return;
  }
  Flags(thing) |= GOING;
  Flags(thing) &= ~GOING_TWICE;

  /* Present informative messages, and do recursive destruction. */
  switch (Typeof(thing)) {
  case TYPE_ROOM:
    DOLIST(tmp, Exits(thing)) {
      pre_destroy(player, tmp);
    }
    break;
  case TYPE_PLAYER:
    if (DESTROY_POSSESSIONS) {
      for (tmp = 0; tmp < db_top; tmp++) {
	if (Owner(tmp) == thing &&
	    (tmp != thing) && (!REALLY_SAFE || !Safe(thing))
	  ) {
	  pre_destroy(player, tmp);
	}
      }
    }
    break;
  case TYPE_THING:
    break;
  case TYPE_EXIT:
    /* This is the only case in which we might end up destroying something
     * whose owner hasn't already been notified. */
    if ((Owner(thing) != Owner(Source(thing))) &&
	Going(Source(thing))) {
      if (!Owns(player, thing)) {
	notify(Owner(thing), tprintf("%s has scheduled your %s for destruction.",
				     Name(player),
				     object_header(Owner(thing), thing)));
      }
    }
    break;
  default:
    do_log(LT_ERR, NOTHING, NOTHING, "Surprising type in pre_destroy.");
    return;
  }

  if (ADESTROY_ATTR)
    did_it(player, thing, NULL, NULL, NULL, NULL, "ADESTROY", NOTHING);

  return;

}


/* Not undestroy, quite--it's actually 'remove it from its status as about
 * to be destroyed.'
 */
void
undestroy(player, thing)
    dbref player;
    dbref thing;
{
  dbref tmp;
  if (!Going(thing)) {
    return;
  }
  Flags(thing) &= ~GOING;
  Flags(thing) &= ~GOING_TWICE;
  if (Startup(thing) && !Halted(thing)) {
    ATTR *s;
    char *r;
    s = atr_get_noparent(thing, "STARTUP");
    if (s) {
      r = safe_uncompress(s->value);
      parse_que(thing, r, thing);
      mush_free(r, "safe_uncompress.buff");
    }
  }
  /* undestroy owner, if need be. */
  if (Going(Owner(thing))) {
    if (Owner(thing) != player) {
      notify(player,
	     tprintf("%s has been spared from destruction.",
		     object_header(player, Owner(thing)))
	);
      notify(Owner(thing),
	     tprintf("You have been spared from destruction by %s.",
		     Name(player))
	);
    } else {
      notify(player, "You have been spared from destruction.");
    }
    undestroy(player, Owner(thing));
  }
  switch (Typeof(thing)) {
  case TYPE_PLAYER:
    if (DESTROY_POSSESSIONS)
      /* Undestroy all objects owned by players, except exits that are in
       * rooms owned by other players that are set GOING, since those will
       * be purged when the room is purged.
       */
      for (tmp = 0; tmp < db_top; tmp++) {
	if (Owns(thing, tmp) &&
	    (tmp != thing) &&
	    !(Typeof(tmp) == TYPE_EXIT &&
	      !Owns(thing, Source(tmp)) &&
	      Going(Source(tmp)))) {
	  undestroy(player, tmp);
	}
      }
    break;
  case TYPE_THING:
    break;
  case TYPE_EXIT:
    /* undestroy containing room. */
    if (Going(Source(thing))) {
      undestroy(player, Source(thing));
      notify(player,
	     tprintf("The room %s has been spared from destruction.",
		     object_header(player, Source(thing))));
      if (Owner(Source(thing)) != player) {
	notify(Owner(Source(thing)),
	   tprintf("The room %s has been spared from destruction by %s.",
		   object_header(Owner(Source(thing)), Source(thing)))
	  );
      }
    }
    break;
  case TYPE_ROOM:
    /* undestroy exits in this room, except exits that are going to be
       * destroyed anyway due to a GOING player.
     */
    DOLIST(tmp, Exits(thing)) {
      if (DESTROY_POSSESSIONS ? (!Going(Owner(tmp)) || Safe(tmp)) : 1) {
	undestroy(player, tmp);
      }
    }
    break;
  default:
    do_log(LT_ERR, NOTHING, NOTHING, "Surprising type in un_destroy.");
    return;
  }
}


static void
free_object(thing)
    dbref thing;
/* Does the real work of freeing all the memory and unlinking an object. */
/* This is going to have to be very tightly coupled with the implementation;
 * if the database format changes, this will likely have to change too.
 */
{
  dbref i;
#ifdef LOCAL_DATA
  local_data_free(thing);
#endif
  switch (Typeof(thing)) {
  case TYPE_THING:
    clear_thing(thing);
    break;
  case TYPE_PLAYER:
    clear_player(thing);
    break;
  case TYPE_EXIT:
    clear_exit(thing);
    break;
  case TYPE_ROOM:
    clear_room(thing);
    break;
  default:
    do_log(LT_ERR, NOTHING, NOTHING, "Unknown type on #%d in free_object.", thing);
    return;
  }
#ifdef QUOTA
  change_quota(Owner(thing), QUOTA_COST);
#endif				/* QUOTA */
  do_halt(thing, "", thing);
  nfy_que(thing, 2, 0);		/* The equivalent of an @drain. */

  /* if something is zoned or parented or linked or chained or located
   * to/in destroyed object, undo */
  for (i = 0; i < db_top; i++) {
    if (Zone(i) == thing) {
      Zone(i) = NOTHING;
    }
    if (Parent(i) == thing) {
      Parent(i) = NOTHING;
    }
    if (Home(i) == thing) {
      switch (Typeof(i)) {
      case TYPE_PLAYER:
      case TYPE_THING:
	Home(i) = PLAYER_START;
	break;
      case TYPE_EXIT:
	/* Huh.  An exit that claims to be from here, but wasn't linked
	 * in properly. */
	do_rawlog(LT_ERR, "ERROR: Exit %s leading from invalid room #%d destroyed.",
		  real_unparse(GOD, i, 0), thing);
	free_object(i);
	break;
      case TYPE_ROOM:
	/* Hrm.  It claims we're an exit from it, but we didn't agree.
	 * Clean it up anyway. */
	do_log(LT_ERR, NOTHING, NOTHING,
	       "Found a destroyed exit #%d in room #%d", thing, i);
	break;
      }
    }
    /* The location check MUST be done AFTER the home check. */
    if (Location(i) == thing) {
      switch (Typeof(i)) {
      case TYPE_PLAYER:
      case TYPE_THING:
	/* Huh.  It thought it was here, but we didn't agree. */
	enter_room(i, Home(i));
	break;
      case TYPE_EXIT:
	/* If our destination is destroyed, then we relink to the
	 * source room (so that the exit can't be stolen). Yes, it's
	 * inconsistent with the treatment of exits leading from
	 * destroyed rooms, but it's a lot better than turning exits
	 * into nasty limbo exits.
	 */
	Destination(i) = Source(i);
	break;
      case TYPE_ROOM:
	/* Just remove a dropto. */
	Location(i) = NOTHING;
	break;
      }
    }
    if (Next(i) == thing) {
      Next(i) = NOTHING;
    }
  }

  /* chomp chomp */
  atr_free(thing);
  db[thing].list = NULL;
  /* don't eat name otherwise examine will crash */

  free_locks(Locks(thing));
  Locks(thing) = NULL;

  s_Pennies(thing, 0);
  Owner(thing) = GOD;
  db[thing].parent = NOTHING;
  Zone(thing) = NOTHING;
  Flags(thing) = TYPE_GARBAGE;
#ifdef CHAT_SYSTEM
  remove_all_obj_chan(thing);
#endif
  Location(thing) = NOTHING;
  SET(Name(thing), "Garbage");
  Exits(thing) = NOTHING;

  Next(thing) = first_free;
  first_free = thing;

#ifdef HAS_ASSERT
  assert(Destroyed(thing));
#endif
}

static void
empty_contents(thing)
    dbref thing;
{
  /* Destroy any exits they may be carrying, send everything else home. */
  dbref first;
  dbref rest;
  notify_except(Contents(thing), NOTHING,
		"The floor disappears under your feet, you fall through NOTHINGness and then:");
  first = Contents(thing);
  Contents(thing) = NOTHING;
  /* send all objects to nowhere */
  DOLIST(rest, first) {
    Location(rest) = NOTHING;
  }
  /* now send them home */
  while (first != NOTHING) {
    rest = Next(first);
    /* if home is in thing set it to limbo */
    switch (Typeof(first)) {
    case TYPE_EXIT:		/* if holding exits, destroy it */
      free_object(first);
      break;
    case TYPE_THING:		/* move to home */
    case TYPE_PLAYER:
      if (Home(first) == thing || Typeof(Home(first)) == TYPE_EXIT)
	Home(first) = PLAYER_START;
      if (Home(first) != NOTHING) {
	PUSH(first, Contents(Home(first)));
	Location(first) = Home(first);
	/* notify players they have been moved */
	if (Hearer(first)) {
	  enter_room(first, HOME);
	}
      }
      break;
    }
    first = rest;
  }
}

static void
clear_thing(thing)
    dbref thing;
{
  dbref loc;
  int a;
  /* Remove object from room's contents */
  loc = Location(thing);
  if (loc != NOTHING) {
    Contents(loc) = remove_first(Contents(loc), thing);
  }
  /* give player money back */
  giveto(db[thing].owner, (a = OBJECT_DEPOSIT(Pennies(thing))));
  empty_contents(thing);
  if (Toggles(thing) & THING_PUPPET)
    Toggles(thing) &= ~THING_PUPPET;
  if (!Quiet(thing) && !Quiet(Owner(thing)))
    notify(Owner(thing),
	   tprintf("You get your %d %s deposit back for %s.",
		   a, ((a == 1) ? MONEY : MONIES),
		   object_header(Owner(thing), thing)));
}

static void
clear_player(thing)
    dbref thing;
{
  dbref i;
  ATTR *atemp;
  char alias[PLAYER_NAME_LIMIT];

  /* Clear out mail. */
#ifdef USE_MAILER
  do_mail_clear(thing, NULL);
  do_mail_purge(thing);
#endif

  /* Chown any chat channels they own to God */
#ifdef CHAT_SYSTEM
  chan_chownall(thing, GOD);
#endif

  /* Clear out names from the player list */
  delete_player(thing, NULL);
  if ((atemp = atr_get_noparent(thing, "ALIAS")) != NULL) {
    strcpy(alias, uncompress(atemp->value));
    delete_player(thing, alias);
  }
  /* Do all the thing-esque manipulations. */
  clear_thing(thing);

  /* Deal with objects owned by the player. */
  for (i = 0; i < db_top; i++) {
    if (Owner(i) == thing && i != thing) {
      if (DESTROY_POSSESSIONS ? (REALLY_SAFE ? Safe(i) : 0) : 1) {
	chown_object(GOD, i, GOD);
      } else {
	free_object(i);
      }
    }
  }
}

static void
clear_room(thing)
    dbref thing;
{
  dbref first, rest;
  /* give player money back */
  giveto(Owner(thing), ROOM_COST);
  empty_contents(thing);
  /* Remove exits */
  first = Exits(thing);
  Exits(thing) = NOTHING;
  /* set destination of all exits to nothing */
  DOLIST(rest, first) {
    Destination(rest) = NOTHING;
  }
  /* Clear all exits out of exit list */
  while (first != NOTHING) {
    rest = Next(first);
    if (Typeof(first) == TYPE_EXIT) {
      free_object(first);
    }
    first = rest;
  }
}


static void
clear_exit(thing)
    dbref thing;
{
  dbref loc;
  loc = Source(thing);
  if (GoodObject(loc)) {
    Exits(loc) = remove_first(Exits(loc), thing);
  };
  giveto(Owner(thing), EXIT_COST);
}


/* return a cleaned up object off the free list or NOTHING */
dbref
free_get()
{
  dbref newobj;
  if (first_free == NOTHING)
    return (NOTHING);
  newobj = first_free;
  first_free = Next(first_free);
  /* Make sure this object really should be in free list */
  if (!Destroyed(newobj)) {
    static int nrecur = 0;
    dbref temp;
    if (nrecur++ == 20) {
      first_free = NOTHING;
      report();
      do_rawlog(LT_ERR, "ERROR: Removed free list and continued\n");
      return (NOTHING);
    }
    report();
    do_rawlog(LT_TRACE, "ERROR: Object #%d should not be free\n", newobj);
    do_rawlog(LT_TRACE, "ERROR: Corrupt free list, fixing\n");
    fix_free_list();
    temp = free_get();
    nrecur--;
    return (temp);
  }
  /* free object name */
  SET(Name(newobj), NULL);
  return (newobj);
}

/* Build the free list with a sledgehammer. Only do this when it's actually
 * necessary.
 * Since we only do it if things are corrupted, we do not free any memory.
 * Presumably, this will only waste a reasonable amount of memory, since
 * it's only called in exceptional cases.
 */
void
fix_free_list()
{
  dbref thing;
  first_free = NOTHING;
  for (thing = 0; thing < db_top; thing++) {
    if (Destroyed(thing)) {
      Next(thing) = first_free;
      first_free = thing;
    }
  }
}



/* Destroy all the objects we said we would destroy later. */
void
purge()
{
  dbref thing;
  for (thing = 0; thing < db_top; thing++) {
    if (Destroyed(thing)) {
      continue;
    } else if (Going(thing)) {
      if (Going_Twice(thing)) {
	free_object(thing);
      } else {
	Flags(thing) |= GOING_TWICE;
      }
    } else {
      continue;
    }
  }
}


/* Destroy objects slated for destruction. */
void
do_purge(player)
    dbref player;
{
  if (Wizard(player)) {
    purge();
    notify(player, "Purge complete.");
  } else
    notify(player, "Sorry, you are a mortal.");
}



/* Section III: dbck() and related functions. */

/* The regular db checkup. */
void
dbck()
{
  check_fields();
  check_contents();
  check_locations();
  check_connected_rooms();
}

/* Do sanity checks on non-destroyed objects. */
static void
check_fields()
{
  dbref thing;
  for (thing = 0; thing < db_top; thing++) {
    if (Destroyed(thing)) {
      /* The only relevant thing is that the Next field ought to be pointing
       * to a destroyed object.
       */
      dbref next;
      next = Next(thing);
      if ((!GoodObject(next) || !Destroyed(next)) && (next != NOTHING)) {
	do_rawlog(LT_ERR, "ERROR: Invalid next pointer #%d from object %s",
		  next, real_unparse(GOD, thing, 0));
	Next(thing) = NOTHING;
	fix_free_list();
      }
      continue;
    } else {
      /* Do sanity checks on non-destroyed objects */
      dbref zone, loc, parent, home, owner, next;
      zone = Zone(thing);
      if (GoodObject(zone) && Destroyed(zone)) {
	Zone(thing) = NOTHING;
      }
      parent = Parent(thing);
      if (GoodObject(parent) && Destroyed(parent)) {
	db[thing].parent = NOTHING;
      }
      owner = Owner(thing);
      if (!GoodObject(owner) || Destroyed(owner) ||
	  (Typeof(owner) != TYPE_PLAYER)) {
	do_rawlog(LT_ERR, "ERROR: Invalid object owner on %s(%d)", Name(thing),
		  thing);
	report();
	db[thing].owner = GOD;
      }
      next = Next(thing);
      if ((!GoodObject(next) || Destroyed(next)) && (next != NOTHING)) {
	do_rawlog(LT_ERR, "ERROR: Invalid next pointer #%d from object %s",
		  next, real_unparse(GOD, thing, 0));
	Next(thing) = NOTHING;
      }
      /* This next bit has to be type-specific because of different uses
       * of the home and location fields.
       */
      home = Home(thing);
      loc = Location(thing);
      switch (Typeof(thing)) {
      case TYPE_PLAYER:
      case TYPE_THING:
	if (GoodObject(home) &&
	    (Destroyed(home) ||
	     (Typeof(home) == TYPE_EXIT))) {
	  Home(thing) = PLAYER_START;
	}
	if (GoodObject(loc) &&
	    (Destroyed(loc) ||
	     (Typeof(loc) == TYPE_EXIT))) {
	  enter_room(thing, Home(thing));
	}
	break;
      case TYPE_EXIT:
	if (GoodObject(home) &&
	    (Destroyed(home) ||
	     (Typeof(home) != TYPE_ROOM))) {
	  /* If our source is destroyed, just destroy the exit. */
	  do_rawlog(LT_ERR, "ERROR: Exit %s leading from invalid room #%d destroyed.",
		    real_unparse(GOD, thing, 0), home);
	  free_object(thing);
	}
	if (GoodObject(loc) && Destroyed(loc)) {
	  /* If our destination is destroyed, then we relink to the
	   * source room (so that the exit can't be stolen). Yes, it's
	   * inconsistent with the treatment of exits leading from
	   * destroyed rooms, but it's a lot better than turning exits
	   * into nasty limbo exits.
	   */
	  Destination(thing) = Source(thing);
	}
	break;
      case TYPE_ROOM:
	if (GoodObject(home) && Destroyed(home)) {
	  /* Eww. Destroyed exit. This isn't supposed to happen. */
	  do_log(LT_ERR, NOTHING, NOTHING,
		 "Found a destroyed exit #%d in room #%d", home, thing);
	}
	if (GoodObject(loc) &&
	    (Destroyed(loc) ||
	     (Typeof(loc) == TYPE_EXIT))) {
	  /* Just remove a dropto. */
	  Location(thing) = NOTHING;
	}
	break;
      }
    }
  }
}

static void
check_connected_rooms()
{
  mark_connected(BASE_ROOM);
  check_connected_marks();
}

static void
mark_connected(loc)
    dbref loc;
{
  dbref thing;
  if (!GoodObject(loc) ||
      Marked(loc) ||
      (Typeof(loc) != TYPE_ROOM))
    return;
  Flags(loc) |= MARKED;
  /* recursively trace */
  for (thing = Exits(loc); thing != NOTHING; thing = Next(thing))
    mark_connected(Destination(thing));
}

static void
check_connected_marks()
{
  dbref loc;
  for (loc = 0; loc < db_top; loc++)
    if (Marked(loc))
      Flags(loc) &= ~MARKED;
    else if (Typeof(loc) == TYPE_ROOM) {
      if (!Floating(loc) &&
	  (!EXITS_CONNECT_ROOMS || (Exits(loc) == NOTHING))) {
	if (Name(loc)) {
	  notify(Owner(loc), tprintf("You own a disconnected room, %s",
				     object_header(Owner(loc), loc)));
	} else
	  do_log(LT_ERR, NOTHING, NOTHING, "ERROR: no name for room #%d.", loc);
      }
    }
}


/* An auxiliary function for check_contents. */
static void
mark_contents(thing)
    dbref thing;
{
  /* In this next macro, field must be an lvalue whose evaluation has
   * no side effects.  All hell will break loose if this is not so.
   */
#define CHECK(field)            \
  if ((field) != NOTHING) { \
     if (!GoodObject(field) || Destroyed(field)) { \
       do_rawlog(LT_ERR, "Bad reference #%d from %s severed.", \
                 (field), real_unparse(GOD, thing, 0)); \
       (field) = NOTHING; \
     } else if (Typeof(field) == TYPE_ROOM) { \
       do_rawlog(LT_ERR, "Reference to room #%d from %s severed.", \
                 (field), real_unparse(GOD, thing, 0)); \
       (field) = NOTHING; \
     } else if (Marked(field)) {  \
       do_rawlog(LT_ERR, "Multiple references to %s. Reference from #%d severed.", \
                 real_unparse(GOD, (field), 0), thing); \
       (field) = NOTHING; \
     } else { \
       Flags(field) |= MARKED; \
       mark_contents(field); \
     } \
  }

  if (!GoodObject(thing))
    return;

  Flags(thing) |= MARKED;
  switch (Typeof(thing)) {
  case TYPE_ROOM:
    CHECK(Exits(thing));
    CHECK(Contents(thing));
    break;
  case TYPE_PLAYER:
  case TYPE_THING:
    CHECK(Contents(thing));
    CHECK(Next(thing));
    break;
  case TYPE_EXIT:
    CHECK(Next(thing));
    break;
  default:
    do_rawlog(LT_ERR, "Bad object type found for %s in mark_contents",
	      real_unparse(GOD, thing, 0));
    break;
  }
#undef CHECK
}

/* Check that for every thing, player, and exit, you can trace exactly one
 * path to that object from a room by following the exits field of rooms,
 * the next field of non-rooms, and the contents field of non-exits.
 */
static void
check_contents()
{
  dbref thing;
  for (thing = 0; thing < db_top; thing++) {
    if (Typeof(thing) == TYPE_ROOM) {
      mark_contents(thing);
    }
  }
  for (thing = 0; thing < db_top; thing++) {
    if (!Marked(thing) && Typeof(thing) != TYPE_ROOM && !Destroyed(thing)) {
      do_rawlog(LT_ERR, "Object %s not pointed to by anything.",
		real_unparse(GOD, thing, 0));
      notify(Owner(thing), tprintf("You own an object %s that was \'orphaned\'.",
				   object_header(Owner(thing), thing)));
      /* We try to fix this by trying to send players and things to
       * their current location, to their home, or to PLAYER_START, in
       * that order, and relinking exits to their source.
       */
      Next(thing) = NOTHING;
      switch (Typeof(thing)) {
      case TYPE_PLAYER:
      case TYPE_THING:
	if (GoodObject(Location(thing)) &&
	    !Destroyed(Location(thing)) &&
	    Marked(Location(thing))) {
	  PUSH(thing, Contents(Location(thing)));
	} else if (GoodObject(Home(thing)) &&
		   !Destroyed(Home(thing)) &&
		   Marked(Home(thing))) {
	  Contents(Location(thing)) = remove_first(Contents(Location(thing)), thing);
	  PUSH(thing, Contents(Home(thing)));
	  Location(thing) = Home(thing);
	} else {
	  Contents(Location(thing)) = remove_first(Contents(Location(thing)), thing);
	  PUSH(thing, Contents(PLAYER_START));
	  Location(thing) = PLAYER_START;
	}
	enter_room(thing, Location(thing));
	/* If we've managed to reconnect it, then we've reconnected
	 * its contents. */
	mark_contents(Contents(thing));
	notify(Owner(thing), tprintf("It was moved to %s.",
			  object_header(Owner(thing), Location(thing))));
	do_rawlog(LT_ERR, "Moved to %s.", real_unparse(GOD, Location(thing), 0));
	break;
      case TYPE_EXIT:
	if (GoodObject(Source(thing)) &&
	    !Destroyed(Source(thing)) &&
	    (Typeof(Source(thing)) == TYPE_ROOM)) {
	  PUSH(thing, Exits(Home(thing)));
	  notify(Owner(thing), tprintf("It was moved to %s.",
			    object_header(Owner(thing), Source(thing))));
	  do_rawlog(LT_ERR, "Moved to %s.", real_unparse(GOD, Source(thing), 0));
	} else {
	  /* Just destroy the exit. */
	  Source(thing) = NOTHING;
	  notify(Owner(thing), "It was destroyed.");
	  do_rawlog(LT_ERR, "Orphaned exit destroyed.");
	  free_object(thing);
	}
	break;
      case TYPE_ROOM:
	/* We should never get here. */
	do_log(LT_ERR, NOTHING, NOTHING, "Disconnected room. So what?");
	break;
      default:
	do_log(LT_ERR, NOTHING, NOTHING,
	       "Surprising type on #%d found in check_cycles.", thing);
	break;
      }
    }
  }
  for (thing = 0; thing < db_top; thing++) {
    Flags(thing) &= ~MARKED;
  }
}


/* Check that every player and thing occurs in the contents list of its
 * location, and that every exit occurs in the exit list of its source.
 */
static void
check_locations()
{
  dbref thing;
  dbref loc;
  for (loc = 0; loc < db_top; loc++) {
    if (Typeof(loc) != TYPE_EXIT) {
      for (thing = Contents(loc); thing != NOTHING; thing = Next(thing)) {
	if (!((Typeof(thing) == TYPE_THING) ||
	      (Typeof(thing) == TYPE_PLAYER))) {
	  do_rawlog(LT_ERR,
	     "ERROR: Contents of object %d corrupt at object %d cleared",
		    loc, thing);
	  /* Remove this from the list and start over. */
	  Contents(loc) = remove_first(Contents(loc), thing);
	  thing = Contents(loc);
	  continue;
	} else if (Location(thing) != loc) {
	  /* Well, it would fit here, and it can't be elsewhere because
	   * we've done a check_contents already, so let's just put it
	   * here.
	   */
	  do_rawlog(LT_ERR, "Incorrect location on object %s. Reset to #%d.",
		    real_unparse(GOD, thing, 0), loc);
	  Location(thing) = loc;
	}
	Flags(thing) |= MARKED;
      }
    }
    if (Typeof(loc) == TYPE_ROOM) {
      for (thing = Exits(loc); thing != NOTHING; thing = Next(thing)) {
	if (Typeof(thing) != TYPE_EXIT) {
	  do_rawlog(LT_ERR,
		  "ERROR: Exits of room %d corrupt at object %d cleared",
		    loc, thing);
	  /* Remove this from the list and start over. */
	  Contents(loc) = remove_first(Contents(loc), thing);
	  thing = Contents(loc);
	  continue;
	} else if (Source(thing) != loc) {
	  do_rawlog(LT_ERR,
		    "Incorrect source on exit %s. Reset to #%d.",
		    real_unparse(GOD, thing, 0), loc);
	}
      }
    }
  }


  for (thing = 0; thing < db_top; thing++)
    if (Marked(thing))
      Flags(thing) &= ~MARKED;
    else if (((Typeof(thing) == TYPE_PLAYER) || (Typeof(thing) == TYPE_THING))) {
      do_rawlog(LT_ERR, "ERROR DBCK: Moved object %d", thing);
      moveto(thing, PLAYER_START);
    }
}


/* Do database checkup. */
/* N.B. Unlike before, this is only a user command. The real work is
 * farmed out to the dbck() routine. When a dbck is done automatically,
 * call dbck(), not do_dbck()!
 */
void
do_dbck(player)
    dbref player;
{
  if (!Wizard(player)) {
    notify(player, "Silly mortal chicks are for kids!");
    return;
  }
  notify(player, "GAME: Performing database consistency check.");
  do_log(LT_WIZ, player, NOTHING, "DBCK done.");
  dbck();
  notify(player, "GAME: Database consistency check complete.");
}