1stMud/CVS/
1stMud/area/CVS/
1stMud/backup/CVS/
1stMud/bin/
1stMud/bin/CVS/
1stMud/bin/extras/
1stMud/bin/extras/CVS/
1stMud/data/CVS/
1stMud/data/i3/CVS/
1stMud/doc/1stMud/
1stMud/doc/1stMud/CVS/
1stMud/doc/CVS/
1stMud/doc/Diku/
1stMud/doc/Diku/CVS/
1stMud/doc/MPDocs/CVS/
1stMud/doc/Merc/CVS/
1stMud/doc/Rom/
1stMud/doc/Rom/CVS/
1stMud/log/CVS/
1stMud/notes/
1stMud/notes/CVS/
1stMud/player/CVS/
1stMud/player/backup/CVS/
1stMud/player/deleted/CVS/
1stMud/src/CVS/
1stMud/src/config/CVS/
1stMud/src/h/CVS/
1stMud/src/o/CVS/
1stMud/win/CVS/
/**************************************************************************
*  Original Diku Mud copyright (C) 1990, 1991 by Sebastian Hammer,        *
*  Michael Seifert, Hans Henrik St{rfeldt, Tom Madsen, and Katja Nyboe.   *
*                                                                         *
*  Merc Diku Mud improvements copyright (C) 1992, 1993 by Michael         *
*  Chastain, Michael Quan, and Mitchell Tse.                              *
*                                                                         *
*  In order to use any part of this Merc Diku Mud, you must comply with   *
*  both the original Diku license in 'license.doc' as well the Merc       *
*  license in 'license.txt'.  In particular, you may not remove either of *
*  these copyright notices.                                               *
*                                                                         *
*  Much time and thought has gone into this software and you are          *
*  benefiting.  We hope that you share your changes too.  What goes       *
*  around, comes around.                                                  *
***************************************************************************
*       ROM 2.4 is copyright 1993-1998 Russ Taylor                        *
*       ROM has been brought to you by the ROM consortium                 *
*           Russ Taylor (rtaylor@hypercube.org)                           *
*           Gabrielle Taylor (gtaylor@hypercube.org)                      *
*           Brian Moore (zump@rom.org)                                    *
*       By using this code, you have agreed to follow the terms of the    *
*       ROM license, in the file Rom24/doc/rom.license                    *
***************************************************************************
*          1stMud ROM Derivative (c) 2001-2004 by Markanth                *
*            http://www.firstmud.com/  <markanth@firstmud.com>            *
*         By using this code you have agreed to follow the term of        *
*             the 1stMud license in ../doc/1stMud/LICENSE                 *
***************************************************************************/

#include "merc.h"
#include "recycle.h"
#include "tables.h"
#include "interp.h"
#include "vnums.h"

static ObjData *rgObjNest[MAX_NEST];


Proto (void write_char, (CharData *, FileData *));
Proto (void write_pet, (CharData *, FileData *));
Proto (void read_pet, (CharData *, FileData *));
Proto (void read_descriptor, (Descriptor *, FileData *));
Proto (void write_descriptor, (Descriptor *, FileData *));

#define TAB_SIZE    8

char *
format_tabs (int len)
{
  if (len < TAB_SIZE)
    return "\t\t\t";
  else if (len < TAB_SIZE * 2)
    return "\t\t";
  else if (len < TAB_SIZE * 3)
    return "\t";
  else
    return FORMATF ("%*s", Max (1, len - (TAB_SIZE)), " ");
}

const char *
pfile_filename (const char *name)
{
  static int i;
  static char rbuf[5][MSL];
  char buf[MSL], first_name[MSL];


  ++i, i %= 5;

  strcpy (buf, name);


  one_argument (buf, first_name);

  sprintf (rbuf[i], "%s.plr", first_name);

  return (rbuf[i]);
}

char *
pfilename (const char *name, pfile_t type)
{
  static int i;
  static char rbuf[5][MSL];
  char filename[MSL];


  ++i, i %= 5;

  strcpy (filename, pfile_filename (name));

  switch (type)
    {
    case PFILE_NORMAL:
#if defined(NO_INITIAL_ALPHA_PFILEDIRS)
      sprintf (rbuf[i], PLAYER_DIR "%s", filename);
#else
      sprintf (rbuf[i], PLAYER_DIR "%c" DIR_SYM "%s", filename[0], filename);
#endif
      break;
    case PFILE_BACKUP:
#if defined(NO_INITIAL_ALPHA_PFILEDIRS)
      sprintf (rbuf[i], PLAYER_BACKUP "%s", filename);
#else
      sprintf (rbuf[i], PLAYER_BACKUP "%c" DIR_SYM "%s", filename[0],
	       filename);
#endif
      break;
    case PFILE_DELETED:
      sprintf (rbuf[i], DELETE_DIR "%s", filename);
      break;
    default:
      sprintf (rbuf[i], "ERROR_PFILENAME_%s_INCORRECT_TYPE_%d",
	       filename, (int) type);
      bugf ("ERROR_PFILENAME_INCORRECT_TYPE %d - %s", (int) type, filename);
      break;
    }
  return (rbuf[i]);
}

bool
save_char_obj_to_filename (CharData * ch, const char *filename)
{
  FileData *fp;

  if ((fp = f_open (filename, "w")) == NULL)
    {
      bugf ("Save_char_obj: file open on %s", ch->name);
      log_error (filename);
      return false;
    }
  else
    {
      write_char (ch, fp);
      if (ch->carrying_first != NULL)
	write_obj (ch, ch->carrying_last, fp, 0, 0, SAVE_CHAR);

      if (ch->pet != NULL && ch->pet->in_room == ch->in_room)
	{
	  write_pet (ch->pet, fp);
	  write_obj (ch->pet, ch->pet->carrying_last, fp, 0, 0, SAVE_PET);
	}
      if (ch->desc && (crs_info.status == CRS_COPYOVER || crash_info.crashed))
	write_descriptor (ch->desc, fp);
      f_printf (fp, "#%s" LF, END_MARK);
      f_close (fp);
      return true;
    }
}


void
save_char_obj (CharData * ch)
{
  if (IsNPC (ch))
    return;

  if (ch->desc != NULL && ch->desc->original != NULL)
    ch = ch->desc->original;

  if (get_trust (ch) < mud_info.min_save_lvl)
    return;

  save_char_obj_to_filename (ch, pfilename (ch->name, PFILE_NORMAL));
  return;
}

void
backup_char_obj (CharData * ch)
{
  char buf[MIL];

  if (IsNPC (ch))
    return;

  if (ch->desc != NULL && ch->desc->original != NULL)
    ch = ch->desc->original;

  if (get_trust (ch) < mud_info.min_save_lvl * 3)
    return;

  strcpy (buf, pfilename (ch->name, PFILE_BACKUP));
  if (save_char_obj_to_filename (ch, buf))
    {
      ch->pcdata->backup = ch->pcdata->played;
#ifdef unix
      system (FORMATF ("gzip -fq %s", buf));
#endif
    }
  return;
}


