1stMUD4.0/bin/
1stMUD4.0/doc/MPDocs/
1stMUD4.0/player/
1stMUD4.0/win32/
1stMUD4.0/win32/rom/
/**************************************************************************
*  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-2003 by Ryan Jennings              *
*            http://1stmud.dlmud.com/  <r-jenn@shaw.ca>                   *
***************************************************************************/
#include "merc.h"
#include "recycle.h"
#include "tables.h"
#include "lookup.h"
#include "interp.h"
#include "save.h"
#include "signals.h"

static OBJ_DATA *rgObjNest[MAX_NEST];

PROTOTYPE(void new_warlist, (CHAR_DATA *));
PROTOTYPE(void free_warlist, (WAR_DATA *));
PROTOTYPE(void new_gqlist, (CHAR_DATA *));
PROTOTYPE(void free_gqlist, (GQ_DATA *));

/*
 * Local functions.
 */
PROTOTYPE(void fwrite_char, (CHAR_DATA *, FILE *));
PROTOTYPE(void fwrite_pet, (CHAR_DATA *, FILE *));
PROTOTYPE(void read_pet, (CHAR_DATA *, READ_DATA *));
PROTOTYPE(void read_descriptor, (DESCRIPTOR_DATA *, READ_DATA *));
PROTOTYPE(void fwrite_descriptor, (DESCRIPTOR_DATA *, FILE *));

#define TAB_SIZE    8

char *format_tabs(int len)
{
	if (len <= 0)
		return "";
	else if (len < TAB_SIZE)
		return "\t\t";
	else if (len < (TAB_SIZE * 2))
		return "\t";
	else
		return " ";
}

/* print *str if not the same as *not, formating *name */
void fwrite_string(FILE * fp, const char *name, const char *str,
				   const char *def)
{
	if (IS_NULLSTR(name))
		fprintf(fp, "%s~\n", fix_string(str));
	else if (!IS_NULLSTR(str) && (IS_NULLSTR(def) || str_cmp(str, def)))
		fprintf(fp, "%s%s%s~\n", name, format_tabs(strlen(name)),
				fix_string(str));
}

/* wrap *str in quotes ('') if not the same as *not, formating *name */
void fwrite_word(FILE * fp, const char *name, const char *str, const char *def)
{
	if (IS_NULLSTR(name))
		fprintf(fp, "'%s'\n", fix_string(str));
	else if (!IS_NULLSTR(str) && (IS_NULLSTR(def) || str_cmp(str, def)))
		fprintf(fp, "%s%s'%s'\n", name, format_tabs(strlen(name)),
				fix_string(str));
}

/* print bit if not the same as not, formating *name */

void fwrite_bit(FILE * fp, const char *name, flag_t bit, flag_t def)
{
	if (IS_NULLSTR(name))
		fprintf(fp, "%s\n", fwrite_flags(bit));
	else if (bit != def)
		fprintf(fp, "%s%s%s\n", name, format_tabs(strlen(name)),
				fwrite_flags(bit));
}

/* print *format, formating *name */

void fwritef(FILE * fp, const char *name, const char *format, ...)
{
	va_list args;

	va_start(args, format);

	if (IS_NULLSTR(name))
		vfprintf(fp, format, args);
	else
	{
		fprintf(fp, "%s%s", name, format_tabs(strlen(name)));
		vfprintf(fp, format, args);
	}
	va_end(args);
	fprintf(fp, "\n");
}

/*
 * Save a character and inventory.
 * Would be cool to save NPC's too for quest purposes,
 *   some of the infrastructure is provided.
 */
void save_char_obj(CHAR_DATA * ch)
{
	char strsave[MAX_INPUT_LENGTH];
	WRITE_DATA *fp;

	if (IS_NPC(ch))
		return;

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

	sprintf(strsave, "%s%s", PLAYER_DIR, capitalize(ch->name));
	if ((fp = open_write(strsave)) == NULL)
	{
		bug("Save_char_obj: file open");
		log_error(strsave);
	}
	else
	{
		fwrite_char(ch, fp->stream);
		if (ch->first_carrying != NULL)
			fwrite_obj(ch, ch->first_carrying, fp->stream, 0, "O");
		/* save the pets */
		if (ch->pet != NULL && ch->pet->in_room == ch->in_room)
			fwrite_pet(ch->pet, fp->stream);
		if (ch->desc && (crs_info.status == CRS_COPYOVER || crash_info.crashed))
			fwrite_descriptor(ch->desc, fp->stream);
		fprintf(fp->stream, "#END\n");
		close_write(fp);
	}
	return;
}

/*
 * Write the char.
 */
