pennmush/game/data/
pennmush/game/log/
pennmush/game/save/
pennmush/game/txt/evt/
pennmush/game/txt/nws/
pennmush/os2/
pennmush/po/
pennmush/win32/msvc.net/
pennmush/win32/msvc6/
/**
 * \file destroy.c
 *
 * \brief Destroying objects and consistency checking.
 *
 * 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().
 * 
 *
 * 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. 
 *
 */

#include "config.h"

#include <ctype.h>
#include <assert.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>

#include "copyrite.h"
#include "conf.h"
#include "mushdb.h"
#include "match.h"
#include "externs.h"
#include "log.h"
#include "game.h"
#include "extmail.h"
#include "malias.h"
#include "attrib.h"
#include "dbdefs.h"
#include "flags.h"
#include "lock.h"
#include "confmagic.h"



dbref first_free = NOTHING;   /**< Object at top of free list */

static dbref what_to_destroy(dbref player, char *name, int confirm);
static void pre_destroy(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);
static void check_zones(void);
static int attribute_owner_helper
  (dbref player, dbref thing, dbref parent, char const *pattern, ATTR *atr,
   void *args);

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

extern struct db_stat_info current_state;

/** Mark an object */
#define SetMarked(x)    Type(x) |= TYPE_MARKED
/** Unmark an object */
#define ClearMarked(x)  Type(x) &= ~TYPE_MARKED


/* 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.  
 */


/** Determine what object to destroy and if we're allowed.
 * Do all matching and permissions checking. Returns the object to be
 * destroyed if all the permissions checks are successful, otherwise
 * return NOTHING.
 */