void
write_char (CharData * ch, FileData * fp)
{
  AffectData *paf;
  int sn, gn, pos;
  int i, j;

  f_printf (fp, "#%s" LF,
	    get_char_save_header (IsNPC (ch) ? SAVE_MOB : SAVE_CHAR));

  write_string (fp, "Name", ch->name, NULL);
  write_int (fp, "Id", "%ld", ch->id, 0);
  write_time (fp, "LogO", current_time, false);
  write_int (fp, "Vers", "%d", PFILE_VERSION, 0);
  write_string (fp, "ShD", ch->short_descr, NULL);
  write_string (fp, "LnD", ch->long_descr, NULL);
  write_string (fp, "Desc", ch->description, NULL);
  write_string (fp, "Prom", ch->prompt, NULL);
  write_string (fp, "GProm", ch->gprompt, NULL);
  write_string (fp, "Race", ch->race->name, NULL);
  if (CharClan (ch) != NULL)
    {
      write_string (fp, "Clan", CharClan (ch)->name, NULL);
      write_int (fp, "Rank", "%d", ch->rank, -1);
    }
  write_int (fp, "Sex", "%d", ch->sex, -1);
  write_array (fp, "Cla", "%d", ch->Class, MAX_MCLASS);
  write_int (fp, "PrClass", "%d", ch->pcdata->prime_class, 0);
  if (ch->pcdata->stay_race)
    f_printf (fp, "StayRace" LF);
  write_int (fp, "Levl", "%d", ch->level, 0);
  write_int (fp, "Tru", "%d", ch->trust, 0);
  write_int (fp, "Sec", "%d", ch->pcdata->security, 0);
  write_int (fp, "Plyd", "%d",
	     ch->pcdata->played + (int) (current_time - ch->logon), 0);
  write_int (fp, "Scro", "%d", ch->lines, -1);
  write_int (fp, "Cols", "%d", ch->columns, -1);
  write_int (fp, "Room", "%ld",
	     (ch->in_room == get_room_index (ROOM_VNUM_LIMBO) &&
	      ch->was_in_room !=
	      NULL) ? ch->was_in_room->vnum : ch->in_room ==
	     NULL ? 3001 : ch->in_room->vnum, 0);

  f_writef (fp, "HMV", "%ld %ld %ld %ld %ld %ld" LF, ch->hit,
	    ch->max_hit, ch->mana, ch->max_mana, ch->move, ch->max_move);
  write_int (fp, "Gold", "%ld", ch->gold, 0);
  write_int (fp, "Silv", "%ld", ch->silver, 0);
  write_int (fp, "Exp", "%d", ch->exp, 0);
  write_bit (fp, "Act", ch->act, 0);
  write_bit (fp, "AfBy", ch->affected_by, 0);
  write_bit (fp, "Comm", ch->comm, 0);
  write_bit (fp, "Wizn", ch->wiznet, 0);
  write_int (fp, "Invi", "%d", ch->invis_level, 0);
  write_int (fp, "Inco", "%d", ch->incog_level, 0);
  write_int (fp, "Pos", "%d",
	     ch->position == POS_FIGHTING ? POS_STANDING : ch->position, -1);
  write_int (fp, "Prac", "%d", ch->practice, 0);
  write_int (fp, "Trai", "%d", ch->train, 0);
  write_int (fp, "Save", "%d", ch->saving_throw, 0);
  write_int (fp, "Alig", "%d", ch->alignment, 0);
  write_int (fp, "Hit", "%d", ch->hitroll, 0);
  write_int (fp, "Dam", "%d", ch->damroll, 0);
  write_array (fp, "ACs", "%d", ch->armor, MAX_AC);
  write_array (fp, "Stance", "%d", ch->stance, MAX_STANCE);
  write_int (fp, "Wimp", "%d", ch->wimpy, 0);
  write_array (fp, "Attr", "%d", ch->perm_stat, STAT_MAX);
  write_array (fp, "AMod", "%d", ch->mod_stat, STAT_MAX);
  if (IsNPC (ch))
    {
      write_int (fp, "Vnum", "%ld", ch->pIndexData->vnum, 0);
    }
  else
    {
      write_string (fp, "Pass", ch->pcdata->pwd, NULL);
      write_string (fp, "Bin", ch->pcdata->bamfin, NULL);
      write_string (fp, "Bout", ch->pcdata->bamfout, NULL);
      write_string (fp, "Titl", ch->pcdata->title, NULL);
      write_int (fp, "Pnts", "%d", ch->pcdata->points, 0);
      write_int (fp, "TSex", "%d", ch->pcdata->true_sex, -1);
      write_int (fp, "LLev", "%d", ch->pcdata->last_level, 0);
      f_writef (fp, "HMVP", "%ld %ld %ld" LF, ch->pcdata->perm_hit,
		ch->pcdata->perm_mana, ch->pcdata->perm_move);
      write_array (fp, "Cnd", "%d", ch->pcdata->condition, 4);
      write_int (fp, "QuestPnts", "%d", ch->pcdata->quest.points, 0);
      write_int (fp, "QuestTime", "%d", ch->pcdata->quest.time, 0);
      if (ch->pcdata->quest.giver)
	write_int (fp, "QuestGiver", "%ld",
		   ch->pcdata->quest.giver->pIndexData->vnum, 0);
      if (ch->pcdata->quest.room)
	write_int (fp, "QuestLoc", "%ld", ch->pcdata->quest.room->vnum, 0);
      if (ch->pcdata->quest.obj)
	write_int (fp, "QuestObj", "%ld",
		   ch->pcdata->quest.obj->pIndexData->vnum, 0);
      if (ch->pcdata->quest.mob)
	write_int (fp, "QuestMob", "%ld",
		   ch->pcdata->quest.mob->pIndexData->vnum, 0);
      write_int (fp, "Trivia", "%d", ch->pcdata->trivia, 0);
      if (ch->pcdata->str_ed_key != '.' && ch->pcdata->str_ed_key != ' ')
	f_writef (fp, "StrEdKey", "%c" LF, ch->pcdata->str_ed_key);
      write_int (fp, "TimeZone", "%d", ch->pcdata->timezone, -1);
      write_int (fp, "AWins", "%d", ch->pcdata->awins, 0);
      write_int (fp, "ALosses", "%d", ch->pcdata->alosses, 0);
      write_int (fp, "BankG", "%ld", ch->pcdata->gold_bank, 0);
      write_int (fp, "Shares", "%d", ch->pcdata->shares, 0);
      if (ch->deity != NULL)
	write_string (fp, "Deity", ch->deity->name, NULL);
      if (Gquester (ch))
	{
	  write_array (fp, "GQmobs", "%ld", ch->gquest->gq_mobs,
		       gquest_info.mob_count);
	}
      if (war_info.status != WAR_OFF && ch->war != NULL)
	{
	  f_writef (fp, "WarInfo", "%ld %ld %ld %s" LF, ch->war->hit,
		    ch->war->mana, ch->war->move,
		    write_flags (ch->war->flags));
	}
      if (HAS_HOME (ch))
	{
	  write_array (fp, "Homes", "%ld", ch->pcdata->home, MAX_HOME_VNUMS);
	}
      f_writef (fp, "Colo", "%d", MAX_CUSTOM_COLOR);
      for (i = 0; i < MAX_CUSTOM_COLOR; i++)
	for (j = 0; j < CT_MAX; j++)
	  f_printf (fp, " %d", ch->pcdata->color[i].at[j]);
      f_printf (fp, LF);
      write_string (fp, "WhoD", ch->pcdata->who_descr, NULL);
      write_array (fp, "GStats", "%ld", ch->pcdata->gamestat, MAX_GAMESTAT);

      write_bit (fp, "Vt100", ch->pcdata->vt100, 0);

      for (pos = 0; pos < MAX_ALIAS; pos++)
	{
	  if (NullStr (ch->pcdata->alias[pos]) ||
	      NullStr (ch->pcdata->alias_sub[pos]))
	    break;

	  f_writef (fp, "Alias", "%s %s~" LF, ch->pcdata->alias[pos],
		    ch->pcdata->alias_sub[pos]);
	}
      for (pos = 0; pos < MAX_BUDDY; pos++)
	{
	  if (NullStr (ch->pcdata->buddies[pos]))
	    break;

	  write_string (fp, "Buddy", ch->pcdata->buddies[pos], NULL);
	}
      for (pos = 0; pos < MAX_IGNORE; pos++)
	{
	  if (NullStr (ch->pcdata->ignore[pos]))
	    break;

	  f_writef (fp, "Ignore", "%s~ %s" LF, ch->pcdata->ignore[pos],
		    write_flags (ch->pcdata->ignore_flags[pos]));
	}


      f_writef (fp, "Boards", "%d", MAX_BOARD);
      for (i = 0; i < MAX_BOARD; i++)
	f_printf (fp, " %s " TIME_T_FMT, boards[i].short_name,
		  ch->pcdata->last_note[i]);
      f_printf (fp, LF);

      f_writef (fp, "Subscribe", "%d", MAX_BOARD);
      for (i = 0; i < MAX_BOARD; i++)
	f_printf (fp, " %s %d", boards[i].short_name,
		  ch->pcdata->unsubscribed[i]);
      f_printf (fp, LF);

      for (sn = 0; sn < top_skill; sn++)
	{
	  if (skill_table[sn].name != NULL && ch->pcdata->learned[sn] > 0)
	    {
	      f_writef (fp, "Sk", "%d '%s'" LF, ch->pcdata->learned[sn],
			skill_table[sn].name);
	    }
	}

      for (gn = 0; gn < top_group; gn++)
	{
	  if (group_table[gn].name != NULL && ch->pcdata->group_known[gn])
	    {
	      f_writef (fp, "Gr", "'%s'" LF, group_table[gn].name);
	    }
	}
    }

  for (paf = ch->affect_first; paf != NULL; paf = paf->next)
    {
      if (paf->type < 0 || paf->type >= top_skill)
	continue;

      f_writef (fp, "Affc", "'%s' %3d %3d %3d %3d %3d %s" LF,
		skill_table[paf->type].name, paf->where, paf->level,
		paf->duration, paf->modifier, paf->location,
		write_flags (paf->bitvector));
    }
  write_rle (ch->pcdata->explored, fp);

#ifndef DISABLE_I3

  i3save_char (ch, fp);
#endif

  f_printf (fp, END_MARK LF);
  return;
}