void fwrite_char(CHAR_DATA * ch, FILE * fp)
{
	AFFECT_DATA *paf;
	int sn, gn, pos;
	int i, j;

	fprintf(fp, "#%s\n", IS_NPC(ch) ? "MOB" : "PLAYER");

	fwrite_string(fp, "Name", ch->name, NULL);
	fwrite_int(fp, "Id", "%ld", ch->id, 0);
	fwrite_int(fp, "LogO", "%ld", current_time, 0);
	fwrite_int(fp, "Vers", "%d", 9, 0);
	fwrite_string(fp, "ShD", ch->short_descr, NULL);
	fwrite_string(fp, "LnD", ch->long_descr, NULL);
	fwrite_string(fp, "Desc", ch->description, NULL);
	fwrite_string(fp, "Prom", ch->prompt, NULL);
	fwrite_string(fp, "Race", ch->race->name, NULL);
	if (ch->clan != NULL)
	{
		fwrite_string(fp, "Clan", ch->clan->name, NULL);
		fwrite_int(fp, "Rank", "%d", ch->rank, -1);
	}
	fwrite_int(fp, "Sex", "%d", ch->sex, -1);
	fwritef(fp, "Cla", "%s", class_numbers(ch, TRUE));
	if (ch->pcdata->stay_race)
		fprintf(fp, "StayRace\n");
	fwrite_int(fp, "Levl", "%d", ch->level, 0);
	fwrite_int(fp, "Tru", "%d", ch->trust, 0);
	fwrite_int(fp, "Sec", "%d", ch->pcdata->security, 0);	/* OLC */
	fwrite_int(fp, "Plyd", "%d", ch->played + (int) (current_time - ch->logon),
			   0);
	fwrite_int(fp, "Scro", "%d", ch->lines, -1);
	fwrite_int(fp, "Cols", "%d", ch->columns, -1);
	fwrite_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);

	fwritef(fp, "HMV", "%ld %ld %ld %ld %ld %ld", ch->hit,
			ch->max_hit, ch->mana, ch->max_mana, ch->move, ch->max_move);
	fwrite_int(fp, "Gold", "%ld", ch->gold, 0);
	fwrite_int(fp, "Silv", "%ld", ch->silver, 0);
	fwrite_int(fp, "Exp", "%d", ch->exp, 0);
	fwrite_bit(fp, "Act", ch->act, 0);
	fwrite_bit(fp, "AfBy", ch->affected_by, 0);
	fwrite_bit(fp, "Comm", ch->comm, 0);
	fwrite_bit(fp, "Wizn", ch->wiznet, 0);
	fwrite_int(fp, "Invi", "%d", ch->invis_level, 0);
	fwrite_int(fp, "Inco", "%d", ch->incog_level, 0);
	fwrite_int(fp, "Pos", "%d",
			   ch->position == POS_FIGHTING ? POS_STANDING : ch->position, -1);
	fwrite_int(fp, "Prac", "%d", ch->practice, 0);
	fwrite_int(fp, "Trai", "%d", ch->train, 0);
	fwrite_int(fp, "Save", "%d", ch->saving_throw, 0);
	fwrite_int(fp, "Alig", "%d", ch->alignment, 0);
	fwrite_int(fp, "Hit", "%d", ch->hitroll, 0);
	fwrite_int(fp, "Dam", "%d", ch->damroll, 0);
	fwrite_array("ACs", "%d", ch->armor, 4);
	fwrite_array("Stance", "%d", ch->stance, MAX_STANCE);
	fwrite_int(fp, "Wimp", "%d", ch->wimpy, 0);
	fwrite_array("Attr", "%d", ch->perm_stat, MAX_STATS);
	fwrite_array("AMod", "%d", ch->mod_stat, MAX_STATS);
	if (IS_NPC(ch))
	{
		fwrite_int(fp, "Vnum", "%ld", ch->pIndexData->vnum, 0);
	}
	else
	{
		fwrite_string(fp, "Pass", ch->pcdata->pwd, NULL);
		fwrite_string(fp, "Bin", ch->pcdata->bamfin, NULL);
		fwrite_string(fp, "Bout", ch->pcdata->bamfout, NULL);
		fwrite_string(fp, "Titl", ch->pcdata->title, NULL);
		fwrite_int(fp, "Pnts", "%d", ch->pcdata->points, 0);
		fwrite_int(fp, "TSex", "%d", ch->pcdata->true_sex, -1);
		fwrite_int(fp, "LLev", "%d", ch->pcdata->last_level, 0);
		fwritef(fp, "HMVP", "%ld %ld %ld", ch->pcdata->perm_hit,
				ch->pcdata->perm_mana, ch->pcdata->perm_move);
		fwrite_array("Cnd", "%d", ch->pcdata->condition, 4);
		fwrite_int(fp, "QuestPnts", "%d", ch->pcdata->questpoints, 0);
		fwrite_int(fp, "QuestNext", "%d", ch->pcdata->nextquest, 0);
		fwrite_int(fp, "QuestCount", "%d", ch->pcdata->countdown, 0);
		fwrite_int(fp, "QuestGiver", "%ld", ch->pcdata->questgiver, 0);
		fwrite_int(fp, "QuestLoc", "%ld", ch->pcdata->questloc, 0);
		fwrite_int(fp, "QuestObj", "%ld", ch->pcdata->questobj, 0);
		fwrite_int(fp, "QuestMob", "%ld", ch->pcdata->questmob, 0);
		fwrite_int(fp, "Trivia", "%d", ch->pcdata->trivia, 0);
		if (ch->pcdata->str_ed_key != '.' && ch->pcdata->str_ed_key != ' ')
			fwritef(fp, "StrEdKey", "%c", ch->pcdata->str_ed_key);
		fwrite_int(fp, "TimeZone", "%d", ch->pcdata->timezone, -1);
		fwrite_int(fp, "AWins", "%d", ch->pcdata->awins, 0);
		fwrite_int(fp, "ALosses", "%d", ch->pcdata->alosses, 0);
		fwrite_int(fp, "BankS", "%ld", ch->pcdata->silver_bank, 0);
		fwrite_int(fp, "BankG", "%ld", ch->pcdata->gold_bank, 0);
		fwrite_int(fp, "Shares", "%d", ch->pcdata->shares, 0);
		if (ch->deity != NULL)
			fwrite_string(fp, "Deity", ch->deity->name, NULL);
		if (ON_GQUEST(ch))
		{
			fwrite_array("GQmobs", "%ld", ch->gquest->gq_mobs,
						 gquest_info.mob_count);
		}
		if (war_info.status != WAR_OFF && ch->war != NULL)
		{
			fwritef(fp, "WarInfo", "%ld %ld %ld %s", ch->war->hit,
					ch->war->mana, ch->war->move, fwrite_flags(ch->war->flags));
		}
		if (count_home(ch) > 0)
		{
			fwrite_array("Homes", "%ld", ch->pcdata->home, MAX_HOUSE_ROOMS);
			fwrite_int(fp, "HKey", "%ld", ch->pcdata->home_key, 0);
		}
		fprintf(fp, "Colo\t\t%d", MAX_CUSTOM_COLOUR);
		for (i = 0; i < MAX_CUSTOM_COLOUR; i++)
			for (j = 0; j < CT_MAX; j++)
				fprintf(fp, " %d", ch->pcdata->colour[i][j]);
		fprintf(fp, "\n");
		fwrite_string(fp, "WhoD", ch->pcdata->who_descr, NULL);
		fwrite_array("GStats", "%ld", ch->pcdata->gamestat, MAX_GAMESTAT);
		/* write alias */
		for (pos = 0; pos < MAX_ALIAS; pos++)
		{
			if (ch->pcdata->alias[pos] == NULL ||
				ch->pcdata->alias_sub[pos] == NULL)
				break;

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

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

			fwritef(fp, "Ignore", "%s~ %s", ch->pcdata->ignore[pos],
					fwrite_flags(ch->pcdata->ignore_flags[pos]));
		}
		/* Save note board status */
		/* Save number of boards in case that number changes */
		fprintf(fp, "Boards\t\t %d", MAX_BOARD);
		for (i = 0; i < MAX_BOARD; i++)
			fprintf(fp, " %s %ld", boards[i].short_name,
					ch->pcdata->last_note[i]);
		fprintf(fp, "\n");
		for (sn = 0; sn < maxSkill; sn++)
		{
			if (skill_table[sn].name != NULL && ch->pcdata->learned[sn] > 0)
			{
				fwritef(fp, "Sk", "%d '%s'",
						ch->pcdata->learned[sn], skill_table[sn].name);
			}
		}

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

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

		fwritef(fp, "Affc", "'%s' %3d %3d %3d %3d %3d %s",
				skill_table[paf->type].name, paf->where, paf->level,
				paf->duration, paf->modifier, paf->location,
				fwrite_flags(paf->bitvector));
	}
	fwrite_rle(ch->pcdata->explored, fp);
	fprintf(fp, "End\n\n");
	return;
}

