/***************************************************************************
 *  Original Diku Mud copyright (C) 1990, 1991 by Sebastian Hammer,        *
 *  Michael Seifert, Hans Henrik St{rfeldt, Tom Madsen, and Katja Nyboe.   *
 *                                                                         *
 *  Merc Diku Mud improvments copyright (C) 1992, 1993 by Michael          *
 *  Chastain, Michael Quan, and Mitchell Tse.                              *
 *                                                                         *
 *  In order to use any part of this Merc Diku Mud, you must comply with   *
 *  both the original Diku license in 'license.doc' as well the Merc       *
 *  license in 'license.txt'.  In particular, you may not remove either of *
 *  these copyright notices.                                               *
 *                                                                         *
 *  Much time and thought has gone into this software and you are          *
 *  benefitting.  We hope that you share your changes too.  What goes      *
 *  around, comes around.                                                  *
 ***************************************************************************/

#if defined(macintosh)
#include <types.h>
#else
#include <sys/types.h>
#endif
#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include "merc.h"

#if !defined(macintosh)
extern int _filbuf args( ( FILE * ) );
#endif

#if !defined(macintosh) && !defined(MSDOS)
void system args( ( char *string ) );
#endif

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



/*
 * Local functions.
 */
void fwrite_char args( ( CHAR_DATA * ch, FILE * fp ) );
void fwrite_obj args( ( CHAR_DATA * ch, OBJ_DATA * obj, FILE * fp, int iNest ) );
void fread_char args( ( CHAR_DATA * ch, FILE * fp ) );
void fread_obj args( ( CHAR_DATA * ch, FILE * fp ) );


/* Courtesy of Yaz of 4th Realm */
char *initial( const char *str )
{
   static char strint[MAX_STRING_LENGTH];

   strint[0] = LOWER( str[0] );
   return strint;

}

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

   if( IS_NPC( ch ) || ch->level < 2 )
      return;

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

   ch->save_time = current_time;
   fclose( fpReserve );

   /*
    * player files parsed directories by Yaz 4th Realm 
    */
#if !defined(machintosh) && !defined(MSDOS)
   sprintf( strsave, "%s%s%s%s", PLAYER_DIR, initial( ch->name ), "/", capitalize( ch->name ) );
#else
   sprintf( strsave, "%s%s", PLAYER_DIR, capitalize( ch->name ) );