void
write_pet (CharData * pet, FileData * fp)
{
  AffectData *paf;

  f_printf (fp, "#%s" LF, get_char_save_header (SAVE_PET));

  write_int (fp, "Vnum", "%ld", pet->pIndexData->vnum, 0);

  write_string (fp, "Name", pet->name, NULL);
  write_time (fp, "LogO", current_time, false);
  write_string (fp, "ShD", pet->short_descr, pet->pIndexData->short_descr);
  write_string (fp, "LnD", pet->long_descr, pet->pIndexData->long_descr);
  write_string (fp, "Desc", pet->description, pet->pIndexData->description);
  if (pet->race != pet->pIndexData->race)
    write_string (fp, "Race", pet->race->name, NULL);
  write_int (fp, "Sex", "%d", pet->sex, -1);
  write_int (fp, "Levl", "%d", pet->level, pet->pIndexData->level);
  f_writef (fp, "HMV", "%ld %ld %ld %ld %ld %ld" LF, pet->hit,
	    pet->max_hit, pet->mana, pet->max_mana, pet->move, pet->max_move);
  write_int (fp, "Gold", "%ld", pet->gold, 0);
  write_int (fp, "Silv", "%ld", pet->silver, 0);
  write_int (fp, "Exp", "%d", pet->exp, 0);
  write_bit (fp, "Act", pet->act, pet->pIndexData->act);
  write_bit (fp, "AfBy", pet->affected_by, pet->pIndexData->affected_by);
  write_bit (fp, "Comm", pet->comm, 0);
  write_int (fp, "Pos", "%d", pet->position =
	     POS_FIGHTING ? POS_STANDING : pet->position, -1);
  write_int (fp, "Save", "%d", pet->saving_throw, 0);
  write_int (fp, "Alig", "%d", pet->alignment, pet->pIndexData->alignment);
  write_int (fp, "Hit", "%d", pet->hitroll, pet->pIndexData->hitroll);
  write_int (fp, "Dam", "%d", pet->damroll,
	     pet->pIndexData->damage[DICE_BONUS]);

  write_array (fp, "ACs", "%d", pet->armor, MAX_AC);

  write_array (fp, "Attr", "%d", pet->perm_stat, STAT_MAX);

  write_array (fp, "AMod", "%d", pet->mod_stat, STAT_MAX);

  for (paf = pet->affect_first; paf != NULL; paf = paf->next)
    {
      if (paf->type < 0 || paf->type >= top_skill)
	continue;

      f_writef (fp, "Affc", "'%s' %3d %3d %3d %3d %3d %s" LF,
		skill_table[paf->type].name, paf->where, paf->level,
		paf->duration, paf->modifier, paf->location,
		write_flags (paf->bitvector));
    }

  f_printf (fp, END_MARK LF);
  return;
}

char *
get_obj_save_header (save_t type)
{
  switch (type)
    {
    case SAVE_CHAR:
      return "OBJECT";
    case SAVE_CORPSE:
      return "CORPSE";
    case SAVE_ROOM:
      return "ROOMOBJ";
    case SAVE_PET:
      return "PETOBJ";
    default:
      bugf ("Bad save type (%d)", type);
      return "OBJECT";
    }
}

char *
get_char_save_header (save_t type)
{
  switch (type)
    {
    case SAVE_CHAR:
      return "PLAYER";
    case SAVE_MOB:
      return "MOB";
    case SAVE_PET:
      return "PET";
    default:
      return "CHAR";
    }
}

#define get_desc_save_header    "DESC"


void
write_obj (CharData * ch, ObjData * obj, FileData * fp, int iNest,
	   int iNext, save_t type)
{
  ExDescrData *ed;
  AffectData *paf;
  vnum_t where = ROOM_VNUM_MORGUE;

  if (!obj)
    return;


  if (obj->prev_content != NULL)
    {
      if (obj->prev_content->in_room == NULL
	  || (type == SAVE_ROOM && iNext < 35))
	write_obj (ch, obj->prev_content, fp, iNest, iNext + 1, type);
    }

  if (type == SAVE_CORPSE || type == SAVE_ROOM)
    {
      if (obj->in_obj != NULL)
	where = 1;
      if (obj->in_room != NULL)
	where = obj->in_room->vnum;
    }


  if (ch && !IsObjStat (obj, ITEM_QUEST) &&
      (type == SAVE_CHAR || type == SAVE_PET) && obj->prev_content == NULL)
    {
      if ((ch->level < obj->level - lvl_bonus (ch)))
	return;

      if ((obj->item_type == ITEM_KEY && (IsNPC (ch)
					  || obj->pIndexData->vnum !=
					  ch->pcdata->home_key))
	  || (obj->item_type == ITEM_MAP && !obj->value[0]))
	return;
    }

  f_printf (fp, "#%s" LF, get_obj_save_header (type));
  write_int (fp, "Vnum", "%ld", obj->pIndexData->vnum, 0);
  if (type == SAVE_CORPSE || type == SAVE_ROOM)
    write_int (fp, "Where", "%ld", where, ROOM_VNUM_MORGUE);
  write_string (fp, "Owner", obj->owner, NULL);
  if (!obj->pIndexData->new_format)
    f_printf (fp, "Oldstyle" LF);
  if (obj->enchanted)
    f_printf (fp, "Enchanted" LF);
  write_int (fp, "Nest", "%d", iNest, -1);



  write_string (fp, "Name", obj->name, obj->pIndexData->name);
  write_string (fp, "ShD", obj->short_descr, obj->pIndexData->short_descr);
  write_string (fp, "Desc", obj->description, obj->pIndexData->description);
  write_bit (fp, "ExtF", obj->extra_flags, obj->pIndexData->extra_flags);
  write_bit (fp, "WeaF", obj->wear_flags, obj->pIndexData->wear_flags);
  write_int (fp, "Ityp", "%d", obj->item_type, obj->pIndexData->item_type);
  write_int (fp, "Wt", "%d", obj->weight, obj->pIndexData->weight);
  write_int (fp, "Cond", "%d", obj->condition, obj->pIndexData->condition);



  write_int (fp, "Wear", "%d", obj->wear_loc, WEAR_NONE);
  write_int (fp, "Lev", "%d", obj->level, obj->pIndexData->level);
  write_int (fp, "Time", "%d", obj->timer, 0);
  write_int (fp, "Cost", "%ld", obj->cost, 0);
  if (obj->enchanted
      || memcmp (obj->value, obj->pIndexData->value, sizeof (obj->value)))
    write_array (fp, "Valu", "%ld", obj->value, 5);

  switch (obj->item_type)
    {
    case ITEM_POTION:
    case ITEM_SCROLL:
    case ITEM_PILL:
      if (obj->value[1] > 0)
	{
	  f_writef (fp, "Spell 1", "'%s'" LF,
		    skill_table[obj->value[1]].name);
	}

      if (obj->value[2] > 0)
	{
	  f_writef (fp, "Spell 2", "'%s'" LF,
		    skill_table[obj->value[2]].name);
	}

      if (obj->value[3] > 0)
	{
	  f_writef (fp, "Spell 3", "'%s'" LF,
		    skill_table[obj->value[3]].name);
	}

      break;

    case ITEM_STAFF:
    case ITEM_WAND:
      if (obj->value[3] > 0)
	{
	  f_writef (fp, "Spell 3", "'%s'" LF,
		    skill_table[obj->value[3]].name);
	}

      break;
    default:
      break;
    }

  for (paf = obj->affect_first; paf != NULL; paf = paf->next)
    {
      if (paf->type < 0 || paf->type >= top_skill)
	continue;
      f_writef (fp, "Affc", "'%s' %3d %3d %3d %3d %3d %s" LF,
		skill_table[paf->type].name, paf->where, paf->level,
		paf->duration, paf->modifier, paf->location,
		write_flags (paf->bitvector));
    }

  for (ed = obj->ed_first; ed != NULL; ed = ed->next)
    {
      f_writef (fp, "ExDe", "%s~ %s~" LF, ed->keyword, ed->description);
    }

  f_printf (fp, END_MARK LF);

  if (obj->content_last != NULL)
    write_obj (ch, obj->content_last, fp, iNest + 1, iNext, type);

  return;
}

void
write_descriptor (Descriptor * d, FileData * fp)
{
  f_printf (fp, "#%s" LF, get_desc_save_header);
  write_string (fp, "Host", d->host, NULL);
  write_int (fp, "Descr", "%d", d->descriptor, 0);
  write_int (fp, "Connected", "%d", d->connected, 0);
  write_int (fp, "IP", "%ld", d->ip, 0);
  write_int (fp, "Port", "%d", d->port, 0);
  write_bit (fp, "Flags", d->desc_flags, 0);
  write_int (fp, "ScrW", "%u", d->scr_width, 80);
  write_int (fp, "ScrH", "%u", d->scr_height, 24);
  write_int (fp, "ByteN", "%d", d->bytes_normal, 0);
#ifndef DISABLE_MCCP

  write_int (fp, "ByteC", "%d", d->bytes_compressed, 0);
  write_int (fp, "CVersion", "%d", d->mccp_version, 0);
#endif

  write_string (fp, "TType", d->ttype, NULL);
  if (IsMXP (d))
    {
      write_string (fp, "MXPSup", d->mxp.supports, NULL);
      write_int (fp, "MXPVer", "%.2f", d->mxp.mxp_ver, 0);
      write_int (fp, "MXPClVer", "%.2f", d->mxp.client_ver, 0);
      write_int (fp, "MXPStyl", "%.2f", d->mxp.style_ver, 0);
      write_string (fp, "MXPClien", d->mxp.client, NULL);
      write_int (fp, "MXPReg", "%d", d->mxp.registered, 0);
      write_bit (fp, "MXPFlag1", d->mxp.flags, 0);
      write_bit (fp, "MXPFlag2", d->mxp.flags2, 0);
    }
  if (IsPortal (d))
    {
      write_int (fp, "Keycode", "%u", d->portal.keycode, 0);
      write_string (fp, "PortVer", d->portal.version, NULL);
    }
  if (IsFireCl (d))
    write_int (fp, "IMPver", "%.2f", d->imp_vers, 0);
  if (IsPueblo (d))
    write_int (fp, "Pueblo", "%.2f", d->pueblo_vers, 0);
  f_printf (fp, END_MARK LF);
  return;
}

