deltamud/deltamud/
deltamud/deltamud/bin/
deltamud/deltamud/cnf/
deltamud/deltamud/lib/
deltamud/deltamud/lib/etc/
deltamud/deltamud/lib/misc/
deltamud/deltamud/lib/plrobjs/
deltamud/deltamud/lib/text/
deltamud/deltamud/lib/text/help/
deltamud/deltamud/lib/world/
deltamud/deltamud/lib/world/trg/
/* ************************************************************************
   *   File: objsave.c                                     Part of CircleMUD *
   *  Usage: loading/saving player objects for rent and crash-save           *
   *                                                                         *
   *  All rights reserved.  See license.doc for complete information.        *
   *                                                                         *
   *  Copyright (C) 1993, 94 by the Trustees of the Johns Hopkins University *
   *  CircleMUD is based on DikuMUD, Copyright (C) 1990, 1991.               *
   ************************************************************************ */

/* now with auto-equip - BK */

#include "conf.h"
#include "sysdep.h"

#include "structs.h"
#include "comm.h"
#include "handler.h"
#include "db.h"
#include "interpreter.h"
#include "utils.h"
#include "spells.h"

/* these factors should be unique integers */
#define RENT_FACTOR 	1
#define CRYO_FACTOR 	4

extern struct str_app_type str_app[];
extern struct room_data *world;
extern struct index_data *mob_index;
extern struct index_data *obj_index;
extern struct descriptor_data *descriptor_list;
extern struct player_index_element *player_table;
extern int top_of_p_table;
extern int min_rent_cost;
extern struct citizen_type citizen_titles[7];
int charge_less_rent(struct char_data *ch);
void write_aliases(struct char_data *ch);

/* Extern functions */
ACMD (do_action);
SPECIAL (receptionist);
SPECIAL (cryogenicist);


struct obj_data *
Obj_from_store_to (struct obj_file_elem object, int *locate)
{
  struct obj_data *obj;
  int j;

  if (real_object (object.item_number) > -1)
    {
      obj = read_object (object.item_number, VIRTUAL);
      *locate = (int) object.locate;
      GET_OBJ_VAL (obj, 0) = object.value[0];
      GET_OBJ_VAL (obj, 1) = object.value[1];
      GET_OBJ_VAL (obj, 2) = object.value[2];
      GET_OBJ_VAL (obj, 3) = object.value[3];
      GET_OBJ_EXTRA (obj) = object.extra_flags;
      GET_OBJ_WEIGHT (obj) = object.weight;
      GET_OBJ_TIMER (obj) = object.timer;
      obj->obj_flags.bitvector = object.bitvector;
      GET_OBJ_CSLOTS(obj) = object.curr_slots;
      GET_OBJ_TSLOTS(obj) = object.total_slots;
      obj->min_level = object.min_level;
      for (j = 0; j < MAX_OBJ_AFFECT; j++)
	obj->affected[j] = object.affected[j];

      return obj;
    }
  else
    return NULL;
}



/* this function used in house.c */
struct obj_data *
Obj_from_store (struct obj_file_elem object)
{
  int locate;

  return Obj_from_store_to (object, &locate);
}



int 
Obj_to_store_from (struct obj_data *obj, FILE * fl, int locate)
{
  int j;
  struct obj_file_elem object;

  object.item_number = GET_OBJ_VNUM (obj);
  object.locate = (long) locate;	/* where worn or inventory? */
  object.value[0] = GET_OBJ_VAL (obj, 0);
  object.value[1] = GET_OBJ_VAL (obj, 1);
  object.value[2] = GET_OBJ_VAL (obj, 2);
  object.value[3] = GET_OBJ_VAL (obj, 3);
  object.extra_flags = GET_OBJ_EXTRA (obj);
  object.weight = GET_OBJ_WEIGHT (obj);
  object.timer = GET_OBJ_TIMER (obj);
  object.bitvector = obj->obj_flags.bitvector;
  object.curr_slots = GET_OBJ_CSLOTS(obj);
  object.total_slots = GET_OBJ_TSLOTS(obj);
  object.min_level = obj->min_level;
  for (j = 0; j < MAX_OBJ_AFFECT; j++)
    object.affected[j] = obj->affected[j];

  if (fwrite (&object, sizeof (struct obj_file_elem), 1, fl) < 1)
    {
      perror ("SYSERR: error writing object in Obj_to_store");
      return 0;
    }
  return 1;
}



int 
Obj_to_store (struct obj_data *obj, FILE * fl)
{
  return Obj_to_store_from (obj, fl, 0);
}



int 
Crash_delete_file (char *name)
{
  char filename[50];
  FILE *fl;

  if (!get_filename (name, filename, CRASH_FILE))
    return 0;
  if (!(fl = fopen (filename, "rb")))
    {
      if (errno != ENOENT)
	{			/* if it fails but NOT because of no file */
	  sprintf (buf1, "SYSERR: deleting crash file %s (1)", filename);
	  perror (buf1);
	}
      return 0;
    }
  fclose (fl);

  if (unlink (filename) < 0)
    {
      if (errno != ENOENT)
	{			/* if it fails, NOT because of no file */
	  sprintf (buf1, "SYSERR: deleting crash file %s (2)", filename);
	  perror (buf1);
	}
    }
  return (1);
}