#endif
   if( ( fp = fopen( strsave, "w" ) ) == NULL )
   {
      bug( "Save_char_obj: fopen", 0 );
      perror( strsave );
   }
   else
   {
      fwrite_char( ch, fp );
      if( ch->carrying != NULL )
         fwrite_obj( ch, ch->carrying, fp, 0 );
      fprintf( fp, "#END\n" );
   }
   fclose( fp );
   fpReserve = fopen( NULL_FILE, "r" );
   return;
}



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

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

   fprintf( fp, "Name         %s~\n", ch->name );
   fprintf( fp, "ShortDescr   %s~\n", ch->short_descr );
   fprintf( fp, "LongDescr    %s~\n", ch->long_descr );
   fprintf( fp, "Description  %s~\n", ch->description );
   fprintf( fp, "Prompt       %s~\n", ch->prompt );
   fprintf( fp, "Sex          %d\n", ch->sex );
   fprintf( fp, "Class        %d\n", ch->class );
   fprintf( fp, "Race         %d\n", ch->race );
   fprintf( fp, "Level        %d\n", ch->level );
   fprintf( fp, "Trust        %d\n", ch->trust );
   fprintf( fp, "Wizbit       %d\n", ch->wizbit );
   fprintf( fp, "Played       %d\n", ch->played + ( int )( current_time - ch->logon ) );
   fprintf( fp, "Note         %ld\n", ch->last_note );
   fprintf( fp, "Room         %d\n",
            ( ch->in_room == get_room_index( ROOM_VNUM_LIMBO )
              && ch->was_in_room != NULL ) ? ch->was_in_room->vnum : ch->in_room->vnum );

   fprintf( fp, "HpManaMove   %d %d %d %d %d %d\n", ch->hit, ch->max_hit, ch->mana, ch->max_mana, ch->move, ch->max_move );
   fprintf( fp, "Gold         %d\n", ch->gold );
   fprintf( fp, "Exp          %d\n", ch->exp );
   fprintf( fp, "Act          %d\n", ch->act );
   fprintf( fp, "AffectedBy   %d\n", ch->affected_by );
   /*
    * Bug fix from Alander 
    */
   fprintf( fp, "Position     %d\n", ch->position == POS_FIGHTING ? POS_STANDING : ch->position );

   fprintf( fp, "Practice     %d\n", ch->practice );
   fprintf( fp, "SavingThrow  %d\n", ch->saving_throw );
   fprintf( fp, "Alignment    %d\n", ch->alignment );
   fprintf( fp, "Hitroll      %d\n", ch->hitroll );
   fprintf( fp, "Damroll      %d\n", ch->damroll );
   fprintf( fp, "Armor        %d\n", ch->armor );
   fprintf( fp, "Wimpy        %d\n", ch->wimpy );
   fprintf( fp, "Deaf         %d\n", ch->deaf );

   if( IS_NPC( ch ) )
   {
      fprintf( fp, "Vnum         %d\n", ch->pIndexData->vnum );
   }
   else
   {
      fprintf( fp, "Password     %s~\n", ch->pcdata->pwd );
      fprintf( fp, "Bamfin       %s~\n", ch->pcdata->bamfin );
      fprintf( fp, "Bamfout      %s~\n", ch->pcdata->bamfout );
      fprintf( fp, "Title        %s~\n", ch->pcdata->title );
      fprintf( fp, "AttrPerm     %d %d %d %d %d\n",
               ch->pcdata->perm_str,
               ch->pcdata->perm_int, ch->pcdata->perm_wis, ch->pcdata->perm_dex, ch->pcdata->perm_con );

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

      fprintf( fp, "Condition    %d %d %d\n", ch->pcdata->condition[0], ch->pcdata->condition[1], ch->pcdata->condition[2] );

      fprintf( fp, "Pagelen      %d\n", ch->pcdata->pagelen );

      for( sn = 0; sn < MAX_SKILL; sn++ )
      {
         if( skill_table[sn].name != NULL && ch->pcdata->learned[sn] > 0 )
         {
            fprintf( fp, "Skill        %d '%s'\n", ch->pcdata->learned[sn], skill_table[sn].name );
         }
      }
   }

   for( paf = ch->affected; paf != NULL; paf = paf->next )
   {
      fprintf( fp, "Affect %3d %3d %3d %3d %10d\n", paf->type, paf->duration, paf->modifier, paf->location, paf->bitvector );
   }

#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 )
{
   EXTRA_DESCR_DATA *ed;
   AFFECT_DATA *paf;

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

   /*
    * Castrate storage characters.
    */
   if( ch->level < obj->level || obj->item_type == ITEM_KEY || obj->item_type == ITEM_POTION )
      return;

   fprintf( fp, "#OBJECT\n" );
   fprintf( fp, "Nest         %d\n", iNest );
   fprintf( fp, "Name         %s~\n", obj->name );
   fprintf( fp, "ShortDescr   %s~\n", obj->short_descr );
   fprintf( fp, "Description  %s~\n", obj->description );
   fprintf( fp, "Vnum         %d\n", obj->pIndexData->vnum );
   fprintf( fp, "ExtraFlags   %d\n", obj->extra_flags );
   fprintf( fp, "WearFlags    %d\n", obj->wear_flags );
   fprintf( fp, "WearLoc      %d\n", obj->wear_loc );
   fprintf( fp, "ItemType     %d\n", obj->item_type );
   fprintf( fp, "Weight       %d\n", obj->weight );
   fprintf( fp, "Level        %d\n", obj->level );
   fprintf( fp, "Timer        %d\n", obj->timer );
   fprintf( fp, "Cost         %d\n", obj->cost );
   fprintf( fp, "Values       %d %d %d %d\n", obj->value[0], obj->value[1], obj->value[2], obj->value[3] );

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

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

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

         break;

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

         break;
   }

   for( paf = obj->affected; paf != NULL; paf = paf->next )
   {
      fprintf( fp, "Affect       %d %d %d %d %d\n", paf->type, paf->duration, paf->modifier, paf->location, paf->bitvector );
   }

   for( ed = obj->extra_descr; ed != NULL; ed = ed->next )
   {
      fprintf( fp, "ExtraDescr   %s~ %s~\n", ed->keyword, ed->description );
   }

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

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

   return;
}