void
set_player_level (CharData * ch, int Old, int New, int version)
{
  int diff = MAX_LEVEL - LEVEL_IMMORTAL;
  int imm_level = Old - diff;
  int mod = New - Old;

  if (ch->version >= version)
    return;

  if (ch->level >= imm_level)
    ch->level += mod;
  if (ch->trust >= imm_level)
    ch->trust += mod;
  save_char_obj (ch);
}

void
pload_default (CharData * ch)
{
  int stat;

  ch->race = default_race;
  ch->act =
    PLR_NOSUMMON | PLR_AUTOMAP | PLR_AUTOEXIT | PLR_AUTODAMAGE |
    PLR_AUTOASSIST | PLR_AUTOGOLD | PLR_AUTOLOOT | PLR_AUTOSAC |
    PLR_AUTOSPLIT | PLR_AUTOPROMPT;
  ch->comm = COMM_COMBINE | COMM_PROMPT;
  ch->prompt = str_dup (DEFAULT_PROMPT);
  for (stat = 0; stat < STAT_MAX; stat++)
    ch->perm_stat[stat] = 13;
  ch->pcdata->security = 0;
  default_color (ch, -1);
  ch->pcdata->trivia = 0;
  end_quest (ch, 0);
#ifndef DISABLE_I3

  i3init_char (ch);
#endif
}

void
pload_found (CharData * ch)
{
  int i;

  if (ch->race == NULL)
    ch->race = default_race;

  ch->size = ch->race->size;
  ch->dam_type = 17;

  for (i = 0; i < MAX_RACE_SKILL; i++)
    {
      if (ch->race->skills[i] == NULL)
	break;
      group_add (ch, ch->race->skills[i], false);
    }
  ch->affected_by = ch->affected_by | ch->race->aff;
  ch->imm_flags = ch->imm_flags | ch->race->imm;
  ch->res_flags = ch->res_flags | ch->race->res;
  ch->vuln_flags = ch->vuln_flags | ch->race->vuln;
  ch->form = ch->race->form;
  ch->parts = ch->race->parts;


  set_player_level (ch, 0, 0, 0);

  ch->Class[CLASS_COUNT] = 0;
  while (ch->Class[ch->Class[CLASS_COUNT]] != -1)
    ch->Class[CLASS_COUNT] += 1;

  if (ch->version < 11)
    {
      ch->pcdata->home[PC_HOME_COUNT] = 0;
      while (get_room_index (ch->pcdata->home[PC_HOME_COUNT]) != NULL)
	ch->pcdata->home[PC_HOME_COUNT] += 1;
    }

  if (ch->version < 12)
    default_color (ch, -1);
}


bool
load_char_obj (Descriptor * d, const char *name)
{
  CharData *ch;
  static FileData *fp;
  static bool found;
  char buf[MIL];

  ch = new_char ();

  ch->pcdata = new_pcdata ();

  d->character = ch;
  ch->desc = d;
  ch->name = str_dup (capitalize (name));
  ch->id = get_pc_id ();
  pload_default (ch);
  found = false;

  sprintf (buf, "%s.gz", pfilename (name, PFILE_NORMAL));


#ifdef GZFILEIO

  if ((fp = f_open (buf, "rb")) == NULL)
#else

  if ((fp = f_open (buf, "r")) != NULL)
    {
      f_close (fp);
#ifdef unix

      system (FORMATF ("gzip -dfq %s", buf));
#else

      bugf ("File '%s' is compressed!", buf);
      AttemptJump = false;
      return found;
#endif

    }
#endif
  sprintf (buf, pfilename (name, PFILE_NORMAL));
  if ((fp = f_open (buf, "r")) != NULL)
    {
      int iNest;

      AttemptJump = true;

      if (setjmp (jump_env) == 1)
	{
	  size_t iLine, iChar;
	  char c;

	  iChar = f_tell (fp);
	  f_seek (fp, 0, SEEK_SET);
	  for (iLine = 0; (size_t) f_tell (fp) < iChar; iLine++)
	    {
	      while ((c = f_getc (fp)) != '\n' && c != EOF)
		;
	    }
	  f_seek (fp, iChar, SEEK_SET);

	  bugf ("[*LCO*] Error in file '%s' line %d", capitalize (name),
		iLine);
	  f_close (fp);
	  return found;
	}

      for (iNest = 0; iNest < MAX_NEST; iNest++)
	rgObjNest[iNest] = NULL;

      found = true;
      for (;;)
	{
	  char letter;
	  char *word;

	  letter = read_letter (fp);
	  if (letter == '*')
	    {
	      read_to_eol (fp);
	      continue;
	    }

	  if (letter != '#')
	    {
	      bugf ("# not found. (%c)", letter);
	      break;
	    }

	  word = read_word (fp);
	  if (!str_cmp (word, get_char_save_header (SAVE_CHAR)))
	    read_char (ch, fp);
	  else if (!str_cmp (word, get_obj_save_header (SAVE_CHAR)))
	    read_obj (ch, fp, SAVE_CHAR);
	  else if (!str_cmp (word, get_char_save_header (SAVE_PET)))
	    read_pet (ch, fp);
	  else if (!str_cmp (word, get_obj_save_header (SAVE_PET)))
	    read_obj (ch, fp, SAVE_PET);
	  else if (!str_cmp (word, get_desc_save_header))
	    read_descriptor (d, fp);
	  else if (!str_cmp (word, END_MARK))
	    break;
	  else
	    {
	      bug ("Load_char_obj: bad section.");
	      break;
	    }
	}
      f_close (fp);
    }

  AttemptJump = false;


  if (found)
    {
      pload_found (ch);
    }

  return found;
}