static dbref
what_to_destroy(dbref player, char *name, int confirm)
{
  dbref thing;

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

  if (IsGarbage(thing)) {
    notify(player, T("Destroying that again is hardly necessary."));
    return NOTHING;
  }
  if (God(thing)) {
    notify(player, T("Destroying God would be blasphemous."));
    return NOTHING;
  }
  /* To destroy, you must either:
   * 1. Control it
   * 2. Control its source or destination if it's an exit
   * 3. Be dealing with a dest-ok thing and pass its lock/destroy
   */
  if (!controls(player, thing) &&
      !(IsExit(thing) &&
	(controls(player, Destination(thing)) ||
	 controls(player, Source(thing)))) &&
      !(DestOk(thing) && eval_lock(player, thing, Destroy_Lock))) {
    notify(player, T("Permission denied."));
    return NOTHING;
  }
  if (thing == PLAYER_START || thing == MASTER_ROOM || thing == BASE_ROOM ||
      thing == DEFAULT_HOME || God(thing)) {
    notify(player, T("That is too special to be destroyed."));
    return NOTHING;
  }
  if (REALLY_SAFE) {
    if (Safe(thing) && !DestOk(thing)) {
      notify(player,
	     T
	     ("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, T("That object is marked SAFE. Use @nuke to destroy it."));
      return NOTHING;
    }
  }
  /* check to make sure there's no accidental destruction */
  if (!confirm && !Owns(player, thing) && !DestOk(thing)) {
    notify(player,
	   T("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 (!IsPlayer(player)) {
      notify(player, T("Programs don't kill people; people kill people!"));
      return NOTHING;
    }
    /* The only player a player can own() is themselves...
     * If they somehow manage to own() another player, they can't
     * nuke that one either...which seems like a good plan, although
     * the error message is a bit confusing. -DTC
     */
    if (!Wizard(player)) {
      notify(player, T("Sorry, no suicide allowed."));
      return NOTHING;
    }
    /* Already checked for God(thing), so use Wizard() */
    if (Wizard(thing) && !God(player)) {
      notify(player, T("Even you can't do that!"));
      return NOTHING;
    }
    if (Connected(thing)) {
      notify(player,
	     T("How gruesome. You may not destroy players who are connected."));
      return NOTHING;
    }
    if (!confirm) {
      notify(player, T("You must use @nuke to destroy a player."));
      return NOTHING;
    }
    break;
  case TYPE_THING:
    if (!confirm && Wizard(thing)) {
      notify(player,
	     T("That object is set WIZARD. You must use @nuke to destroy it."));
      return NOTHING;
    }
    break;
  case TYPE_ROOM:
    break;
  case TYPE_EXIT:
    break;
  }
  return thing;

}


/** User interface to destroy an object.
 * \verbatim
 * This is the top-level function for @destroy.
 * \endverbatim
 * \param player the enactor.
 * \param name name of object to destroy.
 * \param confirm if 1, called with /override (or nuke).
 */
void
do_destroy(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, T("Destroyed."));
    return;
  }
  /* Present informative messages. */
  if (!REALLY_SAFE && Safe(thing))
    notify(player,
	   T
	   ("Warning: Target is set SAFE, but scheduling for destruction anyway."));
  switch (Typeof(thing)) {
  case TYPE_ROOM:
    /* wait until dbck */
    notify_except(Contents(thing), NOTHING,
		  T("The room shakes and begins to crumble."), 0);
    if (Owns(player, thing))
      notify_format(player,
		    T("You will be rewarded shortly for %s."),
		    object_header(player, thing));
    else {
      notify_format(player,
		    T
		    ("The wrecking ball is on its way for %s's %s and its exits."),
		    Name(Owner(thing)), object_header(player, thing));
      notify_format(Owner(thing),
		    T("%s has scheduled your room %s to be destroyed."),
		    Name(player), object_header(Owner(thing), thing));
    }
    break;
  case TYPE_PLAYER:
    /* wait until dbck */
    notify_format(player,
		  (DESTROY_POSSESSIONS ?
		   (REALLY_SAFE ?
		    T
		    ("%s and all their (non-SAFE) objects are scheduled to be destroyed.")
		    :
		    T
		    ("%s and all their objects are scheduled to be destroyed."))
		   : T("%s is scheduled to be destroyed.")),
		  object_header(player, thing));
    break;
  case TYPE_THING:
    if (!Owns(player, thing)) {
      notify_format(player, T("%s's %s is scheduled to be destroyed."),
		    Name(Owner(thing)), object_header(player, thing));
      if (!DestOk(thing))
	notify_format(Owner(thing),
		      T("%s has scheduled your %s for destruction."),
		      Name(player), object_header(Owner(thing), thing));
    } else {
      notify_format(player, T("%s is scheduled to be destroyed."),
		    object_header(player, thing));
    }
    break;
  case TYPE_EXIT:
    if (!Owns(player, thing)) {
      notify_format(Owner(thing),
		    T("%s has scheduled your %s for destruction."),
		    Name(player), object_header(Owner(thing), thing));
      notify_format(player,
		    T("%s's %s is scheduled to be destroyed."),
		    Name(Owner(thing)), object_header(player, thing));
    } else
      notify_format(player,
		    T("%s is scheduled to be destroyed."),
		    object_header(player, thing));
    break;
  default:
    do_log(LT_ERR, NOTHING, NOTHING, T("Surprising type in do_destroy."));
    return;
  }

  pre_destroy(player, thing);
  return;
}


/** Spare an object from slated destruction.
 * \verbatim
 * This is the top-level function for @undestroy.
 * Not undestroy, quite--it's actually 'remove it from its status as about
 * to be destroyed.'
 * \endverbatim
 * \param player the enactor.
 * \param name name of object to be spared.
 */
void
do_undestroy(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, T("Alas, your efforts of mercy are in vain."));
    return;
  }
  if (undestroy(player, thing)) {
    notify_format(Owner(thing),
		  T("Your %s has been spared from destruction."),
		  object_header(Owner(thing), thing));
    if (player != Owner(thing)) {
      notify_format(player,
		    T("%s's %s has been spared from destruction."),
		    Name(Owner(thing)), object_header(player, thing));
    }
  } else {
    notify(player, T("That can't be undestroyed."));
  }
}



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

