dotd-2.3.7/area/
dotd-2.3.7/clans/
dotd-2.3.7/classes/
dotd-2.3.7/councils/
dotd-2.3.7/deity/
dotd-2.3.7/dict/
dotd-2.3.7/doc/mudprogs/
dotd-2.3.7/player/a/
dotd-2.3.7/player/g/
/****************************************************************************
 * [S]imulated [M]edieval [A]dventure multi[U]ser [G]ame      |   \\._.//   *
 * -----------------------------------------------------------|   (0...0)   *
 * SMAUG 1.0 (C) 1994, 1995, 1996 by Derek Snider             |    ).:.(    *
 * -----------------------------------------------------------|    {o o}    *
 * SMAUG code team: Thoric, Altrag, Blodkai, Narn, Haus,      |   / ' ' \   *
 * Scryn, Rennard, Swordbearer, Gorog, Grishnakh and Tricops  |~'~.VxvxV.~'~*
 * ------------------------------------------------------------------------ *
 * Merc 2.1 Diku Mud improvments copyright (C) 1992, 1993 by Michael        *
 * Chastain, Michael Quan, and Mitchell Tse.                                *
 * Original Diku Mud copyright (C) 1990, 1991 by Sebastian Hammer,          *
 * Michael Seifert, Hans Henrik St{rfeldt, Tom Madsen, and Katja Nyboe.     *
 * ------------------------------------------------------------------------ *
 *		     Character saving and loading module		    *
 ****************************************************************************/

/*static char rcsid[] = "$Id: save.c,v 1.82 2004/04/06 22:00:11 dotd Exp $";*/

#include <sys/types.h>
#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <sys/stat.h>
#include <dirent.h>
#include "mud.h"
#include "gsn.h"
#include "recylist.h"
#include "christen.h"
#include "rareobj.h"
#include "currency.h"

extern int              char_affects;

/*
 * Increment with every major format change.
 */
#define SAVEVERSION	3

/*
 * Array to keep track of equipment temporarily.		-Thoric
 */
OBJ_DATA *save_equipment[MAX_WEAR][8];
CHAR_DATA *quitting_char, *loading_char, *saving_char;

int file_ver;

/*
 * Externals
 */
void fwrite_comments( CHAR_DATA *ch, FILE *fp );
void fread_comment( CHAR_DATA *ch, FILE *fp );

/*
 * Array of containers read for proper re-nesting of objects.
 */
static	OBJ_DATA *	rgObjNest	[MAX_NEST];

/*
 * Local functions.
 */
void	  fwrite_char	args( ( CHAR_DATA *ch, FILE *fp ) );
CHAR_DATA *fread_char	args( ( CHAR_DATA *ch, FILE *fp, bool preload ) );
void	  write_corpses	args( ( CHAR_DATA *ch, char *name ) );


/*
 * Un-equip character before saving to ensure proper	-Thoric
 * stats are saved in case of changes to or removal of EQ
 */
void de_equip_char( CHAR_DATA *ch )
{
    char buf[MAX_STRING_LENGTH];
    OBJ_DATA *obj;
    int x,y;

    for ( x = 0; x < MAX_WEAR; x++ )
        for ( y = 0; y < MAX_LAYERS; y++ )
            save_equipment[x][y] = NULL;
    for ( obj = ch->first_carrying; obj; obj = obj->next_content )
        if ( obj->wear_loc > -1 && obj->wear_loc < MAX_WEAR )
        {
            if ( char_ego(ch) >= item_ego(obj) )
            {
                for ( x = 0; x < MAX_LAYERS; x++ )
                    if ( !save_equipment[obj->wear_loc][x] )
                    {
                        save_equipment[obj->wear_loc][x] = obj;
                        break;
                    }
                if ( x == MAX_LAYERS )
                {
                    sprintf( buf, "%s had on more than %d layers of clothing in one location (%d): %s",
                             ch->name, MAX_LAYERS, obj->wear_loc, obj->name );
                    bug( buf, 0 );
                }
            }
            else
            {
                sprintf( buf, "%s had on %s:  ch ego = %d  obj ego = %d",
                         ch->name, obj->name,
                         char_ego(ch), item_ego(obj) );
                bug( buf, 0 );
            }
            unequip_char(ch, obj);
        }
}

/*
 * Re-equip character					-Thoric
 */
void re_equip_char( CHAR_DATA *ch )
{
    int x,y;

    for ( x = 0; x < MAX_WEAR; x++ )
        for ( y = 0; y < MAX_LAYERS; y++ )
            if ( save_equipment[x][y] != NULL )
            {
                if ( quitting_char != ch )
                    equip_char(ch, save_equipment[x][y], x);
                save_equipment[x][y] = NULL;
            }
            else
                break;
}


/*
 * 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_DATA *follower, *nextinroom;
    char strsave[MAX_INPUT_LENGTH];
    char strback[MAX_INPUT_LENGTH];
    FILE *fp;

    if ( !ch )
    {
        bug( "Save_char_obj: null ch!" );
        return;
    }

    if ( IS_NPC(ch ) )
        return;

    if ( GetMaxLevel(ch) < 2 )
        return;

    saving_char = ch;
    /* save pc's clan's data while we're at it to keep the data in sync */
    if ( !IS_NPC(ch) && ch->pcdata->clan )
        save_clan( ch->pcdata->clan );

    /* save deity's data to keep it in sync -ren */
    if ( !IS_NPC(ch) && ch->pcdata->deity )
        save_deity( ch->pcdata->deity );

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

    de_equip_char( ch );

    ch->save_time = current_time;
    sprintf( strsave, "%s%c/%s", PLAYER_DIR, tolower(ch->name[0]),
             capitalize( ch->name ) );

    /*
     * Auto-backup pfile (can cause lag with high disk access situtations
     */
    if ( IS_SET( sysdata.save_flags, SV_BACKUP ) )
    {
        sprintf( strback, "%s%c/%s", BACKUP_DIR, tolower(ch->name[0]),
                 capitalize( ch->name ) );
        rename( strsave, strback );
    }

    /*
     * Save immortal stats, level & vnums for wizlist		-Thoric
     * and do_vnums command
     *
     * Also save the player flags so we the wizlist builder can see
     * who is a guest and who is retired.
     */
    if ( IS_IMMORTAL(ch) )
    {
        sprintf( strback, "%s%s", GOD_DIR, capitalize( ch->name ) );

        if ( ( fp = fopen( strback, "w" ) ) == NULL )
        {
            bug( "Save_god_level: fopen" );
            perror( strsave );
        }
        else
        {
            fprintf( fp, "Level        %d\n", GetMaxLevel(ch) );
            fprintf( fp, "Pcflags      %d\n", ch->pcdata->flags );
            if ( ch->pcdata->r_range_lo || ch->pcdata->r_range_hi )
                fprintf( fp, "RoomRange    %d %d\n", ch->pcdata->r_range_lo,
                         ch->pcdata->r_range_hi	);
            if ( ch->pcdata->o_range_lo || ch->pcdata->o_range_hi )
                fprintf( fp, "ObjRange     %d %d\n", ch->pcdata->o_range_lo,
                         ch->pcdata->o_range_hi	);
            if ( ch->pcdata->m_range_lo || ch->pcdata->m_range_hi )
                fprintf( fp, "MobRange     %d %d\n", ch->pcdata->m_range_lo,
                         ch->pcdata->m_range_hi	);
            FCLOSE( fp );
        }
    }

    if ( ( fp = fopen( strsave, "w" ) ) == NULL )
    {
        bug( "Save_char_obj: fopen" );
        send_to_char("Your character is having problems.\n\r", ch);
        perror( strsave );
    }
    else
    {
        fwrite_char( ch, fp );
        if ( ch->first_carrying )
            fwrite_obj( ch, ch->last_carrying, fp, 0, OS_CARRY );
        if ( ch->vars )
            fwrite_variables( ch->vars, fp );
        if ( ch->comments )                 /* comments */
            fwrite_comments( ch, fp );        /* comments */
        for (follower = ch->in_room->first_person; follower; follower = nextinroom)
        {
            nextinroom = follower->next_in_room;
            if ( (follower != ch) && (follower->master == ch) &&
                 IS_NPC(follower) )
            {
                fwrite_char(follower, fp);
                if (follower->first_carrying)
                    fwrite_obj( follower, follower->last_carrying, fp, 0, OS_CARRY );
                if (follower->vars)
                    fwrite_variables( follower->vars, fp );
            }
        }

#ifdef VTRACK
        if ( !IS_NPC(ch) && ch->pcdata->vtrack )
            fwrite_vtracks(ch, fp);
#endif

        fprintf( fp, "#END\n" );
        FCLOSE( fp );
    }

    ClassSpecificStuff(ch);

    re_equip_char( ch );

    write_corpses(ch, NULL);
    quitting_char = NULL;
    saving_char   = NULL;

    update_char_rare_obj(ch);

    return;
}



/*
 * Write the char.
 */