void
read_char (CharData * ch, FileData * fp)
{
  char buf[MAX_STRING_LENGTH];
  const char *word;
  bool fMatch;
  int count = 0;
  int count2 = 0;
  int lastlogoff = current_time;
  int percent;
  int ignore = 0;

  logf ("Loading %s.", ch->name);

  for (;;)
    {
      word = f_eof (fp) ? END_MARK : read_word (fp);
      fMatch = false;

      switch (toupper (word[0]))
	{
	case '*':
	  fMatch = true;
	  read_to_eol (fp);
	  break;

	case 'A':
	  Key ("Act", ch->act, read_flag (fp));
	  Key ("AffectedBy", ch->affected_by, read_flag (fp));
	  Key ("AfBy", ch->affected_by, read_flag (fp));
	  Key ("Alignment", ch->alignment, read_number (fp));
	  Key ("Alig", ch->alignment, read_number (fp));
	  Key ("AWins", ch->pcdata->awins, read_number (fp));
	  Key ("ALosses", ch->pcdata->alosses, read_number (fp));

	  if (!str_cmp (word, "Alia"))
	    {
	      if (count >= MAX_ALIAS)
		{
		  read_to_eol (fp);
		  fMatch = true;
		  break;
		}

	      ch->pcdata->alias[count] = str_dup (read_word (fp));
	      ch->pcdata->alias_sub[count] = str_dup (read_word (fp));
	      count++;
	      fMatch = true;
	      break;
	    }

	  if (!str_cmp (word, "Alias"))
	    {
	      if (count >= MAX_ALIAS)
		{
		  read_to_eol (fp);
		  fMatch = true;
		  break;
		}

	      ch->pcdata->alias[count] = str_dup (read_word (fp));
	      ch->pcdata->alias_sub[count] = read_string (fp);
	      count++;
	      fMatch = true;
	      break;
	    }
	  Key_Ignore ("AC");
	  Key_Ignore ("Armor");

	  if (!str_cmp (word, "ACs"))
	    {
	      if (ch->version < 9)
		{
		  int i;

		  for (i = 0; i < 4; i++)
		    ch->armor[i] = read_number (fp);
		}
	      else
		read_array (fp, ch->armor, MAX_AC, 100);
	      fMatch = true;
	      break;
	    }

	  if (!str_cmp (word, "AffD"))
	    {
	      AffectData *paf;
	      int sn;

	      paf = new_affect ();

	      sn = skill_lookup (read_word (fp));
	      if (sn < 0)
		bug ("read_char: unknown skill.");
	      else
		paf->type = sn;

	      paf->level = read_number (fp);
	      paf->duration = read_number (fp);
	      paf->modifier = read_number (fp);
	      paf->location = read_enum (apply_t, fp);
	      paf->bitvector = read_number (fp);
	      Link (paf, ch->affect, next, prev);
	      fMatch = true;
	      break;
	    }

	  if (!str_cmp (word, "Affc"))
	    {
	      AffectData *paf;
	      int sn;

	      paf = new_affect ();

	      sn = skill_lookup (read_word (fp));
	      if (sn < 0)
		bug ("read_char: unknown skill.");
	      else
		paf->type = sn;

	      paf->where = read_enum (where_t, fp);
	      paf->level = read_number (fp);
	      paf->duration = read_number (fp);
	      paf->modifier = read_number (fp);
	      paf->location = read_enum (apply_t, fp);
	      paf->bitvector = read_flag (fp);
	      Link (paf, ch->affect, next, prev);
	      fMatch = true;
	      break;
	    }

	  if (!str_cmp (word, "AttrMod") || !str_cmp (word, "AMod"))
	    {
	      if (ch->version < 9)
		{
		  int stat;

		  for (stat = 0; stat < STAT_MAX; stat++)
		    ch->mod_stat[stat] = read_number (fp);
		}
	      else
		read_array (fp, ch->mod_stat, STAT_MAX, 3);
	      fMatch = true;
	      break;
	    }

	  if (!str_cmp (word, "AttrPerm") || !str_cmp (word, "Attr"))
	    {
	      if (ch->version < 9)
		{
		  int stat;

		  for (stat = 0; stat < STAT_MAX; stat++)
		    ch->perm_stat[stat] = read_number (fp);
		}
	      else
		read_array (fp, ch->perm_stat, STAT_MAX, 3);
	      fMatch = true;
	      break;
	    }
	  break;

	case 'B':
	  Key_Str ("Bamfin", ch->pcdata->bamfin);
	  Key_Str ("Bamfout", ch->pcdata->bamfout);
	  Key_Str ("Bin", ch->pcdata->bamfin);
	  Key_Str ("Bout", ch->pcdata->bamfout);
	  Key ("BankG", ch->pcdata->gold_bank, read_number (fp));
	  if (!str_cmp (word, "Buddy"))
	    {
	      if (count2 >= MAX_BUDDY)
		{
		  read_to_eol (fp);
		  fMatch = true;
		  break;
		}
	      ch->pcdata->buddies[count2] = read_string (fp);
	      count2++;
	      fMatch = true;
	      break;
	    }

	  if (!str_cmp (word, "Boards"))
	    {
	      int i, num = read_number (fp);
	      char *boardname;

	      for (; num; num--)
		{
		  boardname = read_word (fp);
		  i = board_lookup (boardname);

		  if (i == BOARD_NOTFOUND)
		    {
		      sprintf (buf,
			       "read_char: %s had unknown board name: %s. Skipped.",
			       ch->name, boardname);
		      log_string (buf);
		      read_number (fp);
		    }
		  else
		    ch->pcdata->last_note[i] = read_number (fp);
		}

	      fMatch = true;
	      break;
	    }
	  break;

	case 'C':
	  if (!str_cmp (word, "Cla") || !str_cmp (word, "Class"))
	    {
	      if (ch->version < 10)
		{
		  int i, j;

		  for (i = 0; i < MAX_MCLASS; i++)
		    {
		      ch->Class[i] = read_number (fp);
		      if (ch->Class[i] == -1)
			break;
		    }
		  for (i = j = 0; i < MAX_MCLASS; i++)
		    {
		      if (ch->Class[i] < 0 || ch->Class[i] >= top_class)
			{
			  ch->Class[i] = -1;
			  j++;
			  continue;
			}
		      ch->Class[i - j] = ch->Class[i];
		      ch->Class[i - j + 1] = -1;
		    }
		}
	      else
		{
		  read_array (fp, ch->Class, MAX_MCLASS, -1);
		}
	      fMatch = true;
	      break;
	    }
	  Key_SFun ("Clan", ch->pcdata->clan, clan_lookup);
	  if (!str_cmp (word, "Condition") || !str_cmp (word, "Cond"))
	    {
	      ch->pcdata->condition[0] = read_number (fp);
	      ch->pcdata->condition[1] = read_number (fp);
	      ch->pcdata->condition[2] = read_number (fp);
	      fMatch = true;
	      break;
	    }
	  if (!str_cmp (word, "Cnd"))
	    {
	      if (ch->version < 9)
		{
		  ch->pcdata->condition[0] = read_number (fp);
		  ch->pcdata->condition[1] = read_number (fp);
		  ch->pcdata->condition[2] = read_number (fp);
		  ch->pcdata->condition[3] = read_number (fp);
		}
	      else
		read_array (fp, ch->pcdata->condition, 4, 0);
	      fMatch = true;
	      break;
	    }
	  Key ("Comm", ch->comm, read_flag (fp));
	  Key ("Cols", ch->columns, read_number (fp));
	  if (!str_cmp (word, "Colo"))
	    {
	      if (ch->version >= 8)
		{
		  int i, j, num = read_number (fp);

		  for (i = 0; i < Min (num, MAX_CUSTOM_COLOR); i++)
		    {
		      if (i >= MAX_CUSTOM_COLOR)
			break;
		      for (j = 0; j < CT_MAX; j++)
			ch->pcdata->color[i].at[j] = read_number (fp);
		    }
		}
	      read_to_eol (fp);
	      fMatch = true;
	      break;
	    }
	  break;

	case 'D':
	  Key ("Damroll", ch->damroll, read_number (fp));
	  Key ("Dam", ch->damroll, read_number (fp));
	  Key_Str ("Description", ch->description);
	  Key_Str ("Desc", ch->description);
	  Key_SFun ("Deity", ch->deity, deity_lookup);
	  break;

	case 'E':
	  if (!str_cmp (word, END_MARK))
	    {
	      if (ch->in_room == NULL)
		ch->in_room = get_room_index (ROOM_VNUM_LIMBO);


	      percent = (current_time - lastlogoff) * 25 / (2 * 60 * 60);

	      percent = Min (percent, 100);

	      if (percent > 0 && !IsAffected (ch, AFF_POISON)
		  && !IsAffected (ch, AFF_PLAGUE))
		{
		  ch->hit += (ch->max_hit - ch->hit) * percent / 100;
		  ch->mana += (ch->max_mana - ch->mana) * percent / 100;
		  ch->move += (ch->max_move - ch->move) * percent / 100;
		}
	      return;
	    }
	  Key ("Exp", ch->exp, read_number (fp));
	  break;

	case 'G':
	  Key ("Gold", ch->gold, read_number (fp));
	  if (!str_cmp (word, "GQmobs"))
	    {
	      if (gquest_info.running != GQUEST_OFF)
		{
		  ch->gquest = new_gqlist ();
		  alloc_mem (ch->gquest->gq_mobs, vnum_t,
			     gquest_info.mob_count);
		  read_array (fp, ch->gquest->gq_mobs,
			      gquest_info.mob_count, -1);
		  ch->gquest->ch = ch;
		  Link (ch->gquest, gqlist, next, prev);
		}
	      fMatch = true;
	      break;
	    }
	  if (!str_cmp (word, "Group") || !str_cmp (word, "Gr"))
	    {
	      int gn;
	      char *temp;

	      temp = read_word (fp);
	      gn = group_lookup (temp);

	      if (gn < 0)
		{
		  bugf ("read_char: unknown group (%s). ", temp);
		}
	      else
		gn_add (ch, gn);
	      fMatch = true;
	      break;
	    }
	  Key_Array ("Gstats", ch->pcdata->gamestat, MAX_GAMESTAT, 0);
	  Key_Str ("GProm", ch->gprompt);
	  break;

	case 'H':
	  Key ("Hitroll", ch->hitroll, read_number (fp));
	  Key ("Hit", ch->hitroll, read_number (fp));
	  Key ("HKey", ch->pcdata->home_key, read_number (fp));
	  Key ("HRoom", ch->pcdata->home_room, read_number (fp));
	  Key_Array ("Homes", ch->pcdata->home, MAX_HOME_VNUMS, 0);

	  if (!str_cmp (word, "HpManaMove") || !str_cmp (word, "HMV"))
	    {
	      ch->hit = read_number (fp);
	      ch->max_hit = read_number (fp);
	      ch->mana = read_number (fp);
	      ch->max_mana = read_number (fp);
	      ch->move = read_number (fp);
	      ch->max_move = read_number (fp);
	      fMatch = true;
	      break;
	    }

	  if (!str_cmp (word, "HpManaMovePerm") || !str_cmp (word, "HMVP"))
	    {
	      ch->pcdata->perm_hit = read_number (fp);
	      ch->pcdata->perm_mana = read_number (fp);
	      ch->pcdata->perm_move = read_number (fp);
	      fMatch = true;
	      break;
	    }

	  break;

	case 'I':
	  Key ("Id", ch->id, read_number (fp));
	  Key ("InvisLevel", ch->invis_level, read_number (fp));
	  Key ("Inco", ch->incog_level, read_number (fp));
	  Key ("Invi", ch->invis_level, read_number (fp));
	  if (!str_cmp (word, "Ignore"))
	    {
	      if (ignore >= MAX_IGNORE)
		{
		  read_to_eol (fp);
		  fMatch = true;
		  break;
		}
	      ch->pcdata->ignore[ignore] = read_string (fp);
	      ch->pcdata->ignore_flags[ignore] = read_flag (fp);
	      ignore++;
	      fMatch = true;
	      break;
	    }
#ifndef DISABLE_I3
	  if ((fMatch = i3load_char (ch, fp, word)))
	    break;
#endif

	  break;

	case 'L':
	  Key ("LastLevel", ch->pcdata->last_level, read_number (fp));
	  Key ("LLev", ch->pcdata->last_level, read_number (fp));
	  Key ("Level", ch->level, read_number (fp));
	  Key ("Lev", ch->level, read_number (fp));
	  Key ("Levl", ch->level, read_number (fp));
	  Key ("LogO", lastlogoff, read_number (fp));
	  Key_Str ("LongDescr", ch->long_descr);
	  Key_Str ("LnD", ch->long_descr);
	  break;

	case 'N':
	  Key_Str ("Name", ch->name);
	  Key_Ignore ("Not");
	  break;

	case 'P':
	  Key_Str ("Password", ch->pcdata->pwd);
	  Key_Str ("Pass", ch->pcdata->pwd);
	  Key ("Played", ch->pcdata->played, read_number (fp));
	  Key ("Plyd", ch->pcdata->played, read_number (fp));
	  Key ("Points", ch->pcdata->points, read_number (fp));
	  Key ("Pnts", ch->pcdata->points, read_number (fp));
	  Key ("Position", ch->position, read_enum (position_t, fp));
	  Key ("Pos", ch->position, read_enum (position_t, fp));
	  Key ("Practice", ch->practice, read_number (fp));
	  Key ("Prac", ch->practice, read_number (fp));
	  Key ("PrClass", ch->pcdata->prime_class, read_number (fp));
	  Key_Str ("Prompt", ch->prompt);
	  Key_Str ("Prom", ch->prompt);
	  break;

	case 'Q':
	  Key ("QuestPnts", ch->pcdata->quest.points, read_number (fp));
	  Key ("QuestTime", ch->pcdata->quest.time, read_number (fp));
	  Key ("QuestStatus", ch->pcdata->quest.status,
	       read_enum (quest_t, fp));
	  Key_Do ("QuestLoc",
		  (ch->pcdata->quest.room =
		   get_room_index (read_number (fp))));
	  Key_Do ("QuestMob",
		  (ch->pcdata->quest.mob =
		   find_quest_char (ch, read_number (fp))));
	  Key_Do ("QuestObj",
		  (ch->pcdata->quest.obj =
		   create_quest_obj (ch, read_number (fp))));
	  Key_Do ("QuestGiver",
		  (ch->pcdata->quest.giver =
		   find_quest_char (ch, read_number (fp))));
	  break;

	case 'R':
	  Key ("Rank", ch->rank, read_number (fp));
	  Key_SFun ("Race", ch->race, race_lookup);
	  Key_Do ("RoomRLE", read_rle (ch->pcdata->explored, fp));
	  Key_Do ("Room", (ch->in_room = get_room_index (read_number (fp))));
	  break;

	case 'S':
	  Key ("SavingThrow", ch->saving_throw, read_number (fp));
	  Key ("Save", ch->saving_throw, read_number (fp));
	  Key ("Scro", ch->lines, read_number (fp));
	  Key ("Sex", ch->sex, read_enum (sex_t, fp));
	  Key_Str ("ShortDescr", ch->short_descr);
	  Key_Str ("ShD", ch->short_descr);
	  Key ("Sec", ch->pcdata->security, read_number (fp));
	  Key ("Silv", ch->silver, read_number (fp));
	  Key ("Shares", ch->pcdata->shares, read_number (fp));
	  Key ("StrEdKey", ch->pcdata->str_ed_key, read_letter (fp));
	  Key_Do ("StayRace", (ch->pcdata->stay_race = true));
	  Key_Array ("Stance", ch->stance, MAX_STANCE, 0);
	  if (!str_cmp (word, "Skill") || !str_cmp (word, "Sk"))
	    {
	      int sn;
	      int value;
	      char *temp;

	      value = read_number (fp);
	      temp = read_word (fp);
	      sn = skill_lookup (temp);

	      if (sn < 0)
		{
		  bugf ("read_char: unknown skill. (%s)", temp);
		}
	      else
		ch->pcdata->learned[sn] = value;
	      fMatch = true;
	      break;
	    }
	  if (!str_cmp (word, "Subscribe"))
	    {
	      int i, num = read_number (fp);

	      char *boardname;

	      for (; num; num--)
		{

		  boardname = read_word (fp);
		  i = board_lookup (boardname);

		  if (i == BOARD_NOTFOUND)
		    {

		      sprintf (buf,
			       "read_char: %s had unknown board name: %s. Skipped.",
			       ch->name, boardname);
		      log_string (buf);
		      read_number (fp);

		    }
		  else

		    ch->pcdata->unsubscribed[i] = read_number (fp);
		}

	      fMatch = true;
	      break;
	    }

	  break;

	case 'T':
	  Key ("TrueSex", ch->pcdata->true_sex, read_enum (sex_t, fp));
	  Key ("TSex", ch->pcdata->true_sex, read_enum (sex_t, fp));
	  Key ("Trai", ch->train, read_number (fp));
	  Key ("Trust", ch->trust, read_number (fp));
	  Key ("Tru", ch->trust, read_number (fp));
	  Key ("Trivia", ch->pcdata->trivia, read_number (fp));
	  Key ("TimeZone", ch->pcdata->timezone, read_number (fp));
	  if (!str_cmp (word, "Title") || !str_cmp (word, "Titl"))
	    {
	      ch->pcdata->title = read_string (fp);
	      if (ch->pcdata->title[0] != '.' &&
		  ch->pcdata->title[0] != ',' &&
		  ch->pcdata->title[0] != '!' && ch->pcdata->title[0] != '?')
		{
		  sprintf (buf, " %s", ch->pcdata->title);
		  replace_str (&ch->pcdata->title, buf);
		}
	      fMatch = true;
	      break;
	    }

	  break;

	case 'V':
	  Key ("Version", ch->version, read_number (fp));
	  Key ("Vers", ch->version, read_number (fp));
	  Key ("Vt100", ch->pcdata->vt100, read_flag (fp));
	  Key_Do ("Vnum",
		  (ch->pIndexData = get_char_index (read_number (fp))));
	  break;

	case 'W':
	  if (!str_cmp (word, "WarInfo"))
	    {
	      if (war_info.status != WAR_OFF)
		{
		  ch->war = new_warlist ();
		  ch->war->hit = read_number (fp);
		  ch->war->mana = read_number (fp);
		  ch->war->move = read_number (fp);
		  ch->war->flags = read_flag (fp);
		  ch->war->ch = ch;
		  Link (ch->war, warlist, next, prev);
		}
	      fMatch = true;
	      break;
	    }
	  Key ("Wimpy", ch->wimpy, read_number (fp));
	  Key ("Wimp", ch->wimpy, read_number (fp));
	  Key ("Wizn", ch->wiznet, read_flag (fp));
	  Key_Str ("WhoD", ch->pcdata->who_descr);
	  break;
	}

      if (!fMatch)
	{
	  bugf ("read_char: no match for %s->%s.", ch->name, word);
	  read_to_eol (fp);
	}
    }
}


