/* destroy.c */

#include "copyright.h"
#include "config.h"

#ifdef DESTROY
#include <ctype.h>

#include "db.h"
#include "externs.h"
#include "globals.h"

dbref first_free = NOTHING;

#ifdef DESTROY
void do_destroy(player, name, confirm)
    dbref player;
    char *name;
    int confirm;
{
  dbref thing;
  dbref loc;
  int a;
  
  init_match(player, name, NOTYPE);
  match_everything();
  thing = last_match_result();
  if ((thing != NOTHING) &&
      !controls(player, thing) &&
      !((Typeof(thing) == TYPE_EXIT) &&
        (controls(player,db[thing].location) ||
	 controls(player,db[thing].exits))) &&
      !((db[thing].flags & THING_DEST_OK) &&
        (Typeof(thing) == TYPE_THING))) {
      notify(player, "Permission denied.");
      return;
  }
  if (thing == PLAYER_START || thing == MASTER_ROOM) {
	  notify(player, "Permission denied.");
	  return;
  }
	  
  if (thing == NOTHING)
    thing = match_controlled(player, name);
  if (thing <= 0)		/* I hope no wizard is this stupid but just
				 * in case */
    return;
  /* 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;
      }
      if (!Wizard(player) && (db[thing].owner == thing)) {
	notify(player, "Sorry, no suicide allowed.");
	return;
      }
      if (Wizard(thing) && !God(player)) {
	notify(player, "Even you can't do that!");
	return;
      }
      if(God(player) && player == thing) {
        notify(player, "Sorry, God cannot destroy himself.");
        return;
      }
      if (!confirm) {
	notify(player, "You must use @nuke to destroy a player.");
	return;
      }
      /* bye bye */
      do_halt(thing, "");
      fprintf(stderr, "*** PLAYER NUKED*** %s(#%d) destroyed %s(#%d)\n",
	      db[player].name, player, db[thing].name, thing);
      notify(player, tprintf("You get your 0 %s deposit back.", MONEY));
      boot_off(thing);
      do_chownall(player, name, "");
#ifdef USE_MAILER
      do_mail(thing, "clear", "");
#endif
      delete_player(thing);
      loc = db[thing].location;
      if(loc != NOTHING) {
	db[loc].contents = remove_first(db[loc].contents, thing);
	free_object(thing);
      }
      break;
    case TYPE_THING:
      /* check to make sure there's no accidental destruction */
      if (!confirm && Wizard(player) && !(db[thing].flags & THING_DEST_OK)
	  && (db[thing].owner != player)) {
	notify(player,
          "That object does not belong to you. Use @nuke to destroy it.");
	return;
      }
      if (!confirm && (db[thing].flags & THING_SAFE)) {
	notify(player, "That object is marked SAFE. Use @nuke to destroy it.");
	return;
      }
      if (!confirm && (db[thing].flags & WIZARD)) {
	notify(player,
          "That object is set WIZARD. You must use @nuke to destroy it.");
	return;
      }
      do_halt(thing, "");
      /* give player money back */
      giveto(db[thing].owner, (a = OBJECT_DEPOSIT(Pennies(thing))));
#ifdef QUOTA
      add_quota(db[thing].owner, 1);
#endif /* QUOTA */
      if (db[thing].flags & THING_PUPPET) db[thing].flags &= ~THING_PUPPET;
      if(!Quiet(thing) && !Quiet(db[thing].owner))
        notify(db[thing].owner,
	       tprintf("You get your %d %s deposit back for %s.",
		       a, ((a == 1) ? MONEY : MONIES), db[thing].name));
      notify(player, "Destroyed.");
      loc = db[thing].location;
      if(loc != NOTHING) {
	db[loc].contents = remove_first(db[loc].contents, thing);
	free_object(thing);
      }
      break;
    case TYPE_ROOM:
      if (db[thing].flags & GOING) {
	notify(player, "No use beating a dead room.");
	return;
      }
      /* don't let room be deleted if >2 exits */
      if (((loc = db[thing].exits) != NOTHING) &&
	  ((loc = db[loc].next) != NOTHING) &&
	  ((loc = db[loc].next) != NOTHING)) {
	notify(player,
	   "The room must have less than 3 exits before it can be destroyed.");
	return;
      }
      do_halt(thing, "");
      notify_except(db[thing].contents, 0,
		    "The room shakes and begins to crumble.");
      if (player == db[thing].owner)
	notify(db[thing].owner,
	      tprintf("You will be rewarded shortly for %s(#%d).", 
		      db[thing].name, thing));
      else
	notify(player, tprintf("The wrecking ball is on its way for %s(#%d).",
			       db[thing].name, thing));
      db[thing].flags |= GOING;	/* mark for deletion but don't empty yet */
      return;
    case TYPE_EXIT:
      /* Patched 12/1/90 by Michael Stanley */
      if(db[thing].exits == NOTHING)
	loc = find_entrance(thing);
      else
        loc = db[thing].exits;
      db[loc].exits = remove_first(db[loc].exits, thing);
      giveto(db[thing].owner, EXIT_COST);
#ifdef QUOTA
      add_quota(db[thing].owner, 1);
#endif /* QUOTA */
      do_halt(thing, "");
      notify(db[thing].owner,
	   tprintf("You get your %d %s deposit back for %s.", EXIT_COST,
		   ((EXIT_COST == 1) ? MONEY : MONIES), db[thing].name));
      notify(player, "Destroyed.");
      break;
  }
  do_empty(thing);
}
#endif				/* DESTROY */