/*
 * Load a char and inventory into a new ch structure.
 */
bool load_char_obj( DESCRIPTOR_DATA * d, char *name )
{
   static PC_DATA pcdata_zero;
   char strsave[MAX_INPUT_LENGTH];
   char buf[MAX_STRING_LENGTH];
   CHAR_DATA *ch;
   FILE *fp;
   bool found;

   if( char_free == NULL )
   {
      ch = alloc_perm( sizeof( *ch ) );
   }
   else
   {
      ch = char_free;
      char_free = char_free->next;
   }
   clear_char( ch );

   if( pcdata_free == NULL )
   {
      ch->pcdata = alloc_perm( sizeof( *ch->pcdata ) );
   }
   else
   {
      ch->pcdata = pcdata_free;
      pcdata_free = pcdata_free->next;
   }
   *ch->pcdata = pcdata_zero;

   d->character = ch;
   ch->desc = d;
   ch->name = str_dup( name );
   ch->prompt = str_dup( "<%hhp %mm %vmv> " );
   ch->last_note = 0;
   ch->act = PLR_BLANK | PLR_COMBINE | PLR_PROMPT;
   ch->pcdata->pwd = str_dup( "" );
   ch->pcdata->bamfin = str_dup( "" );
   ch->pcdata->bamfout = str_dup( "" );
   ch->pcdata->title = str_dup( "" );
   ch->pcdata->perm_str = 13;
   ch->pcdata->perm_int = 13;
   ch->pcdata->perm_wis = 13;
   ch->pcdata->perm_dex = 13;
   ch->pcdata->perm_con = 13;
   ch->pcdata->condition[COND_THIRST] = 48;
   ch->pcdata->pagelen = 20;
   ch->pcdata->condition[COND_FULL] = 48;
#ifdef IMC
   imc_initchar( ch );
#endif
   found = FALSE;
   fclose( fpReserve );

   /*
    * parsed player file directories by Yaz of 4th Realm 
    */
   /*
    * decompress if .gz file exists - Thx Alander 
    */
#if !defined(macintosh) && !defined(MSDOS)
   sprintf( strsave, "%s%s%s%s%s", PLAYER_DIR, initial( ch->name ), "/", capitalize( name ), ".gz" );
   if( ( fp = fopen( strsave, "r" ) ) != NULL )
   {
      fclose( fp );
      sprintf( buf, "gzip -dfq %s", strsave );
      system( buf );
   }
#endif

#if !defined(machintosh) && !defined(MSDOS)
   sprintf( strsave, "%s%s%s%s", PLAYER_DIR, initial( ch->name ), "/", capitalize( name ) );
#else
   sprintf( strsave, "%s%s", PLAYER_DIR, capitalize( name ) );
#endif
   if( ( fp = fopen( strsave, "r" ) ) != NULL )
   {
      int iNest;

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

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

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

         if( letter != '#' )
         {
            bug( "Load_char_obj: # not found.", 0 );
            break;
         }

         word = fread_word( fp );
         if( !str_cmp( word, "PLAYER" ) )
            fread_char( ch, fp );
         else if( !str_cmp( word, "OBJECT" ) )
            fread_obj( ch, fp );
         else if( !str_cmp( word, "END" ) )
            break;
         else
         {
            bug( "Load_char_obj: bad section.", 0 );
            break;
         }
      }
      fclose( fp );
   }

   fpReserve = fopen( NULL_FILE, "r" );
   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;				\
				}