void fwrite_char( CHAR_DATA *ch, FILE *fp )
{
    AFFECT_DATA *paf;
    ALIAS_DATA *pal;
    int i, sn, track, count;
    SKILLTYPE *skill = NULL;

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

    fprintf( fp, "Version      %d\n",   SAVEVERSION		);
    if (IS_NPC(ch))
        fprintf( fp, "Vnum         %d\n",	ch->vnum	);
    fprintf( fp, "Name         %s~\n",	ch->name		);
    if ( ch->short_descr && ch->short_descr[0] != '\0' )
        fprintf( fp, "ShortDescr   %s~\n",	ch->short_descr	);
    if ( ch->long_descr && ch->long_descr[0] != '\0' )
        fprintf( fp, "LongDescr    %s~\n",	ch->long_descr	);
    if ( ch->description && ch->description[0] != '\0' )
        fprintf( fp, "Description  %s~\n",	ch->description	);
    if ( ch->intro_descr && ch->intro_descr[0] != '\0' )
        fprintf( fp, "IntroDescr    %s~\n",	ch->intro_descr	);
    fprintf( fp, "Sex          %d\n",	ch->sex			);
    fprintf( fp, "Race         %d\n",	ch->race		);
    fprintf( fp, "Languages    %d %d\n", ch->speaks, ch->speaking );

    for (i=0; i < MAX_CLASS; ++i) {
        if (HAS_CLASS(ch,i))
            fprintf( fp, "Level        %d %d %d\n", i,
                     ch->levels[i], ch->classes[i]);
    }

    if ( ch->trust )
        fprintf( fp, "Trust        %d\n",	ch->trust		);
    fprintf( fp, "Played       %d\n",
             ch->played + (int) (current_time - ch->logon)		);
    if (!IS_NPC(ch))
        fprintf( fp, "Room         %d\n", ch->pcdata->home);

    fprintf( fp, "HpManaMove   %d %d %d %d %d %d\n",
             GET_HIT(ch),  ch->max_hit,
             GET_MANA(ch), ch->max_mana,
             GET_MOVE(ch), 0 );
    fprintf( fp, "Regens       %d %d %d\n",
             ch->hit_regen, ch->mana_regen, ch->move_regen );

    if ( ch->gold2 )
    {
        GET_MONEY(ch,CURR_GOLD) += ch->gold2;
        ch->gold2 = 0;
    }
    if ( ch->bank2 )
    {
        GET_BALANCE(ch,CURR_GOLD) += ch->bank2;
        ch->bank2 = 0;
    }

    fprintf( fp, "Money       ");
    for (count=0; count < MAX_CURR_TYPE; count++ )
        fprintf(fp, " %d", GET_MONEY(ch,count)                          );
    fprintf(fp, " -1\n");
    fprintf( fp, "BankMoney   ");
    for (count=0; count < MAX_CURR_TYPE; count++ )
        fprintf(fp, " %d", GET_BALANCE(ch,count)                        );
    fprintf(fp, " -1\n");

    if ( !IS_NPC(ch) && (GET_INTF(ch) != INT_DEFAULT ) )
        fprintf( fp, "Interface    %d\n",   GET_INTF(ch)     	);
    if (ch->antimagicp)
        fprintf( fp, "AMP          %d\n",   ch->antimagicp             );
    if (ch->spellfail != 101)
        fprintf( fp, "Spellfail    %d\n",   ch->spellfail       );
    fprintf( fp, "Exp          %d\n",	ch->exp			);
    if ( ch->act )
        fprintf( fp, "Act          %d\n", ch->act			);
    if ( ch->act2 )
        fprintf( fp, "Act2         %d\n", ch->act2		);
    if ( ch->affected_by )
        fprintf( fp, "AffectedBy   %d\n",	ch->affected_by		);
    if ( ch->affected_by2 )
        fprintf( fp, "AffectedBy2  %d\n",	ch->affected_by2	);
    fprintf( fp, "Position     %d\n",
             ch->position == POS_FIGHTING ? POS_STANDING : ch->position );
    if ( ch->xflags )
        fprintf( fp, "Bodyparts    %d\n",	ch->xflags );

    fprintf( fp, "Practice     %d\n",	ch->practice		);
    fprintf( fp, "SavingThrows %d %d %d %d %d\n",
             ch->saving_poison_death,
             ch->saving_wand,
             ch->saving_para_petri,
             ch->saving_breath,
             ch->saving_spell_staff			);
    fprintf( fp, "Alignment    %d\n",	ch->alignment		);

    if (IS_NPC(ch)) {
        fprintf( fp, "Numattacks   %d\n",	ch->numattacks		);
    } else {
        fprintf( fp, "Favor        %d\n",	ch->pcdata->favor	);
        fprintf( fp, "Glory        %d\n",   ch->pcdata->quest_curr  );
        fprintf( fp, "MGlory       %d\n",   ch->pcdata->quest_accum );
    }
    fprintf( fp, "Hitroll      %d\n",	ch->hitroll		);
    fprintf( fp, "Damroll      %d\n",	ch->damroll		);
    fprintf( fp, "Armor        %d\n",	ch->armor		);
    if ( ch->wimpy )
        fprintf( fp, "Wimpy        %d\n",	ch->wimpy		);
    if ( ch->deaf )
        fprintf( fp, "Deaf         %d\n",	ch->deaf		);
    if ( !IS_NPC(ch) )
    {
        for(i=0;i<MAX_COLOR_TYPE;i++)
            if (ch->colors[i]!=def_color(i))
                fprintf( fp, "Color        %d %d\n", i, ch->colors[i]	);
    }
    if ( ch->resistant )
        fprintf( fp, "Resistant    %d\n",	ch->resistant		);
    if ( ch->immune )
        fprintf( fp, "Immune       %d\n",	ch->immune		);
    if ( ch->susceptible )
        fprintf( fp, "Susceptible  %d\n",	ch->susceptible		);
    if ( ch->absorb )
        fprintf( fp, "Absorb       %d\n",	ch->absorb		);

    if (IS_NPC(ch)) {
        fprintf( fp, "Mobinvis     %d\n",	ch->mobinvis		);
        fprintf( fp, "AttrPerm     %d %d %d %d %d %d %d\n",
                 ch->perm_str,
                 ch->perm_int,
                 ch->perm_wis,
                 ch->perm_dex,
                 ch->perm_con,
                 ch->perm_cha,
                 ch->perm_lck );

        fprintf( fp, "AttrMod      %d %d %d %d %d %d %d\n",
                 ch->mod_str,
                 ch->mod_int,
                 ch->mod_wis,
                 ch->mod_dex,
                 ch->mod_con,
                 ch->mod_cha,
                 ch->mod_lck );
    }
    else
    {
        if ( ch->pcdata->outcast_time )
            fprintf( fp, "Outcast_time %d\n",(int)ch->pcdata->outcast_time );
        if ( ch->pcdata->restore_time )
            fprintf( fp, "Restore_time %d\n",(int)ch->pcdata->restore_time );
        if ( IS_IMMORTAL(ch) )
        {
            if ( !ch->pcdata->time_immortal )
                ch->pcdata->time_immortal = current_time;
            fprintf( fp, "Time_Imm     %d\n",(int)ch->pcdata->time_immortal );
        }
        if ( ch->pcdata->time_to_die )
            fprintf( fp, "Time_To_Die  %d\n",(int)ch->pcdata->time_to_die );
        if ( !ch->pcdata->time_created )
            ch->pcdata->time_created = current_time;
        if ( ch->pcdata->time_created )
            fprintf( fp, "Time_Created %d\n",(int)ch->pcdata->time_created );

        /* increment times_played if logged in for more than 5 minutes but
           be careful to only do it once via inc_times_played */
        if ( !ch->pcdata->inc_times_played && ch->save_time > ch->logon+(5*60))
        {
            ch->pcdata->inc_times_played = TRUE;
            ch->pcdata->times_played++;
        }
        fprintf( fp, "Times_played %d\n", ch->pcdata->times_played );

        if ( ch->mental_state != -10 )
            fprintf( fp, "Mentalstate  %d\n",	ch->mental_state	);
        fprintf( fp, "Password     %s~\n",	ch->pcdata->pwd		);
        for ( pal = ch->pcdata->first_alias; pal; pal = pal->next )
        {
            if ( !pal->name || !pal->cmd || !*pal->name || !*pal->cmd )
                continue;
            fprintf( fp, "Alias        %s~ %s~ %d\n",
                     pal->name,
                     pal->cmd,
                     pal->flags
                   );
        }
        if ( ch->pcdata->bamfin && ch->pcdata->bamfin[0] != '\0' )
            fprintf( fp, "Bamfin       %s~\n",	ch->pcdata->bamfin	);
        if ( ch->pcdata->bamfout && ch->pcdata->bamfout[0] != '\0' )
            fprintf( fp, "Bamfout      %s~\n",	ch->pcdata->bamfout	);
        if ( ch->pcdata->rank && ch->pcdata->rank[0] != '\0' )
            fprintf( fp, "Rank         %s~\n",	ch->pcdata->rank	);
        if ( ch->pcdata->bestowments && ch->pcdata->bestowments[0] != '\0' )
            fprintf( fp, "Bestowments  %s~\n", 	ch->pcdata->bestowments );
        fprintf( fp, "Title        %s~\n",	ch->pcdata->title	);
        if ( ch->pcdata->homepage && ch->pcdata->homepage[0] != '\0' )
            fprintf( fp, "Homepage     %s~\n",	ch->pcdata->homepage	);
        if ( ch->pcdata->bio && ch->pcdata->bio[0] != '\0' )
            fprintf( fp, "Bio          %s~\n",	ch->pcdata->bio 	);
        if ( ch->pcdata->authed_by && ch->pcdata->authed_by[0] != '\0' )
            fprintf( fp, "AuthedBy     %s~\n",	ch->pcdata->authed_by	);
        if ( ch->pcdata->min_snoop )
            fprintf( fp, "Minsnoop     %d\n",	ch->pcdata->min_snoop	);
        if ( ch->pcdata->prompt && *ch->pcdata->prompt )
            fprintf( fp, "Prompt       %s~\n",	ch->pcdata->prompt	);
        if ( ch->pcdata->pagerlen != 24 )
            fprintf( fp, "Pagerlen     %d\n",	ch->pcdata->pagerlen	);

        if ( IS_IMMORTAL( ch ) )
        {
            fprintf( fp, "WizInvis     %d\n",   ch->pcdata->wizinvis    );

            for (i=LOG_NORMAL;i<LOG_LAST;i++)
                if ( ch->pcdata->log_severity[i] != SEV_DEFAULT )
                    fprintf( fp, "LogSeverity  %d %s~\n",
                             ch->pcdata->log_severity[i],
                             sysdata.logdefs[i].name);
            for (i=LOG_NORMAL;i<LOG_LAST;i++)
                if ( ch->pcdata->afk_log_severity[i] != SEV_DEFAULT )
                    fprintf( fp, "AFKSeverity  %d %s~\n",
                             ch->pcdata->afk_log_severity[i],
                             sysdata.logdefs[i].name);
        }

        if ( ch->pcdata->r_range_lo && ch->pcdata->r_range_hi )
            fprintf( fp, "RoomRange    %d %d\n", ch->pcdata->r_range_lo,
                     ch->pcdata->r_range_hi	);
        if ( ch->pcdata->o_range_lo && ch->pcdata->o_range_hi )
            fprintf( fp, "ObjRange     %d %d\n", ch->pcdata->o_range_lo,
                     ch->pcdata->o_range_hi	);
        if ( ch->pcdata->m_range_lo && ch->pcdata->m_range_hi )
            fprintf( fp, "MobRange     %d %d\n", ch->pcdata->m_range_lo,
                     ch->pcdata->m_range_hi	);

        if ( ch->pcdata->council)
            fprintf( fp, "Council      %s~\n", 	ch->pcdata->council_name );
        if ( ch->pcdata->deity_name && ch->pcdata->deity_name[0] != '\0' )
            fprintf( fp, "Deity	     %s~\n",	ch->pcdata->deity_name	 );
        if ( ch->pcdata->clan_name && ch->pcdata->clan_name[0] != '\0' )
            fprintf( fp, "Clan         %s~\n",	ch->pcdata->clan_name	);
        fprintf( fp, "Flags        %d\n",	ch->pcdata->flags	);
        if ( ch->pcdata->release_date > current_time )
            fprintf( fp, "Helled       %d %s~\n",
                     (int)ch->pcdata->release_date, ch->pcdata->helled_by );
        if ( ch->pcdata->pkills )
            fprintf( fp, "PKills       %d\n",	ch->pcdata->pkills	);
        if ( ch->pcdata->pdeaths )
            fprintf( fp, "PDeaths      %d\n",	ch->pcdata->pdeaths	);
        if ( get_timer( ch , TIMER_PKILLED)
             && ( get_timer( ch , TIMER_PKILLED) > 0 ) )
            fprintf( fp, "PTimer       %d\n",     get_timer(ch, TIMER_PKILLED));
        fprintf( fp, "MKills       %d\n",	ch->pcdata->mkills	);
        fprintf( fp, "MDeaths      %d\n",	ch->pcdata->mdeaths	);
        if ( ch->pcdata->illegal_pk )
            fprintf( fp, "IllegalPK    %d\n",	ch->pcdata->illegal_pk	);
        fprintf( fp, "AttrPerm     %d %d %d %d %d %d %d\n",
                 ch->perm_str,
                 ch->perm_int,
                 ch->perm_wis,
                 ch->perm_dex,
                 ch->perm_con,
                 ch->perm_cha,
                 ch->perm_lck );

        fprintf( fp, "AttrMod      %d %d %d %d %d %d %d\n",
                 ch->mod_str,
                 ch->mod_int,
                 ch->mod_wis,
                 ch->mod_dex,
                 ch->mod_con,
                 ch->mod_cha,
                 ch->mod_lck );

        fprintf( fp, "Condition    %d %d %d %d\n",
                 GET_COND(ch, 0),
                 GET_COND(ch, 1),
                 GET_COND(ch, 2),
                 GET_COND(ch, 3) );
        if ( ch->desc && ch->desc->host )
            fprintf( fp, "Site         %s\n", ch->desc->host );
        else
            fprintf( fp, "Site         (Link-Dead)\n" );
        if ( ch->desc && ch->desc->user )
            fprintf( fp, "User         %s\n", ch->desc->user );
        else
            fprintf( fp, "User         None\n" );

        for ( sn = 1; sn < top_sn; sn++ )
        {
            if ( !skill_table[sn]->name )
                continue;

            if ( LEARNED(ch, sn) > 0 )
                switch( skill_table[sn]->type )
                {
                default:
                    fprintf( fp, "Skill        %d '%s'\n",
			    LEARNED(ch, sn), skill_table[sn]->name );
                    break;
                case SKILL_SPELL:
		    fprintf( fp, "Spell        %d '%s'\n",
			    LEARNED(ch, sn), skill_table[sn]->name );
                    break;
                case SKILL_WEAPON:
		    fprintf( fp, "Weapon       %d '%s'\n",
			    LEARNED(ch, sn), skill_table[sn]->name );
		    break;
                case SKILL_TONGUE:
		    fprintf( fp, "Tongue       %d '%s'\n",
			    LEARNED(ch, sn), skill_table[sn]->name );
                    break;
                case SKILL_LORE:
                    fprintf( fp, "Lore         %d '%s'\n",
			    LEARNED(ch, sn), skill_table[sn]->name );
                    break;
                case SKILL_PSISPELL:
		    fprintf( fp, "PsiSpell     %d '%s'\n",
			    LEARNED(ch, sn), skill_table[sn]->name );
		    break;
                }

            if ( MEMORIZED(ch, sn) )
                fprintf( fp, "Memorized    %d '%s'\n",
                         MEMORIZED(ch, sn), skill_table[sn]->name );
        }
    }

    for ( paf = ch->first_affect; paf; paf = paf->next )
    {
        if ( paf->type >= 0 && (skill=get_skilltype(paf->type)) == NULL )
            continue;

        if ( paf->type >= 0 && paf->type < TYPE_PERSONAL )
            fprintf( fp, "AffectData   '%s' %3d %3d %3d %10d\n",
                     skill->name,
                     paf->duration,
                     paf->modifier,
                     paf->location,
                     paf->bitvector
                   );
        else
            fprintf( fp, "Affect       %3d %3d %3d %3d %10d\n",
                     paf->type,
                     paf->duration,
                     paf->modifier,
                     paf->location,
                     paf->bitvector
                   );
    }


    if (!IS_NPC(ch))
    {
       track = URANGE( 2, ((GetMaxLevel(ch)+3) * MAX_KILLTRACK)/LEVEL_AVATAR,MAX_KILLTRACK );
       for ( sn = 0; sn < track; sn++ )
       {
           if ( ch->pcdata->killed[sn].vnum == 0 )
               break;
           fprintf( fp, "Killed       %d %d\n",
                     ch->pcdata->killed[sn].vnum,
                     ch->pcdata->killed[sn].count );
       }

#ifdef I3
       i3save_char(ch, fp);
#endif
#ifdef IMC
       imc_savechar(ch, fp);
#endif
    }

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

/*
 * Write an object and its contents.
 */
void fwrite_obj( CHAR_DATA *ch, OBJ_DATA *obj, FILE *fp, int iNest,
                 sh_int os_type )
{
    EXTRA_DESCR_DATA *ed;
    AFFECT_DATA *paf;
    sh_int wear, wear_loc, x;

    if ( iNest >= MAX_NEST )
    {
        bug( "fwrite_obj: iNest hit MAX_NEST %d", iNest );
        return;
    }

    /*
     * Slick recursion to write lists backwards,
     *   so loading them will load in forwards order.
     */
    if ( obj->prev_content && os_type != OS_CORPSE )
        fwrite_obj( ch, obj->prev_content, fp, iNest, OS_CARRY );

    /*
     * Castrate storage characters.
     */
    if ( obj->item_type == ITEM_KEY && !IS_OBJ_STAT(obj, ITEM_CLANOBJECT) )
        return;

    /*
     * Catch deleted objects					-Thoric
     */
    if ( obj_extracted(obj) )
        return;

    /*
     * Do NOT save prototype items!				-Thoric
     */
    if ( IS_OBJ_STAT( obj, ITEM_PROTOTYPE ) )
        return;

    /*
     * Don't save objs with negative rent
     */
    if ( obj->rent < 0 )
        return;

    /* Corpse saving. -- Altrag */
    fprintf( fp, (os_type == OS_CORPSE ? "#CORPSE\n" : "#OBJECT\n") );

    fprintf( fp, "* %s\n", obj->name );
    if ( iNest )
        fprintf( fp, "Nest         %d\n",	iNest		     );
    if ( obj->count > 1 )
        fprintf( fp, "Count        %d\n",	obj->count	     );
    if ( QUICKMATCH( obj->name, obj->pIndexData->name ) == 0 )
        fprintf( fp, "Name         %s~\n",	obj->name	     );
    if ( QUICKMATCH( obj->short_descr, obj->pIndexData->short_descr ) == 0 )
        fprintf( fp, "ShortDescr   %s~\n",	obj->short_descr     );
    if ( QUICKMATCH( obj->description, obj->pIndexData->description ) == 0 )
        fprintf( fp, "Description  %s~\n",	obj->description     );
    if ( QUICKMATCH( obj->action_desc, obj->pIndexData->action_desc ) == 0 )
        fprintf( fp, "ActionDesc   %s~\n",	obj->action_desc     );
    fprintf( fp, "Vnum         %d\n",	obj->vnum	     );
    if ( obj->christened )
        fprintf( fp, "CVnum        %d\n",	obj->christened->cvnum);
    if ( os_type == OS_CORPSE && obj->in_room )
        fprintf( fp, "Room         %d\n",   obj->in_room->vnum       );
    if ( obj->extra_flags != obj->pIndexData->extra_flags )
        fprintf( fp, "ExtraFlags   %d\n",	obj->extra_flags     );
    if ( obj->extra_flags2 != obj->pIndexData->extra_flags2 )
        fprintf( fp, "ExtraFlags2  %d\n",	obj->extra_flags2    );
    if ( obj->magic_flags != obj->pIndexData->magic_flags )
        fprintf( fp, "MagicFlags   %d\n",	obj->magic_flags     );
    if ( obj->wear_flags != obj->pIndexData->wear_flags )
        fprintf( fp, "WearFlags    %d\n",	obj->wear_flags	     );
    wear_loc = -1;
    for ( wear = 0; wear < MAX_WEAR; wear++ )
        for ( x = 0; x < MAX_LAYERS; x++ )
            if ( obj == save_equipment[wear][x] )
            {
                wear_loc = wear;
                break;
            }
            else
                if ( !save_equipment[wear][x] )
                    break;
    if ( wear_loc != -1 )
        fprintf( fp, "WearLoc      %d\n",	wear_loc	     );
    if ( obj->item_type != obj->pIndexData->item_type )
        fprintf( fp, "ItemType     %d\n",	obj->item_type	     );
    if ( obj->weight != obj->pIndexData->weight )
        fprintf( fp, "Weight       %d\n",	obj->weight		     );
    if ( obj->timer )
        fprintf( fp, "Timer        %d\n",	obj->timer		     );
    if ( obj->cost != obj->pIndexData->cost )
        fprintf( fp, "Cost         %d\n",	obj->cost		     );
    if ( obj->currtype != obj->pIndexData->currtype )
        fprintf( fp, "CurrType     %d\n",	obj->currtype		     );
    if (is_rare_obj(obj))
        fprintf( fp, "Rent         %d\n",	obj->rent		     );
    if ( obj->value[0] || obj->value[1] || obj->value[2]
         ||   obj->value[3] || obj->value[4] || obj->value[5] )
        fprintf( fp, "Values       %d %d %d %d %d %d\n",
                 obj->value[0], obj->value[1], obj->value[2],
                 obj->value[3], obj->value[4], obj->value[5]     );

    switch ( obj->item_type )
    {
    case ITEM_PILL: /* was down there with staff and wand, wrongly - Scryn */
    case ITEM_POTION:
    case ITEM_SCROLL:
        if ( IS_VALID_SN(obj->value[1]) )
            fprintf( fp, "Spell 1      '%s'\n",
                     skill_table[obj->value[1]]->name );

        if ( IS_VALID_SN(obj->value[2]) )
            fprintf( fp, "Spell 2      '%s'\n",
                     skill_table[obj->value[2]]->name );

        if ( IS_VALID_SN(obj->value[3]) )
            fprintf( fp, "Spell 3      '%s'\n",
                     skill_table[obj->value[3]]->name );

        break;

    case ITEM_STAFF:
    case ITEM_WAND:
        if ( IS_VALID_SN(obj->value[3]) )
            fprintf( fp, "Spell 3      '%s'\n",
                     skill_table[obj->value[3]]->name );

        break;
    case ITEM_SALVE:
        if ( IS_VALID_SN(obj->value[4]) )
            fprintf( fp, "Spell 4      '%s'\n",
                     skill_table[obj->value[4]]->name );

        if ( IS_VALID_SN(obj->value[5]) )
            fprintf( fp, "Spell 5      '%s'\n",
                     skill_table[obj->value[5]]->name );
        break;
    }

    for ( paf = obj->first_affect; paf; paf = paf->next )
    {
        /*
         * Save extra object affects				-Thoric
         */
        if ( paf->type < 0 || paf->type >= top_sn )
        {
            fprintf( fp, "Affect       %d %d %d %d %d\n",
                     paf->type,
                     paf->duration,
                     ((paf->location == APPLY_WEAPONSPELL
                       || paf->location == APPLY_WEARSPELL
                       || paf->location == APPLY_REMOVESPELL
                       || paf->location == APPLY_EAT_SPELL
                       || paf->location == APPLY_IMMUNESPELL
                       || paf->location == APPLY_STRIPSN)
                      && IS_VALID_SN(paf->modifier))
                     ? skill_table[paf->modifier]->slot : paf->modifier,
                     paf->location,
                     paf->bitvector
                   );
        }
        else
            fprintf( fp, "AffectData   '%s' %d %d %d %d\n",
                     skill_table[paf->type]->name,
                     paf->duration,
                     ((paf->location == APPLY_WEAPONSPELL
                       || paf->location == APPLY_WEARSPELL
                       || paf->location == APPLY_REMOVESPELL
                       || paf->location == APPLY_EAT_SPELL
                       || paf->location == APPLY_IMMUNESPELL
                       || paf->location == APPLY_STRIPSN)
                      && IS_VALID_SN(paf->modifier))
                     ? skill_table[paf->modifier]->slot : paf->modifier,
                     paf->location,
                     paf->bitvector
                   );
    }

    for ( ed = obj->first_extradesc; ed; ed = ed->next )
        fprintf( fp, "ExtraDescr   %s~ %s~\n",
                 ed->keyword, ed->description );

    if ( obj->nickname )
        fprintf( fp, "Nickname     %s~\n",
                 obj->nickname );

    if ( obj->vars )
        fwrite_variables( obj->vars, fp );

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

    if ( obj->first_content )
        fwrite_obj( ch, obj->last_content, fp, iNest + 1, OS_CARRY );

    return;
}



/*
 * Load a char and inventory into a new ch structure.
 */
bool load_char_obj( DESCRIPTOR_DATA *d, char *name, bool preload,
                    ROOM_INDEX_DATA *PetRoom, bool loadpets )
{
    char strsave[MAX_INPUT_LENGTH];
    CHAR_DATA *ch, *mob = NULL, *lastloaded = NULL;
    FILE *fp;
    bool found;
    struct stat fst;
    int i, x;
    extern FILE *fpArea;
    extern char strArea[MAX_INPUT_LENGTH];
    char buf[MAX_INPUT_LENGTH];


    ch = new_char();

    for ( x = 0; x < MAX_WEAR; x++ )
        for ( i = 0; i < MAX_LAYERS; i++ )
            save_equipment[x][i] = NULL;
    clear_char( ch );
    loading_char = ch;

    CREATE( ch->pcdata, PC_DATA, 1 );
    d->character			= ch;
    ch->desc				= d;
    ch->name				= STRALLOC( name );
    ch->act				= PLR_BLANK
        | PLR_COMBINE
        | PLR_PROMPT;
    ch->act2				= 0;
    ch->gold2				= 0;
    ch->bank2				= 0;
    ch->pcdata->interface		= INT_DEFAULT;
    ch->perm_str			= 13;
    ch->perm_int			= 13;
    ch->perm_wis			= 13;
    ch->perm_dex			= 13;
    ch->perm_con			= 13;
    ch->perm_cha			= 13;
    ch->perm_lck			= 13;
    ch->pcdata->condition[COND_THIRST]	= MAX_COND_VAL;
    ch->pcdata->condition[COND_FULL]	= MAX_COND_VAL;
    ch->pcdata->condition[COND_BLOODTHIRST] = 10;
    ch->pcdata->wizinvis		= 0;
    for (i = LOG_NORMAL; i < LOG_LAST; i++)
    {
        ch->pcdata->log_severity[i]    	= SEV_DEFAULT;
        ch->pcdata->afk_log_severity[i] = SEV_DEFAULT;
    }
    ch->mental_state			= -10;
    ch->mobinvis			= 0;
    for(i = 0; i < MAX_SKILL; i++)
        ch->pcdata->learned[i]		= 0;
    ch->pcdata->release_date		= 0;
    ch->pcdata->helled_by		= NULL;
    ch->pcdata->outcast_time            = 0;
    ch->pcdata->time_to_die             = 0;
    ch->pcdata->time_created            = 0;
    ch->pcdata->time_immortal           = 0;
    ch->pcdata->inc_times_played        = FALSE;
    ch->pcdata->times_played            = 0;
    ch->saving_poison_death 		= 0;
    ch->saving_wand			= 0;
    ch->saving_para_petri		= 0;
    ch->saving_breath			= 0;
    ch->saving_spell_staff		= 0;
    ch->comments                        = NULL;    /* comments */
    ch->pcdata->pagerlen		= 24;

#ifdef IMC
    imc_initchar( ch );
#endif
#ifdef I3
    i3init_char( ch );
#endif

    found = FALSE;
    sprintf( strsave, "%s%c/%s", PLAYER_DIR, tolower(name[0]),
             capitalize( name ) );
    if ( stat( strsave, &fst ) != -1 )
    {
        if ( fst.st_size == 0 )
        {
            sprintf( strsave, "%s%c/%s", BACKUP_DIR, tolower(name[0]),
                     capitalize( name ) );
            send_to_char( "Restoring your backup player file...", ch );
        }
        else
        {
            sprintf( buf, "%s player data for: %s (%dK)",
                     preload ? "Preloading" : "Loading", ch->name,
                     (int) fst.st_size/1024 );
            log_string_plus( buf, LOG_COMM, LEVEL_GREATER, SEV_INFO );
        }
    }
    /* else no player file */

    if ( ( fp = fopen( strsave, "r" ) ) != NULL )
    {
        int iNest;

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

        found = TRUE;
        /* Cheat so that bug will show line #'s -- Altrag */
        fpArea = fp;
        strcpy(strArea, strsave);
        for ( ; ; )
        {
            char letter;
            char *word;

            letter = fread_letter( fp );
            if ( letter == '*' )
            {
                fread_to_eol( fp );
                continue;
            }

            if ( letter != '#' )
            {
                bug( "Load_char_obj: # not found, got %c instead in %s.",
                     letter, name );
                break;
            }

            word = fread_word( fp );
            if ( !str_cmp( word, "PLAYER" ) )
            {
                fread_char ( ch, fp, preload );
                lastloaded = ch;
                if ( preload )
                    break;
            }
            else if ( !str_cmp( word, "OBJECT" ) )	/* Objects	*/
            {
                if (!lastloaded) lastloaded = ch;
                fread_obj  ( lastloaded, fp, OS_CARRY );
            }
            else if ( !str_cmp( word, "VARIABLE" ) )
            {
                fread_variable(&ch->vars, fp);
            }
            else if ( !str_cmp( word, "COMMENT") )
            {
                fread_comment(ch, fp );		/* Comments	*/
            }
#ifdef VTRACK
            else if ( !str_cmp( word, "VTRACK" ) )
            {
                fread_vtracks(ch, fp);
            }
#endif
            else if ( !str_cmp( word, "END"    ) )	/* Done		*/
            {
                break;
            }
            else if ( !str_cmp( word, "MOB" ) )
            {

                if (!loadpets) {
                    bug( "Load_char_obj: !loadpets, skipping." );
                    break;
                }
                mob = fread_char(mob, fp, preload);
                if (mob) {
                    lastloaded = mob;
                    if (ch) add_follower(mob, ch);
                    if (PetRoom)
                    { char_to_room(mob, PetRoom); }
                    else if (ch)
                    { char_to_room(mob, get_room_index(ch->pcdata->home)); }
                    else extract_char(mob, TRUE);
                }
            }
            else
            {
                bug( "Load_char_obj: bad section %s for %s.",
                     word?word:"(null)", name );
                break;
            }
        }
        FCLOSE( fp );
        fpArea = NULL;
        strcpy(strArea, "$");
    }


    if ( !found )
    {
        if (d)
        {
#ifdef MXP
            if (d->mxp_detected)
                SET_PLR2_FLAG(ch, PLR2_MXP);
#endif
#ifdef MSP
            if (d->msp_detected)
                SET_PLR2_FLAG(ch, PLR2_MSP);
#endif
        }
        ch->short_descr			= STRALLOC( "" );
        ch->long_descr			= STRALLOC( "" );
        ch->description			= STRALLOC( "" );
        ch->intro_descr			= str_dup( "" );
        ch->editor			= NULL;
        ch->pcdata->clan_name		= STRALLOC( "" );
        ch->pcdata->clan		= NULL;
        ch->pcdata->council_name 	= STRALLOC( "" );
        ch->pcdata->council 		= NULL;
        ch->pcdata->deity_name		= STRALLOC( "" );
        ch->pcdata->deity		= NULL;
        ch->pcdata->first_alias		= NULL;
        ch->pcdata->last_alias		= NULL;
        ch->pcdata->pwd			= str_dup( "" );
        ch->pcdata->bamfin		= STRALLOC( "" );
        ch->pcdata->bamfout		= STRALLOC( "" );
        ch->pcdata->rank		= str_dup( "Mortal" );
        ch->pcdata->bestowments		= str_dup( "" );
        ch->pcdata->title		= STRALLOC( "" );
        ch->pcdata->homepage		= str_dup( "" );
        ch->pcdata->bio 		= STRALLOC( "" );
        ch->pcdata->authed_by		= STRALLOC( "" );
        ch->pcdata->prompt		= STRALLOC( "" );
        ch->pcdata->r_range_lo		= 0;
        ch->pcdata->r_range_hi		= 0;
        ch->pcdata->m_range_lo		= 0;
        ch->pcdata->m_range_hi		= 0;
        ch->pcdata->o_range_lo		= 0;
        ch->pcdata->o_range_hi		= 0;
        ch->pcdata->wizinvis		= 0;
        for (i = LOG_NORMAL; i < LOG_LAST; i++)
        {
            ch->pcdata->log_severity[i] = SEV_DEFAULT;
            ch->pcdata->afk_log_severity[i] = SEV_DEFAULT;
        }
        ch->pcdata->game_board		= NULL;
        ch->pcdata->time_created        = current_time;
        ch->pcdata->time_to_die         = 0;
        ch->pcdata->time_immortal       = 0;
        ch->pcdata->inc_times_played    = FALSE;
        ch->pcdata->times_played        = 0;
    }
    else
    {
        if ( !ch->pcdata->clan_name )
        {
            ch->pcdata->clan_name	= STRALLOC( "" );
            ch->pcdata->clan	= NULL;
        }
        if ( !ch->pcdata->council_name )
        {
            ch->pcdata->council_name = STRALLOC( "" );
            ch->pcdata->council 	= NULL;
        }
        if ( !ch->pcdata->deity_name )
        {
            ch->pcdata->deity_name = STRALLOC( "" );
            ch->pcdata->deity	 = NULL;
        }
        if ( !ch->pcdata->bio )
            ch->pcdata->bio	 = STRALLOC( "" );

        if ( !ch->pcdata->authed_by )
            ch->pcdata->authed_by	 = STRALLOC( "" );

        if ( !IS_NPC( ch ) && IS_IMMORTAL( ch ) )
        {
            assign_area( ch );
        }
        if ( file_ver > 1 )
        {
            for ( i = 0; i < MAX_WEAR; i++ )
                for ( x = 0; x < MAX_LAYERS; x++ )
                    if ( save_equipment[i][x] )
                    {
                        equip_char( ch, save_equipment[i][x], i );
                        save_equipment[i][x] = NULL;
                    }
                    else
                        break;
        }
        if (ch->trust > 0)
            log_printf_plus(LOG_DEBUG, GetMaxLevel(ch), SEV_SPAM, "load_char_obj: trust: %d", ch->trust);
        ClassSpecificStuff(ch);

        update_speaks(ch);
    }

#ifndef PLR2_AUTOGAIN
    if (IS_PLR2_FLAG(ch, BV05))
        REMOVE_PLR2_FLAG(ch, BV05);
#endif
    if (IS_PLR_FLAG(ch, PLR_AFK))
	REMOVE_PLR_FLAG(ch, PLR_AFK);

    loading_char = NULL;

    update_char_rare_obj(ch);

    return found;
}



/*
 * Read in a char.
 */

#if defined(KEY)
#undef KEY
#endif

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

CHAR_DATA *fread_char( CHAR_DATA *ch, FILE *fp, bool preload )
{
    char buf[MAX_STRING_LENGTH];
    char *line = NULL;
    const char *word = NULL;
    int x1, x2, x3, x4, x5, x6, x7, i;
    sh_int killcnt;
    bool fMatch = FALSE;

    file_ver = 0;
    killcnt = 0;
    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( "Act",		ch->act,		fread_number( fp ) );
            KEY( "Act2",	ch->act2,		fread_number( fp ) );
            KEY( "AffectedBy",	ch->affected_by,	fread_number( fp ) );
            KEY( "AffectedBy2",	ch->affected_by2,	fread_number( fp ) );
            KEY( "Alignment",	ch->alignment,		fread_number( fp ) );
            KEY( "AMP",         ch->antimagicp,		fread_number( fp ) );
            KEY( "Armor",	ch->armor,		fread_number( fp ) );
            KEY( "Absorb",	ch->absorb,		fread_number( fp ) );
            KEY( "AuthedBy",	ch->pcdata->authed_by,	fread_string( fp ) );

            if ( !str_cmp( word, "Affect" ) || !str_cmp( word, "AffectData" ) )
            {
                AFFECT_DATA *paf;

                if ( preload )
                {
                    fMatch = TRUE;
                    fread_to_eol( fp );
                    break;
                }
                CREATE( paf, AFFECT_DATA, 1 );
                if ( !str_cmp( word, "Affect" ) )
                {
                    paf->type	= fread_number( fp );
                }
                else
                {
                    int sn;
                    char *sname = fread_word(fp);

                    if ( (sn=skill_lookup(sname)) < 0 )
                    {
                        if ( (sn=herb_lookup(sname)) < 0 )
                            bug( "Fread_char: unknown skill." );
                        else
                            sn += TYPE_HERB;
                    }
                    paf->type = sn;
                }

                paf->duration	= fread_number( fp );
                paf->modifier	= fread_number( fp );
                paf->location	= fread_number( fp );
                paf->bitvector	= fread_number( fp );
                LINK(paf, ch->first_affect, ch->last_affect, next, prev );
                char_affects++;
                fMatch = TRUE;
                break;
            }

            if ( !str_cmp( word, "Alias" ) )
            {
                ALIAS_DATA *pal;

                if ( preload )
                {
                    fMatch = TRUE;
                    fread_to_eol( fp );
                    break;
                }
                CREATE( pal, ALIAS_DATA, 1 );

                pal->name	= fread_string_nohash( fp );
                pal->cmd	= fread_string_nohash( fp );

                line = fread_line( fp );
                x1=0;
                sscanf( line, "%d", &x1 );
                pal->flags = x1;

                LINK(pal, ch->pcdata->first_alias, ch->pcdata->last_alias, next, prev );
                fMatch = TRUE;
                break;
            }

            if ( !str_cmp( word, "AttrMod"  ) )
            {
                line = fread_line( fp );
                x1=x2=x3=x4=x5=x6=x7=13;
                sscanf( line, "%d %d %d %d %d %d %d",
                        &x1, &x2, &x3, &x4, &x5, &x6, &x7 );
                ch->mod_str = x1;
                ch->mod_int = x2;
                ch->mod_wis = x3;
                ch->mod_dex = x4;
                ch->mod_con = x5;
                ch->mod_cha = x6;
                ch->mod_lck = x7;
                if (!x7)
                    ch->mod_lck = 0;
                fMatch = TRUE;
                break;
            }

            if ( !str_cmp( word, "AttrPerm" ) )
            {
                line = fread_line( fp );
                x1=x2=x3=x4=x5=x6=x7=0;
                sscanf( line, "%d %d %d %d %d %d %d",
                        &x1, &x2, &x3, &x4, &x5, &x6, &x7 );
                ch->perm_str = x1;
                ch->perm_int = x2;
                ch->perm_wis = x3;
                ch->perm_dex = x4;
                ch->perm_con = x5;
                ch->perm_cha = x6;
                ch->perm_lck = x7;
                if (!x7 || x7 == 0)
                    ch->perm_lck = 13;
                fMatch = TRUE;
                break;
            }
            if ( !str_cmp( word, "AFKSeverity" ) )
            {
                char *logname;

                x1 = fread_number(fp);
                logname = fread_string_nohash(fp);

                for (i = LOG_NORMAL; i < LOG_LAST; i++)
                    if (!str_cmp(logname, sysdata.logdefs[i].name))
                    {
                        ch->pcdata->afk_log_severity[i] = x1;
                        break;
                    }

                DISPOSE(logname);

                fMatch = TRUE;
                break;
            }
            break;

        case 'B':
            KEY( "Bamfin",	ch->pcdata->bamfin,	fread_string( fp ) );
            KEY( "Bamfout",	ch->pcdata->bamfout,	fread_string( fp ) );
            KEY( "Bank",	ch->bank2,		fread_number( fp ) );
            KEY( "Bestowments", ch->pcdata->bestowments,fread_string_nohash( fp ) );
            KEY( "Bio",		ch->pcdata->bio,	fread_string( fp ) );
            KEY( "Bodyparts",	ch->xflags,		fread_number( fp ) );
            if ( !str_cmp( word, "BankMoney" ) )
            {
                x1=x2=0;
                while ((x1=fread_number(fp))>=0)
                    if (x2<MAX_CURR_TYPE)
                        GET_BALANCE(ch,x2++) = x1;
                fMatch = TRUE;
                break;
            }
            break;

        case 'C':
            if ( !str_cmp( word, "Clan" ) )
            {
                ch->pcdata->clan_name = fread_string( fp );

                if ( !preload
                     &&   ch->pcdata->clan_name[0] != '\0'
                     && ( ch->pcdata->clan = get_clan( ch->pcdata->clan_name )) == NULL )
                {
                    sprintf( buf, "Warning: the organization %s no longer exists, and therefore you no longer\n\rbelong to that organization.\n\r",
                             ch->pcdata->clan_name );
                    send_to_char( buf, ch );
                    STRFREE( ch->pcdata->clan_name );
                    ch->pcdata->clan_name = STRALLOC( "" );
                }
                fMatch = TRUE;
                break;
            }

            if ( !str_cmp( word, "Color" ) )
            {
                x1=x2=0;
                line = fread_line( fp );
                sscanf( line, "%d %d",
                        &x1, &x2 );
                if ( x1 < 0 ||  x1 > MAX_COLOR_TYPE)
                {
                    bug( "Fread_char: Unknown color index." );
                    break;
                }
                ch->colors[x1] = x2;
                fMatch = TRUE;
                break;
            }

            if ( !str_cmp( word, "Condition" ) )
            {
                line = fread_line( fp );
                sscanf( line, "%d %d %d %d",
                        &x1, &x2, &x3, &x4 );
                ch->pcdata->condition[0] = x1;
                ch->pcdata->condition[1] = x2;
                ch->pcdata->condition[2] = x3;
                ch->pcdata->condition[3] = x4;
                fMatch = TRUE;
                break;
            }

            if ( !str_cmp( word, "Council" ) )
            {
                ch->pcdata->council_name = fread_string( fp );
                if ( !preload
                     &&   ch->pcdata->council_name[0] != '\0'
                     && ( ch->pcdata->council = get_council( ch->pcdata->council_name )) == NULL )
                {
                    sprintf( buf, "Warning: the council %s no longer exists, and herefore you no longer\n\rbelong to a council.\n\r",
                             ch->pcdata->council_name );
                    send_to_char( buf, ch );
                    STRFREE( ch->pcdata->council_name );
                    ch->pcdata->council_name = STRALLOC( "" );
                }
                fMatch = TRUE;
                break;
            }
            break;

        case 'D':
            KEY( "Damroll",	ch->damroll,		fread_number( fp ) );
            KEY( "Deaf",	ch->deaf,		fread_number( fp ) );
            if ( !str_cmp( word, "Deity" ) )
            {
                ch->pcdata->deity_name = fread_string( fp );

                if ( !preload
                     &&   ch->pcdata->deity_name[0] != '\0'
                     && ( ch->pcdata->deity = get_deity( ch->pcdata->deity_name )) == NULL )
                {
                    sprintf( buf, "Warning: the deity %s no longer exists.\n\r",
                             ch->pcdata->deity_name );
                    send_to_char( buf, ch );
                    STRFREE( ch->pcdata->deity_name );
                    ch->pcdata->deity_name = STRALLOC( "" );
                    ch->pcdata->favor = 0;
                }
                fMatch = TRUE;
                break;
            }
            KEY( "Description",	ch->description,	fread_string( fp ) );
            break;

            /* 'E' was moved to after 'S' */
        case 'F':
            KEY( "Favor",	ch->pcdata->favor,	fread_number( fp ) );
            KEY( "Flags",	ch->pcdata->flags,	fread_number( fp ) );
            break;

        case 'G':
            KEY( "Glory",       ch->pcdata->quest_curr, fread_number( fp ) );
            KEY( "Gold",	ch->gold2,		fread_number( fp ) );
            /* temporary measure */
            if ( !str_cmp( word, "Guild" ) )
            {
                ch->pcdata->clan_name = fread_string( fp );

                if ( !preload
                     &&   ch->pcdata->clan_name[0] != '\0'
                     && ( ch->pcdata->clan = get_clan( ch->pcdata->clan_name )) == NULL )
                {
                    sprintf( buf, "Warning: the organization %s no longer exists, and therefore you no longer\n\rbelong to that organization.\n\r",
                             ch->pcdata->clan_name );
                    send_to_char( buf, ch );
                    STRFREE( ch->pcdata->clan_name );
                    ch->pcdata->clan_name = STRALLOC( "" );
                }
                fMatch = TRUE;
                break;
            }
            break;

        case 'H':
            if ( !str_cmp(word, "Helled") )
            {
                ch->pcdata->release_date = fread_number(fp);
                ch->pcdata->helled_by = fread_string(fp);
                if ( ch->pcdata->release_date < current_time )
                {
                    STRFREE(ch->pcdata->helled_by);
                    ch->pcdata->helled_by = NULL;
                    ch->pcdata->release_date = 0;
                }
                fMatch = TRUE;
                break;
            }

            KEY( "Hitroll",	ch->hitroll,		fread_number( fp ) );
            KEY( "Homepage",	ch->pcdata->homepage,	fread_string_nohash( fp ) );

            if ( !str_cmp( word, "HpManaMove" ) )
            {
                GET_HIT(ch)	= fread_number( fp );
                ch->max_hit	= fread_number( fp );
                GET_MANA(ch)	= fread_number( fp );
                ch->max_mana	= fread_number( fp );
                GET_MOVE(ch)	= fread_number( fp );
                ch->max_move	= fread_number( fp );
                fMatch = TRUE;
                break;
            }
            break;

        case 'I':
            KEY( "IllegalPK",	ch->pcdata->illegal_pk,	fread_number( fp ) );
            KEY( "Immune",	ch->immune,		fread_number( fp ) );
            KEY( "Interface",   ch->pcdata->interface,  fread_number( fp ) );
            KEY( "IntroDescr",	ch->intro_descr,	fread_string_nohash( fp ) );
#ifdef IMC
	    if ((fMatch = imc_loadchar(ch, fp, word)))
                break;
#endif
#ifdef I3
	    if ((fMatch = i3load_char(ch, fp, word)))
                break;
#endif
            break;

        case 'K':
            if ( !str_cmp( word, "Killed" ) )
            {
                fMatch = TRUE;
                if ( killcnt >= MAX_KILLTRACK )
                    bug( "fread_char: killcnt (%d) >= MAX_KILLTRACK", killcnt );
                else
                {
                    ch->pcdata->killed[killcnt].vnum    = fread_number( fp );
                    ch->pcdata->killed[killcnt++].count = fread_number( fp );
                }
            }
            break;

        case 'L':
            if ( !str_cmp( word, "Level" ) )
            {
                i = fread_number( fp );
                if (i < 0 || i >= MAX_CLASS)
                {
                    log_printf_plus(LOG_BUG, GetMaxLevel(ch), SEV_NOTICE,
                                    "fread_char: Unknown class %d", i );
                    break;
                }
                ch->levels[i]     = fread_number( fp );
                ch->classes[i]    = fread_number( fp );
                fMatch = TRUE;
                break;
            }

            if ( !str_cmp( word, "LogSeverity" ) )
            {
                char *logname;

                x1 = fread_number(fp);
                logname = fread_string_nohash(fp);

                for (i = LOG_NORMAL; i < LOG_LAST; i++)
                    if (!str_cmp(logname, sysdata.logdefs[i].name))
                    {
                        ch->pcdata->log_severity[i] = x1;
                        break;
                    }

                DISPOSE(logname);

                fMatch = TRUE;
                break;
            }

            KEY( "LongDescr",	ch->long_descr,		fread_string( fp ) );

            if ( !str_cmp( word, "Languages" ) )
            {
                ch->speaks = fread_number( fp );
                ch->speaking = fread_number( fp );
                fMatch = TRUE;
            }

            if ( !str_cmp( word, "Lore" ) )
            {
                int sn;
                int value;

                if ( preload )
                    word = "End";
                else
                {
                    value = fread_number( fp );

                    sn = bsearch_skill_exact( fread_word( fp ), gsn_first_lore, GSN_LAST_LORE );
                    if ( sn < 0 )
                        bug( "Fread_char: unknown lore." );
                    else
                    {
                        if ( ShouldSaveSkill(ch, sn) )
                            ch->pcdata->learned[sn] = value;
                        else
                        {
                            ch->pcdata->learned[sn] = 0;
                            ch->practice++;
                        }
                    }
                    fMatch = TRUE;
                }
                break;
            }
            break;

        case 'M':
            KEY( "MDeaths",	ch->pcdata->mdeaths,	fread_number( fp ) );
            KEY( "Mentalstate", ch->mental_state,	fread_number( fp ) );
            KEY( "MGlory",      ch->pcdata->quest_accum,fread_number( fp ) );
            KEY( "Minsnoop",	ch->pcdata->min_snoop,	fread_number( fp ) );
            KEY( "MKills",	ch->pcdata->mkills,	fread_number( fp ) );
            KEY( "Mobinvis",	ch->mobinvis,		fread_number( fp ) );
            if ( !str_cmp( word, "MobRange" ) )
            {
                if (!ch->pcdata->m_range_lo)
                    ch->pcdata->m_range_lo = fread_number( fp );
                if (!ch->pcdata->m_range_hi)
                    ch->pcdata->m_range_hi = fread_number( fp );
                fMatch = TRUE;
            }
            if ( !str_cmp( word, "Money" ) )
            {
                x1=x2=0;
                while ((x1=fread_number(fp))>=0)
                    if (x2<MAX_CURR_TYPE)
                        GET_MONEY(ch,x2++) = x1;
                if (ch->gold2)
                {
                    GET_MONEY(ch,CURR_GOLD) = ch->gold2;
                    ch->gold2    = 0;
                }
                GET_MONEY(ch,CURR_NONE) = 0;
                fMatch = TRUE;
                break;
            }
            if ( !str_cmp( word, "Memorized" ) )
            {
                int sn;
                int value;

                if ( preload )
                    word = "End";
                else
                {
                    value = fread_number( fp );
                    word = fread_word( fp );
                    if ( file_ver < 3 )
                        sn = skill_lookup( word );
                    else
                        sn = bsearch_skill_exact( word, gsn_first_spell, GSN_LAST_SPELL );
                    if ( sn < 0 )
                    {
                        bug( "Fread_char: unknown spell (memorized): %s", word );
                    }
                    else
                        ch->pcdata->memorized[sn] = UMAX(0, value);
                    fMatch = TRUE;
                    break;
                }
            }
            break;

        case 'N':
            KEY("Numattacks", 	ch->numattacks,		fread_number( fp ) );
            if ( !str_cmp( word, "Name" ) )
            {
                /*
                 * Name already set externally.
                 */
                fread_to_eol( fp );
                fMatch = TRUE;
                break;
            }
            break;

        case 'O':
            /*
             KEY("OldClass", ch->class, fread_number(fp));
             KEY( "OldLevel",	ch->level,		fread_number( fp ) );
             */
            KEY( "Outcast_time", ch->pcdata->outcast_time, fread_number( fp ) );
            if ( !str_cmp( word, "ObjRange" ) )
            {
                if (!ch->pcdata->o_range_lo)
                    ch->pcdata->o_range_lo = fread_number( fp );
                if (!ch->pcdata->o_range_hi)
                    ch->pcdata->o_range_hi = fread_number( fp );
                fMatch = TRUE;
            }
            break;

        case 'P':
            KEY( "Pagerlen",	ch->pcdata->pagerlen,	fread_number( fp ) );
            KEY( "Password",	ch->pcdata->pwd,	fread_string_nohash( fp ) );
            KEY( "PDeaths",	ch->pcdata->pdeaths,	fread_number( fp ) );
            KEY( "PKills",	ch->pcdata->pkills,	fread_number( fp ) );
            KEY( "Played",	ch->played,		fread_number( fp ) );
            KEY( "Position",	ch->position,		fread_number( fp ) );
            KEY( "Practice",	ch->practice,		fread_number( fp ) );
            KEY( "Prompt",	ch->pcdata->prompt,	fread_string( fp ) );
            if (!str_cmp ( word, "Password2" ) )
            {
                /* plain text password */
                if ( ch->pcdata->pwd )
                    DISPOSE( ch->pcdata->pwd );
                ch->pcdata->pwd =
                    str_dup( crypt(fread_string(fp), ch->name) );
                fMatch = TRUE;
                break;
            }

            if (!str_cmp ( word, "PTimer" ) )
            {
                add_timer( ch , TIMER_PKILLED, fread_number(fp), NULL, 0 );
                fMatch = TRUE;
                break;
            }
            if ( !str_cmp( word, "PsiSpell" ) )
            {
                int sn;
                int value;

                if ( preload )
                    word = "End";
                else
                {
                    value = fread_number( fp );
                    word = fread_word( fp );

                    sn = bsearch_skill_exact( word, gsn_first_psispell, GSN_LAST_PSISPELL );
                    if ( sn < 0 )
                    {
                        bug( "Fread_char: unknown psispell: %s", word );
                    }
		    else
		    {
                        if ( ShouldSaveSkill(ch, sn) )
                            ch->pcdata->learned[sn] = UMAX(0, value);
                        else
                        {
                            bug( "Fread_char: can't use: %s", word );
                            ch->pcdata->learned[sn] = 0;
                            ch->practice++;
                        }
                    }
                    fMatch = TRUE;
                    break;
                }
            }
            if ( str_cmp( word, "End" ) )
                break;
            break;

        case 'R':
            KEY( "Race",        ch->race,		fread_number( fp ) );
            KEY( "Rank",        ch->pcdata->rank,	fread_string_nohash( fp ) );
            KEY( "Resistant",	ch->resistant,		fread_number( fp ) );
            KEY( "Restore_time",ch->pcdata->restore_time, fread_number( fp ) );

            if ( !str_cmp( word, "Regens" ) )
            {
                ch->hit_regen	= fread_number( fp );
                ch->mana_regen	= fread_number( fp );
                ch->move_regen	= fread_number( fp );
                fMatch = TRUE;
                break;
            }

            if ( !str_cmp( word, "Room" ) )
	    {
		int rvnum = fread_number( fp );

                load_area_demand( rvnum );

                ch->in_room = get_room_index( rvnum );
                if ( !ch->in_room || ch->in_room->vnum == ROOM_VNUM_LIMBO )
                {
                    if ( IsBadSide(ch) )
		    {
                        load_area_demand( ROOM_START_EVIL );
                        ch->in_room = get_room_index( ROOM_START_EVIL );
                        ch->pcdata->home = ROOM_START_EVIL;
                    }
                    else
                    {
                        load_area_demand( ROOM_START_GOOD );
                        ch->in_room = get_room_index( ROOM_START_GOOD );
                        ch->pcdata->home = ROOM_START_GOOD;
                    }
                }
		if ( !ch->in_room )
		{
		    load_area_demand( ROOM_VNUM_LIMBO );
		    ch->in_room = get_room_index( ROOM_VNUM_LIMBO );
		}
                ch->pcdata->home = ch->in_room->vnum;
                fMatch = TRUE;
                break;
            }
            if ( !str_cmp( word, "RoomRange" ) )
            {
                if (!ch->pcdata->r_range_lo)
                    ch->pcdata->r_range_lo = fread_number( fp );
                if (!ch->pcdata->r_range_hi)
                    ch->pcdata->r_range_hi = fread_number( fp );
                fMatch = TRUE;
            }
            break;

        case 'S':
            KEY( "Sex",		ch->sex,		fread_number( fp ) );
            KEY( "ShortDescr",	ch->short_descr,	fread_string( fp ) );
            KEY( "Spellfail",   ch->spellfail,          fread_number( fp ) );
            KEY( "Susceptible",	ch->susceptible,	fread_number( fp ) );
            if ( !str_cmp( word, "SavingThrow" ) )
            {
                ch->saving_wand 	= fread_number( fp );
                ch->saving_poison_death = ch->saving_wand;
                ch->saving_para_petri 	= ch->saving_wand;
                ch->saving_breath 	= ch->saving_wand;
                ch->saving_spell_staff 	= ch->saving_wand;
                fMatch = TRUE;
                break;
            }

            if ( !str_cmp( word, "SavingThrows" ) )
            {
                ch->saving_poison_death = fread_number( fp );
                ch->saving_wand 	= fread_number( fp );
                ch->saving_para_petri 	= fread_number( fp );
                ch->saving_breath 	= fread_number( fp );
                ch->saving_spell_staff 	= fread_number( fp );
                fMatch = TRUE;
                break;
            }

            if ( !str_cmp( word, "Site" ) )
            {
                if ( !preload )
                {
                    sprintf( buf, "Last connected from: %s", fread_word( fp ) );
                    log_string_plus( buf, LOG_COMM, LEVEL_GREATER, SEV_INFO+1 );
                    send_to_char( buf, ch );
                    send_to_char( "\n\r", ch );
                }
                else
                    fread_to_eol( fp );
                fMatch = TRUE;
                if ( preload )
                    word = "End";
                else
                    break;
            }

            if ( !str_cmp( word, "Skill" ) )
            {
                int sn;
                int value;

                if ( preload )
                    word = "End";
                else
                {
                    value = fread_number( fp );
                    word = fread_word( fp );
                    if ( file_ver < 3 )
                        sn = skill_lookup( word );
                    else
                        sn = bsearch_skill_exact( word, gsn_first_skill, GSN_LAST_SKILL );
                    if ( sn < 0 )
                    {
                        bug( "Fread_char: unknown skill: %s", word );
                    }
                    else
                    {
                        if ( ShouldSaveSkill(ch, sn) )
                            ch->pcdata->learned[sn] = UMAX(0, value);
                        else
                        {
                            ch->pcdata->learned[sn] = 0;
                            ch->practice++;
                        }
                    }
                    fMatch = TRUE;
                    break;
                }
            }

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

                if ( preload )
                    word = "End";
                else
                {
                    value = fread_number( fp );
                    word = fread_word( fp );

                    sn = bsearch_skill_exact( word, gsn_first_spell, GSN_LAST_SPELL );
                    if ( sn < 0 )
                    {
                        bug( "Fread_char: unknown spell: %s", word );
                        sn = bsearch_skill_exact( word, gsn_first_skill, GSN_LAST_SKILL );
                        if ( sn < 0 )
                        {
                            bug( "Fread_char: unknown skill/spell: %s", word );
                            fread_to_eol( fp );
                            fMatch = TRUE;
                            break;
                        }
                    }
                    /* else */
                    {
                        if ( ShouldSaveSkill(ch, sn) )
                            ch->pcdata->learned[sn] = UMAX(0, value);
                        else
                        {
                            bug( "Fread_char: can't use: %s", word );
                            ch->pcdata->learned[sn] = 0;
                            ch->practice++;
                        }
                    }
                    fMatch = TRUE;
                    break;
                }
            }
            if ( str_cmp( word, "End" ) )
                break;

        case 'E':
            if ( !str_cmp( word, "End" ) )
            {
               if (!IS_NPC(ch)) {
                if (!ch->short_descr)
                    ch->short_descr	= STRALLOC( "" );
                if (!ch->long_descr)
                    ch->long_descr	= STRALLOC( "" );
                if (!ch->description)
                    ch->description	= STRALLOC( "" );
                if (!ch->intro_descr)
                    ch->intro_descr	= str_dup( "" );
                if (!ch->pcdata->pwd)
                    ch->pcdata->pwd	= str_dup( "" );
                if (!ch->pcdata->bamfin)
                    ch->pcdata->bamfin	= STRALLOC( "" );
                if (!ch->pcdata->bamfout)
                    ch->pcdata->bamfout	= STRALLOC( "" );
                if (!ch->pcdata->bio)
                    ch->pcdata->bio	= STRALLOC( "" );
                if (!ch->pcdata->rank)
                    ch->pcdata->rank	= str_dup( "" );
                if (!ch->pcdata->bestowments)
                    ch->pcdata->bestowments = str_dup( "" );
                if (!ch->pcdata->title)
                    ch->pcdata->title	= STRALLOC( "" );
                if (!ch->pcdata->homepage)
                    ch->pcdata->homepage	= str_dup( "" );
                if (!ch->pcdata->authed_by)
                    ch->pcdata->authed_by = STRALLOC( "" );
                if (!ch->pcdata->prompt )
                    ch->pcdata->prompt	= STRALLOC( "" );
                ch->editor		= NULL;
                killcnt = URANGE( 2, ((GetMaxLevel(ch)+3) * MAX_KILLTRACK)/LEVEL_AVATAR, MAX_KILLTRACK );
                if ( killcnt < MAX_KILLTRACK )
                    ch->pcdata->killed[killcnt].vnum = 0;

                /* no good for newbies at all */
                if ( !IS_IMMORTAL( ch ) && !ch->speaking )
                    ch->speaking = LANG_COMMON;
                /*	ch->speaking = race_table[ch->race].language; */
                if ( IS_IMMORTAL( ch ) )
                {
                    ch->speaks = ~0;
                    if ( ch->speaking == 0 )
                        ch->speaking = ~0;
                }
                if ( !ch->pcdata->prompt )
                    ch->pcdata->prompt = STRALLOC("");
                }
                return ch;
            }
            KEY( "Exp",		ch->exp,		fread_number( fp ) );
            break;

        case 'T':
            KEY( "Time_Created", ch->pcdata->time_created,  fread_number( fp ) );
            KEY( "Time_To_Die",  ch->pcdata->time_to_die,   fread_number( fp ) );
            KEY( "Time_Imm",     ch->pcdata->time_immortal, fread_number( fp ) );
            KEY( "Times_played", ch->pcdata->times_played,  fread_number( fp ) );
            if ( !str_cmp( word, "Tongue" ) )
            {
                int sn;
                int value;

                if ( preload )
                    word = "End";
                else
                {
                    value = fread_number( fp );

                    sn = bsearch_skill_exact( fread_word( fp ), gsn_first_tongue, GSN_LAST_TONGUE );
                    if ( sn < 0 )
                        bug( "Fread_char: unknown tongue." );
                    else
                    {
                        if ( ShouldSaveSkill(ch, sn) )
                            ch->pcdata->learned[sn] = UMAX(0, value);
                        else
                        {
                            ch->pcdata->learned[sn] = 0;
                            ch->practice++;
                        }
                    }
                    fMatch = TRUE;
                }
                break;
            }
            KEY( "Trust", ch->trust, fread_number( fp ) );
            /* Let no character be trusted higher than one below maxlevel -- Narn */
/*            ch->trust = UMIN( ch->trust, MAX_LEVEL ); */

            if ( !str_cmp( word, "Title" ) )
            {
                ch->pcdata->title = fread_string( fp );
                if ( isalpha(ch->pcdata->title[0])
                     ||   isdigit(ch->pcdata->title[0]) )
                {
                    sprintf( buf, " %s", ch->pcdata->title );
                    if ( ch->pcdata->title )
                        STRFREE( ch->pcdata->title );
                    ch->pcdata->title = STRALLOC( buf );
                }
                fMatch = TRUE;
                break;
            }

            break;

        case 'U':
            if ( !str_cmp( word, "User" ) )
            {
                if ( !preload )
                {
                    sprintf( buf, "Last connected as: %s", fread_word( fp ) );
                    log_string_plus( buf, LOG_COMM, LEVEL_GREATER, SEV_INFO+1 );
                }
                else
                    fread_to_eol( fp );
                fMatch = TRUE;
                if ( preload )
                    word = "End";
                else
                    break;
            }
            break;

        case 'V':
            if ( !str_cmp( word, "Vnum" ) )
            {
                if (ch) {
                    ch->pIndexData = get_mob_index( fread_number( fp ) );
                } else {
                    if ((ch = create_mobile( fread_number( fp ) )) == NULL)
                    {
                        bug( "Fread_char: bad vnum or error creating pet");
                        break;
                    }
                }
                fMatch = TRUE;
                break;
            }
            KEY( "Version",	file_ver,		fread_number( fp ) );
            break;

        case 'W':
            if ( !str_cmp( word, "Weapon" ) )
            {
                int sn;
                int value;

                if ( preload )
                    word = "End";
                else
                {
                    value = fread_number( fp );

                    sn = bsearch_skill_exact( fread_word( fp ), gsn_first_weapon, GSN_LAST_WEAPON );
                    if ( sn < 0 )
                        bug( "Fread_char: unknown weapon." );
                    else
                    {
                        if ( ShouldSaveSkill(ch, sn) )
                            ch->pcdata->learned[sn] = UMAX(0, value);
                        else
                        {
                            ch->pcdata->learned[sn] = 0;
                            ch->practice++;
                        }
                    }
                    fMatch = TRUE;
                }
                break;
            }
            KEY( "Wimpy",	ch->wimpy,		fread_number( fp ) );
            KEY( "WizInvis",	ch->pcdata->wizinvis,	fread_number( fp ) );
            break;
        }

        if ( !fMatch )
        {
            bug( "Fread_char: no match: %s", word );
            fread_to_eol( fp );
        }
    }
}