int 
Crash_delete_crashfile (struct char_data *ch)
{
  char fname[MAX_INPUT_LENGTH];
  struct rent_info rent;
  FILE *fl;

  if (!get_filename (GET_NAME (ch), fname, CRASH_FILE))
    return 0;
  if (!(fl = fopen (fname, "rb")))
    {
      if (errno != ENOENT)
	{			/* if it fails, NOT because of no file */
	  sprintf (buf1, "SYSERR: checking for crash file %s (3)", fname);
	  perror (buf1);
	}
      return 0;
    }
  if (!feof (fl))
    fread (&rent, sizeof (struct rent_info), 1, fl);
  fclose (fl);

  if (rent.rentcode == RENT_CRASH)
    Crash_delete_file (GET_NAME (ch));

  return 1;
}


int 
Crash_clean_file (char *name)
{
  char fname[MAX_STRING_LENGTH], filetype[20];
  struct rent_info rent;
  extern int rent_file_timeout, crash_file_timeout;
  FILE *fl;

  if (!get_filename (name, fname, CRASH_FILE))
    return 0;
  /*
   * open for write so that permission problems will be flagged now, at boot
   * time.
   */
  if (!(fl = fopen (fname, "r+b")))
    {
      if (errno != ENOENT)
	{			/* if it fails, NOT because of no file */
	  sprintf (buf1, "SYSERR: OPENING OBJECT FILE %s (4)", fname);
	  perror (buf1);
	}
      return 0;
    }
  if (!feof (fl))
    fread (&rent, sizeof (struct rent_info), 1, fl);
  fclose (fl);

  if ((rent.rentcode == RENT_CRASH) ||
      (rent.rentcode == RENT_FORCED) || (rent.rentcode == RENT_TIMEDOUT))
    {
      if (rent.time < time (0) - (crash_file_timeout * SECS_PER_REAL_DAY))
	{
	  Crash_delete_file (name);
	  switch (rent.rentcode)
	    {
	    case RENT_CRASH:
	      strcpy (filetype, "crash");
	      break;
	    case RENT_FORCED:
	      strcpy (filetype, "forced rent");
	      break;
	    case RENT_TIMEDOUT:
	      strcpy (filetype, "idlesave");
	      break;
	    default:
	      strcpy (filetype, "UNKNOWN!");
	      break;
	    }
	  sprintf (buf, "    Deleting %s's %s file.", name, filetype);
	  log (buf);
	  return 1;
	}
      /* Must retrieve rented items w/in 30 days */
    }
  else if (rent.rentcode == RENT_RENTED)
    if (rent.time < time (0) - (rent_file_timeout * SECS_PER_REAL_DAY))
      {
	Crash_delete_file (name);
	sprintf (buf, "    Deleting %s's rent file.", name);
	log (buf);
	return 1;
      }
  return (0);
}



void 
update_obj_file (void)
{
  int i;

  for (i = 0; i <= top_of_p_table; i++)
    Crash_clean_file ((player_table + i)->name);
  return;
}



void 
Crash_listrent (struct char_data *ch, char *name)
{
  FILE *fl;
  char fname[MAX_INPUT_LENGTH], buf[MAX_STRING_LENGTH];
  struct obj_file_elem object;
  struct obj_data *obj;
  struct rent_info rent;

  if (!get_filename (name, fname, CRASH_FILE))
    return;
  if (!(fl = fopen (fname, "rb")))
    {
      sprintf (buf, "%s has no rent file.\r\n", name);
      send_to_char (buf, ch);
      return;
    }
  sprintf (buf, "%s\r\n", fname);
  if (!feof (fl))
    fread (&rent, sizeof (struct rent_info), 1, fl);
  switch (rent.rentcode)
    {
    case RENT_RENTED:
      strcat (buf, "Rent\r\n");
      break;
    case RENT_CRASH:
      strcat (buf, "Crash\r\n");
      break;
    case RENT_CRYO:
      strcat (buf, "Cryo\r\n");
      break;
    case RENT_TIMEDOUT:
    case RENT_FORCED:
      strcat (buf, "TimedOut\r\n");
      break;
    default:
      strcat (buf, "Undef\r\n");
      break;
    }
  while (!feof (fl))
    {
      fread (&object, sizeof (struct obj_file_elem), 1, fl);
      if (ferror (fl))
	{
	  fclose (fl);
	  return;
	}
      if (!feof (fl))
	if (real_object (object.item_number) > -1)
	  {
	    obj = read_object (object.item_number, VIRTUAL);
	    sprintf (buf, "%s [%5d] (%5dau) <%2d> %-20s\r\n", buf,
		     (int) object.item_number, GET_OBJ_RENT (obj),
		     (int) object.locate, obj->short_description);
	    extract_obj (obj);
	  }
    }
  send_to_char (buf, ch);
  fclose (fl);
}



int 
Crash_write_rentcode (struct char_data *ch, FILE * fl, struct rent_info *rent)
{
  if (fwrite (rent, sizeof (struct rent_info), 1, fl) < 1)
    {
      perror ("SYSERR: writing rent code");
      return 0;
    }
  return 1;
}



