Lyonesse/bin/
Lyonesse/doc/eng/
Lyonesse/doc/ita/
Lyonesse/lib/
Lyonesse/lib/buildings/
Lyonesse/lib/clans/
Lyonesse/lib/data/
Lyonesse/lib/etc/
Lyonesse/lib/house/
Lyonesse/lib/misc/
Lyonesse/lib/plralias/A-E/
Lyonesse/lib/plralias/F-J/
Lyonesse/lib/plralias/K-O/
Lyonesse/lib/plralias/P-T/
Lyonesse/lib/plralias/U-Z/
Lyonesse/lib/plralias/ZZZ/
Lyonesse/lib/plrobjs/A-E/
Lyonesse/lib/plrobjs/F-J/
Lyonesse/lib/plrobjs/K-O/
Lyonesse/lib/plrobjs/P-T/
Lyonesse/lib/plrobjs/U-Z/
Lyonesse/lib/plrobjs/ZZZ/
Lyonesse/lib/plrsave/A-E/
Lyonesse/lib/plrsave/F-J/
Lyonesse/lib/plrsave/K-O/
Lyonesse/lib/plrsave/P-T/
Lyonesse/lib/plrsave/U-Z/
Lyonesse/lib/plrsave/ZZZ/
Lyonesse/lib/ships/
Lyonesse/lib/stables/
Lyonesse/lib/text/help/
Lyonesse/lib/world/
Lyonesse/lib/world/bld/
Lyonesse/lib/world/ship/
Lyonesse/lib/world/shp/
Lyonesse/lib/world/wls/
Lyonesse/lib/world/wls/Life/
Lyonesse/lib/world/wls/Map/
Lyonesse/log/
/**************************************************************************
 * #   #   #   ##   #  #  ###   ##   ##  ###       http://www.lyonesse.it *
 * #    # #   #  #  ## #  #    #    #    #                                *
 * #     #    #  #  # ##  ##    #    #   ##   ## ##  #  #  ##             *
 * #     #    #  #  # ##  #      #    #  #    # # #  #  #  # #            *
 * ###   #     ##   #  #  ###  ##   ##   ###  #   #  ####  ##    Ver. 1.0 *
 *                                                                        *
 * -Based on CircleMud & Smaug-     Copyright (c) 2001-2002 by Mithrandir *
 *                                                                        *
 * ********************************************************************** *
 *                                                                        *
 * File: save.c                                                           *
 *                                                                        *
 * Players load & save routines                                           *
 *                                                                        *
 **************************************************************************/

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

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

extern SPELL_INFO_DATA	spell_info[TOP_SPELL_DEFINE + 1];
extern VEHICLE_DATA		*first_vehicle;
extern VEHICLE_DATA		*last_vehicle;
extern int				max_obj_save;	/* change in config.c */

/* external functions */
SPELLBOOK *new_spellbook(OBJ_DATA *obj, int type, bool rand);
BOOK_PAGE *new_book_page(void);
VEHICLE_DATA *fread_rent_vehicle( FILE *fp, ROOM_DATA *sRoom );
CHAR_DATA *fread_rent_mount( FILE *fp, ROOM_DATA *sRoom );
bitvector_t asciiflag_conv(char *flag);
long	get_ptable_by_name(const char *name);
void	fwrite_rent_vehicle(VEHICLE_DATA *vehicle, FILE *fp);
void	extract_vehicle(VEHICLE_DATA *vehicle, int mode);
int		learn_course(CHAR_DATA *ch, int cnum);

/* locals */
OBJ_DATA *fread_one_obj( FILE *fp, int *location );
int fwrite_one_obj( OBJ_DATA *obj, FILE *fp, int mode, int location );

#define LOC_INVENTORY	0
#define MAX_BAG_ROWS	5

#if defined(KEY)
#undef KEY
#endif

#define KEY( literal, field, value )					\
				if ( !strcmp( word, literal ) )			\
				{										\
				    field  = value;						\
				    fMatch = TRUE;						\
				    break;								\
				}


bool LoadingCharObj;

/* ======================================================================= */