void fread_obj( CHAR_DATA *ch, FILE *fp, sh_int os_type )
{
    OBJ_DATA *obj;
    const char *word;
    int iNest;
    bool fMatch;
    bool fNest;
    bool fVnum;
    ROOM_INDEX_DATA *room = NULL;

    obj = new_obj();

    obj->count		= 1;
    obj->wear_loc	= -1;
    obj->weight		= 1;
    obj->timer          = 0;

    fNest		= TRUE;		/* Requiring a Nest 0 is a waste */
    fVnum		= TRUE;
    iNest		= 0;

    for ( ; ; )
    {
        word   = feof( fp ) ? "End" : fread_word( fp );
        fMatch = FALSE;

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

        case '#':
            if ( !str_cmp( word, "#VARIABLE" ) )
            {
                fread_variable(&obj->vars, fp);
                fMatch = TRUE;
                break;
            }
            break;

        case 'A':
            KEY( "ActionDesc",	obj->action_desc,	fread_string( fp ) );
            if ( !str_cmp( word, "Affect" ) || !str_cmp( word, "AffectData" ) )
            {
                AFFECT_DATA *paf;
                int pafmod;

                CREATE( paf, AFFECT_DATA, 1 );
                if ( !str_cmp( word, "Affect" ) )
                {
                    paf->type	= fread_number( fp );
                }
                else
                {
                    int sn;

                    sn = skill_lookup( fread_word( fp ) );
                    if ( sn < 0 )
                        bug( "Fread_obj: unknown skill." );
                    else
                        paf->type = sn;
                }
                paf->duration	= fread_number( fp );
                pafmod		= fread_number( fp );
                paf->location	= fread_number( fp );
                paf->bitvector	= fread_number( fp );
                if ( paf->location == APPLY_WEAPONSPELL
                     ||   paf->location == APPLY_WEARSPELL
                     ||   paf->location == APPLY_REMOVESPELL
                     ||   paf->location == APPLY_EAT_SPELL
                     ||   paf->location == APPLY_IMMUNESPELL
                     ||   paf->location == APPLY_STRIPSN )
                     paf->modifier		= slot_lookup( pafmod );
                else
                    paf->modifier		= pafmod;
                LINK(paf, obj->first_affect, obj->last_affect, next, prev );
                fMatch				= TRUE;
                break;
            }
            break;

        case 'C':
            KEY( "Cost",	obj->cost,		fread_number( fp ) );
            KEY( "Count",	obj->count,		fread_number( fp ) );
            KEY( "CurrType",    obj->currtype,		fread_number( fp ) );
            if ( !str_cmp( word, "CVnum" ) )
            {
                int vnum;

                vnum = fread_number( fp );

                obj->christened = get_christen(vnum);

                fMatch = TRUE;
                break;
            }
            break;

        case 'D':
            KEY( "Description",	obj->description,	fread_string( fp ) );
            break;

        case 'E':
            KEY( "ExtraFlags",	obj->extra_flags,	fread_number( fp ) );
            KEY( "ExtraFlags2",	obj->extra_flags2,	fread_number( fp ) );

            if ( !str_cmp( word, "ExtraDescr" ) )
            {
                EXTRA_DESCR_DATA *ed;

                CREATE( ed, EXTRA_DESCR_DATA, 1 );
                ed->keyword		= fread_string( fp );
                ed->description		= fread_string( fp );
                LINK(ed, obj->first_extradesc, obj->last_extradesc, next, prev );
                fMatch 				= TRUE;
            }

            if ( !str_cmp( word, "End" ) )
            {
                if ( !fNest || !fVnum )
                {
                    bug( "Fread_obj: incomplete object: %s", obj->name?obj->name:"(null)" );
                    if ( obj->name )
                        STRFREE( obj->name        );
                    if ( obj->description )
                        STRFREE( obj->description );
                    if ( obj->short_descr )
                        STRFREE( obj->short_descr );
                    if ( obj->action_desc )
                        STRFREE( obj->action_desc );
                    DISPOSE( obj );
                    return;
                }
                else
                {
                    sh_int wear_loc = obj->wear_loc;

                    if ( !obj->name )
                        obj->name = QUICKLINK( obj->pIndexData->name );
                    if ( !obj->description )
                        obj->description = QUICKLINK( obj->pIndexData->description );
                    if ( !obj->short_descr )
                        obj->short_descr = QUICKLINK( obj->pIndexData->short_descr );
                    if ( !obj->action_desc )
                        obj->action_desc = QUICKLINK( obj->pIndexData->action_desc );
                    LINK(obj, first_object, last_object, next, prev );
                    obj->pIndexData->count += obj->count;
                    if ( fNest )
                        rgObjNest[iNest] = obj;
                    numobjsloaded += obj->count;
                    ++physicalobjects;
                    if ( file_ver > 1 || obj->wear_loc < -1
                         ||   obj->wear_loc >= MAX_WEAR )
                        obj->wear_loc = -1;
                    /* Corpse saving. -- Altrag */
                    if ( os_type == OS_CORPSE )
                    {
                        if ( !room )
                        {
                            bug( "Fread_obj: Corpse without room");
                            room = get_room_index(ROOM_VNUM_LIMBO);
                        }
                        obj = obj_to_room( obj, room );
                    }
                    else if ( iNest == 0 || rgObjNest[iNest] == NULL )
                    {
                        int slot = 0;
                        bool reslot = FALSE;

                        if ( file_ver > 1
                             &&   wear_loc > -1
                             &&   wear_loc < MAX_WEAR )
                        {
                            int x;

                            for ( x = 0; x < MAX_LAYERS; x++ )
                                if ( !save_equipment[wear_loc][x] )
                                {
                                    save_equipment[wear_loc][x] = obj;
                                    slot = x;
                                    reslot = TRUE;
                                    break;
                                }
                            if ( x == MAX_LAYERS )
                                bug( "Fread_obj: too many layers %d", wear_loc );
                        }
                        obj = obj_to_char( obj, ch );
                        if ( reslot )
                            save_equipment[wear_loc][slot] = obj;
                    }
                    else
                    {
                        if ( rgObjNest[iNest-1] )
                        {
                            separate_obj( rgObjNest[iNest-1] );
                            obj = obj_to_obj( obj, rgObjNest[iNest-1] );
                        }
                        else
                            bug( "Fread_obj: nest layer missing %d", iNest-1 );
                    }
                    if ( fNest )
                        rgObjNest[iNest] = obj;
                    return;
                }
            }
            break;

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

        case 'M':
            KEY( "MagicFlags",	obj->magic_flags,	fread_number( fp ) );
            break;

        case 'N':
            KEY( "Name",	obj->name,		fread_string( fp ) );
            KEY( "Nickname",	obj->nickname,		fread_string( fp ) );

            if ( !str_cmp( word, "Nest" ) )
            {
                iNest = fread_number( fp );
                if ( iNest < 0 || iNest >= MAX_NEST )
                {
                    bug( "Fread_obj: bad nest %d.", iNest );
                    iNest = 0;
                    fNest = FALSE;
                }
                fMatch = TRUE;
            }
            break;

        case 'R':
            KEY( "Rent",	obj->rent,		fread_number( fp ) );
            KEY( "Room", room, get_room_index(fread_number(fp)) );

        case 'S':
            KEY( "ShortDescr",	obj->short_descr,	fread_string( fp ) );

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

                iValue = fread_number( fp );
                sn     = skill_lookup( fread_word( fp ) );
                if ( iValue < 0 || iValue > 5 )
                    bug( "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,		fread_number( fp ) );
            break;

        case 'V':
            if ( !str_cmp( word, "Values" ) )
            {
                int x1,x2,x3,x4,x5,x6;
                char *ln = fread_line( fp );

                x1=x2=x3=x4=x5=x6=0;
                sscanf( ln, "%d %d %d %d %d %d", &x1, &x2, &x3, &x4, &x5, &x6 );
                /* clean up some garbage */
                if ( file_ver < 3 )
                    x5=x6=0;

                obj->value[0]	= x1;
                obj->value[1]	= x2;
                obj->value[2]	= x3;
                obj->value[3]	= x4;
                obj->value[4]	= x5;
                obj->value[5]	= x6;
                fMatch		= TRUE;
                break;
            }

            if ( !str_cmp( word, "Vnum" ) )
            {
                obj->vnum = fread_number( fp );
                /*  bug( "Fread_obj: bad vnum %d.", vnum );  */
                if ( ( obj->pIndexData = get_obj_index( obj->vnum ) ) == NULL )
                    fVnum = FALSE;
                else
                {
                    fVnum = TRUE;
                    obj->cost = obj->pIndexData->cost;
                    obj->currtype = obj->pIndexData->currtype;
                    obj->rent = obj->pIndexData->rent;
                    obj->weight = obj->pIndexData->weight;
                    obj->item_type = obj->pIndexData->item_type;
                    obj->wear_flags = obj->pIndexData->wear_flags;
                    obj->magic_flags = obj->pIndexData->magic_flags;
                    obj->extra_flags = obj->pIndexData->extra_flags;
                    obj->extra_flags2 = obj->pIndexData->extra_flags2;
                }
                fMatch = TRUE;
                break;
            }
            break;

        case 'W':
            KEY( "WearFlags",	obj->wear_flags,	fread_number( fp ) );
            KEY( "WearLoc",	obj->wear_loc,		fread_number( fp ) );
            KEY( "Weight",	obj->weight,		fread_number( fp ) );
            break;

        }

        if ( !fMatch )
        {
            EXTRA_DESCR_DATA *ed;
            AFFECT_DATA *paf;

            bug( "Fread_obj: no match %s", word?word:"(null)" );
            fread_to_eol( fp );
            if ( obj->name )
                STRFREE( obj->name        );
            if ( obj->description )
                STRFREE( obj->description );
            if ( obj->short_descr )
                STRFREE( obj->short_descr );
            if ( obj->action_desc )
                STRFREE( obj->action_desc );
            while ( (ed=obj->first_extradesc) != NULL )
            {
                STRFREE( ed->keyword );
                STRFREE( ed->description );
                UNLINK( ed, obj->first_extradesc, obj->last_extradesc, next, prev );
                DISPOSE( ed );
            }
            while ( (paf=obj->first_affect) != NULL )
            {
                UNLINK( paf, obj->first_affect, obj->last_affect, next, prev );
                DISPOSE( paf );
            }
            DISPOSE( obj );
            return;
        }
    }
}