/* so this is gonna be the auto equip (hopefully) */
void 
auto_equip (struct char_data *ch, struct obj_data *obj, int locate)
{
  int j;

  if (locate > 0)
    {				/* was worn */
      switch (j = locate - 1)
	{
	case WEAR_LIGHT:
	  break;
	case WEAR_FINGER_R:
	case WEAR_FINGER_L:
	  if (!CAN_WEAR (obj, ITEM_WEAR_FINGER))	/* not fitting :( */
	    locate = 0;
	  break;
	case WEAR_NECK_1:
	case WEAR_NECK_2:
	  if (!CAN_WEAR (obj, ITEM_WEAR_NECK))
	    locate = 0;
	  break;
	case WEAR_BODY:
	  if (!CAN_WEAR (obj, ITEM_WEAR_BODY))
	    locate = 0;
	  break;
	case WEAR_HEAD:
	  if (!CAN_WEAR (obj, ITEM_WEAR_HEAD))
	    locate = 0;
	  break;
	case WEAR_LEGS:
	  if (!CAN_WEAR (obj, ITEM_WEAR_LEGS))
	    locate = 0;
	  break;
	case WEAR_FEET:
	  if (!CAN_WEAR (obj, ITEM_WEAR_FEET))
	    locate = 0;
	  break;
	case WEAR_HANDS:
	  if (!CAN_WEAR (obj, ITEM_WEAR_HANDS))
	    locate = 0;
	  break;
	case WEAR_ARMS:
	  if (!CAN_WEAR (obj, ITEM_WEAR_ARMS))
	    locate = 0;
	  break;
	case WEAR_SHIELD:
	  if (!CAN_WEAR (obj, ITEM_WEAR_SHIELD))
	    locate = 0;
	  break;
	case WEAR_ABOUT:
	  if (!CAN_WEAR (obj, ITEM_WEAR_ABOUT))
	    locate = 0;
	  break;
	case WEAR_WAIST:
	  if (!CAN_WEAR (obj, ITEM_WEAR_WAIST))
	    locate = 0;
	  break;
	case WEAR_WRIST_R:
	case WEAR_WRIST_L:
	  if (!CAN_WEAR (obj, ITEM_WEAR_WRIST))
	    locate = 0;
	  break;
	case WEAR_WIELD:
	  if (!CAN_WEAR (obj, ITEM_WEAR_WIELD))
	    locate = 0;
	  break;
	case WEAR_HOLD:
	  if (!CAN_WEAR (obj, ITEM_WEAR_HOLD) &&
	      !(IS_WARRIOR (ch) &&
		CAN_WEAR (obj, ITEM_WEAR_WIELD) && GET_OBJ_TYPE (obj) == ITEM_WEAPON))
	    locate = 0;
	  break;
	default:
	  locate = 0;
	}
      if (locate > 0) {
	if (!GET_EQ (ch, j))
	  {
/* check ch's alignment to prevent $M from being zapped through auto-equip */
	    if ((IS_OBJ_STAT (obj, ITEM_ANTI_EVIL) && IS_EVIL (ch)) ||
		(IS_OBJ_STAT (obj, ITEM_ANTI_GOOD) && IS_GOOD (ch)) ||
		(IS_OBJ_STAT (obj, ITEM_ANTI_NEUTRAL) && IS_NEUTRAL (ch)))
	      locate = 0;
	    else
	      equip_char (ch, obj, j);
	  }
	else			/* oops - saved player with double equipment[j]? */
	  locate = 0;
      }
    }
  if (locate <= 0)
    obj_to_char (obj, ch);
}



#define MAX_BAG_ROW 5
/* should be enough - who would carry a bag in a bag in a bag in a
   bag in a bag in a bag ?!? */