/* object must be empty and reference free before being freed */
void free_object(obj)
dbref obj;
{
	db[obj].next = first_free;
	first_free = obj;
}


/*
 * unlink all dead rooms+build new free list.  Must be called whenever
 * database is read from disk.  May also be called at other times to
 * straighten out the free list.
 */
#define CHECK_REF(a) if ((((a)>-1) && (db[a].flags & GOING)) || (a>=db_top)\
  || (a<-3))
/* check for free list corruption */
#define NOT_OK(thing)\
 ((db[thing].location!=NOTHING) || (db[thing].owner!=GOD) ||\
 ((db[thing].flags & ~ACCESSED)!=(TYPE_THING | GOING)))

/* 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 = db[first_free].next;
  /* Make sure this object really should be in free list */
  /* NOTE: A little problem here, a wiz can @teleport an object */
  /* out of the free list, when that object comes up for recycling */
  /* it will be caught here and the free list will be rebuilt. */
  if (NOT_OK(newobj)) {
    static nrecur = 0;
    if (nrecur++ == 20) {
      first_free = NOTHING;
      report();
      fprintf(stderr, "ERROR: Removed free list and continued\n");
      return (NOTHING);
    }
    report();
    fprintf(stderr, "ERROR: Object #%d should not free\n", newobj);
    fprintf(stderr, "ERORR: Corrupt free list, fixing\n");
    FIX;
    nrecur--;
    return (free_get());
  }
  /* free object name */
  SET(db[newobj].name, NULL);
  return (newobj);
}