void fread_one_char(CHAR_DATA *ch, FILE *fp)
{
	AFFECTED_DATA af;
	char *word;
	bool fMatch;

	for ( ; ; )
	{
		word	= feof( fp ) ? "End" : fread_word( fp );
		fMatch	= FALSE;
		
		switch ( UPPER(word[0]) )
		{
		case '*':
			fMatch = TRUE;
			fread_to_eol( fp );
			break;

		case 'A':
			KEY( "Ab_Cha",		ch->real_abils.cha,		fread_number(fp) );
			KEY( "Ab_Con",		ch->real_abils.con,		fread_number(fp) );
			KEY( "Ab_Dex",		ch->real_abils.dex,		fread_number(fp) );
			KEY( "Ab_Int",		ch->real_abils.intel,	fread_number(fp) );
			KEY( "Ab_Str",		ch->real_abils.str,		fread_number(fp) );
			KEY( "Ab_Wis",		ch->real_abils.wis,		fread_number(fp) );
			KEY( "Act",			PLR_FLAGS(ch),			asciiflag_conv(fread_word(fp)) );
			KEY( "AffFlags",	AFF_FLAGS(ch),			asciiflag_conv(fread_word(fp)) );
			if ( !strcmp( word, "Affection" ) )
			{
				af.type			= fread_number(fp);
				af.duration		= fread_number(fp);
				af.modifier		= fread_number(fp);
				af.location		= fread_number(fp);
				af.bitvector	= asciiflag_conv(fread_word(fp));
				affect_to_char(ch, &af);
				fMatch = TRUE;
				break;
			}
			KEY( "Alignment",	GET_ALIGNMENT(ch),		fread_number(fp) );
			KEY( "Armor",		GET_AC(ch),				fread_number(fp) );
			break;
			
		case 'B':
			KEY( "Badpws",		GET_BAD_PWS(ch),		fread_number(fp) );
			KEY( "Bank",		GET_BANK_GOLD(ch),		fread_number(fp) );
			KEY( "Birth",		ch->player.time.birth,	fread_number(fp) );
			break;
			
		case 'C':
			KEY( "Clan",		GET_CLAN(ch),			fread_number(fp) );
			KEY( "ClanRank",	GET_CLAN_RANK(ch),		fread_number(fp) );
			KEY( "Class",		GET_CLASS(ch),			fread_number(fp) );
			KEY( "CondDrunk",	GET_COND(ch, DRUNK),	fread_number(fp) );
			KEY( "CondHunger",	GET_COND(ch, FULL),		fread_number(fp) );
			KEY( "CondThirst",	GET_COND(ch, THIRST),	fread_number(fp) );
			if (!strcmp(word, "Course"))
			{
				learn_course(ch, fread_number(fp));
				fMatch = TRUE;
				break;
			}
			break;
			
		case 'D':
			KEY( "Damroll",		GET_DAMROLL(ch),		fread_number(fp) );
			KEY( "DeathsMob",	GET_MOB_DEATHS(ch),		fread_number(fp) );
			KEY( "DeathsPc",	GET_PLR_DEATHS(ch),		fread_number(fp) );
			KEY( "Descr",		GET_DDESC(ch),			fread_string_nospace(fp) );
			break;
			
		case 'E':
			KEY( "Exp",			GET_EXP(ch),			fread_number(fp) );
			if ( !strcmp( word, "End" ) )
				return;
			break;
			
		case 'F':
			KEY( "FreezeLev",	GET_FREEZE_LEV(ch),		fread_number(fp) );
			break;
			
		case 'H':
			KEY( "Height",		GET_HEIGHT(ch),			fread_number(fp) );
			KEY( "Home",		GET_HOME(ch),			fread_number(fp) );
			KEY( "Host",		ch->player_specials->host,	fread_string_nospace(fp) );
			KEY( "Hitroll",		GET_HITROLL(ch),		fread_number(fp) );
			break;
			
		case 'I':
			KEY( "Id",			GET_IDNUM(ch),			fread_number(fp) );
			KEY( "InvisLev",	GET_INVIS_LEV(ch),		fread_number(fp) );
			break;

		case 'K':
			KEY( "KillsMob",	GET_MOB_KILLS(ch),		fread_number(fp) );
			KEY( "KillsPc",		GET_PLR_KILLS(ch),		fread_number(fp) );
			KEY("KillsCurrPos",	GET_KILLS_CURPOS(ch),	fread_number(fp));
			if (!str_cmp(word, "Kills"))
			{
				int i = fread_number(fp);

				GET_KILLS_VNUM(ch, i)	= fread_number(fp);
				GET_KILLS_AMOUNT(ch, i)	= fread_number(fp);
				fMatch = TRUE;
				break;
			}
			if (!str_cmp(word, "Knowings"))
			{
				long idnum;
				char chname[256];

				idnum = fread_number(fp);
				strcpy(chname, fread_word(fp));

				know_add_load(ch, idnum, chname);
				
				fMatch = TRUE;
				break;
			}
			break;

		case 'L':
			KEY( "LastLogon",	ch->player.time.logon,	fread_number(fp) );
			KEY( "Level",		GET_LEVEL(ch),			fread_number(fp) );
			KEY( "LevelTot",	GET_TOT_LEVEL(ch),		fread_number(fp) );
			if ( !strcmp( word, "LoadCoord" ) )
			{
				if ( !ch->player_specials->load_coord )
					CREATE( ch->player_specials->load_coord, COORD_DATA, 1 );
				
				GET_LOAD_Y(ch) = fread_number(fp);
				GET_LOAD_X(ch) = fread_number(fp);
				fMatch = TRUE;
				break;
			}
			KEY( "LoadRoom",	GET_LOADROOM(ch),		fread_number(fp) );
			if (!strcmp(word, "LoadBuilding"))
			{
				GET_LOADBUILDING(ch)	= fread_number(fp);
				GET_LOADROOM(ch)		= fread_number(fp);
				fMatch = TRUE;
				break;
			}
			if (!strcmp(word, "LoadShip"))
			{
				GET_LOADSHIP(ch)		= fread_number(fp);
				GET_LOADROOM(ch)		= fread_number(fp);
				fMatch = TRUE;
				break;
			}
			break;

		case 'M':
			KEY( "Multiclass",		MULTICLASS(ch),		asciiflag_conv(fread_word(fp)) );
			if ( !strcmp( word, "Memorized" ) )
			{
				int sn = fread_number(fp);

				fMatch = TRUE;

				if ( sn < 1 || sn >= MAX_SPELLS )
					break;

				MEMORIZED(ch, sn) = fread_number(fp);

				break;
			}
			break;

		case 'N':
			KEY( "Name",		GET_PC_NAME(ch),		str_dup(fread_word(fp)) );
			break;
			
		case 'P':
			if ( !strcmp( word, "Points_Hit" ) )
			{
				GET_HIT(ch)			= fread_number(fp);
				GET_MAX_HIT(ch)		= fread_number(fp);
				fMatch = TRUE;
				break;
			}
			if ( !strcmp( word, "Points_Mana" ) )
			{
				GET_MANA(ch)		= fread_number(fp);
				GET_MAX_MANA(ch)	= fread_number(fp);
				fMatch = TRUE;
				break;
			}
			if ( !strcmp( word, "Points_Move" ) )
			{
				GET_MOVE(ch)		= fread_number(fp);
				GET_MAX_MOVE(ch)	= fread_number(fp);
				fMatch = TRUE;
				break;
			}
			if ( !strcmp( word, "Password" ) )
			{
				char *pw = fread_word(fp);
				strcpy( GET_PASSWD(ch), pw );
				fMatch = TRUE;
				break;
			}
			KEY( "Practices",	GET_PRACTICES(ch),		fread_number(fp) );
			KEY( "Played",		ch->player.time.played,	fread_number(fp) );
			KEY( "PoofIn",		POOFIN(ch),				fread_string_nospace(fp) );
			KEY( "PoofOut",		POOFOUT(ch),			fread_string_nospace(fp) );
			KEY( "PrfFlags",	PRF_FLAGS(ch),			asciiflag_conv(fread_word(fp)) );
			break;

		case 'R':
			KEY( "Race",		GET_RACE(ch),			fread_number(fp) );
			break;

		case 'S':
			if ( !strcmp( word, "SavingThrow" ) )
			{
				GET_SAVE(ch, 0) = fread_number(fp);
				GET_SAVE(ch, 1) = fread_number(fp);
				GET_SAVE(ch, 2) = fread_number(fp);
				GET_SAVE(ch, 3) = fread_number(fp);
				GET_SAVE(ch, 4) = fread_number(fp);
				fMatch = TRUE;
				break;
			}
			KEY( "Sex",			GET_SEX(ch),			fread_number(fp) );
			if ( !strcmp( word, "Skill" ) )
			{
				int sn = fread_number(fp);

				fMatch = TRUE;

				if ( sn < 0 || sn >= MAX_SKILLS )
					break;

				GET_SKILL(ch, sn) = fread_number(fp);

				break;
			}
			break;
			
		case 'T':
			KEY( "Title",		GET_TITLE(ch),			fread_string_nospace(fp) );
			break;
			
		case 'W':
			KEY( "Weight",		GET_WEIGHT(ch),			fread_number(fp) );
			KEY( "Wimp",		GET_WIMP_LEV(ch),		fread_number(fp) );
			break;
			
		default:
			log("SYSERR: Unknown word %s in pfile.", word);
			break;
		}
	}
}

/*
 * Load a char, TRUE if loaded, FALSE if not
 */
int load_char(char *name, CHAR_DATA *ch)
{
	FILE *fp;
	char filename[40];
	int id, i;
	
	if ( ( id = get_ptable_by_name( name ) ) < 0 )
		return ( -1 );
	
	if (!get_filename(name, filename, PLR_FILE))
		return ( -1 );

	if ( !( fp = fopen( filename, "r" ) ) )
	{
		log( "SYSERR: Couldn't open player file %s", filename );
		return ( -1 );
	}
	
	/* character initializations */
	// initializations necessary to keep some things straight
	ch->affected				= NULL;
	
	GET_LOADROOM(ch)			= NOWHERE;
	GET_LOADBUILDING(ch)		= NOWHERE;
	GET_LOADSHIP(ch)			= NOTHING;
	GET_LOADCOORD(ch)			= NULL;

	for (i = 1; i <= MAX_SKILLS; i++)
		GET_SKILL(ch, i)		= 0;

	for (i = 0; i < 5; i++)
		GET_SAVE(ch, i)			= 0;

	for (i = 1; i < 101; i++)
	{
		GET_KILLS_VNUM(ch, i)	= 0;
		GET_KILLS_AMOUNT(ch, i)	= 0;
	}

	GET_SEX(ch)					= 0;
	GET_CLASS(ch)				= 0;
	GET_RACE(ch)				= 0;
	GET_LEVEL(ch)				= 0;
	GET_TOT_LEVEL(ch)			= 0;
	GET_HOME(ch)				= 0;
	GET_HEIGHT(ch)				= 0;
	GET_WEIGHT(ch)				= 0;
	GET_ALIGNMENT(ch)			= 0;
	PLR_FLAGS(ch)				= 0;
	AFF_FLAGS(ch)				= 0;
	MULTICLASS(ch)				= 0;
	GET_INVIS_LEV(ch)			= 0;
	GET_FREEZE_LEV(ch)			= 0;
	GET_WIMP_LEV(ch)			= 0;
	GET_COND(ch, FULL)			= 0;
	GET_COND(ch, THIRST)		= 0;
	GET_COND(ch, DRUNK)			= 0;
	GET_BAD_PWS(ch)				= 0;
	PRF_FLAGS(ch)				= 0;
	GET_PRACTICES(ch)			= 0;
	GET_BANK_GOLD(ch)			= 0;
	GET_EXP(ch)					= 0;
	GET_HITROLL(ch)				= 0;
	GET_DAMROLL(ch)				= 0;
	GET_AC(ch)					= 0;
	GET_HIT(ch)					= 0;
	GET_MAX_HIT(ch)				= 0;
	GET_MANA(ch)				= 0;
	GET_MAX_MANA(ch)			= 0;
	GET_MOVE(ch)				= 0;
	GET_MAX_MOVE(ch)			= 0;
	ch->real_abils.str			= 0;
	ch->real_abils.dex			= 0;
	ch->real_abils.intel		= 0;
	ch->real_abils.wis			= 0;
	ch->real_abils.con			= 0;
	ch->real_abils.cha			= 0;
	GET_KILLS_CURPOS(ch)		= 1;
	GET_MOB_KILLS(ch)			= 0;
	GET_MOB_DEATHS(ch)			= 0;
	GET_PLR_KILLS(ch)			= 0;
	GET_PLR_DEATHS(ch)			= 0;

	GET_CLAN(ch)				= -1;
	GET_CLAN_RANK(ch)			= -1;

	/* end of character initializations */

	// Now read from file.. 
	for ( ; ; )
	{
		char letter;
		char *word;
		
		letter = fread_letter( fp );
		if ( letter == '*' )
		{
			fread_to_eol( fp );
			continue;
		}
		
		if ( feof( fp ) )
			break;

		if ( letter != '#' )
		{
			log( "load_char: # not found." );
			break;
		}
		
		word = fread_word( fp );

		if (!str_cmp(word, "PLAYER"))
			fread_one_char(ch, fp);
		else if (!str_cmp(word, "END"))		// Done
			break;
		else
			log("SYSERR: unknown word %s.", word);
	}

	ch->aff_abils = ch->real_abils;

	affect_total(ch);
	
	// initialization for imms
	if (IS_IMMORTAL(ch))
	{
		for (i = 1; i <= MAX_SKILLS; i++)
			GET_SKILL(ch, i)	= 100;

		GET_COND(ch, FULL)		= -1;
		GET_COND(ch, THIRST)	= -1;
		GET_COND(ch, DRUNK)		= -1;
	}
	fclose(fp);
	return (id);
}