int 
Crash_load (struct char_data *ch)
/* return values:
   0 - successful load, keep char in rent room.
   1 - load failure or load of crash items -- put char in temple.
   2 - rented equipment lost (no $)
 */
{
  void Crash_crashsave (struct char_data *ch);

  FILE *fl;
  char fname[MAX_STRING_LENGTH];
  struct obj_file_elem object;
  struct rent_info rent;
  int cost, orig_rent_code;
  float num_of_days;
  struct obj_data *obj;
  int locate, j;
  struct obj_data *obj1;
  struct obj_data *cont_row[MAX_BAG_ROW];

  if (!get_filename (GET_NAME (ch), fname, CRASH_FILE))
    return 1;
  if (!(fl = fopen (fname, "r+b")))
    {
      if (errno != ENOENT)
	{			/* if it fails, NOT because of no file */
	  sprintf (buf1, "SYSERR: READING OBJECT FILE %s (5)", fname);
	  perror (buf1);
	  send_to_char ("\r\n********************* NOTICE *********************\r\n"
		   "There was a problem loading your objects from disk.\r\n"
			"Contact a God for assistance.\r\n", ch);
	}
      sprintf (buf, "%s entering game with no equipment.", GET_NAME (ch));
      mudlog (buf, NRM, MAX (LVL_IMMORT, GET_INVIS_LEV (ch)), TRUE);
      return 1;
    }
  if (!feof (fl))
    fread (&rent, sizeof (struct rent_info), 1, fl);

  if (rent.rentcode == RENT_RENTED || rent.rentcode == RENT_TIMEDOUT)
    {
      num_of_days = (float) (time (0) - rent.time) / SECS_PER_REAL_DAY;
      cost = (int) (rent.net_cost_per_diem * num_of_days);
      if (cost > GET_GOLD (ch) + GET_BANK_GOLD (ch))
	{
	  fclose (fl);
	  sprintf (buf, "%s entering game, rented equipment lost (no $).",
		   GET_NAME (ch));
	  mudlog (buf, BRF, MAX (LVL_IMMORT, GET_INVIS_LEV (ch)), TRUE);
	  Crash_crashsave (ch);
	  return 2;
	}
      else
	{
	  GET_BANK_GOLD (ch) -= MAX (cost - GET_GOLD (ch), 0);
	  GET_GOLD (ch) = MAX (GET_GOLD (ch) - cost, 0);
	  save_char (ch, NOWHERE);
	}
    }
  switch (orig_rent_code = rent.rentcode)
    {
    case RENT_RENTED:
      sprintf (buf, "%s un-renting and entering game.", GET_NAME (ch));
      mudlog (buf, NRM, MAX (LVL_IMMORT, GET_INVIS_LEV (ch)), TRUE);
      break;
    case RENT_CRASH:
      sprintf (buf, "%s retrieving crash-saved items and entering game.", GET_NAME (ch));
      mudlog (buf, NRM, MAX (LVL_IMMORT, GET_INVIS_LEV (ch)), TRUE);
      break;
    case RENT_CRYO:
      sprintf (buf, "%s un-cryo'ing and entering game.", GET_NAME (ch));
      mudlog (buf, NRM, MAX (LVL_IMMORT, GET_INVIS_LEV (ch)), TRUE);
      break;
    case RENT_FORCED:
    case RENT_TIMEDOUT:
      sprintf (buf, "%s retrieving force-saved items and entering game.", GET_NAME (ch));
      mudlog (buf, NRM, MAX (LVL_IMMORT, GET_INVIS_LEV (ch)), TRUE);
      break;
    default:
      sprintf (buf, "WARNING: %s entering game with undefined rent code.", GET_NAME (ch));
      mudlog (buf, BRF, MAX (LVL_IMMORT, GET_INVIS_LEV (ch)), TRUE);
      break;
    }

  for (j = 0; j < MAX_BAG_ROW; j++)
    cont_row[j] = NULL;		/* empty all cont lists (you never know ...) */

  while (!feof (fl))
    {
      fread (&object, sizeof (struct obj_file_elem), 1, fl);
      if (ferror (fl))
	{
	  perror ("SYSERR: Reading crash file: Crash_load");
	  fclose (fl);
	  return 1;
	}
      if (!feof (fl))
	if ((obj = Obj_from_store_to (object, &locate)))
	  {
	    auto_equip (ch, obj, locate);

/*
   what to do with a new loaded item:

   if there's a list with <locate> less than 1 below this:
   (equipped items are assumed to have <locate>==0 here) then its
   container has disappeared from the file   *gasp*
   -> put all the list back to ch's inventory

   if there's a list of contents with <locate> 1 below this:
   check if it's a container
   - if so: get it from ch, fill it, and give it back to ch (this way the
   container has its correct weight before modifying ch)
   - if not: the container is missing -> put all the list to ch's inventory

   for items with negative <locate>:
   if there's already a list of contents with the same <locate> put obj to it
   if not, start a new list

   Confused? Well maybe you can think of some better text to be put here ...

   since <locate> for contents is < 0 the list indices are switched to
   non-negative
 */

	    if (locate > 0)
	      {			/* item equipped */
		for (j = MAX_BAG_ROW - 1; j > 0; j--)
		  if (cont_row[j])
		    {		/* no container -> back to ch's inventory */
		      for (; cont_row[j]; cont_row[j] = obj1)
			{
			  obj1 = cont_row[j]->next_content;
			  obj_to_char (cont_row[j], ch);
			}
		      cont_row[j] = NULL;
		    }
		if (cont_row[0])
		  {		/* content list existing */
		    if (GET_OBJ_TYPE (obj) == ITEM_CONTAINER)
		      {
			/* rem item ; fill ; equip again */
			obj = nunequip_char (ch, locate - 1);
			obj->contains = NULL;	/* should be empty - but who knows */
			for (; cont_row[0]; cont_row[0] = obj1)
			  {
			    obj1 = cont_row[0]->next_content;
			    obj_to_obj (cont_row[0], obj);
			  }
			nequip_char (ch, obj, locate - 1);
		      }
		    else
		      {		/* object isn't container -> empty content list */
			for (; cont_row[0]; cont_row[0] = obj1)
			  {
			    obj1 = cont_row[0]->next_content;
			    obj_to_char (cont_row[0], ch);
			  }
			cont_row[0] = NULL;
		      }
		  }
	      }
	    else
	      {			/* locate <= 0 */
		for (j = MAX_BAG_ROW - 1; j > -locate; j--)
		  if (cont_row[j])
		    {		/* no container -> back to ch's inventory */
		      for (; cont_row[j]; cont_row[j] = obj1)
			{
			  obj1 = cont_row[j]->next_content;
			  obj_to_char (cont_row[j], ch);
			}
		      cont_row[j] = NULL;
		    }

		if (j == -locate && cont_row[j])
		  {		/* content list existing */
		    if (GET_OBJ_TYPE (obj) == ITEM_CONTAINER)
		      {
			/* take item ; fill ; give to char again */
			obj_from_char (obj);
			obj->contains = NULL;
			for (; cont_row[j]; cont_row[j] = obj1)
			  {
			    obj1 = cont_row[j]->next_content;
			    obj_to_obj (cont_row[j], obj);
			  }
			obj_to_char (obj, ch);	/* add to inv first ... */
		      }
		    else
		      {		/* object isn't container -> empty content list */
			for (; cont_row[j]; cont_row[j] = obj1)
			  {
			    obj1 = cont_row[j]->next_content;
			    obj_to_char (cont_row[j], ch);
			  }
			cont_row[j] = NULL;
		      }
		  }

		if (locate < 0 && locate >= -MAX_BAG_ROW)
		  {
		    /* let obj be part of content list
		       but put it at the list's end thus having the items
		       in the same order as before renting */
		    obj_from_char (obj);
		    if ((obj1 = cont_row[-locate - 1]))
		      {
			while (obj1->next_content)
			  obj1 = obj1->next_content;
			obj1->next_content = obj;
		      }
		    else
		      cont_row[-locate - 1] = obj;
		  }
	      }
	  }
    }

  /* turn this into a crash file by re-writing the control block */
  rent.rentcode = RENT_CRASH;
  rent.time = time (0);
  rewind (fl);
  Crash_write_rentcode (ch, fl, &rent);

  fclose (fl);

  if ((orig_rent_code == RENT_RENTED) || (orig_rent_code == RENT_CRYO))
    return 0;
  else
    return 1;
}