/* Schedule something to be destroyed, run @adestroy, etc. */
static void
pre_destroy(dbref player, dbref thing)
{
  dbref tmp;
  if (Going(thing) || IsGarbage(thing)) {
    /* we've already covered this thing. No need to do so again. */
    return;
  }
  set_flag_internal(thing, "GOING");
  clear_flag_internal(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_format(Owner(thing),
		      T("%s has scheduled your %s for destruction."),
		      Name(player), object_header(Owner(thing), thing));
      }
    }
    break;
  default:
    do_log(LT_ERR, NOTHING, NOTHING, T("Surprising type in pre_destroy."));
    return;
  }

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

  return;

}


/** Spare an object from destruction.
 * Not undestroy, quite--it's actually 'remove it from its status as about
 * to be destroyed.' This is the internal function used in hardcode.
 * \param player the enactor.
 * \param thing dbref of object to be spared.
 * \return 1 successful undestruction.
 * \return 0 thing is not a valid object to undestroy.
 */
int
undestroy(dbref player, dbref thing)
{
  dbref tmp;
  if (!Going(thing) || IsGarbage(thing)) {
    return 0;
  }
  clear_flag_internal(thing, "GOING");
  clear_flag_internal(thing, "GOING_TWICE");
  if (!Halted(thing))
    (void) queue_attribute_noparent(thing, "STARTUP", thing);
  /* undestroy owner, if need be. */
  if (Going(Owner(thing))) {
    if (Owner(thing) != player) {
      notify_format(player,
		    T("%s has been spared from destruction."),
		    object_header(player, Owner(thing)));
      notify_format(Owner(thing),
		    T("You have been spared from destruction by %s."),
		    Name(player));
    } else {
      notify(player, T("You have been spared from destruction."));
    }
    (void) 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) &&
	    !(IsExit(tmp) && !Owns(thing, Source(tmp)) && Going(Source(tmp)))) {
	  (void) undestroy(player, tmp);
	}
      }
    break;
  case TYPE_THING:
    break;
  case TYPE_EXIT:
    /* undestroy containing room. */
    if (Going(Source(thing))) {
      (void) undestroy(player, Source(thing));
      notify_format(player,
		    T("The room %s has been spared from destruction."),
		    object_header(player, Source(thing)));
      if (Owner(Source(thing)) != player) {
	notify_format(Owner(Source(thing)),
		      T("The room %s has been spared from destruction by %s."),
		      object_header(Owner(Source(thing)), Source(thing)),
		      Name(player));
      }
    }
    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) {
	(void) undestroy(player, tmp);
      }
    }
    break;
  default:
    do_log(LT_ERR, NOTHING, NOTHING, T("Surprising type in un_destroy."));
    return 0;
  }
  return 1;
}


/* 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.
 */