/* ======================================================================= */


/*
 * write the vital data of a player to the player file
 */
void save_char(CHAR_DATA *ch, ROOM_DATA *load_room)
{
	FILE *fl;
	AFFECTED_DATA *aff, tmp_aff[MAX_AFFECT];
	OBJ_DATA *char_eq[NUM_WEARS];
	char outname[MAX_NAME_LENGTH+3], buf[MAX_STRING_LENGTH];
	char filename[50];
	char dflags[128], dpref[128], bits[127];
	int i;

	if (IS_NPC(ch) || GET_PFILEPOS(ch) < 0)
		return;

	/*
	 * This version of save_char allows ch->desc to be null (to allow
	 * "set file" to use it).  This causes the player's last host
	 * and probably last login to null out.
	 */
	if (!PLR_FLAGGED(ch, PLR_LOADROOM))
	{
		if (load_room == NULL)
		{
			GET_LOADCOORD(ch)		= NULL;
			GET_LOADROOM(ch)		= NOWHERE;
			GET_LOADBUILDING(ch)	= NOWHERE;
			GET_LOADSHIP(ch)		= NOWHERE;
		}
		else if ( IS_WILD( load_room ) )
		{
			if ( !GET_LOADCOORD(ch) )
				CREATE( ch->player_specials->load_coord, COORD_DATA, 1 );
			GET_LOAD_Y(ch)			= load_room->coord->y;
			GET_LOAD_X(ch)			= load_room->coord->x;
			GET_LOADROOM(ch)		= NOWHERE;
			GET_LOADSHIP(ch)		= NOTHING;
		}
		else if (IS_BUILDING(load_room))
		{
			GET_LOADROOM(ch)		= load_room->number;
			GET_LOADBUILDING(ch)	= ch->in_building->vnum;
			GET_LOADSHIP(ch)		= NOTHING;
		}
		else if ( IS_SHIP( load_room ) )
		{
			GET_LOADCOORD(ch)		= NULL;
			GET_LOADROOM(ch)		= load_room->number;
			GET_LOADSHIP(ch)		= ch->in_ship->vnum;
			GET_LOADBUILDING(ch)	= NOTHING;
		}
		else
		{
			GET_LOADCOORD(ch)		= NULL;
			GET_LOADROOM(ch)		= load_room->number;
			GET_LOADBUILDING(ch)	= NOWHERE;
			GET_LOADSHIP(ch)		= NOTHING;
		}
	}

	strcpy( outname, GET_PC_NAME(ch) );
	if (!get_filename(outname, filename, PLR_FILE))
		return;

	if ( !( fl = fopen( filename, "w" ) ) )
	{
		log("SYSERR: Couldn't open player file %s for write", filename);
		return;
	}
	
	/* Unaffect everything a character can be affected by */
	for (i = 0; i < NUM_WEARS; i++)
	{
		if ( GET_EQ(ch, i) )
			char_eq[i] = unequip_char(ch, i);
		else
			char_eq[i] = NULL;
	}
	
	for ( aff = ch->affected, i = 0; i < MAX_AFFECT; i++ )
	{
		if (aff)
		{
			tmp_aff[i]				= *aff;
			tmp_aff[i].next			= NULL;
			aff = aff->next;
		}
		else
		{
			tmp_aff[i].type			= 0;
			tmp_aff[i].duration		= 0;
			tmp_aff[i].modifier		= 0;
			tmp_aff[i].location		= 0;
			tmp_aff[i].bitvector	= 0;
			tmp_aff[i].next			= NULL;
		}
	}
	
	/*
	 * remove the affections so that the raw values are stored; otherwise the
	 * effects are doubled when the char logs back in.
	 */
	while (ch->affected)
		affect_remove(ch, ch->affected);

	//ch->aff_abils = ch->real_abils;

	if (PLR_FLAGGED(ch, PLR_NOTDEADYET))
		REMOVE_BIT(PLR_FLAGS(ch), PLR_NOTDEADYET);

	ch->player.time.played += (long) (time(0) - ch->player.time.logon);
	ch->player.time.logon = time(0);

	fprintf(fl, "#PLAYER\n");
	fprintf(fl, "Id           %d\n",		GET_IDNUM(ch));
	if (GET_PC_NAME(ch))
		fprintf(fl, "Name         %s\n",	GET_PC_NAME(ch));
	if (GET_PASSWD(ch))
		fprintf(fl, "Password     %s\n",	GET_PASSWD(ch));
	if (GET_TITLE(ch))
		fprintf(fl, "Title        %s~\n",	GET_TITLE(ch));
	if (GET_DDESC(ch) && *GET_DDESC(ch))
	{
		strcpy(buf, GET_DDESC(ch));
		strip_cr(buf);
		fprintf(fl, "Descr        %s~\n",	buf);
	}

	fprintf(fl, "Sex          %d\n",		GET_SEX(ch)); 
	fprintf(fl, "Class        %d\n",		GET_CLASS(ch)); 
	fprintf(fl, "Race         %d\n",		GET_RACE(ch)); 
	if (MULTICLASS(ch))
	{
		sprintascii(dpref, MULTICLASS(ch));
		fprintf(fl, "Multiclass   %s\n",	dpref);
	}
	fprintf(fl, "Level        %d\n",		GET_LEVEL(ch));
	fprintf(fl, "LevelTot     %d\n",		GET_TOT_LEVEL(ch));

	fprintf(fl, "Clan         %d\n",		GET_CLAN(ch)); 
	fprintf(fl, "ClanRank     %d\n",		GET_CLAN_RANK(ch)); 

	fprintf(fl, "Ab_Str       %hd\n",		ch->real_abils.str);
	fprintf(fl, "Ab_Int       %hd\n",		ch->real_abils.intel);
	fprintf(fl, "Ab_Wis       %hd\n",		ch->real_abils.wis);
	fprintf(fl, "Ab_Dex       %hd\n",		ch->real_abils.dex);
	fprintf(fl, "Ab_Con       %hd\n",		ch->real_abils.con);
	fprintf(fl, "Ab_Cha       %hd\n",		ch->real_abils.cha);
	
	fprintf(fl, "Points_Hit   %hd %hd\n",	GET_HIT(ch), GET_MAX_HIT(ch));
	fprintf(fl, "Points_Mana  %hd %hd\n",	GET_MANA(ch), GET_MAX_MANA(ch));
	fprintf(fl, "Points_Move  %hd %hd\n",	GET_MOVE(ch), GET_MAX_MOVE(ch));
	fprintf(fl, "Armor        %hd\n",		GET_AC(ch));
	fprintf(fl, "Exp          %d\n",		GET_EXP(ch));
	fprintf(fl, "Hitroll      %hd\n",		GET_HITROLL(ch));
	fprintf(fl, "Damroll      %hd\n",		GET_DAMROLL(ch));

	fprintf(fl, "KillsMob     %d\n",		GET_MOB_KILLS(ch) );
	fprintf(fl, "KillsPc      %d\n",		GET_PLR_KILLS(ch) );
	fprintf(fl, "DeathsMob    %d\n",		GET_MOB_DEATHS(ch) );
	fprintf(fl, "DeathsPc     %d\n",		GET_PLR_DEATHS(ch) );

	fprintf(fl, "Home         %d\n",		GET_HOME(ch));
	fprintf(fl, "Birth        %d\n",		ch->player.time.birth);
	fprintf(fl, "Played       %d\n",		ch->player.time.played);
	fprintf(fl, "LastLogon    %d\n",		ch->player.time.logon);
	fprintf(fl, "Height       %d\n",		GET_HEIGHT(ch));
	fprintf(fl, "Weight       %d\n",		GET_WEIGHT(ch));
	fprintf(fl, "Alignment    %d\n",		GET_ALIGNMENT(ch));

	if (PLR_FLAGS(ch))
	{
		sprintascii(dpref, PLR_FLAGS(ch));
		fprintf(fl, "Act          %s\n",	dpref);
	}

	if (GET_PRACTICES(ch))
		fprintf(fl, "Practices    %d\n",	GET_PRACTICES(ch));

	if (GET_BANK_GOLD(ch))
		fprintf(fl, "Bank         %d\n",	GET_BANK_GOLD(ch));

	if ( ch->player_specials->host )
		fprintf(fl, "Host         %s\n",	ch->player_specials->host);

	if ( AFF_FLAGS(ch) )
	{
		sprintascii(bits, AFF_FLAGS(ch));
		fprintf(fl, "AffFlags     %s\n",	bits);
	}

	fprintf(fl, "SavingThrow  %d %d %d %d %d\n",
		GET_SAVE(ch, 0), GET_SAVE(ch, 1), GET_SAVE(ch, 2),
		GET_SAVE(ch, 3), GET_SAVE(ch, 4));

	if (GET_WIMP_LEV(ch))
		fprintf(fl, "Wimp         %d\n",	GET_WIMP_LEV(ch));
	if (GET_FREEZE_LEV(ch))
		fprintf(fl, "FreezeLev    %d\n",	GET_FREEZE_LEV(ch));
	if (GET_INVIS_LEV(ch))
		fprintf(fl, "InvisLev     %d\n",	GET_INVIS_LEV(ch));
	if (GET_LOADBUILDING(ch) != NOWHERE)
		fprintf(fl, "LoadBuilding %d\n",	GET_LOADBUILDING(ch));
	if (GET_LOADROOM(ch) != NOWHERE)
		fprintf(fl, "LoadRoom     %d\n",	GET_LOADROOM(ch));
	if ( GET_LOADCOORD(ch) )
		fprintf(fl, "LoadCoord    %d %d\n", GET_LOAD_Y(ch), GET_LOAD_X(ch) );
	if ( GET_LOADSHIP(ch) != NOTHING )
		fprintf(fl, "LoadShip     %d %d\n", GET_LOADSHIP(ch), GET_LOADROOM(ch));
	if ( GET_LOADBUILDING(ch) != NOTHING )
		fprintf(fl, "LoadBuilding %d %d\n", GET_LOADBUILDING(ch), GET_LOADROOM(ch));

	if (PRF_FLAGS(ch))
	{
		sprintascii(bits, PRF_FLAGS(ch));
		fprintf(fl, "PrfFlags     %s\n",	bits);
	}
	if (GET_BAD_PWS(ch))
		fprintf(fl, "Badpws       %d\n",	GET_BAD_PWS(ch));

	/* affected_type */
	for (i = 0; i < MAX_AFFECT; i++)
	{
		if (tmp_aff[i].type)
		{
			sprintascii( dflags, tmp_aff[i].bitvector );
			fprintf(fl, "Affection    %d %d %d %d %s\n",
				tmp_aff[i].type, tmp_aff[i].duration,
				tmp_aff[i].modifier, tmp_aff[i].location, dflags);
		}
	}

	if (IS_IMMORTAL(ch))
	{
		if ( POOFIN(ch) )	fprintf(fl, "PoofIn       %s~\n", POOFIN(ch));
		if ( POOFOUT(ch) )	fprintf(fl, "PoofOut      %s~\n", POOFOUT(ch));
	}

	/* Mortal only stuffs */
	if (IS_MORTAL(ch))
	{
		if (GET_COND(ch, FULL))
			fprintf(fl, "CondHunger   %d\n", GET_COND(ch, FULL));
		if (GET_COND(ch, THIRST))
			fprintf(fl, "CondThirst   %d\n", GET_COND(ch, THIRST));
		if (GET_COND(ch, DRUNK))
			fprintf(fl, "CondDrunk    %d\n", GET_COND(ch, DRUNK));

		/* Skills */
		for (i = 1; i <= MAX_SKILLS; i++)
		{
			if ( GET_SKILL(ch, i) > 0 )
				fprintf(fl, "Skill        %d %d\n", i, GET_SKILL(ch, i) );
		}

		/* introduction system */
		if ( ch->player_specials->first_known )
		{
			KNOWN_DATA *pKnow;
			
			for ( pKnow = ch->player_specials->first_known; pKnow; pKnow = pKnow->next )
				fprintf( fl, "Knowings     %d %s\n", pKnow->idnum, pKnow->name );
		}

	
		if ( ch->player_specials->courses )
		{
			KNOWN_COURSE *kCourse;

			for (kCourse = ch->player_specials->courses; kCourse; kCourse = kCourse->next)
				fprintf(fl, "Course       %d\n", kCourse->coursenum);
		}
	}

	for ( i = 1; i <= MAX_SPELLS; i++ )
	{
		if ( MEMORIZED(ch, i) )
			fprintf(fl, "Memorized    %d %d\n", i, MEMORIZED(ch, i));
	}

	fprintf(fl, "KillsCurrPos %d\n",		GET_KILLS_CURPOS(ch));
	for (i = 1; i < 101; i++)
	{
		if (GET_KILLS_VNUM(ch, i) == 0)
			continue;

		fprintf(fl, "Kills        %d %d %hd\n", i, GET_KILLS_VNUM(ch, i), GET_KILLS_AMOUNT(ch, i));
	}

	fprintf(fl, "End\n" );

	fprintf(fl, "#END\n" );

	fclose(fl);
	
	/* more char_to_store code to restore affects */
	
	/* add spell and eq affections back in now */
	for (i = 0; i < MAX_AFFECT; i++)
	{
		if (tmp_aff[i].type)
			affect_to_char(ch, &tmp_aff[i]);
	}
	
	for (i = 0; i < NUM_WEARS; i++)
	{
		if (char_eq[i])
			equip_char(ch, char_eq[i], i);
	}
}