int 
Crash_save (struct obj_data *obj, FILE * fp, int locate)
{
  struct obj_data *tmp;
  int result;

  if (obj)
    {
      Crash_save (obj->next_content, fp, locate);
      Crash_save (obj->contains, fp, MIN (0, locate) - 1);
      result = Obj_to_store_from (obj, fp, locate);

      for (tmp = obj->in_obj; tmp; tmp = tmp->in_obj)
	GET_OBJ_WEIGHT (tmp) -= GET_OBJ_WEIGHT (obj);

      if (!result)
	return 0;
    }
  return TRUE;
}


void 
Crash_restore_weight (struct obj_data *obj)
{
  if (obj)
    {
      Crash_restore_weight (obj->contains);
      Crash_restore_weight (obj->next_content);
      if (obj->in_obj)
	GET_OBJ_WEIGHT (obj->in_obj) += GET_OBJ_WEIGHT (obj);
    }
}



void 
Crash_extract_objs (struct obj_data *obj)
{
  if (obj)
    {
      Crash_extract_objs (obj->contains);
      Crash_extract_objs (obj->next_content);
      extract_obj (obj);
    }
}


int 
Crash_is_unrentable (struct obj_data *obj)
{
  if (!obj)
    return 0;

  if (IS_OBJ_STAT (obj, ITEM_NORENT) || GET_OBJ_RENT (obj) < 0 ||
      GET_OBJ_RNUM (obj) <= NOTHING || GET_OBJ_TYPE (obj) == ITEM_KEY)
    return 1;

  if (obj->carried_by || obj->worn_by)
    if (GET_LEVEL(obj->carried_by ? obj->carried_by : obj->worn_by) + 10 < obj->min_level)
      return 1;

  return 0;
}


void 
Crash_extract_norents (struct obj_data *obj)
{
  if (obj)
    {
      Crash_extract_norents (obj->contains);
      Crash_extract_norents (obj->next_content);
      if (Crash_is_unrentable (obj))
	extract_obj (obj);
    }
}


/* get norent items from eq to inventory and
   extract norents out of worn containers */
void 
Crash_extract_norents_from_equipped (struct char_data *ch)
{
  int j;

  for (j = 0; j < NUM_WEARS; j++)
    {
      if (GET_EQ (ch, j))
	{
	  if (IS_OBJ_STAT (GET_EQ (ch, j), ITEM_NORENT) ||
	      GET_OBJ_RENT (GET_EQ (ch, j)) < 0 ||
	      GET_OBJ_RNUM (GET_EQ (ch, j)) <= NOTHING ||
	      GET_OBJ_TYPE (GET_EQ (ch, j)) == ITEM_KEY)
	    obj_to_char (unequip_char (ch, j), ch);
	  else
	    Crash_extract_norents (GET_EQ (ch, j));
	}
    }
}


void 
Crash_extract_expensive (struct obj_data *obj)
{
  struct obj_data *tobj, *max;

  max = obj;
  for (tobj = obj; tobj; tobj = tobj->next_content)
    if (GET_OBJ_RENT (tobj) > GET_OBJ_RENT (max))
      max = tobj;
  extract_obj (max);
}



void 
Crash_calculate_rent (struct obj_data *obj, int *cost)
{
  if (obj)
    {
      *cost += MAX (0, GET_OBJ_RENT (obj));
      Crash_calculate_rent (obj->contains, cost);
      Crash_calculate_rent (obj->next_content, cost);
    }
}


void 
Crash_crashsave (struct char_data *ch)
{
  char buf[MAX_INPUT_LENGTH];
  struct rent_info rent;
  int j;
  FILE *fp;

  if (IS_NPC (ch))
    return;

  if (!get_filename (GET_NAME (ch), buf, CRASH_FILE))
    return;
  if (!(fp = fopen (buf, "wb")))
    return;

  rent.rentcode = RENT_CRASH;
  rent.time = time (0);
  if (!Crash_write_rentcode (ch, fp, &rent))
    {
      fclose (fp);
      return;
    }
  for (j = 0; j < NUM_WEARS; j++)
    if (GET_EQ (ch, j))
      {
	if (!Crash_save (GET_EQ (ch, j), fp, j + 1))
	  {
	    fclose (fp);
	    return;
	  }
	Crash_restore_weight (GET_EQ (ch, j));
      }

  if (!Crash_save (ch->carrying, fp, 0))
    {
      fclose (fp);
      return;
    }
  Crash_restore_weight (ch->carrying);

  fclose (fp);
  REMOVE_BIT (PLR_FLAGS (ch), PLR_CRASH);
}