/* write a pet */
void fwrite_pet(CHAR_DATA * pet, FILE * fp)
{
	AFFECT_DATA *paf;

	fprintf(fp, "#PET\n");

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

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

	fwrite_array("ACs", "%d", pet->armor, 4);

	fwrite_array("Attr", "%d", pet->perm_stat, MAX_STATS);

	fwrite_array("AMod", "%d", pet->mod_stat, MAX_STATS);

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

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

	fprintf(fp, "End\n");
	return;
}

/*
 * Write an object and its contents.
 */
void fwrite_obj(CHAR_DATA * ch, OBJ_DATA * obj, FILE * fp, int iNest,
				char *marker)
{
	EXTRA_DESCR_DATA *ed;
	AFFECT_DATA *paf;
	vnum_t where = ROOM_VNUM_MORGUE;

	/*
	 * Slick recursion to write lists backwards,
	 *   so loading them will load in forwards order.
	 */
	if (obj->next_content != NULL)
		fwrite_obj(ch, obj->next_content, fp, iNest, marker);

	if (!ch)
	{
		if (obj->in_obj != NULL)
			where = 1;
		if (obj->in_room != NULL)
			where = obj->in_room->vnum;
	}

	/*
	 * Castrate storage characters.
	 */
	if (ch
		&& ((ch->level < obj->level - 2 && obj->item_type != ITEM_CONTAINER)
			|| obj->item_type == ITEM_KEY || (obj->item_type == ITEM_MAP
											  && !obj->value[0])))
		return;

	fwrite_word(fp, "#", marker, NULL);
	fwrite_int(fp, "Vnum", "%ld", obj->pIndexData->vnum, 0);
	if (!ch)
		fwrite_int(fp, "Where", "%ld", where, ROOM_VNUM_MORGUE);
	fwrite_string(fp, "Owner", obj->owner, NULL);
	if (!obj->pIndexData->new_format)
		fprintf(fp, "Oldstyle\n");
	if (obj->enchanted)
		fprintf(fp, "Enchanted\n");
	fwrite_int(fp, "Nest", "%d", iNest, -1);

	/* these data are only used if they do not match the defaults */
	/* new saving functions handle defaults - Markanth 07/07/2003 */
	fwrite_string(fp, "Name", obj->name, obj->pIndexData->name);
	fwrite_string(fp, "ShD", obj->short_descr, obj->pIndexData->short_descr);
	fwrite_string(fp, "Desc", obj->description, obj->pIndexData->description);
	fwrite_bit(fp, "ExtF", obj->extra_flags, obj->pIndexData->extra_flags);
	fwrite_bit(fp, "WeaF", obj->wear_flags, obj->pIndexData->wear_flags);
	fwrite_int(fp, "Ityp", "%d", obj->item_type, obj->pIndexData->item_type);
	fwrite_int(fp, "Wt", "%d", obj->weight, obj->pIndexData->weight);
	fwrite_int(fp, "Cond", "%d", obj->condition, obj->pIndexData->condition);

	/* variable data */

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

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

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

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

		break;

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

		break;
	}

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

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

	fprintf(fp, "End\n\n");

	if (obj->first_content != NULL)
		fwrite_obj(ch, obj->first_content, fp, iNest + 1, marker);

	return;
}