/* ************************************************************************* */
/*                             Related Functions                             */
/* ************************************************************************* */



/* ******************************************************************* */
/* Object Save Routine                                                 */
/* ******************************************************************* */

int DeleteCharFile(char *name)
{
	FILE *fl;
	char filename[50];
	
	if (!get_filename(name, filename, OBJS_FILE))
		return (0);
	if (!(fl = fopen(filename, "r")))
	{
		if (errno != ENOENT)		/* if it fails but NOT because of no file */
			log("SYSERR: deleting crash file %s (1): %s", filename, strerror(errno));
		return (0);
	}
	fclose(fl);
	
	/* if it fails, NOT because of no file */
	if (remove(filename) < 0 && errno != ENOENT)
		log("SYSERR: deleting crash file %s (2): %s", filename, strerror(errno));
	
	return (1);
}

/* returns TRUE if item can NOT be saved, FALSE otherwise */
bool ItemNoSave(OBJ_DATA *obj)
{
	if (!obj)
		return (FALSE);
	
	if ( GET_OBJ_TYPE(obj) == ITEM_KEY )
		return (TRUE);
	
	if ( OBJ_FLAGGED(obj, ITEM_NOSAVE) ) 
		return (TRUE);
	
	if ( GET_OBJ_RNUM(obj) <= NOTHING && !OBJ_FLAGGED(obj, ITEM_UNIQUE) )
		return (TRUE);
	
	return (FALSE);
}

/* look for items no-save in inventory and containers */
void ExtractItemNoSave(OBJ_DATA *obj)
{
	if (obj)
	{
		ExtractItemNoSave(obj->first_content);
		ExtractItemNoSave(obj->next_content);
		if (ItemNoSave(obj))
			extract_obj(obj);
	}
}

/* look for equipped items no-save */
void ExtractItemNoSaveEq(CHAR_DATA *ch)
{
	int j;
	
	for (j = 0; j < NUM_WEARS; j++)
	{
		if (GET_EQ(ch, j) == NULL)
			continue;
		
		if (ItemNoSave(GET_EQ(ch, j)))
			// item cannot be saved, will take care later ExtractItemNoSave()
			obj_to_char(unequip_char(ch, j), ch);
		else
			// look for items in equipped containers..
			ExtractItemNoSave(GET_EQ(ch, j));
	}
}

void RestoreWeight(OBJ_DATA *obj)
{
	if (obj)
	{
		RestoreWeight(obj->first_content);
		RestoreWeight(obj->next_content);
		if (obj->in_obj)
			GET_OBJ_WEIGHT(obj->in_obj) += get_real_obj_weight(obj);
	}
}

void ExtractSavedItems(OBJ_DATA * obj)
{
	if (obj)
	{
		ExtractSavedItems(obj->first_content);
		ExtractSavedItems(obj->next_content);
		extract_obj(obj);
	}
}