void 
Crash_idlesave (struct char_data *ch)
{
  char buf[MAX_INPUT_LENGTH];
  struct rent_info rent;
  int j;
  int cost, cost_eq;
  FILE *fp;

  if (IS_NPC (ch))
    return;

  if (!get_filename (GET_NAME (ch), buf, CRASH_FILE))
    return;
  if (!(fp = fopen (buf, "wb")))
    return;

  Crash_extract_norents_from_equipped (ch);

  Crash_extract_norents (ch->carrying);

  cost = 0;
  Crash_calculate_rent (ch->carrying, &cost);

  cost_eq = 0;
  for (j = 0; j < NUM_WEARS; j++)
    Crash_calculate_rent (GET_EQ (ch, j), &cost_eq);

  cost <<= 1;			/* forcerent cost is 2x normal rent */
  cost_eq <<= 1;

  if (cost + cost_eq > GET_GOLD (ch) + GET_BANK_GOLD (ch))
    {
      for (j = 0; j < NUM_WEARS; j++)	/* unequip player with low money */
	if (GET_EQ (ch, j))
	  obj_to_char (unequip_char (ch, j), ch);
      cost += cost_eq;
      cost_eq = 0;

      while ((cost > GET_GOLD (ch) + GET_BANK_GOLD (ch)) && ch->carrying)
	{
	  Crash_extract_expensive (ch->carrying);
	  cost = 0;
	  Crash_calculate_rent (ch->carrying, &cost);
	  cost <<= 1;
	}
    }

  if (!ch->carrying)
    {
      for (j = 0; j < NUM_WEARS && !(GET_EQ (ch, j)); j++)
	;
      if (j == NUM_WEARS)
	{			/* no eq nor inv */
	  fclose (fp);
	  Crash_delete_file (GET_NAME (ch));
	  return;
	}
    }
  rent.net_cost_per_diem = cost;

  rent.rentcode = RENT_TIMEDOUT;
  rent.time = time (0);
  rent.gold = GET_GOLD (ch);
  rent.account = GET_BANK_GOLD (ch);
  if (!Crash_write_rentcode (ch, fp, &rent))
    {
      fclose (fp);
      return;
    }

  for (j = 0; j < NUM_WEARS; j++)
    if (GET_EQ (ch, j))
      {
	if (!Crash_save (GET_EQ (ch, j), fp, j + 1))
	  {
	    fclose (fp);
	    return;
	  }
	Crash_restore_weight (GET_EQ (ch, j));
	Crash_extract_objs (GET_EQ (ch, j));
      }
  if (!Crash_save (ch->carrying, fp, 0))
    {
      fclose (fp);
      return;
    }
  fclose (fp);

  Crash_extract_objs (ch->carrying);
}


void 
Crash_rentsave (struct char_data *ch, int cost)
{
  extern int circle_shutdown;
  char buf[MAX_INPUT_LENGTH];
  struct rent_info rent;
  int j;
  FILE *fp;

  if (IS_NPC (ch))
    return;

  if (!get_filename (GET_NAME (ch), buf, CRASH_FILE))
    return;
  if (!(fp = fopen (buf, "wb")))
    return;

  Crash_extract_norents_from_equipped (ch);

  Crash_extract_norents (ch->carrying);

  rent.net_cost_per_diem = cost;
  rent.rentcode = RENT_RENTED;
  rent.time = time (0);
  rent.gold = GET_GOLD (ch);
  rent.account = GET_BANK_GOLD (ch);
  if (!Crash_write_rentcode (ch, fp, &rent))
    {
      fclose (fp);
      return;
    }
  for (j = 0; j < NUM_WEARS; j++)
    if (GET_EQ (ch, j))
      {
	if (!Crash_save (GET_EQ (ch, j), fp, j + 1))
	  {
	    fclose (fp);
	    return;
	  }
	Crash_restore_weight (GET_EQ (ch, j));
	Crash_extract_objs (GET_EQ (ch, j));
      }
  if (!Crash_save (ch->carrying, fp, 0))
    {
      fclose (fp);
      return;
    }
  fclose (fp);

  Crash_extract_objs (ch->carrying);
  if (circle_shutdown == 10) return;
GET_LOADROOM (ch) = world[ch->in_room].number;
save_char (ch, ch->in_room);
}


void 
Crash_cryosave (struct char_data *ch, int cost)
{
  char buf[MAX_INPUT_LENGTH];
  struct rent_info rent;
  int j;
  FILE *fp;

  if (IS_NPC (ch))
    return;

  if (!get_filename (GET_NAME (ch), buf, CRASH_FILE))
    return;
  if (!(fp = fopen (buf, "wb")))
    return;

  Crash_extract_norents_from_equipped (ch);

  Crash_extract_norents (ch->carrying);

  GET_GOLD (ch) = MAX (0, GET_GOLD (ch) - cost);

  rent.rentcode = RENT_CRYO;
  rent.time = time (0);
  rent.gold = GET_GOLD (ch);
  rent.account = GET_BANK_GOLD (ch);
  rent.net_cost_per_diem = 0;
  if (!Crash_write_rentcode (ch, fp, &rent))
    {
      fclose (fp);
      return;
    }
  for (j = 0; j < NUM_WEARS; j++)
    if (GET_EQ (ch, j))
      {
	if (!Crash_save (GET_EQ (ch, j), fp, j + 1))
	  {
	    fclose (fp);
	    return;
	  }
	Crash_restore_weight (GET_EQ (ch, j));
	Crash_extract_objs (GET_EQ (ch, j));
      }
  if (!Crash_save (ch->carrying, fp, 0))
    {
      fclose (fp);
      return;
    }
  fclose (fp);

  Crash_extract_objs (ch->carrying);
  SET_BIT (PLR_FLAGS (ch), PLR_CRYO);
GET_LOADROOM (ch) = world[ch->in_room].number;
save_char (ch, ch->in_room);
}