static void
free_object(dbref thing)
{
  dbref i, loc;
  if (!GoodObject(thing))
    return;
  local_data_free(thing);
  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, T("Unknown type on #%d in free_object."),
	   thing);
    return;
  }
  change_quota(Owner(thing), QUOTA_COST);
  do_halt(thing, "", thing);
  /* The equivalent of an @drain/any/all: */
  dequeue_semaphores(thing, NULL, INT_MAX, 1, 1);

  /* 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) = DEFAULT_HOME;
	break;
      case TYPE_EXIT:
	/* Huh.  An exit that claims to be from here, but wasn't linked
	 * in properly. */
	do_rawlog(LT_ERR,
		  T("ERROR: Exit %s leading from invalid room #%d destroyed."),
		  unparse_object(GOD, i), 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,
	       T("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), 0);
	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);
  List(thing) = NULL;
  /* don't eat name otherwise examine will crash */

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

  s_Pennies(thing, 0);
  Owner(thing) = GOD;
  Parent(thing) = NOTHING;
  Zone(thing) = NOTHING;
  remove_all_obj_chan(thing);

  switch (Typeof(thing)) {
    /* Make absolutely sure we are removed from Location's content or
       exit list. If we are in a room we own and destroy_possessions
       is yes, this can happen, causing much ickyness: All garbage
       items would be in DEFAULT_HOME. */
  case TYPE_PLAYER:
  case TYPE_THING:
    loc = Location(thing);
    if (GoodObject(loc))
      Contents(loc) = remove_first(Contents(loc), thing);
    if (Typeof(thing) == TYPE_THING)
      current_state.things--;
    else
      current_state.players--;
    break;
  case TYPE_EXIT:		/* This probably won't be needed, but lets make sure */
    loc = Source(thing);
    if (GoodObject(loc))
      Exits(loc) = remove_first(Exits(loc), thing);
    current_state.exits--;
    break;
  case TYPE_ROOM:
    current_state.rooms--;
    break;
  default:
    /* Do nothing for rooms. */
    break;
  }

  Type(thing) = TYPE_GARBAGE;
  destroy_flag_bitmask(Flags(thing));
  Flags(thing) = NULL;
  destroy_flag_bitmask(Powers(thing));
  Powers(thing) = NULL;
  Location(thing) = NOTHING;
  set_name(thing, "Garbage");
  Exits(thing) = NOTHING;
  Home(thing) = NOTHING;

  clear_objdata(thing);

  Next(thing) = first_free;
  first_free = thing;

  current_state.garbage++;

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

static void
empty_contents(dbref thing)
{
  /* Destroy any exits they may be carrying, send everything else home. */
  dbref first;
  dbref rest;
  dbref target;
  notify_except(Contents(thing), NOTHING,
		T
		("The floor disappears under your feet, you fall through NOTHINGness and then:"),
		0);
  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:
      /* Make sure the home is a reasonable object. */
      if (!GoodObject(Home(first)) || IsExit(Home(first)) ||
	  Home(first) == thing)
	Home(first) = DEFAULT_HOME;
      target = Home(first);
      /* If home isn't a good place to send it, send it to DEFAULT_HOME. */
      if (!GoodObject(target) || recursive_member(target, first, 0))
	target = DEFAULT_HOME;
      if (target != NOTHING) {
	/* Use enter_room() on everything so that AENTER and such
	 * are all triggered properly. */
	enter_room(first, target, 0);
      }
      break;
    }
    first = rest;
  }
}

static void
clear_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);
  }
  /* Remove object from any following chains */
  clear_followers(thing, 0);
  clear_following(thing, 0);
  /* give player money back */
  giveto(Owner(thing), (a = Pennies(thing)));
  empty_contents(thing);
  clear_flag_internal(thing, "PUPPET");
  if (!Quiet(thing) && !Quiet(Owner(thing)))
    notify_format(Owner(thing),
		  T("You get your %d %s deposit back for %s."),
		  a, ((a == 1) ? MONEY : MONIES),
		  object_header(Owner(thing), thing));
}

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

  /* Clear out mail. */
  do_mail_clear(thing, NULL);
  do_mail_purge(thing);
  malias_cleanup(thing);

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

  /* Clear out names from the player list */
  delete_player(thing, NULL);
  if ((atemp = atr_get_noparent(thing, "ALIAS")) != NULL) {
    strcpy(alias, atr_value(atemp));
    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, 0);
      } else {
	free_object(i);
      }
    }
  }
}

static void
clear_room(dbref thing)
{
  dbref first, rest;
  /* give player money back */
  giveto(Owner(thing), ROOM_COST);
  empty_contents(thing);
  /* Remove exits */
  first = Exits(thing);
  Source(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 (IsExit(first)) {
      free_object(first);
    }
    first = rest;
  }
}