int save_objs(OBJ_DATA *obj, FILE *fp, int mode, int location)
{
	OBJ_DATA *tmp;
	int result;
	
	if (obj)
	{
		save_objs(obj->prev_content, fp, mode, location);
		save_objs(obj->last_content, fp, mode, MIN(0, location) - 1);

		result = fwrite_one_obj(obj, fp, mode, location);
		
		for (tmp = obj->in_obj; tmp; tmp = tmp->in_obj)
			GET_OBJ_WEIGHT(tmp) -= get_real_obj_weight(obj);
		
		if (!result)
			return (0);
	}
	return (TRUE);
}

void SaveCharObj(CHAR_DATA *ch, bool quitting)
{
	FILE *fp;
	char fname[MAX_STRING_LENGTH];
	int j;

	if ( IS_NPC(ch) )
		return;

	if (!get_filename(GET_NAME(ch), fname, OBJS_FILE))
		return;
	if (!(fp = fopen(fname, "w")))
		return;

	ExtractItemNoSaveEq(ch);
	ExtractItemNoSave(ch->first_carrying);

	for (j = 0; j < NUM_WEARS; j++)
	{
		if ( GET_EQ(ch, j) )
		{
			if (!save_objs(GET_EQ(ch,j), fp, 0, j + 1))
			{
				fclose(fp);
				return;
			}
			if (quitting)
			{
				RestoreWeight(GET_EQ(ch, j));
				ExtractSavedItems(GET_EQ(ch, j));
			}
		}
	}

	if (!save_objs(ch->last_carrying, fp, 0, 0))
	{
		fclose(fp);
		return;
	}

	if (PLR_FLAGGED(ch, PLR_CAMPED))
	{
		CHAR_DATA *mount;
		VEHICLE_DATA *pVeh, *veh_next = NULL;
		// scan the room for mounts and/or vehicles...

		for (mount = ch->in_room->people; mount; mount = mount->next_in_room)
		{
			if (!IS_NPC(mount) || GET_MOB_VNUM(mount) == NOTHING || MOB_OWNER(mount) != GET_IDNUM(ch))
				continue;

			fprintf(fp, "#MOUNT\n");
			fprintf(fp, "Mob_vnum     %d\n", GET_MOB_VNUM(mount));
			fprintf(fp, "End\n");
			extract_char(mount);
		}

		for (pVeh = ch->in_room->vehicles; pVeh; pVeh = veh_next)
		{
			veh_next = pVeh->next_in_room;

			if (pVeh->owner_id != GET_IDNUM(ch))
				continue;
			fwrite_rent_vehicle(pVeh, fp);
			extract_vehicle(pVeh, 1);
		}
	}

	fprintf(fp, "#END\n");
	fclose(fp);

	if (quitting)
		ExtractSavedItems(ch->first_carrying);
}


void CharLoseAllItems(CHAR_DATA *ch)
{
	int j;

	for (j = 0; j < NUM_WEARS; j++)
	{
		if (GET_EQ(ch, j))
		{
			RestoreWeight(GET_EQ(ch, j));
			ExtractSavedItems(GET_EQ(ch, j));
		}
	}

	ExtractSavedItems(ch->first_carrying);

	ch->first_carrying	= NULL;
	ch->last_carrying	= NULL;
}


/*
 * AutoEQ by Burkhard Knopf <burkhard.knopf@informatik.tu-clausthal.de>
 */
void AutoEquip(CHAR_DATA *ch, OBJ_DATA *obj, int location)
{
	int j;
	
	/* Lots of checks... */
	if (location > 0)		/* Was wearing it. */
	{	
		switch (j = (location - 1))
		{
		case WEAR_LIGHT:
			break;
		case WEAR_FINGER_R:
		case WEAR_FINGER_L:
			if (!CAN_WEAR(obj, ITEM_WEAR_FINGER)) /* not fitting :( */
				location = LOC_INVENTORY;
			break;
		case WEAR_NECK_1:
		case WEAR_NECK_2:
			if (!CAN_WEAR(obj, ITEM_WEAR_NECK))
				location = LOC_INVENTORY;
			break;
		case WEAR_BODY:
			if (!CAN_WEAR(obj, ITEM_WEAR_BODY))
				location = LOC_INVENTORY;
			break;
		case WEAR_HEAD:
			if (!CAN_WEAR(obj, ITEM_WEAR_HEAD))
				location = LOC_INVENTORY;
			break;
		case WEAR_LEGS:
			if (!CAN_WEAR(obj, ITEM_WEAR_LEGS))
				location = LOC_INVENTORY;
			break;
		case WEAR_FEET:
			if (!CAN_WEAR(obj, ITEM_WEAR_FEET))
				location = LOC_INVENTORY;
			break;
		case WEAR_HANDS:
			if (!CAN_WEAR(obj, ITEM_WEAR_HANDS))
				location = LOC_INVENTORY;
			break;
		case WEAR_ARMS:
			if (!CAN_WEAR(obj, ITEM_WEAR_ARMS))
				location = LOC_INVENTORY;
			break;
		case WEAR_SHIELD:
			if (!CAN_WEAR(obj, ITEM_WEAR_SHIELD))
				location = LOC_INVENTORY;
			break;
		case WEAR_ABOUT:
			if (!CAN_WEAR(obj, ITEM_WEAR_ABOUT))
				location = LOC_INVENTORY;
			break;
		case WEAR_WAIST:
			if (!CAN_WEAR(obj, ITEM_WEAR_WAIST))
				location = LOC_INVENTORY;
			break;
		case WEAR_WRIST_R:
		case WEAR_WRIST_L:
			if (!CAN_WEAR(obj, ITEM_WEAR_WRIST))
				location = LOC_INVENTORY;
			break;
		case WEAR_WIELD:
			if (!CAN_WEAR(obj, ITEM_WEAR_WIELD))
				location = LOC_INVENTORY;
			break;
		case WEAR_HOLD:
			if (CAN_WEAR(obj, ITEM_WEAR_HOLD))
				break;
			if (IS_WARRIOR(ch) && CAN_WEAR(obj, ITEM_WEAR_WIELD) && GET_OBJ_TYPE(obj) == ITEM_WEAPON)
				break;
			location = LOC_INVENTORY;
			break;
		case WEAR_SHOULDERS:
			if ( !CAN_WEAR(obj, ITEM_WEAR_SHOULDER) )
				location = LOC_INVENTORY;
			break;
		default:
			location = LOC_INVENTORY;
		}
		
		if (location > 0)	    /* Wearable. */
		{
			if (!GET_EQ(ch,j))
			{
				/*
				 * Check the characters's alignment to prevent them from being
				 * zapped through the auto-equipping.
				 */
				if (invalid_align(ch, obj) || invalid_class(ch, obj))
					location = LOC_INVENTORY;
				else
					equip_char(ch, obj, j);
			}
			else	/* Oops, saved a player with double equipment? */
			{
				char aeq[128];
				sprintf(aeq, "SYSERR: autoeq: '%s' already equipped in position %d.", GET_NAME(ch), location);
				mudlog(aeq, BRF, LVL_IMMORT, TRUE);
				location = LOC_INVENTORY;
			}
		}
	}

	if (location <= 0)	/* Inventory */
		obj = obj_to_char(obj, ch);
}