/* ************************************************************************
   * Routines used for the receptionist                                   *
   ************************************************************************* */

void 
Crash_rent_deadline (struct char_data *ch, struct char_data *recep,
		     long cost)
{
  long rent_deadline;

  if (!cost)
    return;

  rent_deadline = ((GET_GOLD (ch) + GET_BANK_GOLD (ch)) / cost);
  sprintf (buf,
      "$n tells you, 'You can rent for %ld day%s with the gold you have\r\n"
	   "on hand and in the bank.'\r\n",
	   rent_deadline, (rent_deadline > 1) ? "s" : "");
  act (buf, FALSE, recep, 0, ch, TO_VICT);
}

int 
Crash_report_unrentables (struct char_data *ch, struct char_data *recep,
			  struct obj_data *obj)
{
  char buf[128];
  int has_norents = 0;

  if (obj)
    {
      if (Crash_is_unrentable (obj))
	{
	  has_norents = 1;
	  sprintf (buf, "$n tells you, 'You cannot store %s.'", OBJS (obj, ch));
	  act (buf, FALSE, recep, 0, ch, TO_VICT);
	}
      has_norents += Crash_report_unrentables (ch, recep, obj->contains);
      has_norents += Crash_report_unrentables (ch, recep, obj->next_content);
    }
  return (has_norents);
}



void 
Crash_report_rent (struct char_data *ch, struct char_data *recep,
    struct obj_data *obj, long *cost, long *nitems, int display, int factor)
{
  static char buf[256];

  if (obj)
    {
      if (!Crash_is_unrentable (obj))
	{
	  (*nitems)++;
	  *cost += MAX (0, (GET_OBJ_RENT (obj) * factor));
	  if (display)
	    {
	      sprintf (buf, "$n tells you, '%5d coins for %s..'",
		       (GET_OBJ_RENT (obj) * factor), OBJS (obj, ch));
	      act (buf, FALSE, recep, 0, ch, TO_VICT);
	    }
	}
      Crash_report_rent (ch, recep, obj->contains, cost, nitems, display, factor);
      Crash_report_rent (ch, recep, obj->next_content, cost, nitems, display, factor);
    }
}



int 
Crash_offer_rent (struct char_data *ch, struct char_data *receptionist,
		  int display, int factor)
{
  extern int max_obj_save;	/* change in config.c */
  char buf[MAX_INPUT_LENGTH];
  int i;
  long totalcost = 0, numitems = 0, norent = 0;

  norent = Crash_report_unrentables (ch, receptionist, ch->carrying);
  for (i = 0; i < NUM_WEARS; i++)
    norent += Crash_report_unrentables (ch, receptionist, GET_EQ (ch, i));

  if (norent)
    return 0;

// we don't need this anymore (Mulder)
//  totalcost = min_rent_cost * factor - charge_less_rent(ch);

    totalcost = min_rent_cost * factor;

  Crash_report_rent (ch, receptionist, ch->carrying, &totalcost, &numitems, display, factor);

  for (i = 0; i < NUM_WEARS; i++)
    Crash_report_rent (ch, receptionist, GET_EQ (ch, i), &totalcost, &numitems, display, factor);

  if (!numitems)
    {
      act ("$n tells you, 'But you are not carrying anything!  Just quit!'",
	   FALSE, receptionist, 0, ch, TO_VICT);
      return (0);
    }
  if (numitems > max_obj_save)
    {
      sprintf (buf, "$n tells you, 'Sorry, but I cannot store more than %d items.'",
	       max_obj_save);
      act (buf, FALSE, receptionist, 0, ch, TO_VICT);
      return (0);
    }
  if (display)
    {
      sprintf (buf, "$n tells you, 'Plus, my %d coin fee..'",
	       min_rent_cost * factor);
      act (buf, FALSE, receptionist, 0, ch, TO_VICT);

// no more happy days :(
// sprintf (buf, "$n tells you, 'You're quite famous.. I'll charge %d less.'", charge_less_rent(ch));
//      act (buf, FALSE, receptionist, 0, ch, TO_VICT);

// better keep this in just in case *shrug*
  if (GET_CITIZEN(ch) >= 1) {
     sprintf(buf, "$n tells you, 'Your fame has marked you as a %s, and I honor that with a %ld coin reduction in your rent.'",
 READ_CITIZEN(ch), (long)((GET_CITIZEN(ch) * totalcost) / 100));
     act(buf, FALSE, receptionist, 0, ch, TO_VICT);   
     totalcost -= (totalcost * GET_CITIZEN(ch) / 100);
     totalcost = MAX(1, totalcost);
  }

   if (totalcost < 0)
     totalcost = 0;

      sprintf (buf, "$n tells you, 'For a total of %ld coins%s.'",
	       totalcost, (factor == RENT_FACTOR ? " per day" : ""));
      act (buf, FALSE, receptionist, 0, ch, TO_VICT);
      if (totalcost > GET_GOLD (ch))
	{
	  act ("$n tells you, '...which I see you can't afford.'",
	       FALSE, receptionist, 0, ch, TO_VICT);
	  return (0);
	}
      else if (factor == RENT_FACTOR)
	Crash_rent_deadline (ch, receptionist, totalcost);
    }
  return (totalcost);
}