void
read_pet (CharData * ch, FileData * fp)
{
  const char *word;
  CharData *pet;
  bool fMatch;
  int lastlogoff = current_time;
  int percent;


  word = f_eof (fp) ? END_MARK : read_word (fp);
  if (!str_cmp (word, "Vnum"))
    {
      vnum_t vnum;

      vnum = read_number (fp);
      if (get_char_index (vnum) == NULL)
	{
	  bugf ("Fread_pet: bad vnum %ld.", vnum);
	  pet = create_mobile (get_char_index (MOB_VNUM_FIDO));
	}
      else
	pet = create_mobile (get_char_index (vnum));
    }
  else
    {
      bug ("Fread_pet: no vnum in file.");
      pet = create_mobile (get_char_index (MOB_VNUM_FIDO));
    }

  for (;;)
    {
      word = f_eof (fp) ? END_MARK : read_word (fp);
      fMatch = false;

      switch (toupper (word[0]))
	{
	case '*':
	  fMatch = true;
	  read_to_eol (fp);
	  break;

	case 'A':
	  Key ("Act", pet->act, read_flag (fp));
	  Key ("AfBy", pet->affected_by, read_flag (fp));
	  Key ("Alig", pet->alignment, read_number (fp));

	  if (!str_cmp (word, "ACs"))
	    {
	      if (pet->version < 9)
		{
		  int i;

		  for (i = 0; i < 4; i++)
		    pet->armor[i] = read_number (fp);
		}
	      else
		read_array (fp, pet->armor, MAX_AC, 100);

	      fMatch = true;
	      break;
	    }

	  if (!str_cmp (word, "AffD"))
	    {
	      AffectData *paf;
	      int sn;

	      paf = new_affect ();

	      sn = skill_lookup (read_word (fp));
	      if (sn < 0)
		bug ("read_char: unknown skill.");
	      else
		paf->type = sn;

	      paf->level = read_number (fp);
	      paf->duration = read_number (fp);
	      paf->modifier = read_number (fp);
	      paf->location = read_enum (apply_t, fp);
	      paf->bitvector = read_number (fp);
	      Link (paf, pet->affect, next, prev);
	      fMatch = true;
	      break;
	    }

	  if (!str_cmp (word, "Affc"))
	    {
	      AffectData *paf;
	      int sn;

	      paf = new_affect ();

	      sn = skill_lookup (read_word (fp));
	      if (sn < 0)
		bug ("read_char: unknown skill.");
	      else
		paf->type = sn;

	      paf->where = read_enum (where_t, fp);
	      paf->level = read_number (fp);
	      paf->duration = read_number (fp);
	      paf->modifier = read_number (fp);
	      paf->location = read_enum (apply_t, fp);
	      paf->bitvector = read_flag (fp);
	      Link (paf, pet->affect, next, prev);
	      fMatch = true;
	      break;
	    }

	  if (!str_cmp (word, "AMod"))
	    {
	      if (pet->version < 9)
		{
		  int stat;

		  for (stat = 0; stat < STAT_MAX; stat++)
		    pet->mod_stat[stat] = read_number (fp);
		}
	      else
		read_array (fp, pet->mod_stat, STAT_MAX, 0);
	      fMatch = true;
	      break;
	    }

	  if (!str_cmp (word, "Attr"))
	    {
	      if (ch->version < 9)
		{
		  int stat;

		  for (stat = 0; stat < STAT_MAX; stat++)
		    pet->perm_stat[stat] = read_number (fp);
		}
	      else
		read_array (fp, pet->perm_stat, STAT_MAX, 3);
	      fMatch = true;
	      break;
	    }
	  break;

	case 'C':
	  Key ("Comm", pet->comm, read_flag (fp));
	  break;

	case 'D':
	  Key ("Dam", pet->damroll, read_number (fp));
	  Key_Str ("Desc", pet->description);
	  break;

	case 'E':
	  if (!str_cmp (word, END_MARK))
	    {
	      pet->leader = ch;
	      pet->master = ch;
	      ch->pet = pet;

	      percent = (current_time - lastlogoff) * 25 / (2 * 60 * 60);

	      if (percent > 0 && !IsAffected (ch, AFF_POISON)
		  && !IsAffected (ch, AFF_PLAGUE))
		{
		  percent = Min (percent, 100);
		  pet->hit += (pet->max_hit - pet->hit) * percent / 100;
		  pet->mana += (pet->max_mana - pet->mana) * percent / 100;
		  pet->move += (pet->max_move - pet->move) * percent / 100;
		}
	      return;
	    }
	  Key ("Exp", pet->exp, read_number (fp));
	  break;

	case 'G':
	  Key ("Gold", pet->gold, read_number (fp));
	  break;

	case 'H':
	  Key ("Hit", pet->hitroll, read_number (fp));

	  if (!str_cmp (word, "HMV"))
	    {
	      pet->hit = read_number (fp);
	      pet->max_hit = read_number (fp);
	      pet->mana = read_number (fp);
	      pet->max_mana = read_number (fp);
	      pet->move = read_number (fp);
	      pet->max_move = read_number (fp);
	      fMatch = true;
	      break;
	    }
	  break;

	case 'L':
	  Key ("Levl", pet->level, read_number (fp));
	  Key_Str ("LnD", pet->long_descr);
	  Key ("LogO", lastlogoff, read_number (fp));
	  break;

	case 'N':
	  Key_Str ("Name", pet->name);
	  break;

	case 'P':
	  Key ("Pos", pet->position, read_enum (position_t, fp));
	  break;

	case 'R':
	  Key_SFun ("Race", pet->race, race_lookup);
	  break;

	case 'S':
	  Key ("Save", pet->saving_throw, read_number (fp));
	  Key ("Sex", pet->sex, read_enum (sex_t, fp));
	  Key_Str ("ShD", pet->short_descr);
	  Key ("Silv", pet->silver, read_number (fp));
	  break;

	  if (!fMatch)
	    {
	      bug ("Fread_pet: no match.");
	      read_to_eol (fp);
	    }

	}
    }
}