int LoadCharObj(CHAR_DATA *ch)
{
	FILE *fp;
	VEHICLE_DATA *vehicle = NULL;
	char fname[MAX_STRING_LENGTH];
	int num_objs = 0, j;
	/* AutoEQ addition. */
	OBJ_DATA *obj, *obj2, *cont_row[MAX_BAG_ROWS], *veh_cont_row[MAX_BAG_ROWS];
	int location;
	
	for (j = 0; j < MAX_BAG_ROWS; j++)
	{
		cont_row[j]			= NULL;
		veh_cont_row[j]		= NULL;
	}
	
	if (!get_filename(GET_NAME(ch), fname, OBJS_FILE))
		return (1);
	if (!(fp = fopen(fname, "r")))
	{
		if (errno != ENOENT)	/* if it fails, NOT because of no file */
		{
			log("SYSERR: READING OBJECT FILE %s (5): %s", fname, strerror(errno));
			send_to_char(
				"\r\n********************* NOTICE *********************\r\n"
				"There was a problem loading your objects from disk.\r\n"
				"Contact a God for assistance.\r\n", ch);
		}
		sprintf(buf, "%s entering game with no equipment.", GET_NAME(ch));
		mudlog(buf, NRM, MAX(LVL_IMMORT, GET_INVIS_LEV(ch)), TRUE);
		return (1);
	}
	
	if (feof(fp))
	{
		log("SYSERR: LoadCharObj(): %s's rent file was empty!", GET_NAME(ch));
		return (1);
	}

	LoadingCharObj = TRUE;
	
	for ( ; ; )
	{
		char letter;
		char *word;
		
		letter = fread_letter( fp );
		if ( letter == '*' )
		{
			fread_to_eol( fp );
			continue;
		}
		
		if ( feof( fp ) )
			break;

		if ( letter != '#' )
		{
			log( "LoadCharObj(): # not found." );
			break;
		}
		
		word = fread_word( fp );
		
		if ( !strcmp( word, "OBJECT" ) )	// Objects
		{
			if ( ( obj = fread_one_obj( fp, &location ) ) == NULL )
				continue;
			
			num_objs += obj->count;

			AutoEquip(ch, obj, location);
			
			/*
			 * What to do with a new loaded item:
			 *
			 * If there's a list with location less than 1 below this, then its
			 * container has disappeared from the file so we put the list back into
			 * the character's inventory. (Equipped items are 0 here.)
			 *
			 * If there's a list of contents with location of 1 below this, then we
			 * check if it is a container:
			 *   - Yes: Get it from the character, fill it, and give it back so we
			 *          have the correct weight.
			 *   -  No: The container is missing so we put everything back into the
			 *          character's inventory.
			 *
			 * For items with negative location, we check if there is already a list
			 * of contents with the same location.  If so, we put it there and if not,
			 * we start a new list.
			 *
			 * Since location for contents is < 0, the list indices are switched to
			 * non-negative.
			 *
			 * This looks ugly, but it works.
			 */
			if (location > 0)		/* Equipped */
			{
				for (j = MAX_BAG_ROWS - 1; j > 0; j--)
				{
					if (cont_row[j])	/* No container, back to inventory. */
					{
						for (; cont_row[j]; cont_row[j] = obj2)
						{
							obj2 = cont_row[j]->next_content;
							obj_to_char(cont_row[j], ch);
						}
						cont_row[j] = NULL;
					}
				}
				if (cont_row[0])	/* Content list existing. */
				{
					if (GET_OBJ_TYPE(obj) == ITEM_CONTAINER ||
					    GET_OBJ_TYPE(obj) == ITEM_MISSILE_CONT)
					{
						/* Remove object, fill it, equip again. */
						obj = unequip_char(ch, location - 1);
						
						for (; cont_row[0]; cont_row[0] = obj2)
						{
							obj2 = cont_row[0]->next_content;
							obj_to_obj(cont_row[0], obj);
						}
						equip_char(ch, obj, location - 1);
					}
					else			/* Object isn't container, empty the list. */
					{
						for (; cont_row[0]; cont_row[0] = obj2)
						{
							obj2 = cont_row[0]->next_content;
							obj_to_char(cont_row[0], ch);
						}
						cont_row[0] = NULL;
					}
				}
			}
			else	/* location <= 0 */
			{
				for (j = MAX_BAG_ROWS - 1; j > -location; j--)
				{
					if (cont_row[j])	/* No container, back to inventory. */
					{
						for (; cont_row[j]; cont_row[j] = obj2)
						{
							obj2 = cont_row[j]->next_content;
							cont_row[j] = obj_to_char(cont_row[j], ch);
						}
						cont_row[j] = NULL;
					}
				}
				if (j == -location && cont_row[j])	/* Content list exists. */
				{
					if (GET_OBJ_TYPE(obj) == ITEM_CONTAINER ||
					    GET_OBJ_TYPE(obj) == ITEM_MISSILE_CONT)
					{
						/* Take the item, fill it, and give it back. */
						obj_from_char(obj);
						obj->first_content	= NULL;
						obj->last_content	= NULL;
						for (; cont_row[j]; cont_row[j] = obj2)
						{
							obj2 = cont_row[j]->next_content;
							obj_to_obj(cont_row[j], obj);
						}
						obj = obj_to_char(obj, ch);	/* Add to inventory first. */
					}
					else	/* Object isn't container, empty content list. */
					{
						for (; cont_row[j]; cont_row[j] = obj2)
						{
							obj2 = cont_row[j]->next_content;
							obj_to_char(cont_row[j], ch);
						}
						cont_row[j] = NULL;
					}
				}
				if (location < 0 && location >= -MAX_BAG_ROWS)
				{
					/*
					 * Let the object be part of the content list but put it at the
					 * list's end.  Thus having the items in the same order as before
					 * the character rented.
					 */
					obj_from_char(obj);
					if ((obj2 = cont_row[-location - 1]) != NULL)
					{
						while (obj2->next_content)
							obj2 = obj2->next_content;
						obj2->next_content = obj;
					}
					else
						cont_row[-location - 1] = obj;
				}
			}
		}
		else if (!strcmp(word, "MOUNT"))
		{
			fread_rent_mount(fp, ch->in_room);
		}
		else if (!strcmp(word, "VEHICLE"))
		{
			if ( !( vehicle = fread_rent_vehicle(fp, ch->in_room) ) )
			{
				log( "SYSERR: LoadCharObj() cannot load vehicle data." );
				send_to_char("There is a problem with your vehcile. Tell an Immortal.\r\n", ch);
				fclose( fp );
				LoadingCharObj = FALSE;
				return (1);
			}

			LINK(vehicle, first_vehicle, last_vehicle, next, prev);
		}
		else if (!strcmp(word, "OBJVEH"))
		{
			if ( !vehicle )
			{
				log("SYSERR: LoadCharObj() - trying to load an object without a vehicle.");
				fclose(fp);
				LoadingCharObj = FALSE;
				return (1);
			}

			if ( ( obj = fread_one_obj( fp, &location ) ) == NULL )
				continue;
			
			obj = obj_to_vehicle(obj, vehicle);

			for (j = MAX_BAG_ROWS - 1; j > -location; j--)
			{
				if (veh_cont_row[j])
				{
					for (; veh_cont_row[j]; veh_cont_row[j] = obj2)
					{
						obj2 = veh_cont_row[j]->next_content;
						veh_cont_row[j] = obj_to_vehicle( veh_cont_row[j], vehicle );
					}
					veh_cont_row[j] = NULL;
				}
			}

			if (j == -location && veh_cont_row[j])
			{
				if (GET_OBJ_TYPE(obj) == ITEM_CONTAINER ||
					GET_OBJ_TYPE(obj) == ITEM_MISSILE_CONT)
				{
					obj_from_vehicle(obj);
					obj->first_content = NULL;
					obj->last_content = NULL;
					for (; veh_cont_row[j]; veh_cont_row[j] = obj2)
					{
						obj2 = veh_cont_row[j]->next_content;
						obj_to_obj(veh_cont_row[j], obj);
					}
					obj = obj_to_vehicle( obj, vehicle );
				}
				else
				{
					for (; veh_cont_row[j]; veh_cont_row[j] = obj2)
					{
						obj2 = veh_cont_row[j]->next_content;
						veh_cont_row[j] = obj_to_vehicle( veh_cont_row[j], vehicle );
					}
					veh_cont_row[j] = NULL;
				}
			}

			if (location < 0 && location >= -MAX_BAG_ROWS)
			{
				obj_from_vehicle(obj);
				if ((obj2 = veh_cont_row[-location - 1]) != NULL)
				{
					while (obj2->next_content)
						obj2 = obj2->next_content;
					obj2->next_content = obj;
				}
				else
					veh_cont_row[-location - 1] = obj;
			}
		}
		else if (!strcmp(word, "END"))		// Done
			break;
	}
	
	fclose(fp);

	LoadingCharObj = FALSE;

	/* Little hoarding check. -gg 3/1/98 */
	sprintf(fname, "%s (level %d) has %d object%s (max %d).",
		GET_NAME(ch), GET_LEVEL(ch), num_objs,
		num_objs != 1 ? "s" : "", max_obj_save);
	mudlog(fname, NRM, MAX(GET_INVIS_LEV(ch), LVL_GOD), TRUE);
	
	return (1);
}

/*
 * Write an object
 */
#define IS_VALID_SN(sn)		( (sn) >=0 && (sn) < MAX_SKILLS )