void set_alarm( long seconds )
{
    alarm( seconds );
}

/*
 * Based on last time modified, show when a player was last on	-Thoric
 */
void do_last( CHAR_DATA *ch, char *argument )
{
    char buf [MAX_STRING_LENGTH];
    char arg [MAX_INPUT_LENGTH];
    char name[MAX_INPUT_LENGTH];
    struct stat fst;

    one_argument( argument, arg );
    if ( arg[0] == '\0' )
    {
        if (IS_IMMORTAL(ch))
        {
            show_file(ch, LASTLOG_FILE ".50");
            return;
        }
        send_to_char( "Usage: last <playername>\n\r", ch );
        return;
    }

    if (!check_parse_name(arg))
    {
        send_to_char("That is not a valid player name.\n\r", ch);
        return;
    }

    strcpy( name, capitalize(arg) );
    sprintf( buf, "%s%c/%s", PLAYER_DIR, tolower(arg[0]), name );
    if ( stat( buf, &fst ) != -1 )
        sprintf( buf, "%s was last on: %s\r", name, ctime( &fst.st_mtime ) );
    else
        sprintf( buf, "%s was not found.\n\r", name );
    send_to_char( buf, ch );
}

void write_corpses( CHAR_DATA *ch, char *name )
{
    OBJ_DATA *corpse;
    FILE *fp = NULL;

    /* Name and ch support so that we dont have to have a char to save their
     corpses.. (ie: decayed corpses while offline) */
    if ( ch && IS_NPC(ch) )
    {
	bug( "Write_corpses: writing NPC corpse." );
        return;
    }
    if ( ch )
        name = ch->name;
    /* Go by vnum, less chance of screwups. -- Altrag */
    for ( corpse = first_object; corpse; corpse = corpse->next )
        if ( !corpse || !corpse->pIndexData )
            fprintf(stderr, "!corpse || !corpse->pIndexData in save.c\n");
        else if ( corpse->vnum == OBJ_VNUM_CORPSE_PC &&
                  corpse->in_room != NULL &&
                  !str_cmp(corpse->short_descr+14, name) )
        {
            if ( !fp )
            {
                char buf[127];

                sprintf(buf, "%s%s", CORPSE_DIR, capitalize(name));
                if ( !(fp = fopen(buf, "w")) )
                {
                    bug( "Write_corpses: Cannot open file." );
                    perror(buf);
                    return;
                }
            }
            fwrite_obj(ch, corpse, fp, 0, OS_CORPSE);
        }
    if ( fp )
    {
        fprintf(fp, "#END\n\n");
        FCLOSE(fp);
    }
    else
    {
        char buf[127];

        sprintf(buf, "%s%s", CORPSE_DIR, capitalize(name));
        remove(buf);
    }
    return;
}