void
read_obj (CharData * ch, FileData * fp, save_t type)
{
  ObjData *obj;
  const char *word;
  int iNest;
  bool fMatch;
  bool fNest;
  bool fVnum;
  bool first;
  bool new_format;
  bool make_new;
  vnum_t where;

  fVnum = false;
  obj = NULL;
  first = true;
  new_format = false;
  make_new = false;

  word = f_eof (fp) ? END_MARK : read_word (fp);
  if (!str_cmp (word, "Vnum"))
    {
      vnum_t vnum;
      ObjIndex *pObj;

      first = false;

      vnum = read_number (fp);
      if ((pObj = get_obj_index (vnum)) == NULL)
	{
	  bugf ("Fread_obj: bad vnum %ld.", vnum);
	}
      else
	{
	  obj = create_object (pObj, -1);
	  new_format = true;
	  fVnum = true;
	}

    }

  if (obj == NULL)
    {
      obj = new_obj ();
      obj->name = str_dup ("");
      obj->short_descr = str_dup ("");
      obj->description = str_dup ("");
    }

  fNest = false;
  fVnum = true;
  iNest = 0;
  where = type == SAVE_CORPSE ? ROOM_VNUM_MORGUE : 0;

  for (;;)
    {
      if (first)
	first = false;
      else
	word = f_eof (fp) ? END_MARK : read_word (fp);
      fMatch = false;

      switch (toupper (word[0]))
	{
	case '*':
	  fMatch = true;
	  read_to_eol (fp);
	  break;

	case 'A':
	  if (!str_cmp (word, "AffD"))
	    {
	      AffectData *paf;
	      int sn;

	      paf = new_affect ();

	      sn = skill_lookup (read_word (fp));
	      if (sn < 0)
		bug ("Fread_obj: unknown skill.");
	      else
		paf->type = sn;

	      paf->level = read_number (fp);
	      paf->duration = read_number (fp);
	      paf->modifier = read_number (fp);
	      paf->location = read_enum (apply_t, fp);
	      paf->bitvector = read_number (fp);
	      Link (paf, obj->affect, next, prev);
	      fMatch = true;
	      break;
	    }
	  if (!str_cmp (word, "Affc"))
	    {
	      AffectData *paf;
	      int sn;

	      paf = new_affect ();

	      sn = skill_lookup (read_word (fp));
	      if (sn < 0)
		bug ("Fread_obj: unknown skill.");
	      else
		paf->type = sn;

	      paf->where = read_enum (where_t, fp);
	      paf->level = read_number (fp);
	      paf->duration = read_number (fp);
	      paf->modifier = read_number (fp);
	      paf->location = read_enum (apply_t, fp);
	      paf->bitvector = read_flag (fp);
	      Link (paf, obj->affect, next, prev);
	      fMatch = true;
	      break;
	    }
	  break;

	case 'C':
	  Key ("Cond", obj->condition, read_number (fp));
	  Key ("Cost", obj->cost, read_number (fp));
	  break;

	case 'D':
	  Key_Str ("Description", obj->description);
	  Key_Str ("Desc", obj->description);
	  break;

	case 'E':
	  Key_Do ("Enchanted", (obj->enchanted = true));
	  Key ("ExtraFlags", obj->extra_flags, read_number (fp));
	  Key ("ExtF", obj->extra_flags, read_flag (fp));

	  if (!str_cmp (word, "ExtraDescr") || !str_cmp (word, "ExDe"))
	    {
	      ExDescrData *ed;

	      ed = new_ed ();

	      ed->keyword = read_string (fp);
	      ed->description = read_string (fp);
	      Link (ed, obj->ed, next, prev);
	      fMatch = true;
	    }

	  if (!str_cmp (word, END_MARK))
	    {
	      if (!fNest || (fVnum && obj->pIndexData == NULL))
		{
		  bug ("Fread_obj: incomplete object.");
		  free_obj (obj);
		  return;
		}
	      else
		{
		  if (!fVnum)
		    {
		      free_obj (obj);
		      obj = create_object (get_obj_index (OBJ_VNUM_DUMMY), 0);
		    }

		  if (!new_format)
		    {
		      Link (obj, obj, next, prev);
		      obj->pIndexData->count++;
		    }

		  if (!obj->pIndexData->new_format &&
		      obj->item_type == ITEM_ARMOR && obj->value[1] == 0)
		    {
		      obj->value[1] = obj->value[0];
		      obj->value[2] = obj->value[0];
		    }
		  if (make_new)
		    {
		      wloc_t wear;

		      wear = obj->wear_loc;
		      extract_obj (obj);

		      obj = create_object (obj->pIndexData, 0);
		      obj->wear_loc = wear;
		    }
		  if (iNest == 0 || rgObjNest[iNest] == NULL)
		    {
		      switch (type)
			{
			case SAVE_CORPSE:
			  {
			    RoomIndex *Room;
			    CorpseData *c;

			    c = new_corpse ();
			    c->corpse = obj;
			    Link (c, corpse, next, prev);
			    if ((Room = get_room_index (where)) == NULL)
			      Room = get_room_index (ROOM_VNUM_MORGUE);
			    obj_to_room (obj, Room);
			  }
			  break;
			case SAVE_ROOM:
			  {
			    RoomIndex *Room;

			    if ((Room = get_room_index (where)) == NULL)
			      extract_obj (obj);
			    else
			      obj_to_room (obj, Room);
			  }
			  break;
			case SAVE_CHAR:
			  obj_to_char (obj, ch);
			  break;
			case SAVE_PET:
			  obj_to_char (obj, ch->pet ? ch->pet : ch);
			  break;
			default:
			  bugf ("Bad save type (%d)", type);
			  break;
			}
		    }
		  else
		    obj_to_obj (obj, rgObjNest[iNest - 1]);
		  return;
		}
	    }
	  break;

	case 'I':
	  Key ("ItemType", obj->item_type, read_enum (item_t, fp));
	  Key ("Ityp", obj->item_type, read_enum (item_t, fp));
	  break;

	case 'L':
	  Key ("Level", obj->level, read_number (fp));
	  Key ("Lev", obj->level, read_number (fp));
	  break;

	case 'N':
	  Key_Str ("Name", obj->name);

	  if (!str_cmp (word, "Nest"))
	    {
	      iNest = read_number (fp);
	      if (iNest < 0 || iNest >= MAX_NEST)
		{
		  bugf ("Fread_obj: bad nest %d.", iNest);
		}
	      else
		{
		  rgObjNest[iNest] = obj;
		  fNest = true;
		}
	      fMatch = true;
	    }
	  break;

	case 'O':
	  if (!str_cmp (word, "Oldstyle"))
	    {
	      if (obj->pIndexData != NULL && obj->pIndexData->new_format)
		make_new = true;
	      fMatch = true;
	      break;
	    }
	  Key_Str ("Owner", obj->owner);
	  break;

	case 'S':
	  Key_Str ("ShortDescr", obj->short_descr);
	  Key_Str ("ShD", obj->short_descr);

	  if (!str_cmp (word, "Spell"))
	    {
	      int iValue;
	      int sn;

	      iValue = read_number (fp);
	      sn = skill_lookup (read_word (fp));
	      if (iValue < 0 || iValue > 3)
		{
		  bugf ("Fread_obj: bad iValue %d.", iValue);
		}
	      else if (sn < 0)
		{
		  bug ("Fread_obj: unknown skill.");
		}
	      else
		{
		  obj->value[iValue] = sn;
		}
	      fMatch = true;
	      break;
	    }

	  break;

	case 'T':
	  Key ("Timer", obj->timer, read_number (fp));
	  Key ("Time", obj->timer, read_number (fp));
	  break;

	case 'V':
	  if (!str_cmp (word, "Values") || !str_cmp (word, "Vals"))
	    {
	      obj->value[0] = read_number (fp);
	      obj->value[1] = read_number (fp);
	      obj->value[2] = read_number (fp);
	      obj->value[3] = read_number (fp);
	      if (obj->item_type == ITEM_WEAPON && obj->value[0] == 0)
		obj->value[0] = obj->pIndexData->value[0];
	      fMatch = true;
	      break;
	    }

	  if (!str_cmp (word, "Val"))
	    {
	      obj->value[0] = read_number (fp);
	      obj->value[1] = read_number (fp);
	      obj->value[2] = read_number (fp);
	      obj->value[3] = read_number (fp);
	      obj->value[4] = read_number (fp);
	      fMatch = true;
	      break;
	    }

	  Key_Array ("Valu", obj->value, 5, 0);

	  if (!str_cmp (word, "Vnum"))
	    {
	      vnum_t vnum;
	      ObjIndex *pObj;

	      vnum = read_number (fp);
	      if ((pObj = get_obj_index (vnum)) == NULL)
		bugf ("Fread_obj: bad vnum %ld.", vnum);
	      else
		{
		  obj->pIndexData = pObj;
		  fVnum = true;
		}
	      fMatch = true;
	      break;
	    }
	  break;

	case 'W':
	  Key ("WearFlags", obj->wear_flags, read_number (fp));
	  Key ("WeaF", obj->wear_flags, read_flag (fp));
	  Key ("WearLoc", obj->wear_loc, read_enum (wloc_t, fp));
	  Key ("Wear", obj->wear_loc, read_enum (wloc_t, fp));
	  Key ("Weight", obj->weight, read_number (fp));
	  Key ("Wt", obj->weight, read_number (fp));
	  Key ("Where", where, read_number (fp));
	  break;

	}

      if (!fMatch)
	{
	  bug ("Fread_obj: no match.");
	  read_to_eol (fp);
	}
    }
}