int fwrite_one_obj( OBJ_DATA *obj, FILE *fp, int mode, int location )
{
	EXTRA_DESCR *ed;
	sh_int aff;
	char dflags[128], dwear[128];
	obj_vnum vobj;
	
	switch (mode)
	{
	default:	fprintf( fp, "#OBJECT\n" );		break;
	case 1:		fprintf( fp, "#OBJVEH\n" );		break;
	}

	/* explicit or implicit unique item.. */
	if ( OBJ_FLAGGED(obj, ITEM_UNIQUE) || obj->item_number == NOTHING )
	{
		/* better safe than sorry */
		SET_BIT( GET_OBJ_EXTRA(obj), ITEM_UNIQUE );
		vobj = obj->item_number = NOTHING;

		fprintf( fp, "Vnum         %d\n",		obj->item_number	);
		if ( obj->name )
			fprintf( fp, "Name         %s~\n",	obj->name		);
		if ( obj->short_description )
			fprintf( fp, "ShortDescr   %s~\n",	obj->short_description  );
		if ( obj->description )
			fprintf( fp, "Description  %s~\n",	obj->description	);
		if ( obj->action_description )
			fprintf( fp, "ActionDesc   %s~\n",	obj->action_description );
	}
	/* "normal" item */
	else
	{
		vobj = obj->item_number;

		fprintf( fp, "Vnum         %d\n",		obj_index[vobj].vnum );

		if ( obj->name )
		{
			if ( QUICKMATCH( obj->name, obj_proto[vobj].name ) == 0 )
				fprintf( fp, "Name         %s~\n",	obj->name	     );
		}
		if ( obj->short_description )
		{
			if ( QUICKMATCH( obj->short_description, obj_proto[vobj].short_description ) == 0 )
				fprintf( fp, "ShortDescr   %s~\n",	obj->short_description     );
		}
		if ( obj->description )
		{
			if ( QUICKMATCH( obj->description, obj_proto[vobj].description ) == 0 )
				fprintf( fp, "Description  %s~\n",	obj->description     );
		}
		if ( obj->action_description )
		{
			if ( QUICKMATCH( obj->action_description, obj_proto[vobj].action_description ) == 0 )
				fprintf( fp, "ActionDesc   %s~\n",	obj->action_description     );
		}
	}

	fprintf( fp, "Location     %d\n",	location );

	sprintascii( dflags, GET_OBJ_EXTRA(obj) );
	fprintf( fp, "ExtraFlags   %s\n",	dflags );
	sprintascii( dwear, GET_OBJ_WEAR(obj) );
	fprintf( fp, "WearFlags    %s\n",	dwear );

	if ( OBJ_FLAGGED(obj, ITEM_UNIQUE) )
	{
		fprintf( fp, "ItemType     %d\n",	GET_OBJ_TYPE(obj)	);
		fprintf( fp, "Weight       %d\n",	GET_OBJ_WEIGHT(obj)	);
		fprintf( fp, "Level        %d\n",	GET_OBJ_LEVEL(obj)	);
		fprintf( fp, "Cost         %d\n",	GET_OBJ_COST(obj)	);
	}
	else
	{
		if ( GET_OBJ_TYPE(obj) != obj_proto[obj->item_number].obj_flags.type_flag )
			fprintf( fp, "ItemType     %d\n",	GET_OBJ_TYPE(obj)     );
		if ( GET_OBJ_WEIGHT(obj) != obj_proto[obj->item_number].obj_flags.weight )
			fprintf( fp, "Weight       %d\n",	GET_OBJ_WEIGHT(obj)	     );
		if ( GET_OBJ_LEVEL(obj) != obj_proto[obj->item_number].obj_flags.level )
			fprintf( fp, "Level        %d\n",	GET_OBJ_LEVEL(obj)	     );
		if ( obj->obj_flags.timer )
			fprintf( fp, "Timer        %d\n",	GET_OBJ_TIMER(obj)	     );
		if ( GET_OBJ_COST(obj) != obj_proto[obj->item_number].obj_flags.cost )
			fprintf( fp, "Cost         %d\n",	GET_OBJ_COST(obj)	     );
	}

	fprintf( fp, "Count        %d\n", obj->count );

	fprintf( fp, "Cond_curr    %d\n", GET_OBJ_COND(obj));
	fprintf( fp, "Cond_max     %d\n", GET_OBJ_MAXCOND(obj));

	fprintf( fp, "Quality      %d\n", GET_OBJ_QUALITY(obj) );

	fprintf( fp, "Values       %d %d %d %d\n",
		GET_OBJ_VAL(obj, 0), GET_OBJ_VAL(obj, 1),
		GET_OBJ_VAL(obj, 2), GET_OBJ_VAL(obj, 3));
	if (GET_OBJ_OWNER(obj) != NOBODY)
		fprintf( fp, "Owner        %d\n", GET_OBJ_OWNER(obj) );
	
	// save spells
	switch ( GET_OBJ_TYPE(obj) )
	{
	case ITEM_POTION:
	case ITEM_SCROLL:
		if ( IS_VALID_SN( GET_OBJ_VAL(obj, 1) ) )
			fprintf( fp, "Spell 1      '%s'\n",
				spell_info[GET_OBJ_VAL(obj, 1)].name );
		
		if ( IS_VALID_SN( GET_OBJ_VAL(obj, 2) ) )
			fprintf( fp, "Spell 2      '%s'\n",
				spell_info[GET_OBJ_VAL(obj, 2)].name );
	
		if ( IS_VALID_SN( GET_OBJ_VAL(obj, 3) ) )
			fprintf( fp, "Spell 3      '%s'\n",
				spell_info[GET_OBJ_VAL(obj, 3)].name );
		break;
		
	case ITEM_STAFF:
	case ITEM_WAND:
		if ( IS_VALID_SN( GET_OBJ_VAL(obj, 3) ) )
			fprintf( fp, "Spell 3      '%s'\n",
				spell_info[GET_OBJ_VAL(obj, 3)].name );
		
		break;
	}
	
	// Save affections
	for ( aff = 0; aff < MAX_OBJ_AFF; aff++ )
	{
		if ( obj->affected[aff].location == APPLY_NONE )
			continue;

		fprintf( fp, "Affect       %d %d\n",
			obj->affected[aff].location,
			obj->affected[aff].modifier );
	}

	if ( OBJ_FLAGGED(obj, ITEM_UNIQUE) )
	{
		// Save extra descriptions
		for ( ed = obj->ex_description; ed; ed = ed->next )
		{
			fprintf( fp, "ExtraDescr   %s~ %s~\n",
				ed->keyword, ed->description );
		}
	}
	else
	{
		/* TODO - check obj<->proto extra descr */
	}

	// Save special data attached
	if ( obj->special )
	{
		if ( OBJ_FLAGGED(obj, ITEM_HAS_SPELLS) )
		{
			OBJ_SPELLS_DATA *ospell;

			for (ospell = (OBJ_SPELLS_DATA *) obj->special; ospell; ospell = ospell->next)
			{
				if ( ospell->spellnum == 0 )
					continue;
		
				fprintf( fp, "ObjSpell     '%s' %hd %hd\n",
					skill_name(ospell->spellnum),
					ospell->level, ospell->percent );
			}
		}
		else if ( OBJ_FLAGGED(obj, ITEM_HAS_TRAPS) )
		{
			OBJ_TRAP_DATA *trap;

			for (trap = (OBJ_TRAP_DATA *) obj->special; trap; trap = trap->next)
				fprintf(fp, "ObjTrap      %d %d %d %hd\n",
					trap->action, trap->charges,
					trap->dam_type, trap->whole_room);

		}
		else if ( OBJ_FLAGGED(obj, ITEM_IS_SPELLBOOK) )
		{
			SPELLBOOK *book = (SPELLBOOK *) obj->special;
			BOOK_PAGE *page;
			
			fprintf(fp, "Book         %hd\n", book->type);
			
			for (page = book->first_page; page; page = page->next)
				fprintf(fp, "Page         %hd %hd %hd '%s'\n",
					page->status, page->flags, page->spellnum,
					(page->spellname ? page->spellname : "a blank page") );
		}
	}

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

OBJ_DATA *fread_one_obj( FILE *fp, int *location )
{
	OBJ_DATA *obj;
	char *word;
	bool fMatch;
	int fVnum = 0;
	int j = 0;

	CREATE(obj, OBJ_DATA, 1);
	clear_object(obj);
	
	for ( ; ; )
	{
		word   = feof( fp ) ? "End" : fread_word( fp );
		fMatch = FALSE;
		
		switch ( UPPER(word[0]) )
		{
		case '*':
			fMatch = TRUE;
			fread_to_eol( fp );
			break;
			
		case 'A':
			KEY( "ActionDesc", obj->action_description, 	fread_string_nospace( fp ) );
			if ( !strcmp( word, "Affect" ) )
			{
				if (j >= MAX_OBJ_AFF)
				{
					fMatch = TRUE;
					break;
				}
				
				obj->affected[j].location	= fread_number( fp );
				obj->affected[j].modifier	= fread_number( fp );
				j++;
				fMatch				= TRUE;
				break;
			}
			break;

		case 'B':
			if ( !strcmp(word, "Book") )
			{
				int type = fread_number(fp);

				if ( obj->special )
					log("SYSERR: special already assigned to obj.");
				else if ( !OBJ_FLAGGED(obj, ITEM_IS_SPELLBOOK) )
					log("SYSERR: book special assigned to a non-book obj.");
				else
				{
					SPELLBOOK *book = new_spellbook(obj, type, FALSE);
					book->type	= type;
					obj->special	= book;
				}

				fMatch = TRUE;
				break;
			}
			break;
			
		case 'C':
			KEY( "Cost",		GET_OBJ_COST(obj),		fread_number( fp ) );
			KEY( "Count",		obj->count,				fread_number( fp ) );
			KEY( "Cond_curr",	GET_OBJ_COND(obj),		fread_number( fp ) );
			KEY( "Cond_max",	GET_OBJ_MAXCOND(obj),	fread_number( fp ) );
			break;
			
		case 'D':
			KEY( "Description",	obj->description,		fread_string_nospace( fp ) );
			break;
			
		case 'E':
			KEY( "ExtraFlags",	GET_OBJ_EXTRA(obj),		asciiflag_conv(fread_word(fp)) );
			
			if ( !strcmp( word, "ExtraDescr" ) )
			{
				EXTRA_DESCR *new_descr;
				
				CREATE(new_descr, EXTRA_DESCR, 1);
				new_descr->keyword	= fread_string_nospace(fp);
				new_descr->description	= fread_string_nospace(fp);
				/* add to the obj extradescr list */
				new_descr->next		= obj->ex_description;
				obj->ex_description	= new_descr;
				fMatch = TRUE;
				break;
			}
			
			if ( !strcmp( word, "End" ) )
			{
				if ( !fVnum )
				{
					if ( obj->name )
						sprintf ( buf, "Fread_one_obj: %s incomplete object.", obj->name );
					else
						sprintf ( buf, "Fread_one_obj: incomplete object." );
					log( buf );
					if ( obj->name )
						STRFREE( obj->name        );
					if ( obj->description )
						STRFREE( obj->description );
					if ( obj->short_description )
						STRFREE( obj->short_description );
					if ( obj->action_description )
						STRFREE( obj->action_description );
					DISPOSE( obj );
					return (NULL);
				}
				else if ( fVnum == 2 )	// unique item
				{
					// make checks for unique items
				}
				return (obj);
			}
			break;
			
		case 'I':
			KEY( "ItemType",	GET_OBJ_TYPE(obj),		fread_number( fp ) );
			break;
			
		case 'L':
			KEY( "Level",		GET_OBJ_LEVEL(obj),		fread_number( fp ) );
			KEY( "Location",	*location,				fread_number( fp ) );
			break;

		case 'O':
			if ( !strcmp( word, "ObjSpell" ) )
			{
				OBJ_SPELLS_DATA *ospell, *spells_list = NULL;
				int sn;

				fMatch = TRUE;
				
				if ( obj->special )
				{
					if ( !OBJ_FLAGGED(obj, ITEM_HAS_SPELLS) )
					{
						log("SYSERR: non-spell special already assigned to obj.");
						break;
					}
					spells_list = (OBJ_SPELLS_DATA *) obj->special;
				}

				sn	= find_skill_num( fread_word( fp ) );

				if ( sn < 0 || sn > MAX_SPELLS )
				{
					log( "Fread_one_obj: unknown skill.", 0 );
					break;
				}

				CREATE(ospell, OBJ_SPELLS_DATA, 1);

				ospell->spellnum	= sn;
				ospell->level		= fread_number(fp);
				ospell->percent		= fread_number(fp);

				ospell->next		= spells_list;
				spells_list			= ospell;

				obj->special		= spells_list;
				SET_BIT(GET_OBJ_EXTRA(obj), ITEM_HAS_SPELLS);

				break;
			}
			if ( !strcmp( word, "ObjTrap" ) )
			{
				OBJ_TRAP_DATA *trap, *traps_list = NULL;

				fMatch = TRUE;
				
				if ( obj->special )
				{
					if ( !OBJ_FLAGGED(obj, ITEM_HAS_TRAPS) )
					{
						log("SYSERR: non-trap special already assigned to obj.");
						break;
					}
					traps_list = (OBJ_TRAP_DATA *) obj->special;
				}

				CREATE(trap, OBJ_TRAP_DATA, 1);

				trap->action		= fread_number(fp);
				trap->charges		= fread_number(fp);
				trap->dam_type		= fread_number(fp);
				trap->whole_room	= fread_number(fp);

				trap->next			= traps_list;
				traps_list			= trap;

				obj->special		= traps_list;
				SET_BIT(GET_OBJ_EXTRA(obj), ITEM_HAS_TRAPS);

				break;
			}
			KEY( "Owner",		GET_OBJ_OWNER(obj),		fread_number( fp ) );
			break;

		case 'P':
			if ( !strcmp(word, "Page") )
			{
				if ( !obj->special || !OBJ_FLAGGED(obj, ITEM_IS_SPELLBOOK) )
					log("SYSERR: book page without special spellbook.");
				else
				{
					SPELLBOOK *book = (SPELLBOOK *) obj->special;
					BOOK_PAGE *page;

					CREATE(page, BOOK_PAGE, 1);

					page->status	= fread_number(fp);
					page->flags		= fread_number(fp);
					page->spellnum	= fread_number(fp);
					page->spellname = str_dup(fread_word(fp));

					LINK(page, book->first_page, book->last_page, next, prev);
					book->num_of_pages++;
					if ( page->spellnum != NOTHING )
						book->num_of_spells++;
					else
						DISPOSE(page->spellname);
				}

				fMatch = TRUE;
				break;
			}
			break;

		case 'Q':
			KEY( "Quality",		GET_OBJ_QUALITY(obj),	fread_number( fp ) );

		case 'N':
			KEY( "Name",		obj->name,				fread_string_nospace( fp ) );
			break;
			
		case 'S':
			KEY( "ShortDescr",	obj->short_description,	fread_string_nospace( fp ) );
			if ( !strcmp( word, "Spell" ) )
			{
				int iValue;
				int sn;
				
				iValue	= fread_number( fp );
				sn	= find_skill_num( fread_word(fp) );

				if ( iValue < 0 || iValue > 5 )
					log( "Fread_one__obj: bad iValue %d.", iValue );
				else if ( sn < 0 )
					log( "Fread_one_obj: unknown skill.", 0 );
				else
					GET_OBJ_VAL(obj, iValue) = sn;
				fMatch = TRUE;
				break;
			}
			break;
			
		case 'T':
			KEY( "Timer",	GET_OBJ_TIMER(obj),	fread_number(fp) );
			break;
			
		case 'V':
			if ( !strcmp( word, "Values" ) )
			{
				GET_OBJ_VAL(obj, 0) = fread_number(fp);
				GET_OBJ_VAL(obj, 1) = fread_number(fp);
				GET_OBJ_VAL(obj, 2) = fread_number(fp);
				GET_OBJ_VAL(obj, 3) = fread_number(fp);
				fMatch = TRUE;
				break;
			}
			
			if ( !strcmp( word, "Vnum" ) )
			{
				int vnum;
				
				vnum = fread_number( fp );
				
				if ( vnum == NOTHING )	// unique item
				{
					obj->item_number = NOTHING;
					LINK(obj, first_object, last_object, next, prev);
					fVnum = 2;
				}
				else if ( ( obj = read_object(vnum, VIRTUAL) ) == NULL )
					fVnum = 0;
				else
					fVnum = 1;
				fMatch = TRUE;
				break;
			}
			break;
			
		case 'W':
			KEY( "WearFlags",	GET_OBJ_WEAR(obj),		asciiflag_conv(fread_word(fp)) );
			KEY( "Weight",		GET_OBJ_WEIGHT(obj),	fread_number( fp ) );
			break;
		}
		
		if ( !fMatch )
		{
			EXTRA_DESCR *ed, *temp;
			
			log( "Fread_one_obj: no match." );
			log( word, 0 );
			fread_to_eol( fp );
			if ( obj->name )
				STRFREE( obj->name        );
			if ( obj->description )
				STRFREE( obj->description );
			if ( obj->short_description )
				STRFREE( obj->short_description );
			
			while ( (ed = obj->ex_description) != NULL )
			{
				if ( ed->keyword )
					STRFREE( ed->keyword );
				if ( ed->description )
					STRFREE( ed->description );
				REMOVE_FROM_LIST(ed, obj->ex_description, next );
				DISPOSE( ed );
			}
			DISPOSE( obj );
			return (NULL);
		}
    }
}


void SaveAll(bool bQuit)
{
	DESCRIPTOR_DATA *d;

	for (d = descriptor_list; d; d = d->next)
	{
		if ((STATE(d) == CON_PLAYING) && !IS_NPC(d->character))
		{
			if (PLR_FLAGGED(d->character, PLR_CRASH))
			{
				SaveCharObj(d->character, bQuit);
				save_char(d->character, NULL);
				REMOVE_BIT(PLR_FLAGS(d->character), PLR_CRASH);
			}
		}
	}
}