static void
clear_exit(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.
 * \return a garbage object or NOTHING.
 */
dbref
free_get(void)
{
  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 (!IsGarbage(newobj)) {
    static int nrecur = 0;
    dbref temp;
    if (nrecur++ == 20) {
      first_free = NOTHING;
      report();
      do_rawlog(LT_ERR, T("ERROR: Removed free list and continued\n"));
      return (NOTHING);
    }
    report();
    do_rawlog(LT_TRACE, T("ERROR: Object #%d should not be free\n"), newobj);
    do_rawlog(LT_TRACE, T("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(void)
{
  dbref thing;
  first_free = NOTHING;
  for (thing = 0; thing < db_top; thing++) {
    if (IsGarbage(thing)) {
      Next(thing) = first_free;
      first_free = thing;
    }
  }
}



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


/** Destroy objects slated for destruction.
 * \verbatim
 * This is the top-level function for @purge.
 * \endverbatim
 * \param player the enactor.
 */
void
do_purge(dbref player)
{
  if (Wizard(player)) {
    purge();
    notify(player, T("Purge complete."));
  } else
    notify(player, T("Sorry, you are a mortal."));
}



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

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

/* Do sanity checks on non-destroyed objects. */
static void
check_fields(void)
{
  dbref thing;
  for (thing = 0; thing < db_top; thing++) {
    if (IsGarbage(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) || !IsGarbage(next)) && (next != NOTHING)) {
	do_rawlog(LT_ERR, T("ERROR: Invalid next pointer #%d from object %s"),
		  next, unparse_object(GOD, thing));
	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) && IsGarbage(zone))
	Zone(thing) = NOTHING;
      parent = Parent(thing);
      if (GoodObject(parent) && IsGarbage(parent))
	Parent(thing) = NOTHING;
      owner = Owner(thing);
      if (!GoodObject(owner) || IsGarbage(owner) || !IsPlayer(owner)) {
	do_rawlog(LT_ERR, T("ERROR: Invalid object owner on %s(%d)"),
		  Name(thing), thing);
	report();
	Owner(thing) = GOD;
      }
      next = Next(thing);
      if ((!GoodObject(next) || IsGarbage(next)) && (next != NOTHING)) {
	do_rawlog(LT_ERR, T("ERROR: Invalid next pointer #%d from object %s"),
		  next, unparse_object(GOD, thing));
	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) || IsGarbage(home) || IsExit(home))
	  Home(thing) = DEFAULT_HOME;
	if (!GoodObject(loc) || IsGarbage(loc) || IsExit(loc))
	  enter_room(thing, Home(thing), 0);
	break;
      case TYPE_EXIT:
	if (Contents(thing) != NOTHING) {
	  /* Eww.. Exits can't have contents. Bad news */
	  Contents(thing) = NOTHING;
	  do_rawlog(LT_ERR,
		    T("ERROR: Exit %s has a contents list. Wiping it out."),
		    unparse_object(GOD, thing));
	}
	if (!GoodObject(loc)
	    && !((loc == NOTHING) || (loc == AMBIGUOUS) || (loc == HOME))) {
	  /* Bad news. We're linked to a really impossible object.
	   * Relink to our source
	   */
	  Destination(thing) = Source(thing);
	  do_rawlog(LT_ERR,
		    T
		    ("ERROR: Exit %s leading to invalid room #%d relinked to its source room."),
		    unparse_object(GOD, thing), home);
	} else if (GoodObject(loc) && IsGarbage(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);
	  do_rawlog(LT_ERR,
		    T
		    ("ERROR: Exit %s leading to garbage room #%d relinked to its source room."),
		    unparse_object(GOD, thing), home);
	}
	/* This must come last */
	if (!GoodObject(home) || !IsRoom(home)) {
	  /* If our source is destroyed, just destroy the exit. */
	  do_rawlog(LT_ERR,
		    T
		    ("ERROR: Exit %s leading from invalid room #%d destroyed."),
		    unparse_object(GOD, thing), home);
	  free_object(thing);
	}
	break;
      case TYPE_ROOM:
	if (GoodObject(home) && IsGarbage(home)) {
	  /* Eww. Destroyed exit. This isn't supposed to happen. */
	  do_log(LT_ERR, NOTHING, NOTHING,
		 T("Found a destroyed exit #%d in room #%d"), home, thing);
	}
	if (GoodObject(loc) && (IsGarbage(loc) || IsExit(loc))) {
	  /* Just remove a dropto. */
	  Location(thing) = NOTHING;
	}
	break;
      }
      /* Check attribute ownership. If the attribute is owned by
       * an invalid dbref, change its ownership to God.
       */
      if (!IsGarbage(thing))
	atr_iter_get(GOD, thing, "**", 0, attribute_owner_helper, NULL);
    }
  }
}