void fwrite_descriptor(DESCRIPTOR_DATA * d, FILE * fp)
{
	fprintf(fp, "#DESC\n");
	fwrite_string(fp, "Host", d->host, NULL);
	fwrite_int(fp, "Descr", "%d", d->descriptor, 0);
	fwrite_int(fp, "Connected", "%d", d->connected, 0);
	fwrite_bit(fp, "Flags", d->d_flags, 0);
	fwrite_int(fp, "ScrW", "%u", d->scr_width, 80);
	fwrite_int(fp, "ScrH", "%u", d->scr_height, 24);
	fwrite_int(fp, "ByteN", "%ld", d->bytes_normal, 0);
#if !defined(NO_MCCP)
	fwrite_int(fp, "ByteC", "%ld", d->bytes_compressed, 0);
	fwrite_int(fp, "CVersion", "%d", d->mccp_version, 0);
#endif
	fwrite_string(fp, "TType", d->ttype, NULL);
	if (IS_MXP(d))
	{
		fwrite_string(fp, "MXPSup", d->mxp.supports, NULL);
		fwrite_int(fp, "MXPVer", "%.2f", d->mxp.mxp_ver, 0);
		fwrite_int(fp, "MXPClVer", "%.2f", d->mxp.client_ver, 0);
		fwrite_int(fp, "MXPStyl", "%.2f", d->mxp.style_ver, 0);
		fwrite_string(fp, "MXPClien", d->mxp.client, NULL);
		fwrite_int(fp, "MXPReg", "%d", d->mxp.registered, 0);
		fwrite_bit(fp, "MXPFlag1", d->mxp.flags, 0);
		fwrite_bit(fp, "MXPFlag2", d->mxp.flags2, 0);
	}
	if (IS_PORTAL(d))
	{
		fwrite_int(fp, "Keycode", "%u", d->portal.keycode, 0);
		fwrite_string(fp, "PortVer", d->portal.version, NULL);
	}
	if (IS_FIRECL(d))
		fwrite_int(fp, "IMPver", "%.2f", d->imp_vers, 0);
	if (IS_PUEBLO(d))
		fwrite_int(fp, "Pueblo", "%.2f", d->pueblo_vers, 0);
	fprintf(fp, "End\n\n");
	return;
}