int 
gen_receptionist (struct char_data *ch, struct char_data *recep,
		  int cmd, char *arg, int mode)
{
  int cost = 0;
  extern int free_rent;
  long save_room;
  char *action_table[] =
  {"smile", "dance", "sigh", "blush", "burp",
   "cough", "fart", "twiddle", "yawn"};

  if (!ch->desc || IS_NPC (ch))
    return FALSE;

  if (!cmd && !number (0, 5))
    {
      do_action (recep, "", find_command (action_table[number (0, 8)]), 0);
      return FALSE;
    }
  if (!CMD_IS ("offer") && !CMD_IS ("rent"))
    return FALSE;
  if (!AWAKE (recep))
    {
      send_to_char ("She is unable to talk to you...\r\n", ch);
      return TRUE;
    }
  if (!CAN_SEE (recep, ch) && GET_LEVEL(ch) < LVL_IMMORT)
    {
      act ("$n says, 'I don't deal with people I can't see!'", FALSE, recep, 0, 0, TO_ROOM);
      return TRUE;
    }
  if (free_rent || (GET_LEVEL(ch) >= LVL_IMMORT))
    {
      act ("$n tells you, 'Rent is free here.  Just quit, and your objects will be saved!'",
	   FALSE, recep, 0, ch, TO_VICT);
      return 1;
    }
  if (CMD_IS ("rent"))
    {
      if (!(cost = Crash_offer_rent (ch, recep, FALSE, mode)))
	return TRUE;

   if (cost < 0)
    cost = 0;

      if (mode == RENT_FACTOR) {
        Crash_offer_rent (ch, recep, TRUE, mode);
	sprintf (buf, "$n tells you, 'Rent will cost you %d gold coins per day.'", cost);
        }
      else if (mode == CRYO_FACTOR)
	sprintf (buf, "$n tells you, 'It will cost you %d gold coins to be frozen.'", cost);
      act (buf, FALSE, recep, 0, ch, TO_VICT);
      if (cost > GET_GOLD (ch))
	{
	  act ("$n tells you, '...which I see you can't afford.'",
	       FALSE, recep, 0, ch, TO_VICT);
	  return TRUE;
	}
      if (cost && (mode == RENT_FACTOR))
	Crash_rent_deadline (ch, recep, cost);

      if (mode == RENT_FACTOR)
	{
	  act ("$n stores your belongings and helps you into your private chamber.",
	       FALSE, recep, 0, ch, TO_VICT);
          write_aliases(ch);
	  REMOVE_BIT (PRF2_FLAGS (ch), PRF2_LOCKOUT);      
	  Crash_rentsave (ch, cost);

// this was annoying, removed 8/23/98 --Mulder
// if(GET_LEVEL(ch) < LVL_IMMORT)
//{
//	  sprintf (buf, "&m[&YINFO&m]&n %s has left Deltania. (rented)\r\n", GET_NAME (ch));
//	  send_to_all (buf);
//}
	  sprintf (buf, "%s has rented (%d/day, %d tot.)", GET_NAME (ch),
		   cost, GET_GOLD (ch) + GET_BANK_GOLD (ch));
	}
      else
	{			/* cryo */
	  act ("$n stores your belongings and helps you into your private chamber.\r\n"
	  "A white mist appears in the room, chilling you to the bone...\r\n"
	       "You begin to lose consciousness...",
	       FALSE, recep, 0, ch, TO_VICT);
          write_aliases(ch);
	  Crash_cryosave (ch, cost);
	  sprintf (buf, "%s has wiz-rented.", GET_NAME (ch));
	  SET_BIT (PLR_FLAGS (ch), PLR_CRYO);
	}

      mudlog (buf, NRM, MAX (LVL_IMMORT, GET_INVIS_LEV (ch)), TRUE);
      act ("$n helps $N into $S private chamber.", FALSE, recep, 0, ch, TO_NOTVICT);
      save_room = ch->in_room;
      extract_char (ch);
      save_char (ch, save_room);
    }
  else
    {
      Crash_offer_rent (ch, recep, TRUE, mode);
      act ("$N gives $n an offer.", FALSE, ch, 0, recep, TO_ROOM);
    }
  return TRUE;
}


SPECIAL (receptionist)
{
  return (gen_receptionist (ch, me, cmd, argument, RENT_FACTOR));
}


SPECIAL (cryogenicist)
{
  return (gen_receptionist (ch, me, cmd, argument, CRYO_FACTOR));
}


void 
Crash_save_all (void)
{
  struct descriptor_data *d;
  for (d = descriptor_list; d; d = d->next)
    {
      if ((d->connected == CON_PLAYING) && !IS_NPC (d->character))
	{
	  if (PLR_FLAGGED (d->character, PLR_CRASH))
	    {
	      Crash_crashsave (d->character);
	      save_char (d->character, NOWHERE);
	      REMOVE_BIT (PLR_FLAGS (d->character), PLR_CRASH);
	    }
	}
    }
}