static int
attribute_owner_helper(dbref player __attribute__ ((__unused__)),
		       dbref thing __attribute__ ((__unused__)),
		       dbref parent __attribute__ ((__unused__)),
		       char const *pattern
		       __attribute__ ((__unused__)), ATTR *atr, void *args
		       __attribute__ ((__unused__)))
{
  if (!GoodObject(AL_CREATOR(atr)))
    AL_CREATOR(atr) = GOD;
  return 0;
}

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

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

static void
check_connected_marks(void)
{
  dbref loc;
  for (loc = 0; loc < db_top; loc++)
    if (!IsGarbage(loc) && Marked(loc))
      ClearMarked(loc);
    else if (IsRoom(loc)) {
      if (!Name(loc)) {
	do_log(LT_ERR, NOTHING, NOTHING, T("ERROR: no name for room #%d."),
	       loc);
	set_name(loc, "XXXX");
      }
      if (!Going(loc) && !Floating(loc) && !NoWarnable(loc) &&
	  (!EXITS_CONNECT_ROOMS || (Exits(loc) == NOTHING))) {
	notify_format(Owner(loc), T("You own a disconnected room, %s"),
		      object_header(Owner(loc), loc));
      }
    }
}

/* Warn about objects without @lock/zone used as zones */
static void
check_zones(void)
{
  dbref n, zone = NOTHING, tmp;
  int zone_depth;

  for (n = 0; n < db_top; n++) {
    if (IsGarbage(n))
      continue;
    zone = Zone(n);
    if (!GoodObject(zone))
      continue;
    if (ZONE_CONTROL_ZMP && !IsPlayer(zone))
      continue;
    if (zone != n)		/* Objects can be zoned to themselves */
      for (zone_depth = MAX_ZONES, tmp = Zone(zone);
	   zone_depth-- && GoodObject(tmp); tmp = Zone(tmp)) {
	if (tmp == n) {
	  notify_format(Owner(n),
			T("You own an object in a circular zone chain: %s"),
			object_header(Owner(n), n));
	  break;
	}
	if (tmp == Zone(tmp))	/* Object zoned to itself */
	  break;
      }

    if (Marked(zone))
      continue;
    if (getlock(zone, Zone_Lock) == TRUE_BOOLEXP)
      SetMarked(zone);
  }

  for (n = 0; n < db_top; n++) {
    if (!IsGarbage(n) && Marked(n)) {
      ClearMarked(n);
      notify_format(Owner(n),
		    T
		    ("You own an object without a @lock/zone being used as a zone: %s"),
		    object_header(Owner(n), n));
    }
  }
}

/** In this macro, field must be an lvalue whose evaluation has
 * no side effects and results in a dbref to be checked.
 * All hell will break loose if this is not so.
 */
#define CHECK(field)            \
  if ((field) != NOTHING) { \
     if (!GoodObject(field) || IsGarbage(field)) { \
       do_rawlog(LT_ERR, "Bad reference #%d from %s severed.", \
                 (field), unparse_object(GOD, thing)); \
       (field) = NOTHING; \
     } else if (IsRoom(field)) { \
       do_rawlog(LT_ERR, "Reference to room #%d from %s severed.", \
                 (field), unparse_object(GOD, thing)); \
       (field) = NOTHING; \
     } else if (Marked(field)) {  \
       do_rawlog(LT_ERR, "Multiple references to %s. Reference from #%d severed.", \
                 unparse_object(GOD, (field)), thing); \
       (field) = NOTHING; \
     } else { \
       SetMarked(field); \
       mark_contents(field); \
     } \
  }