void fread_char( CHAR_DATA * ch, FILE * fp )
{
   char buf[MAX_STRING_LENGTH];
   char *word;
   bool fMatch;

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

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

         case 'A':
            KEY( "Act", ch->act, fread_number( fp ) );
            KEY( "AffectedBy", ch->affected_by, fread_number( fp ) );
            KEY( "Alignment", ch->alignment, fread_number( fp ) );
            KEY( "Armor", ch->armor, fread_number( fp ) );

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

               if( affect_free == NULL )
               {
                  paf = alloc_perm( sizeof( *paf ) );
               }
               else
               {
                  paf = affect_free;
                  affect_free = affect_free->next;
               }

               paf->type = fread_number( fp );
               paf->duration = fread_number( fp );
               paf->modifier = fread_number( fp );
               paf->location = fread_number( fp );
               paf->bitvector = fread_number( fp );
               paf->next = ch->affected;
               ch->affected = paf;
               fMatch = TRUE;
               break;
            }

            if( !str_cmp( word, "AttrMod" ) )
            {
               ch->pcdata->mod_str = fread_number( fp );
               ch->pcdata->mod_int = fread_number( fp );
               ch->pcdata->mod_wis = fread_number( fp );
               ch->pcdata->mod_dex = fread_number( fp );
               ch->pcdata->mod_con = fread_number( fp );
               fMatch = TRUE;
               break;
            }

            if( !str_cmp( word, "AttrPerm" ) )
            {
               ch->pcdata->perm_str = fread_number( fp );
               ch->pcdata->perm_int = fread_number( fp );
               ch->pcdata->perm_wis = fread_number( fp );
               ch->pcdata->perm_dex = fread_number( fp );
               ch->pcdata->perm_con = fread_number( fp );
               fMatch = TRUE;
               break;
            }
            break;

         case 'B':
            KEY( "Bamfin", ch->pcdata->bamfin, fread_string( fp ) );
            KEY( "Bamfout", ch->pcdata->bamfout, fread_string( fp ) );
            break;

         case 'C':
            KEY( "Class", ch->class, fread_number( fp ) );

            if( !str_cmp( word, "Condition" ) )
            {
               ch->pcdata->condition[0] = fread_number( fp );
               ch->pcdata->condition[1] = fread_number( fp );
               ch->pcdata->condition[2] = fread_number( fp );
               fMatch = TRUE;
               break;
            }
            break;

         case 'D':
            KEY( "Damroll", ch->damroll, fread_number( fp ) );
            KEY( "Deaf", ch->deaf, fread_number( fp ) );
            KEY( "Description", ch->description, fread_string( fp ) );
            break;

         case 'E':
            if( !str_cmp( word, "End" ) )
               return;
            KEY( "Exp", ch->exp, fread_number( fp ) );
            break;

         case 'G':
            KEY( "Gold", ch->gold, fread_number( fp ) );
            break;

         case 'H':
            KEY( "Hitroll", ch->hitroll, fread_number( fp ) );

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

         case 'I':
#ifdef IMC
            if( ( fMatch = imc_loadchar( ch, fp, word ) ) )
               break;
#endif

         case 'L':
            KEY( "Level", ch->level, fread_number( fp ) );
            KEY( "LongDescr", ch->long_descr, fread_string( fp ) );
            break;

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

         case 'P':
            KEY( "Pagelen", ch->pcdata->pagelen, fread_number( fp ) );
            KEY( "Password", ch->pcdata->pwd, fread_string( 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->prompt, fread_string( fp ) );
            break;

         case 'R':
            KEY( "Race", ch->race, fread_number( fp ) );

            if( !str_cmp( word, "Room" ) )
            {
               ch->in_room = get_room_index( fread_number( fp ) );
               if( ch->in_room == NULL )
                  ch->in_room = get_room_index( ROOM_VNUM_LIMBO );
               fMatch = TRUE;
               break;
            }

            break;

         case 'S':
            KEY( "SavingThrow", ch->saving_throw, fread_number( fp ) );
            KEY( "Sex", ch->sex, fread_number( fp ) );
            KEY( "ShortDescr", ch->short_descr, fread_string( fp ) );

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

               value = fread_number( fp );
               sn = skill_lookup( fread_word( fp ) );
               if( sn < 0 )
                  bug( "Fread_char: unknown skill.", 0 );
               else
                  ch->pcdata->learned[sn] = value;
               fMatch = TRUE;
            }

            break;

         case 'T':
            KEY( "Trust", ch->trust, fread_number( fp ) );

            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 );
                  free_string( ch->pcdata->title );
                  ch->pcdata->title = str_dup( buf );
               }
               fMatch = TRUE;
               break;
            }

            break;

         case 'V':
            if( !str_cmp( word, "Vnum" ) )
            {
               ch->pIndexData = get_mob_index( fread_number( fp ) );
               fMatch = TRUE;
               break;
            }
            break;

         case 'W':
            KEY( "Wimpy", ch->wimpy, fread_number( fp ) );
            KEY( "Wizbit", ch->wizbit, fread_number( fp ) );
            break;
      }

      /*
       * Make sure old chars have this field - Kahn 
       */
      if( !ch->pcdata->pagelen )
         ch->pcdata->pagelen = 20;
      if( !ch->prompt || ch->prompt == '\0' )
         ch->prompt = "<%h %m %mv> ";

      if( !fMatch )
      {
         bug( "Fread_char: no match.", 0 );
         fread_to_eol( fp );
      }
   }
}