void fix_free_list()
{
  dbref thing;
  void dbmark();
  void dbunmark();
  first_free = NOTHING;
  /* destroy all rooms+make sure everything else is really dead */
  for (thing = 0; thing < db_top; thing++)
    if (db[thing].flags & GOING) {
      if (Typeof(thing) == TYPE_ROOM)
	do_empty(thing);
      else
	/*
	 * if something other than room, make sure it is located in
	 * NOTHING.  Otherwise undelete it.  Needed in case @tel is
	 * used on an object.
	 */
      if (NOT_OK(thing))
	db[thing].flags &= ~GOING;
    }
  first_free = NOTHING;
  /* check for references to destroyed objects */
  for (thing = 0; thing < db_top; thing++)
    /* if object is alive make sure it doesn't refer to any dead objects */
    if (!(db[thing].flags & GOING)) {
      /* test object home */
      CHECK_REF(db[thing].exits)
	switch (Typeof(thing)) {
	case TYPE_PLAYER:
	case TYPE_THING:
	  db[thing].exits = PLAYER_START;  /* set home to limbo */
	  break;
	case TYPE_ROOM:	/* yuck probably corrupted set to nothing */
	  {
	    fprintf(stderr, "ERROR: Dead exit in exit list for room #%d\n",
		    thing);
	    report();
	    db[thing].exits = NOTHING;
	  }
      }
      CHECK_REF(db[thing].location)
	switch (Typeof(thing)) {
	case TYPE_PLAYER:	/*
				 * this case shouldn't happen but just
				 * in case...
				 */
	case TYPE_THING:
	  moveit(thing,PLAYER_START);
	  break;
	case TYPE_EXIT:	/* Make exits destination limbo */
	  db[thing].location = 0;
	  SET(db[thing].name, "limbo");
	  db[thing].flags |= GOING;
	  break;
	case TYPE_ROOM:	/* Remove drop to if it goes to dead object */
	  db[thing].location = NOTHING;
      }
      if (((db[thing].next < 0) || (db[thing].next >= db_top)) &&
	  (db[thing].next != NOTHING)) {
	fprintf(stderr, "ERROR: Invalid next pointer from object %s(%d)\n",
		db[thing].name, thing);
	report();
	db[thing].next = NOTHING;
      }
      if ((db[thing].owner < 0) || (db[thing].owner >= db_top)) {
	fprintf(stderr, "ERROR: Invalid object owner %s(%d)\n", db[thing].name,
		db);
	report();
	db[thing].owner = GOD;
      }
    } else
      /* if object is dead stick in free list */
      free_object(thing);
  /* mark all rooms that can be reached from limbo */
  dbmark(0);
  /* look through list and inform any player with an unconnected room */
  dbunmark();
}

/* Check data base for disconnected rooms */
void dbmark(loc)
    dbref loc;
{
  dbref thing;
  if ((loc < 0) || (loc >= db_top) ||
      (db[loc].flags & MARKED) ||
      (Typeof(loc) != TYPE_ROOM))
    return;
  db[loc].flags |= MARKED;
  /* destroy any exits needing destruction */
  do {
    for (thing = db[loc].exits;
	 (thing != NOTHING) && !(db[thing].flags & GOING);
	 thing = db[thing].next) ;
    if (thing != NOTHING) {
      db[loc].exits = remove_first(db[loc].exits, thing);
      do_empty(thing);
    }
  }
  while (thing != NOTHING);
  /* recursively trace */
  for (thing = db[loc].exits; thing != NOTHING; thing = db[thing].next)
    dbmark(db[thing].location);
}

void dbunmark()
{
  dbref loc;
  for (loc = 0; loc < db_top; loc++)
    if (db[loc].flags & MARKED)
      db[loc].flags &= ~MARKED;
    else if (Typeof(loc) == TYPE_ROOM) {
      dest_info(NOTHING, loc);
    }
}

/* Check data base for disconnected objects */
void dbmark1()
{
  dbref thing;
  dbref loc;
  for (loc = 0; loc < db_top; loc++)
    if (Typeof(loc) != TYPE_EXIT) {
      for (thing = db[loc].contents;thing != NOTHING;thing = db[thing].next) {
	if ((db[thing].location != loc) || (Typeof(thing) == TYPE_EXIT)) {
	  fprintf(stderr,
              "ERROR: Contents of object %d corrupt at object %d cleared\n",
	      loc, thing);
	  db[loc].contents = NOTHING;
	  break;
	}
	db[thing].flags |= MARKED;
      }
      if(Typeof(db[loc].owner) != TYPE_PLAYER) {
	fprintf(stderr,"ERROR: Bad owner on object %d changed to %d\n", loc,
		GOD);
        db[loc].owner = GOD;
      }
    } else {	/* lets convert old style exits to new ones. */
		/* but only if it needs it */
      if(db[loc].exits == NOTHING)
	db[loc].exits = find_entrance(loc);
    }
}