void load_corpses( void )
{
    DIR *dp;
    struct dirent *de;
    extern FILE *fpArea;
    extern char strArea[MAX_INPUT_LENGTH];
    extern int falling;

    if ( !(dp = opendir(CORPSE_DIR)) )
    {
        bug( "Load_corpses: can't open CORPSE_DIR");
        perror(CORPSE_DIR);
        return;
    }

    falling = 1; /* Arbitrary, must be >0 though. */
    while ( (de = readdir(dp)) != NULL )
    {
        if ( de->d_name[0] != '.' )
        {
            sprintf(strArea, "%s%s", CORPSE_DIR, de->d_name );
            fprintf(stderr, "Corpse -> %s\n", strArea);
            if ( !(fpArea = fopen(strArea, "r")) )
            {
                perror(strArea);
                continue;
            }
            for ( ; ; )
            {
                char letter;
                char *word;

                letter = fread_letter( fpArea );
                if ( letter == '*' )
                {
                    fread_to_eol(fpArea);
                    continue;
                }
                if ( letter != '#' )
                {
                    bug( "Load_corpses: # not found, got %c instead", letter );
                    break;
                }
                word = fread_word( fpArea );
                if ( !str_cmp(word, "CORPSE" ) )
                    fread_obj( NULL, fpArea, OS_CORPSE );
                else if ( !str_cmp(word, "OBJECT" ) )
                    fread_obj( NULL, fpArea, OS_CARRY );
                else if ( !str_cmp( word, "END" ) )
                    break;
                else
                {
                    bug( "Load_corpses: bad section %s", word?word:"(null)" );
                    break;
                }
            }
            FCLOSE(fpArea);
        }
    }
    fpArea = NULL;
    strcpy(strArea, "$");
    closedir(dp);
    falling = 0;
    return;
}