void
read_descriptor (Descriptor * d, FileData * fp)
{
  const char *word;
  bool fMatch;

  for (;;)
    {
      word = f_eof (fp) ? END_MARK : read_word (fp);
      fMatch = false;
      if (crs_info.status != CRS_COPYOVER_RECOVER)
	{
	  if (!str_cmp (word, END_MARK))
	    return;
	  else
	    {
	      read_to_eol (fp);
	      continue;
	    }
	}
      switch (toupper (word[0]))
	{
	case '*':
	  fMatch = true;
	  read_to_eol (fp);
	  break;
	case 'B':
	  Key ("ByteN", d->bytes_normal, read_number (fp));
#ifndef DISABLE_MCCP

	  Key ("ByteC", d->bytes_compressed, read_number (fp));
#endif

	  break;
	case 'C':
	  Key ("Connected", d->connected, read_enum (connect_t, fp));
#ifndef DISABLE_MCCP

	  Key ("CVersion", d->mccp_version, read_number (fp));
#endif

	  break;
	case 'D':
	  Key ("Descr", d->descriptor, read_number (fp));
	  break;
	case 'E':
	  if (!str_cmp (word, END_MARK))
	    return;
	  break;
	case 'F':
	  Key ("Flags", d->desc_flags, read_flag (fp));
	  break;
	case 'H':
	  Key_Str ("Host", d->host);
	  break;
	case 'I':
	  Key_Do ("IMPver", d->imp_vers = atof (read_word (fp)));
	  Key ("IP", d->ip, read_number (fp));
	  break;
	case 'K':
	  Key ("Keycode", d->portal.keycode, read_number (fp));
	  break;
	case 'M':
	  Key_Str ("MXPSup", d->mxp.supports);
	  Key_Do ("MXPVer", d->mxp.mxp_ver = atof (read_word (fp)));
	  Key_Do ("MXPClVer", d->mxp.client_ver = atof (read_word (fp)));
	  Key_Do ("MXPStyl", d->mxp.style_ver = atof (read_word (fp)));
	  Key_Str ("MXPClien", d->mxp.client);
	  Key ("MXPReg", d->mxp.registered, read_number (fp));
	  Key ("MXPFlag1", d->mxp.flags, read_flag (fp));
	  Key ("MXPFlag2", d->mxp.flags2, read_flag (fp));
	  break;
	case 'P':
	  Key_StrCpy ("PortVer", d->portal.version);
	  Key_Do ("Pueblo", d->pueblo_vers = atof (read_word (fp)));
	  Key ("Port", d->port, read_number (fp));
	  break;
	case 'S':
	  Key ("ScrW", d->scr_width, read_number (fp));
	  Key ("ScrH", d->scr_height, read_number (fp));
	  break;
	case 'T':
	  Key_StrCpy ("TType", d->ttype);
	  break;
	}

      if (!fMatch)
	{
	  bugf ("no match for %s.", word);
	  read_to_eol (fp);
	}
    }
}

void
save_corpses (void)
{
  FileData *fp;
  CorpseData *c;

  if ((fp = f_open (CORPSE_FILE, "w")) == NULL)
    {
      bug ("save_corpses: " CORPSE_FILE " not found.");
    }
  else
    {
      for (c = corpse_first; c != NULL; c = c->next)
	{
	  if (c->corpse->item_type == ITEM_CORPSE_PC)
	    write_obj (NULL, c->corpse, fp, 0, 0, SAVE_CORPSE);
	  else
	    update_corpses (c->corpse, true);
	}
      f_printf (fp, "#" END_MARK LF);
      f_close (fp);
    }
  return;
}

void
load_corpses (void)
{
  FileData *fp;

  log_string ("Loading corpses...");

  if ((fp = f_open (CORPSE_FILE, "r")) == NULL)
    {
      bug ("load_corpses: " CORPSE_FILE " not found");
    }
  else
    {
      for (;;)
	{
	  char letter;
	  char *word;

	  letter = read_letter (fp);
	  if (letter == '*')
	    {
	      read_to_eol (fp);
	      continue;
	    }

	  if (letter != '#')
	    {
	      bugf ("# not found. (%c)", letter);
	      break;
	    }

	  word = read_word (fp);
	  if (!str_cmp (word, get_obj_save_header (SAVE_CORPSE)))
	    read_obj (NULL, fp, SAVE_CORPSE);
	  else if (!str_cmp (word, END_MARK))
	    break;
	  else
	    {
	      bug ("load_corpses: bad section.");
	      break;
	    }
	}
      f_close (fp);
    }
  return;
}

void
update_corpses (ObjData * obj, bool pdelete)
{
  if (obj && obj->item_type == ITEM_CORPSE_PC)
    {
      CorpseData *c;

      for (c = corpse_first; c != NULL; c = c->next)
	if (c->corpse == obj)
	  break;
      if (c != NULL)
	{
	  if (pdelete)
	    {
	      UnLink (c, corpse, next, prev);
	      free_corpse (c);
	      save_corpses ();
	    }
	}
      else if (obj->content_first != NULL && obj->in_room != NULL)
	{
	  c = new_corpse ();
	  c->corpse = obj;
	  Link (c, corpse, next, prev);
	  save_corpses ();
	}
    }
  return;
}

void
checkcorpse (CharData * ch)
{
  CorpseData *c;
  bool found = false;
  int count = 0;

  if (!ch || IsNPC (ch))
    return;

  for (c = corpse_first; c != NULL; c = c->next)
    {
      if (c->corpse && is_name (ch->name, c->corpse->owner))
	{
	  found = true;
	  count++;
	}
    }
  if (found)
    {
      chprintlnf
	(ch, NEWLINE "{f{RWARNING:{x {WYou have %s in the game.{x",
	 intstr (count, "corpse"));
      new_wiznet (ch, intstr (count, "corpse"), 0, 0, 0,
		  "$N has $t in the game.");
    }
}