1stMud4.5.3/
1stMud4.5.3/backup/
1stMud4.5.3/bin/
1stMud4.5.3/bin/extras/
1stMud4.5.3/data/i3/
1stMud4.5.3/doc/1stMud/
1stMud4.5.3/doc/Diku/
1stMud4.5.3/doc/MPDocs/
1stMud4.5.3/doc/Rom/
1stMud4.5.3/notes/
/**************************************************************************
*  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:
#ifndef 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:
#ifndef 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;
	NameList *nl;
	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);
		}

		if (ch->color_prefix && ch->color_prefix != COLORCODE)
		{
			f_writef(fp, "CPref", "%c", ch->color_prefix);
		}

		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->colors[i][j]);
		f_printf(fp, LF);
		write_string(fp, "CTemplate", ch->pcdata->color_scheme->name, NULL);
		write_string(fp, "Pretit", ch->pcdata->pretit, NULL);
		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]));
		}

		for (nl = ch->pcdata->killer_first; nl != NULL; nl = nl->next)
			write_word(fp, "Kller", nl->name, NULL);

		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);

		write_array(fp, "Timers", "%ld", ch->pcdata->timers, MAX_TIMER);

		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, _NONE);
	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, _NONE);
}

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));
				Key("CPref", ch->color_prefix, read_letter(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->colors[i][j] =
									read_enum(color_value_t, fp);
						}
					}
					read_to_eol(fp);
					fMatch = true;
					break;
				}
				if (!str_cmp(word, "CTemplate"))
				{
					const char *str = read_string(fp);
					ColorTemplate *tmp = find_color_template(str);
					ch->pcdata->color_scheme =
						!tmp ? default_color_scheme : tmp;
					free_string(str);
					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 'K':
				if (!str_cmp(word, "Kller"))
				{
					NameList *nl = new_namelist();

					nl->name = str_dup(read_word(fp));
					if (is_player_name(nl->name))
						Link(nl, ch->pcdata->killer, next, prev);
					else
						free_namelist(nl);
					fMatch = true;
					break;
				}
				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_Str("Pretit", ch->pcdata->pretit);
				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;
				}
				Key_Array("Timers", ch->pcdata->timers, MAX_TIMER, 0);
				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.");
	}
}