void set_player_level(CHAR_DATA * 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(CHAR_DATA * ch)
{
	int stat, i, iClass;

	ch->race = default_race;
	ch->act = PLR_NOSUMMON | PLR_AUTOMAP;
	ch->comm = COMM_COMBINE | COMM_PROMPT;
	for (iClass = 0; iClass < MAX_MCLASS; iClass++)
		ch->Class[iClass] = -1;
	ch->prompt = str_dup("<%hhp %mm %vmv> ");
	ch->pcdata->confirm_delete = FALSE;
	ch->pcdata->board = &boards[DEFAULT_BOARD];
	ch->info_settings = INFO_QUIET;	/* this actually turns quiet off */
	ch->pcdata->pwd = str_dup("");
	ch->pcdata->webpass = str_dup("");
	ch->pcdata->bamfin = str_dup("");
	ch->pcdata->bamfout = str_dup("");
	ch->pcdata->title = str_dup("");
	ch->pcdata->who_descr = str_dup("");
	ch->pcdata->afk_msg = str_dup("");
	for (stat = 0; stat < MAX_STATS; stat++)
		ch->perm_stat[stat] = 13;
	ch->pcdata->condition[COND_THIRST] = 48;
	ch->pcdata->condition[COND_FULL] = 48;
	ch->pcdata->condition[COND_HUNGER] = 48;
	ch->pcdata->security = 0;	/* OLC */
	default_colour(ch, -1);
	for (i = 0; i < MAX_GAMESTAT; i++)
		ch->pcdata->gamestat[i] = 0;
	ch->pcdata->trivia = 0;
	end_quest(ch, 0);
	ch->pcdata->timezone = -1;
}

/*
 * Load a char and inventory into a new ch structure.
 */
bool load_char_obj(DESCRIPTOR_DATA * d, const char *name)
{
	char strsave[MAX_INPUT_LENGTH];
	char buf[100];
	CHAR_DATA *ch;
	READ_DATA *fp;
	bool found;
	int i;

	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;

	/* decompress if .gz file exists */
	sprintf(strsave, "%s%s%s", PLAYER_DIR, capitalize(name), ".gz");
	if ((fp = open_read(strsave)) != NULL)
	{
		close_read(fp);
		sprintf(buf, "gzip -dfq %s", strsave);
		system(buf);
	}

	sprintf(strsave, "%s%s", PLAYER_DIR, capitalize(name));
	if ((fp = open_read(strsave)) != NULL)
	{
		int iNest;

		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 != '#')
			{
				bug("Load_char_obj: # not found.");
				break;
			}

			word = read_word(fp);
			if (!str_cmp(word, "PLAYER"))
				read_char(ch, fp);
			else if (!str_cmp(word, "OBJECT"))
				read_obj(ch, fp, FALSE);
			else if (!str_cmp(word, "O"))
				read_obj(ch, fp, FALSE);
			else if (!str_cmp(word, "PET"))
				read_pet(ch, fp);
			else if (!str_cmp(word, "DESC"))
				read_descriptor(d, fp);
			else if (!str_cmp(word, "END"))
				break;
			else
			{
				bug("Load_char_obj: bad section.");
				break;
			}
		}
		close_read(fp);
	}

	/* initialize race */
	if (found)
	{
		if (ch->race == NULL)
			ch->race = default_race;

		ch->size = ch->race->size;
		ch->dam_type = 17;		/*punch */

		for (i = 0; i < 5; 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;

		if (ch->war && war_info.status == WAR_OFF)
			free_warlist(ch->war);

		if (ch->gquest && gquest_info.running == GQUEST_OFF)
			free_gqlist(ch->gquest);

		/* Change this to set player levels when changing max level 
		   The first number is the OLD max level, the second is the
		   NEW max level, and the third is the new version that should be
		   in the pfile after the change. */
		set_player_level(ch, 0, 0, 0);
	}

	return found;
}

/*
 * Read in a char.
 */