/* An auxiliary function for check_contents. */
static void
mark_contents(dbref thing)
{
  if (!GoodObject(thing) || IsGarbage(thing))
    return;

  SetMarked(thing);
  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, T("Bad object type found for %s in mark_contents"),
	      unparse_object(GOD, thing));
    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(void)
{
  dbref thing;
  for (thing = 0; thing < db_top; thing++) {
    if (IsRoom(thing)) {
      mark_contents(thing);
    }
  }
  for (thing = 0; thing < db_top; thing++) {
    if (!IsRoom(thing) && !IsGarbage(thing) && !Marked(thing)) {
      do_rawlog(LT_ERR, T("Object %s not pointed to by anything."),
		unparse_object(GOD, thing));
      notify_format(Owner(thing),
		    T("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 DEFAULT_HOME, 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)) &&
	    !IsGarbage(Location(thing)) && Marked(Location(thing))) {
	  PUSH(thing, Contents(Location(thing)));
	} else if (GoodObject(Home(thing)) &&
		   !IsGarbage(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(DEFAULT_HOME));
	  Location(thing) = DEFAULT_HOME;
	}
	enter_room(thing, Location(thing), 0);
	/* If we've managed to reconnect it, then we've reconnected
	 * its contents. */
	mark_contents(Contents(thing));
	notify_format(Owner(thing), T("It was moved to %s."),
		      object_header(Owner(thing), Location(thing)));
	do_rawlog(LT_ERR, T("Moved to %s."),
		  unparse_object(GOD, Location(thing)));
	break;
      case TYPE_EXIT:
	if (GoodObject(Source(thing)) && IsRoom(Source(thing))) {
	  PUSH(thing, Exits(Source(thing)));
	  notify_format(Owner(thing), T("It was moved to %s."),
			object_header(Owner(thing), Source(thing)));
	  do_rawlog(LT_ERR, T("Moved to %s."),
		    unparse_object(GOD, Source(thing)));
	} else {
	  /* Just destroy the exit. */
	  Source(thing) = NOTHING;
	  notify(Owner(thing), T("It was destroyed."));
	  do_rawlog(LT_ERR, T("Orphaned exit destroyed."));
	  free_object(thing);
	}
	break;
      case TYPE_ROOM:
	/* We should never get here. */
	do_log(LT_ERR, NOTHING, NOTHING, T("Disconnected room. So what?"));
	break;
      default:
	do_log(LT_ERR, NOTHING, NOTHING,
	       T("Surprising type on #%d found in check_cycles."), thing);
	break;
      }
    }
  }
  for (thing = 0; thing < db_top; thing++) {
    if (!IsGarbage(thing))
      ClearMarked(thing);
  }
}


/* 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(void)
{
  dbref thing;
  dbref loc;
  for (loc = 0; loc < db_top; loc++) {
    if (!IsExit(loc)) {
      for (thing = Contents(loc); thing != NOTHING; thing = Next(thing)) {
	if (!Mobile(thing)) {
	  do_rawlog(LT_ERR,
		    T
		    ("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,
		    T("Incorrect location on object %s. Reset to #%d."),
		    unparse_object(GOD, thing), loc);
	  Location(thing) = loc;
	}
	SetMarked(thing);
      }
    }
    if (IsRoom(loc)) {
      for (thing = Exits(loc); thing != NOTHING; thing = Next(thing)) {
	if (!IsExit(thing)) {
	  do_rawlog(LT_ERR,
		    T("ERROR: Exits of room %d corrupt at object %d cleared"),
		    loc, thing);
	  /* Remove this from the list and start over. */
	  Exits(loc) = remove_first(Exits(loc), thing);
	  thing = Exits(loc);
	  continue;
	} else if (Source(thing) != loc) {
	  do_rawlog(LT_ERR,
		    T("Incorrect source on exit %s. Reset to #%d."),
		    unparse_object(GOD, thing), loc);
	}
      }
    }
  }


  for (thing = 0; thing < db_top; thing++)
    if (!IsGarbage(thing) && Marked(thing))
      ClearMarked(thing);
    else if (Mobile(thing)) {
      do_rawlog(LT_ERR, T("ERROR DBCK: Moved object %d"), thing);
      moveto(thing, DEFAULT_HOME);
    }
}


/** Database checkup, user interface.
 * \verbatim
 * This is the top-level function for @dbck. Automatic checks should
 * call dbck(), not this.
 * \endverbatim
 * \param player the enactor.
 */
void
do_dbck(dbref player)
{
  if (!Wizard(player)) {
    notify(player, T("Silly mortal chicks are for kids!"));
    return;
  }
  notify(player, T("GAME: Performing database consistency check."));
  do_log(LT_WIZ, player, NOTHING, T("DBCK done."));
  dbck();
  notify(player, T("GAME: Database consistency check complete."));
}