void fread_obj( CHAR_DATA * ch, FILE * fp )
{
   static OBJ_DATA obj_zero;
   OBJ_DATA *obj;
   char *word;
   int iNest;
   bool fMatch;
   bool fNest;
   bool fVnum;

   if( obj_free == NULL )
   {
      obj = alloc_perm( sizeof( *obj ) );
   }
   else
   {
      obj = obj_free;
      obj_free = obj_free->next;
   }

   *obj = obj_zero;
   obj->name = str_dup( "" );
   obj->short_descr = str_dup( "" );
   obj->description = str_dup( "" );

   fNest = FALSE;
   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 'A':
            if( !str_cmp( word, "Affect" ) )
            {
               AFFECT_DATA *paf;

               if( affect_free == NULL )
               {
                  paf = alloc_perm( sizeof( *paf ) );
               }
               else
               {
                  paf = affect_free;
                  affect_free = affect_free->next;
               }

               paf->type = fread_number( fp );
               paf->duration = fread_number( fp );
               paf->modifier = fread_number( fp );
               paf->location = fread_number( fp );
               paf->bitvector = fread_number( fp );
               paf->next = obj->affected;
               obj->affected = paf;
               fMatch = TRUE;
               break;
            }
            break;

         case 'C':
            KEY( "Cost", obj->cost, fread_number( fp ) );
            break;

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

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

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

               if( extra_descr_free == NULL )
               {
                  ed = alloc_perm( sizeof( *ed ) );
               }
               else
               {
                  ed = extra_descr_free;
                  extra_descr_free = extra_descr_free->next;
               }

               ed->keyword = fread_string( fp );
               ed->description = fread_string( fp );
               ed->next = obj->extra_descr;
               obj->extra_descr = ed;
               fMatch = TRUE;
            }

            if( !str_cmp( word, "End" ) )
            {
               if( !fNest || !fVnum )
               {
                  bug( "Fread_obj: incomplete object.", 0 );
                  free_string( obj->name );
                  free_string( obj->description );
                  free_string( obj->short_descr );
                  obj->next = obj_free;
                  obj_free = obj;
                  return;
               }
               else
               {
                  obj->next = object_list;
                  object_list = obj;
                  obj->pIndexData->count++;
                  if( iNest == 0 || rgObjNest[iNest] == NULL )
                     obj_to_char( obj, ch );
                  else
                     obj_to_obj( obj, rgObjNest[iNest - 1] );
                  return;
               }
            }
            break;

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

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

         case 'N':
            KEY( "Name", obj->name, 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 );
               }
               else
               {
                  rgObjNest[iNest] = obj;
                  fNest = TRUE;
               }
               fMatch = TRUE;
            }
            break;

         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 > 3 )
               {
                  bug( "Fread_obj: bad iValue %d.", iValue );
               }
               else if( sn < 0 )
               {
                  bug( "Fread_obj: unknown skill.", 0 );
               }
               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" ) )
            {
               obj->value[0] = fread_number( fp );
               obj->value[1] = fread_number( fp );
               obj->value[2] = fread_number( fp );
               obj->value[3] = fread_number( fp );
               fMatch = TRUE;
               break;
            }

            if( !str_cmp( word, "Vnum" ) )
            {
               int vnum;

               vnum = fread_number( fp );
               if( ( obj->pIndexData = get_obj_index( vnum ) ) == NULL )
                  bug( "Fread_obj: bad vnum %d.", vnum );
               else
                  fVnum = TRUE;
               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 )
      {
         bug( "Fread_obj: no match.", 0 );
         fread_to_eol( fp );
      }
   }
}