void read_char(CHAR_DATA * ch, READ_DATA * 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;

	sprintf(buf, "Loading %s.", ch->name);
	log_string(buf);

	for (;;)
	{
		word = steof(fp) ? "End" : read_word(fp);
		fMatch = FALSE;

		switch (UPPER(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(ch->armor, 4, 100);
				fMatch = TRUE;
				break;
			}

			if (!str_cmp(word, "AffD"))
			{
				AFFECT_DATA *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->first_affect, ch->last_affect, next, prev);
				fMatch = TRUE;
				break;
			}

			if (!str_cmp(word, "Affc"))
			{
				AFFECT_DATA *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->first_affect, ch->last_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 < MAX_STATS; stat++)
						ch->mod_stat[stat] = read_number(fp);
				}
				else
					read_array(ch->mod_stat, MAX_STATS, 3);
				fMatch = TRUE;
				break;
			}

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

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

		case 'B':
			KEYS("Bamfin", ch->pcdata->bamfin);
			KEYS("Bamfout", ch->pcdata->bamfout);
			KEYS("Bin", ch->pcdata->bamfin);
			KEYS("Bout", ch->pcdata->bamfout);
			KEY("BankS", ch->pcdata->silver_bank, read_number(fp));
			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;
			}
			/* Read in board status */
			if (!str_cmp(word, "Boards"))
			{
				int i, num = read_number(fp);	/* number of boards saved */
				char *boardname;

				for (; num; num--)	/* for each of the board saved */
				{
					boardname = read_word(fp);
					i = board_lookup(boardname);	/* find board number */

					if (i == BOARD_NOTFOUND)	/* Does board still exist ? */
					{
						sprintf(buf,
								"read_char: %s had unknown board name: %s. Skipped.",
								ch->name, boardname);
						log_string(buf);
						read_number(fp);	/* read last_note and skip info */
					}
					else		/* Save it */
						ch->pcdata->last_note[i] = read_number(fp);
				}				/* for */

				fMatch = TRUE;
				break;
			}					/* Boards */
			break;

		case 'C':
			if (!str_cmp(word, "Cla") || !str_cmp(word, "Class"))
			{
				int iClass = 0;
				bool invalid = FALSE;

				fMatch = TRUE;
				for (iClass = 0; iClass < MAX_MCLASS; iClass++)
					ch->Class[iClass] = -1;
				for (iClass = 0; iClass < MAX_MCLASS; iClass++)
				{
					ch->Class[iClass] = read_number(fp);
					if (ch->Class[iClass] == -1)
						break;
				}
				for (iClass = 0; iClass < MAX_MCLASS; iClass++)
				{
					if (invalid)
					{
						ch->Class[iClass - 1] = ch->Class[iClass];
						ch->Class[iClass] = -1;
						continue;
					}
					if (ch->Class[iClass] < 0 || ch->Class[iClass] >= maxClass)
					{
						ch->Class[iClass] = -1;
						invalid = TRUE;
					}
				}
				break;
			}
			KEY_SFUN("Clan", ch->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(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 < UMIN(num, MAX_CUSTOM_COLOUR); i++)
					{
						if (i >= MAX_CUSTOM_COLOUR)
							break;
						for (j = 0; j < CT_MAX; j++)
							ch->pcdata->colour[i][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));
			KEYS("Description", ch->description);
			KEYS("Desc", ch->description);
			KEY_SFUN("Deity", ch->deity, deity_lookup);
			break;

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

				/* adjust hp mana move up  -- here for speed's sake */
				percent = (current_time - lastlogoff) * 25 / (2 * 60 * 60);

				percent = UMIN(percent, 100);

				if (percent > 0 && !IS_AFFECTED(ch, AFF_POISON)
					&& !IS_AFFECTED(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"))
			{
				new_gqlist(ch);
				read_array(ch->gquest->gq_mobs, gquest_info.mob_count, -1);
				fMatch = TRUE;
				break;
			}
			if (!str_cmp(word, "Group") || !str_cmp(word, "Gr"))
			{
				int gn;
				char *temp;

				temp = read_word(fp);
				gn = group_lookup(temp);
				/* gn    = group_lookup( read_word( fp ) ); */
				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);
			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_ARRAY("Homes", ch->pcdata->home, MAX_HOUSE_ROOMS, 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;
			}
			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));
			KEYS("LongDescr", ch->long_descr);
			KEYS("LnD", ch->long_descr);
			break;

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

		case 'P':
			KEYS("Password", ch->pcdata->pwd);
			KEYS("Pass", ch->pcdata->pwd);
			KEY("Played", ch->played, read_number(fp));
			KEY("Plyd", ch->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));
			KEYS("Prompt", ch->prompt);
			KEYS("Prom", ch->prompt);
			break;

		case 'Q':
			KEY("QuestPnts", ch->pcdata->questpoints, read_number(fp));
			KEY("QuestNext", ch->pcdata->nextquest, read_number(fp));
			KEY("QuestCount", ch->pcdata->countdown, read_number(fp));
			KEY("QuestLoc", ch->pcdata->questloc, read_number(fp));
			KEY("QuestObj", ch->pcdata->questobj, read_number(fp));
			KEY("QuestGiver", ch->pcdata->questgiver, read_number(fp));
			KEY("QuestMob", ch->pcdata->questmob, 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_number(fp));
			KEYS("ShortDescr", ch->short_descr);
			KEYS("ShD", ch->short_descr);
			KEY("Sec", ch->pcdata->security, read_number(fp));	/* OLC */
			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);
				/* sn    = skill_lookup( read_word( fp ) ); */
				if (sn < 0)
				{
					bugf("read_char: unknown skill. (%s)", temp);
				}
				else
					ch->pcdata->learned[sn] = value;
				fMatch = TRUE;
				break;
			}

			break;

		case 'T':
			KEY("TrueSex", ch->pcdata->true_sex, read_number(fp));
			KEY("TSex", ch->pcdata->true_sex, read_number(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_string(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_DO("Vnum", (ch->pIndexData = get_mob_index(read_number(fp))));
			break;

		case 'W':
			if (!str_cmp(word, "WarInfo"))
			{
				new_warlist(ch);
				ch->war->hit = read_number(fp);
				ch->war->mana = read_number(fp);
				ch->war->move = read_number(fp);
				ch->war->flags = read_flag(fp);
				fMatch = TRUE;
				break;
			}
			KEY("Wimpy", ch->wimpy, read_number(fp));
			KEY("Wimp", ch->wimpy, read_number(fp));
			KEY("Wizn", ch->wiznet, read_flag(fp));
			KEYS("WhoD", ch->pcdata->who_descr);
			break;
		}

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

/* load a pet from the forgotten reaches */
void read_pet(CHAR_DATA * ch, READ_DATA * fp)
{
	const char *word;
	CHAR_DATA *pet;
	bool fMatch;
	int lastlogoff = current_time;
	int percent;

	/* first entry had BETTER be the vnum or we barf */
	word = steof(fp) ? "END" : read_word(fp);
	if (!str_cmp(word, "Vnum"))
	{
		vnum_t vnum;

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

	for (;;)
	{
		word = steof(fp) ? "END" : read_word(fp);
		fMatch = FALSE;

		switch (UPPER(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(pet->armor, 4, 100);

				fMatch = TRUE;
				break;
			}

			if (!str_cmp(word, "AffD"))
			{
				AFFECT_DATA *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->first_affect, pet->last_affect, next, prev);
				fMatch = TRUE;
				break;
			}

			if (!str_cmp(word, "Affc"))
			{
				AFFECT_DATA *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->first_affect, pet->last_affect, next, prev);
				fMatch = TRUE;
				break;
			}

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

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

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

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

		case 'C':
			KEY_SFUN("Clan", pet->clan, clan_lookup);
			KEY("Comm", pet->comm, read_flag(fp));
			break;

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

		case 'E':
			if (!str_cmp(word, "End"))
			{
				pet->leader = ch;
				pet->master = ch;
				ch->pet = pet;
				/* adjust hp mana move up  -- here for speed's sake */
				percent = (current_time - lastlogoff) * 25 / (2 * 60 * 60);

				if (percent > 0 && !IS_AFFECTED(ch, AFF_POISON)
					&& !IS_AFFECTED(ch, AFF_PLAGUE))
				{
					percent = UMIN(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));
			KEYS("LnD", pet->long_descr);
			KEY("LogO", lastlogoff, read_number(fp));
			break;

		case 'N':
			KEYS("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_number(fp));
			KEYS("ShD", pet->short_descr);
			KEY("Silv", pet->silver, read_number(fp));
			break;

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

		}
	}
}

EXTERN OBJ_DATA *obj_free;

void read_obj(CHAR_DATA * ch, READ_DATA * fp, bool pit)
{
	OBJ_DATA *obj;
	const char *word;
	int iNest;
	bool fMatch;
	bool fNest;
	bool fVnum;
	bool first;
	bool new_format;			/* to prevent errors */
	bool make_new;				/* update object */
	vnum_t where;

	fVnum = FALSE;
	obj = NULL;
	first = TRUE;				/* used to counter fp offset */
	new_format = FALSE;
	make_new = FALSE;

	word = steof(fp) ? "End" : read_word(fp);
	if (!str_cmp(word, "Vnum"))
	{
		vnum_t vnum;
		first = FALSE;			/* fp will be in right place */

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

	}

	if (obj == NULL)			/* either not found or old style */
	{
		obj = new_obj();
		obj->name = str_dup("");
		obj->short_descr = str_dup("");
		obj->description = str_dup("");
	}

	fNest = FALSE;
	fVnum = TRUE;
	iNest = 0;
	where = 0;

	for (;;)
	{
		if (first)
			first = FALSE;
		else
			word = steof(fp) ? "End" : read_word(fp);
		fMatch = FALSE;

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

		case 'A':
			if (!str_cmp(word, "AffD"))
			{
				AFFECT_DATA *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->first_affect, obj->last_affect, next, prev);
				fMatch = TRUE;
				break;
			}
			if (!str_cmp(word, "Affc"))
			{
				AFFECT_DATA *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->first_affect, obj->last_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':
			KEYS("Description", obj->description);
			KEYS("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"))
			{
				EXTRA_DESCR_DATA *ed;

				ed = new_extra_descr();

				ed->keyword = read_string(fp);
				ed->description = read_string(fp);
				LINK(ed, obj->first_extra_descr, obj->last_extra_descr, next,
					 prev);
				fMatch = TRUE;
			}

			if (!str_cmp(word, "End"))
			{
				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, object_first, object_last, 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)
					{
						if (!ch)
						{
							if (!pit)
							{
								ROOM_INDEX_DATA *Room;
								CORPSE_DATA *c;

								c = new_corpse();
								c->corpse = obj;
								LINK(c, corpse_first, corpse_last, next, prev);
								if ((Room = get_room_index(where)) == NULL)
									Room = get_room_index(ROOM_VNUM_MORGUE);
								obj_to_room(obj, Room);
							}
							else
							{
								OBJ_DATA *pit;

								if (!(pit = get_donation_pit()))
									free_obj(obj);
								else
									obj_to_obj(obj, pit);
							}
						}
						else
							obj_to_char(obj, ch);
					}
					else
						obj_to_obj(obj, rgObjNest[iNest - 1]);
					return;
				}
			}
			break;

		case 'I':
			KEY("ItemType", obj->item_type, read_number(fp));
			KEY("Ityp", obj->item_type, read_number(fp));
			break;

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

		case 'N':
			KEYS("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;
			}
			KEYS("Owner", obj->owner);
			break;

		case 'S':
			KEYS("ShortDescr", obj->short_descr);
			KEYS("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;

				vnum = read_number(fp);
				if ((obj->pIndexData = get_obj_index(vnum)) == NULL)
					bugf("Fread_obj: bad vnum %ld.", vnum);
				else
					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_DATA * d, READ_DATA * fp)
{
	const char *word;
	bool fMatch;

	for (;;)
	{
		word = steof(fp) ? "END" : read_word(fp);
		fMatch = FALSE;
		if (crs_info.status != CRS_COPYOVER_RECOVER)
		{
			if (!str_cmp(word, "End"))
				return;
			else
			{
				read_to_eol(fp);
				continue;
			}
		}
		switch (UPPER(word[0]))
		{
		case '*':
			fMatch = TRUE;
			read_to_eol(fp);
			break;
		case 'B':
			KEY("ByteN", d->bytes_normal, read_number(fp));
#if !defined(NO_MCCP)
			KEY("ByteC", d->bytes_compressed, read_number(fp));
#endif
			break;
		case 'C':
			KEY("Connected", d->connected, read_enum(connect_t, fp));
#if !defined(NO_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"))
				return;
			break;
		case 'F':
			KEY("Flags", d->d_flags, read_flag(fp));
			break;
		case 'H':
			KEYS("Host", d->host);
			break;
		case 'I':
			KEY_DO("IMPver", d->imp_vers = atof(read_word(fp)));
			break;
		case 'K':
			KEY("Keycode", d->portal.keycode, read_number(fp));
			break;
		case 'M':
			KEYS("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)));
			KEYS("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':
			KEYS_CPY("PortVer", d->portal.version);
			KEY_DO("Pueblo", d->pueblo_vers = atof(read_word(fp)));
			break;
		case 'S':
			KEY("ScrW", d->scr_width, read_number(fp));
			KEY("ScrH", d->scr_height, read_number(fp));
			break;
		case 'T':
			KEYS_CPY("TType", d->ttype);
			break;
		}

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

void save_corpses(void)
{
	WRITE_DATA *fp;
	CORPSE_DATA *c;

	if ((fp = open_write(CORPSE_FILE)) == 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)
				fwrite_obj(NULL, c->corpse, fp->stream, 0, "C");
			else
				update_corpses(c->corpse, TRUE);
		}
		fprintf(fp->stream, "#END\n");
		fflush(fp->stream);
		close_write(fp);
	}
	return;
}

void load_corpses(void)
{
	READ_DATA *fp;

	if ((fp = open_read(CORPSE_FILE)) == 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 != '#')
			{
				bug("load_corpses: # not found.");
				break;
			}

			word = read_word(fp);
			if ((!str_cmp(word, "CORPSE")) || (!str_cmp(word, "C")))
				read_obj(NULL, fp, FALSE);
			else if (!str_cmp(word, "END"))
				break;
			else
			{
				bug("load_corpses: bad section.");
				break;
			}
		}
		close_read(fp);
	}
	return;
}

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

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

void checkcorpse(CHAR_DATA * ch)
{
	CORPSE_DATA *c;
	bool found = FALSE;
	int count = 0;

	if (!ch || IS_NPC(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, "\n\r{f{RWARNING:{x {WYou have %d corpse%s in the game.{x",
			 count, count > 1 ? "s" : "");
		sprintf(log_buf, "%s has %d corpse%s in the game.", ch->name, count,
				count > 1 ? "s" : "");
		log_string(log_buf);
		wiznet(log_buf, NULL, NULL, 0, 0, 0);
	}
}