void rent_recurse(CHAR_DATA *ch, OBJ_DATA *obj, int *rent, int type)
{
    OBJ_DATA *tobj;
    if (is_rare_obj(obj) || IS_OBJ_STAT2(obj, ITEM2_RENT))
    {
        int orent = obj->rent;
        orent = UMAX(1, obj->rent-10000);
        if (type==0)
            ch_printf(ch, "%-40.40s %6d coins/day (x%d)\n\r",
                      obj->short_descr, orent, obj->count);
        else
            *rent += orent*obj->count;
    }
    if (!obj->first_content)
        return;
    for (tobj=obj->first_content;tobj;tobj=tobj->next_content)
        rent_recurse(ch,tobj,rent,type);
}

void show_rent_list(CHAR_DATA *ch)
{
    OBJ_DATA *obj;

    for (obj=ch->first_carrying;obj;obj=obj->next_content)
        rent_recurse(ch,obj,NULL,0);
}

int calc_rent(CHAR_DATA *ch)
{
    OBJ_DATA *obj;
    int rent=0;

    if (GetMaxLevel(ch)<4)
        return 0;

    for (obj=ch->first_carrying;obj;obj=obj->next_content)
        rent_recurse(ch,obj,&rent,1);

    return(rent);
}


void process_rent(CHAR_DATA *ch)
{
    OBJ_DATA *obj, *prev_obj;
    char strsave[MAX_INPUT_LENGTH];
    struct stat fst;
    double days=0.0;
    int cost, rent=0, type;
    unsigned int x;
    time_t diff=0;

    if (!ch || !ch->pcdata)
        return;

    type = get_primary_curr(ch->in_room);

    rent=calc_rent(ch);
    ch_printf(ch,"\n\rYour rent is %d %s coins per day.\n\r",rent,curr_types[type]);

    strcpy(strsave,ch->in_room->name);
    for (x=0;x<strlen(strsave);x++)
        if (strsave[x]==' ' || strsave[x]=='\'')
            break;
    strncpy(strsave,ch->in_room->name,x);
    if (rent>0 && !strncmp(strsave,GET_NAME(ch),strlen(GET_NAME(ch))))
    {
        rent/=2;
        ch_printf(ch,"Since you own a house, your rent is halved.\n\r");
    }

    sprintf( strsave, "%s%c/%s", PLAYER_DIR, tolower(ch->name[0]),
             capitalize( ch->name ) );

    if ( stat( strsave, &fst ) != -1 )
    {
        diff=(time(0))-(fst.st_mtime);
        days=(double)diff/(double)(60*60*24);
        cost=(int)((double)rent*(double)days);
        ch_printf(ch,"You were gone for %s.\n\rYou owe %d %s coins in rent.\n\r",
                  sec_to_hms(diff), cost, curr_types[type]);

        if (cost >= GET_MONEY(ch,type))
        {
            cost -= ch->money[type];
            ch->money[type] = 0;
        }
        else
        {
            ch->money[type] -= cost;
            cost = 0;
        }

        if (cost > 0 && cost >= GET_BALANCE(ch,type))
        {
            cost -= ch->balance[type];
            ch->balance[type] = 0;
        }
        else
        {
            ch->balance[type] -= cost;
            cost = 0;
        }

        if (cost > 0)
        {
            set_char_color(AT_RED, ch);
            ch_printf(ch, "You can't pay your rent, your rare equipment has been confiscated.\n\r");
            log_printf_plus(LOG_MONITOR, LEVEL_LOG_CSET, SEV_NOTICE, "%s lost equipment due to rent", GET_NAME(ch));

            prev_obj = ch->last_carrying;

            while ( (obj = prev_obj) )
            {
                prev_obj = obj->prev_content;
                if (is_rare_obj(obj))
                    extract_obj( obj );
            }
            return;
        }

    }
}