void dbunmark1()
{
  dbref loc;
  for (loc = 0; loc < db_top; loc++)
    if (db[loc].flags & MARKED)
      db[loc].flags &= ~MARKED;
    else if (((Typeof(loc) == TYPE_PLAYER) || (Typeof(loc) == TYPE_THING))
	     && !(db[loc].flags & GOING)) {
      fprintf(stderr, "ERROR DBCK: Moved object %d\n", loc);
      moveto(loc, 0);
    }
}

void do_dbck(player)
    dbref player;
{
  if (!Wizard(player)) {
    notify(player, "Silly mortal chicks are for kids!");
    return;
  }
  FIX;
  dbmark1();
  dbunmark1();
}

/* send contents of destroyed object home+destroy exits */
/* all objects must be moved to nothing or otherwise unlinked first */
void do_empty(thing)
    dbref thing;
{
  static int nrecur = 0;
  if (nrecur++ > 20) {		/* if run away recursion return */
    report();
    fprintf(stderr, "ERROR: Runaway recursion in do_empty\n");
    nrecur--;
    return;
  }
  switch (Typeof(thing)) {
    case TYPE_ROOM:		/* if room destroy all exits out of it */
      {
	dbref first;
	dbref rest;
	/* before we kill it tell people what is happening */
	dest_info(thing, NOTHING);
	/* return owners deposit */
	if (db[thing].owner > 0) {
	  giveto(db[thing].owner, ROOM_COST);
#ifdef QUOTA
	  add_quota(db[thing].owner, 1);
#endif /* QUOTA */
	}
	first = db[thing].exits;
	db[thing].exits = NOTHING;
	/* set destination of all exits to nothing */
	DOLIST(rest, first) {
	  db[rest].location = NOTHING;
	}
	/* Clear all exits out of exit list */
	while (first != NOTHING) {
	  rest = db[first].next;
	  if (Typeof(first) == TYPE_EXIT) {
	    /* compensate owner for loss then destroy */
	    if (db[first].owner > 0) {
	      giveto(first, EXIT_COST);
#ifdef QUOTA
	      add_quota(db[first].owner, 1);
#endif /* QUOTA */
            }
	    do_empty(first);
	  }
	  first = rest;
	}
      }
    case TYPE_THING:
    case TYPE_PLAYER:		/* if room or player send contents home */
      {
	dbref first;
	dbref rest;
	first = db[thing].contents;
	db[thing].contents = NOTHING;
	/* send all objects to nowhere */
	DOLIST(rest, first) {
	  db[rest].location = NOTHING;
	}
	/* now send them home */
	while (first != NOTHING) {
	  rest = db[first].next;
	  /* if home is in thing set it to limbo */
	  if (db[first].exits == thing)
	    db[first].exits = 0;
	  switch (Typeof(first)) {
	    case TYPE_EXIT:	/* if player holding exits, destroy it */
	      do_empty(first);
	      break;
	    case TYPE_THING:	/* move to home */
	    case TYPE_PLAYER:
	      if (db[first].exits != NOTHING) {
		PUSH(first, db[db[first].exits].contents);
		db[first].location = db[first].exits;
		/* notify players they have been moved */
		if (Typeof(first) == TYPE_PLAYER)
		  dest_info(first,NOTHING);
	      }
	      break;
	  }
	  first = rest;
	}
      }
      break;
  }
  /* chomp chomp */
  atr_free(thing);
  db[thing].list = NULL;
  /* don't eat name otherwise examine will crash */
  free_boolexp(db[thing].key);
  db[thing].key = TRUE_BOOLEXP;
  s_Pennies(thing, 0);
  db[thing].owner = GOD;
  db[thing].flags = GOING | TYPE_THING;	/* toad it */
  db[thing].location = NOTHING;
  SET(db[thing].name, "Garbage");
  db[thing].exits = 0;
  free_object(thing);
  nrecur--;
}
#endif				/* DESTROY */