LOP/
LOP/area/
LOP/boards/
LOP/channels/
LOP/clans/
LOP/classes/
LOP/color/
LOP/councils/
LOP/deity/
LOP/races/
LOP/src/specials/
/*****************************************************************************
 * DikuMUD (C) 1990, 1991 by:                                                *
 *   Sebastian Hammer, Michael Seifert, Hans Henrik Staefeldt, Tom Madsen,   *
 *   and Katja Nyboe.                                                        *
 *---------------------------------------------------------------------------*
 * MERC 2.1 (C) 1992, 1993 by:                                               *
 *   Michael Chastain, Michael Quan, and Mitchell Tse.                       *
 *---------------------------------------------------------------------------*
 * SMAUG 1.4 (C) 1994, 1995, 1996, 1998 by: Derek Snider.                    *
 *   Team: Thoric, Altrag, Blodkai, Narn, Haus, Scryn, Rennard, Swordbearer, *
 *         gorog, Grishnakh, Nivek, Tricops, and Fireblade.                  *
 *---------------------------------------------------------------------------*
 * SMAUG 1.7 FUSS by: Samson and others of the SMAUG community.              *
 *                    Their contributions are greatly appreciated.           *
 *---------------------------------------------------------------------------*
 * LoP (C) 2006, 2007, 2008 by: the LoP team.                                *
 *****************************************************************************/

#include <stdio.h>
#include "h/mud.h"

RACE_TYPE *race_table[MAX_RACE];

void write_race_list( void )
{
   FILE *fp;
   int i;

   if( !( fp = fopen( RACE_LIST, "w" ) ) )
   {
      bug( "%s: Error opening %s.", __FUNCTION__, RACE_LIST );
      perror( RACE_LIST );
      return;
   }
   for( i = 0; i < MAX_PC_RACE && race_table[i]; i++ )
      if( race_table[i] && race_table[i]->name )
         fprintf( fp, "%s.race\n", race_table[i]->name );
   fprintf( fp, "%s", "$\n" );
   fclose( fp );
   fp = NULL;
}

bool create_new_race( int rcindex, char *argument )
{
   int stat, i;

   if( !argument || argument[0] == '\0' )
      return false;
   if( rcindex >= MAX_RACE || race_table[rcindex] )
      return false;
   CREATE( race_table[rcindex], struct race_type, 1 );
   if( !race_table[rcindex] )
      return false;
   argument[0] = UPPER( argument[0] );
   race_table[rcindex]->rlistplace = rcindex;
   race_table[rcindex]->name = STRALLOC( argument );
   race_table[rcindex]->hit = 100;
   race_table[rcindex]->mana = 100;
   race_table[rcindex]->move = 100;
   race_table[rcindex]->uses = 1; /* Set it to use Mana */
   race_table[rcindex]->language = 0;
   race_table[rcindex]->alignment = 0;
   race_table[rcindex]->minalign = -1000;
   race_table[rcindex]->maxalign = 1000;
   race_table[rcindex]->ac_plus = 0;
   race_table[rcindex]->minheight = number_range( 50, 75 );
   race_table[rcindex]->maxheight = number_range( 75, 100 );
   race_table[rcindex]->minweight = number_range( 150, 200 );
   race_table[rcindex]->maxweight = number_range( 200, 250 );
   race_table[rcindex]->hunger_mod = 0;
   race_table[rcindex]->thirst_mod = 0;
   race_table[rcindex]->race_recall = 0;
   for( stat = 0; stat < STAT_MAX; stat++ )
      race_table[rcindex]->base_stats[stat] = 13;
   for( stat = 0; stat < STAT_MAX; stat++ )
      race_table[rcindex]->max_stats[stat] = MAX_LEVEL;
   for( i = 0; i < MAX_WHERE_NAME; ++i )
      race_table[rcindex]->where_name[i] = NULL;
   xCLEAR_BITS( race_table[rcindex]->class_restriction );
   xCLEAR_BITS( race_table[rcindex]->affected );
   xCLEAR_BITS( race_table[rcindex]->resist );
   xCLEAR_BITS( race_table[rcindex]->suscept );
   return true;
}

void write_race_file( int ra )
{
   FILE *fp;
   char filename[MIL];
   RACE_TYPE *race = race_table[ra];
   int i, x, y, stat;

   if( !race )
   {
      bug( "%s: NULL race in race_table[%d].", __FUNCTION__, ra );
      return;
   }

   if( !race->name )
   {
      bug( "%s: Race has NULL name in race_table[%d].", __FUNCTION__, ra );
      return;
   }

   snprintf( filename, sizeof( filename ), "%s%s.race", RACE_DIR, race->name );
   if( !( fp = fopen( filename, "w" ) ) )
   {
      bug( "%s: Can't open: %s for writing", __FUNCTION__, filename );
      perror( filename );
      return;
   }

   fprintf( fp, "Name        %s~\n", race->name );
   if( !xIS_EMPTY( race->class_restriction ) )
      fprintf( fp, "Classes     %s~\n", ext_class_string( &race->class_restriction ) );
   fprintf( fp, "%s", "Stats      " );
   for( stat = 0; stat < STAT_MAX; stat++ )
      fprintf( fp, " %d", race->base_stats[stat] );
   fprintf( fp, "%s", "\n" );
   fprintf( fp, "%s", "MaxStats   " );
   for( stat = 0; stat < STAT_MAX; stat++ )
      fprintf( fp, " %d", race->max_stats[stat] );
   fprintf( fp, "%s", "\n" );
   fprintf( fp, "Hit         %d\n", race->hit );
   fprintf( fp, "Mana        %d\n", race->mana );
   fprintf( fp, "Move        %d\n", race->move );
   fprintf( fp, "Uses        %d\n", race->uses );
   if( !xIS_EMPTY( race->affected ) )
      fprintf( fp, "Affected    %s~\n", ext_flag_string( &race->affected, a_flags ) );
   if( !xIS_EMPTY( race->resist ) )
      fprintf( fp, "Resist      %s~\n", ext_flag_string( &race->resist, ris_flags ) );
   if( !xIS_EMPTY( race->suscept ) )
      fprintf( fp, "Suscept     %s~\n", ext_flag_string( &race->suscept, ris_flags ) );
   fprintf( fp, "Language    %d\n", race->language );
   fprintf( fp, "Align       %d\n", race->alignment );
   fprintf( fp, "Min_Align   %d\n", race->minalign );
   fprintf( fp, "Max_Align   %d\n", race->maxalign );
   fprintf( fp, "AC_Plus     %d\n", race->ac_plus );
   fprintf( fp, "MinHeight   %d\n", race->minheight );
   fprintf( fp, "MaxHeight   %d\n", race->maxheight );
   fprintf( fp, "MinWeight   %d\n", race->minweight );
   fprintf( fp, "MaxWeight   %d\n", race->maxweight );
   fprintf( fp, "Hunger_Mod  %d\n", race->hunger_mod );
   fprintf( fp, "Thirst_mod  %d\n", race->thirst_mod );
   fprintf( fp, "Race_Recall %d\n", race->race_recall );
   for( i = 0; i < MAX_WHERE_NAME; i++ )
   {
      if( race->where_name[i] && str_cmp( race->where_name[i], where_name[i] ) )
         fprintf( fp, "WhereName  %d %s~\n", i, race->where_name[i] );
   }
   for( x = 0; x < top_sn; x++ )
   {
      if( !skill_table[x] || !skill_table[x]->name )
         continue;
      if( ( y = skill_table[x]->race_level[race->rlistplace] ) > 0 )
         fprintf( fp, "Skill '%s' %d %d\n", skill_table[x]->name, y, skill_table[x]->race_adept[race->rlistplace] );
   }
   fprintf( fp, "End\n" );
   fclose( fp );
   fp = NULL;
}

RACE_TYPE *valid_race( char *name )
{
   RACE_TYPE *race = NULL;
   int rcheck;

   if( name && name[0] != '\0' )
   {
      if( is_number( name ) )
      {
         if( ( rcheck = atoi( name ) ) >= 0 && rcheck < MAX_PC_RACE )
            race = race_table[rcheck];
      }
      else
      {
         for( rcheck = 0; rcheck < MAX_PC_RACE; rcheck++ )
         {
            if( !race_table[rcheck] || !race_table[rcheck]->name )
               continue;

            if( !str_cmp( race_table[rcheck]->name, name ) )
            {
               race = race_table[rcheck];
               break;
            }
         }
      }
   }
   return race;
}

void show_race( CHAR_DATA *ch, struct race_type *race )
{
   int i, ct, stat, ra, cnt;
   bool first = true;

   set_pager_color( AT_PLAIN, ch );

   if( !race )
   {
      send_to_char( "No such race.\r\n", ch );
      return;
   }

   ra = race->rlistplace;
   ch_printf( ch, "&wRACE: &W%s\r\n", race->name ? race->name : "Not Set" );

   ch_printf( ch, "&wDisallowed classes:\r\n&W%s\r\n", ext_class_string( &race->class_restriction ) );

   ct = 0;
   send_to_char( "&wAllowed classes:&W\r\n", ch );
   for( i = 0; i < MAX_PC_CLASS; i++ )
   {
      if( !xIS_SET( race->class_restriction, i ) )
      {
         ch_printf( ch, "%s ", class_table[i]->name );
         if( ++ct == 6 )
         {
            send_to_char( "\r\n", ch );
            ct = 0;
         }
      }
   }
   if( ct != 0 )
      send_to_char( "\r\n", ch );

   ch_printf( ch, "&wUses: &W%s\r\n",
      race->uses == 0 ? "Nothing" : race->uses == 1 ? "Mana" : race->uses == 2 ? "Blood" : "Unknown" );
   send_to_char( "&wBase Stats:\r\n", ch );
   ct = 0;
   ch_printf( ch, "&c%14s: &w%4d &c%14s: &w%4d &c%14s: &w%4d\r\n&c%14s: &w%4d &c%14s: &w%4d ",
      "Hit Points", race->hit, race->uses == 2 ? "Blood" : "Mana", race->mana, "Move", race->move,
      "Alignment", race->alignment, "AC", race->ac_plus );
   ct = 2;
   for( stat = 0; stat < STAT_MAX; stat++ )
   {
       ch_printf( ch, "&c%14s: &w%4d", capitalize( stattypes[stat] ), race->base_stats[stat] );
       if( ++ct == 3 )
       {
          send_to_char( "\r\n", ch );
          ct = 0;
       }
       else
          send_to_char( " ", ch );
   }
   if( ct != 0 )
      send_to_char( "\r\n", ch );

   send_to_char( "&wMax Stats: (To set use 'max <stat>')\r\n", ch );
   ct = 0;
   for( stat = 0; stat < STAT_MAX; stat++ )
   {
       ch_printf( ch, "&c%14s: &w%4d", capitalize( stattypes[stat] ), race->max_stats[stat] );
       if( ++ct == 3 )
       {
          send_to_char( "\r\n", ch );
          ct = 0;
       }
       else
          send_to_char( " ", ch );
   }
   if( ct != 0 )
      send_to_char( "\r\n", ch );

   ch_printf( ch, "&wMin Align: &W%4d   &wMax Align: &W%4d\r\n",
      race->minalign, race->maxalign );
   ch_printf( ch, "&wHeight: &W%4d-%4d &win.   Weight: &W%4d-%4d &wlbs. HungerMod: &W%4d  &wThirstMod: &W%d\r\n",
      race->minheight, race->maxheight, race->minweight, race->maxweight, race->hunger_mod, race->thirst_mod );

   ch_printf( ch, "&wSpoken Languages: &W%s\r\n", flag_string( race->language, lang_names ) );
   ch_printf( ch, "&wAffected by:      &W%s\r\n", ext_flag_string( &race->affected, a_flags ) );
   ch_printf( ch, "&wResistant to:     &W%s\r\n", ext_flag_string( &race->resist, ris_flags ) );
   ch_printf( ch, "&wSusceptible to:   &W%s\r\n", ext_flag_string( &race->suscept, ris_flags ) );

   {
      int x, y, low, hi, last = -1;

      low = 0;
      hi = MAX_LEVEL;
      for( x = low; x <= hi; x++ )
      {
         cnt = 0;
         set_pager_color( AT_BLUE, ch );
         for( y = gsn_first_spell; y < gsn_top_sn; y++ )
         {
            if( skill_table[y]->race_level[ra] == x )
            {
               if( first )
                  send_to_pager( "Spells/Skills/Weapons\r\n", ch );
               first = false;
               if( x != last )
               {
                  if( cnt != 0 )
                  {
                     send_to_pager( "\r\n", ch );
                     cnt = 0;
                  }
                  pager_printf( ch, "[Level %d]\r\n", x );
               }
               last = x;
               pager_printf( ch, "  %-7s %-19s%3d     ",
                  skill_tname[skill_table[y]->type], skill_table[y]->name, skill_table[y]->race_adept[ra] );
               if( ++cnt == 2 )
               {
                  send_to_pager( "\r\n", ch );
                  cnt = 0;
               }
            }
         }
         if( cnt != 0 )
            send_to_pager( "\r\n", ch );
      }
   }

   cnt = 0;
   first = true;
   for( i = 0; i < MAX_WHERE_NAME; ++i )
   {
      if( !race->where_name[i] )
         continue;
      if( first )
         send_to_pager( "Where names:\r\n", ch );
      first = false;
      pager_printf( ch, "%10s> %s\r\n", wear_locs[i], race->where_name[i] );
   }
}

/* Modified by Samson to allow setting language by name - 8-6-98 */
CMDF( do_setrace )
{
   RACE_TYPE *race;
   char arg1[MIL], arg2[MIL], arg3[MIL];
   int value, v2, i, stat, ra;

   set_char_color( AT_PLAIN, ch );

   argument = one_argument( argument, arg1 );
   argument = one_argument( argument, arg2 );
   if( !arg1 || arg1[0] == '\0' )
   {
      send_to_char( "Usage: setrace <race> [create/save]\r\n", ch );
      send_to_char( "Usage: setrace <race> <field> <value>\r\n", ch );
      send_to_char( "Usage: setrace <race> wherename <location> <string>\r\n", ch );
      send_to_char( "Usage: setrace <race> skill <skill> <level> <adept>\r\n", ch );
      send_to_char( "\r\nField being one of:\r\n", ch );
      send_to_char( "    ac      uses     wisdom   maxalign  maxweight\r\n", ch );
      send_to_char( "   hit    hunger    classes   minalign  minheight\r\n", ch );
      send_to_char( "  luck    recall    suscept   strength  minweight\r\n", ch );
      send_to_char( "  mana    resist   affected  alignment  constitution\r\n", ch );
      send_to_char( "  move    thirst   charisma  dexterity  intelligence\r\n", ch );
      send_to_char( "  name  language  maxheight\r\n", ch );
      return;
   }

   race = valid_race( arg1 );

   if( !str_cmp( arg2, "create" ) && race )
   {
      send_to_char( "That race already exists!\r\n", ch );
      return;
   }
   else if( !race && str_cmp( arg2, "create" ) )
   {
      send_to_char( "No such race.\r\n", ch );
      return;
   }

   if( !arg2 || arg2[0] == '\0' )
   {
      show_race( ch, race );
      return;
   }

   if( !str_cmp( arg2, "create" ) )
   {
      char filename[1024];

      if( MAX_PC_RACE >= MAX_RACE )
      {
         send_to_char( "You need to up MAX_RACE in mud.h and make clean.\r\n", ch );
         return;
      }
      snprintf( filename, sizeof( filename ), "%s.race", arg1 );
      if( !can_use_path( ch, RACE_DIR, filename ) )
         return;
      if( !create_new_race( MAX_PC_RACE, arg1 ) )
      {
         send_to_char( "Couldn't create a new race.\r\n", ch );
         return;
      }
      write_race_file( MAX_PC_RACE );
      MAX_PC_RACE++;
      write_race_list( );
      send_to_char( "Done.\r\n", ch );
      return;
   }

   ra = race->rlistplace;

   if( !str_cmp( arg2, "save" ) )
   {
      write_race_file( ra );
      send_to_char( "Race saved.\r\n", ch );
      return;
   }

   if( !argument || argument[0] == '\0' )
   {
      send_to_char( "You must specify an argument.\r\n", ch );
      return;
   }

   if( !str_cmp( arg2, "skill" ) )
   {
      SKILLTYPE *skill;
      int sn;

      argument = one_argument( argument, arg2 );
      if( ( sn = skill_lookup( arg2 ) ) > 0 )
      {
         skill = get_skilltype( sn );
         argument = one_argument( argument, arg2 );
         skill->race_level[ra] = URANGE( -1, atoi( arg2 ), MAX_LEVEL );
         argument = one_argument( argument, arg2 );
         skill->race_adept[ra] = URANGE( 0, atoi( arg2 ), 100 );
         write_race_file( ra );
         ch_printf( ch, "Skill \"%s\" added at level %d and %d%%.\r\n",
            skill->name, skill->race_level[ra], skill->race_adept[ra] );
      }
      else
         ch_printf( ch, "No such skill as %s.\r\n", arg2 );
      return;
   }

   if( !str_cmp( arg2, "name" ) )
   {
      char buf[256];
      RACE_TYPE *rcheck;

      one_argument( argument, arg1 );
      if( !arg1 || arg1[0] == '\0' )
      {
         send_to_char( "You can't set a race name to nothing.\r\n", ch );
         return;
      }
      snprintf( buf, sizeof( buf ), "%s.race", arg1 );
      if( !can_use_path( ch, RACE_DIR, buf ) )
         return;
      if( ( rcheck = valid_race( arg1 ) ) )
      {
         ch_printf( ch, "Already a race called %s.\r\n", arg1 );
         return;
      }
      snprintf( buf, sizeof( buf ), "%s%s.race", RACE_DIR, race->name );
      remove_file( buf );
      STRSET( race->name, capitalize( arg1 ) );
      write_race_file( ra );
      write_race_list( );
      send_to_char( "Race name set.\r\n", ch );
      return;
   }

   if( !str_cmp( arg2, "wherename" ) )
   {
      if( !argument || argument[0] == '\0' )
      {
         send_to_char( "Usage: setrace <race> wherename <location> <string>\r\n", ch );
         return;
      }
      argument = one_argument( argument, arg3 );
      value = get_flag( arg3, wear_locs, WEAR_MAX );
      if( value < 0 || value >= WEAR_MAX )
      {
         ch_printf( ch, "Unknown wearlocation (%s), see Help WEARLOCS.\r\n", arg3 );
         return;
      }
      STRSET( race->where_name[value], argument );
      write_race_file( ra );
      send_to_char( "Race wherename set.\r\n", ch );
      return;
   }

   if( !str_cmp( arg2, "affected" ) )
   {
      if( !argument || argument[0] == '\0' )
      {
         send_to_char( "Usage: setrace <race> affected <flag> [flag]...\r\n", ch );
         return;
      }
      while( argument && argument[0] != '\0' )
      {
         argument = one_argument( argument, arg3 );
         value = get_flag( arg3, a_flags, AFF_MAX );
         if( value < 0 || value >= AFF_MAX )
            ch_printf( ch, "Unknown flag: %s\r\n", arg3 );
         else
            xTOGGLE_BIT( race->affected, value );
      }
      write_race_file( ra );
      send_to_char( "Racial affects set.\r\n", ch );
      return;
   }

   if( !str_cmp( arg2, "resist" ) || !str_cmp( arg2, "suscept" ) )
   {
      bool resist = false;

      if( !argument || argument[0] == '\0' )
      {
         ch_printf( ch, "Usage: setrace <race> %s <flag> [flag]...\r\n", arg2 );
         return;
      }
      while( argument[0] != '\0' )
      {
         argument = one_argument( argument, arg3 );
         value = get_flag( arg3, ris_flags, RIS_MAX );
         if( value < 0 || value >= RIS_MAX )
            ch_printf( ch, "Unknown %s: %s\r\n", arg2, arg3 );
         else
         {
            if( !str_cmp( arg2, "resist" ) )
            {
               xTOGGLE_BIT( race->resist, value );
               resist = true;
            }
            else
               xTOGGLE_BIT( race->suscept, value );
         }
      }
      write_race_file( ra );
      ch_printf( ch, "Racial %s set.\r\n", resist ? "resistances" : "susceptabilities" );
      return;
   }

   if( !str_cmp( arg2, "language" ) )
   {
      while( argument && argument[0] != '\0' )
      {
         argument = one_argument( argument, arg3 );
         value = get_langflag( arg3 );
         v2 = get_langnum( arg3 );

         if( v2 == -1 || value == LANG_UNKNOWN )
         {
            ch_printf( ch, "Unknown language: %s\r\n", arg3 );
            continue;
         }
         else
         {
            if( !( value &= VALID_LANGS ) )
            {
               ch_printf( ch, "Player races may not speak %s.\r\n", arg3 );
               continue;
            }
         }
         TOGGLE_BIT( race->language, 1 << v2 );
      }
      write_race_file( ra );
      send_to_char( "Racial language set.\r\n", ch );
      return;
   }

   if( !str_cmp( arg2, "classes" ) )
   {
      bool modified = false;

      while( argument && argument[0] != '\0' )
      {
         argument = one_argument( argument, arg1 );
         for( i = 0; i < MAX_PC_CLASS && class_table[i]; i++ )
         {
            if( !class_table[i] || !class_table[i]->name )
               continue;
            if( !str_cmp( arg1, class_table[i]->name ) )
            {
               xTOGGLE_BIT( race->class_restriction, i );
               modified = true;
            }
         }
      }
      if( !modified )
         send_to_char( "No such class.\r\n", ch );
      else
      {
         write_race_file( ra );
         send_to_char( "Race Classes set.\r\n", ch );
      }
      return;
   }

   /* Check stats for matches */
   for( stat = 0; stat < STAT_MAX; stat++ )
   {
      if( !str_cmp( arg2, stattypes[stat] ) )
      {
         race->base_stats[stat] = ( short )atoi( argument );
         write_race_file( ra );
         ch_printf( ch, "%s set to %d\r\n", capitalize( stattypes[stat] ), atoi( argument ) );
         return;
      }
   }

   if( !str_cmp( arg2, "max" ) )
   {
      argument = one_argument( argument, arg2 );

      /* Check stats for matches */
      for( stat = 0; stat < STAT_MAX; stat++ )
      {
         if( !str_cmp( arg2, stattypes[stat] ) )
         {
            race->max_stats[stat] = ( short )URANGE( 0, atoi( argument ), MAX_LEVEL );
            write_race_file( ra );
            ch_printf( ch, "Max %s set to %d\r\n", capitalize( stattypes[stat] ), atoi( argument ) );
            return;
         }
      }
      send_to_char( "No such max stat to set.\r\n", ch );
      return;
   }

   if( !str_cmp( arg2, "hit" ) )
      race->hit = ( short )atoi( argument );
   else if( !str_cmp( arg2, "mana" ) )
      race->mana = ( short )atoi( argument );
   else if( !str_cmp( arg2, "move" ) )
      race->move = ( short )atoi( argument );
   else if( !str_cmp( arg2, "uses" ) )
   {
      if( is_number( argument ) )
      {
         int tval = atoi( argument );
         if( tval < 0 || tval > 2 )
         {
            send_to_char( "You may only set uses to 0 = Nothing, 1 = Mana, 2 = Blood.\r\n", ch );
            return;
         }
         race->uses = tval;
      }
      else if( !str_cmp( argument, "Mana" ) )
         race->uses = 1;
      else if( !str_cmp( argument, "Blood" ) )
         race->uses = 2;
      else if( !str_cmp( argument, "Nothing" ) )
         race->uses = 0;
      else
      {
         send_to_char( "You may only set uses to 0 = Nothing, 1 = Mana, 2 = Blood.\r\n", ch );
         return;
      }
      write_race_file( ra );
      ch_printf( ch, "Uses set to %s\r\n",
         race->uses == 0 ? "Nothing" : race->uses == 1 ? "Mana" : race->uses == 2 ? "Blood" : "Unknown" );
      return;
   }
   else if( !str_cmp( arg2, "ac" ) )
      race->ac_plus = atoi( argument );
   else if( !str_cmp( arg2, "alignment" ) )
      race->alignment = atoi( argument );
   else if( !str_cmp( arg2, "minalign" ) )
      race->minalign = atoi( argument );
   else if( !str_cmp( arg2, "maxalign" ) )
      race->maxalign = atoi( argument );
   else if( !str_cmp( arg2, "minheight" ) )
      race->minheight = atoi( argument );
   else if( !str_cmp( arg2, "maxheight" ) )
      race->maxheight = atoi( argument );
   else if( !str_cmp( arg2, "minweight" ) )
      race->minweight = atoi( argument );
   else if( !str_cmp( arg2, "maxweight" ) )
      race->maxweight = atoi( argument );
   else if( !str_cmp( arg2, "thirst" ) )
      race->thirst_mod = atoi( argument );
   else if( !str_cmp( arg2, "hunger" ) )
      race->hunger_mod = atoi( argument );
   else if( !str_cmp( arg2, "recall" ) )
      race->race_recall = atoi( argument );
   else
   {
      do_setrace( ch, "" );
      return;
   }
   write_race_file( ra );
   ch_printf( ch, "%s changed and race saved.\r\n", arg2 );
}

CMDF( do_races )
{
   int ra, count;
   RACE_TYPE *Race;

   for( ra = 0, count = ( is_immortal( ch ) ? 0 : 1 ); ra < MAX_PC_RACE; ra++, count++ )
   {
      if( !( Race = race_table[ra] ) || !Race->name )
         continue;
      pager_printf( ch, "&w[&W%2d&w] &W%s&D\r\n", count, Race->name );
   }
}

bool load_race_file( const char *fname )
{
   char buf[MSL], *infoflags, flag[MIL];
   const char *word;
   bool fMatch;
   struct race_type *race;
   FILE *fp;
   int i, wear = 0, value, ra = -1, stat;

   snprintf( buf, sizeof( buf ), "%s%s", RACE_DIR, fname );
   if( !( fp = fopen( buf, "r" ) ) )
   {
      perror( buf );
      return false;
   }

   CREATE( race, struct race_type, 1 );
   race->uses = 0;
   for( i = 0; i < MAX_WHERE_NAME; ++i )
      race->where_name[i] = NULL;
   for( stat = 0; stat < STAT_MAX; stat++ )
   {
      race->base_stats[stat] = 13;
      race->max_stats[stat] = MAX_LEVEL;
   }

   for( ra = 0; ra < MAX_RACE; ra++ )
   {
      if( !race_table[ra] )
         break;
   }
   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( "Align", race->alignment, fread_number( fp ) );
            KEY( "AC_Plus", race->ac_plus, fread_number( fp ) );
            WEXTKEY( "Affected", race->affected, fp, a_flags, AFF_MAX );
            break;

         case 'C':
            if( !str_cmp( word, "Classes" ) )
            {
               int iclass;

               infoflags = fread_flagstring( fp );
               while( infoflags && infoflags[0] != '\0' )
               {
                  infoflags = one_argument( infoflags, flag );
                  for( iclass = 0; iclass < MAX_PC_CLASS; iclass++ )
                  {
                     if( !class_table[iclass] || !class_table[iclass]->name )
                        continue;
                     if( !str_cmp( class_table[iclass]->name, flag ) )
                     {
                        xSET_BIT( race->class_restriction, iclass );
                        break;
                     }
                  }
               }
               fMatch = true;
               break;
            }
            break;

         case 'E':
            if( !str_cmp( word, "End" ) )
            {
               fclose( fp );
               fp = NULL;
               if( ra < 0 || ra >= MAX_RACE )
               {
                  bug( "%s: Race (%s) bad/not found (%d)", __FUNCTION__, race->name ? race->name : "name not found", ra );
                  STRFREE( race->name );
                  for( i = 0; i < MAX_WHERE_NAME; ++i )
                     STRFREE( race->where_name[i] );
                  DISPOSE( race );
                  return false;
               }
               race_table[ra] = race;
               race_table[ra]->rlistplace = ra;
               return true;
            }
            break;

         case 'F':
            if( !str_cmp( word, "FMana" ) )
            {
               int iclass = fread_number( fp );
               if( iclass )
                  race->uses = 1;
               fMatch = true;
               break;
            }
            if( !str_cmp( word, "FBlood" ) )
            {
               int iclass = fread_number( fp );
               if( iclass )
                  race->uses = 2;
               fMatch = true;
               break;
            }
            break;

         case 'H':
            KEY( "Hit", race->hit, fread_number( fp ) );
            KEY( "Hunger_Mod", race->hunger_mod, fread_number( fp ) );
            break;

         case 'L':
            KEY( "Language", race->language, fread_number( fp ) );
            break;

         case 'M':
            KEY( "MinHeight", race->minheight, fread_number( fp ) );
            KEY( "MaxHeight", race->maxheight, fread_number( fp ) );
            KEY( "MinWeight", race->minweight, fread_number( fp ) );
            KEY( "MaxWeight", race->maxweight, fread_number( fp ) );
            KEY( "Mana", race->mana, fread_number( fp ) );
            KEY( "Move", race->move, fread_number( fp ) );
            KEY( "Min_Align", race->minalign, fread_number( fp ) );
            KEY( "Max_Align", race->maxalign, fread_number( fp ) );
            if( !str_cmp( word, "MaxStats" ) )
            {
               for( stat = 0; stat < STAT_MAX; stat++ )
                  race->max_stats[stat] = fread_number( fp );
               fMatch = true;
               break;
            }
            break;

         case 'N':
            KEY( "Name", race->name, fread_string( fp ) );
            break;

         case 'R':
            KEY( "Race_Recall", race->race_recall, fread_number( fp ) );
            WEXTKEY( "Resist", race->resist, fp, ris_flags, RIS_MAX );
            break;

         case 'S':
            if( !str_cmp( word, "Stats" ) )
            {
               for( stat = 0; stat < STAT_MAX; stat++ )
                  race->base_stats[stat] = fread_number( fp );
               fMatch = true;
               break;
            }
            WEXTKEY( "Suscept", race->suscept, fp, ris_flags, RIS_MAX );
            if( !str_cmp( word, "Skill" ) )
            {
               int sn, lev, adp;

               word = fread_word( fp );
               lev = fread_number( fp );
               adp = fread_number( fp );
               sn = skill_lookup( word );
               if( !is_valid_sn( sn ) )
                  bug( "%s: Skill %s unknown", __FUNCTION__, word );
               else
               {
                  if( ra >= 0 && ra < MAX_RACE )
                  {
                     skill_table[sn]->race_level[ra] = lev;
                     skill_table[sn]->race_adept[ra] = adp;
                  }
               }
               fMatch = true;
               break;
            }
            break;

         case 'T':
            KEY( "Thirst_Mod", race->thirst_mod, fread_number( fp ) );
            break;

         case 'U':
            KEY( "Uses", race->uses, fread_number( fp ) );
            break;

         case 'W':
            if( !str_cmp( word, "WhereName" ) )
            {
               wear = fread_number( fp );

               if( wear < MAX_WHERE_NAME )
               {
                  STRFREE( race->where_name[wear] );
                  race->where_name[wear] = fread_string( fp );
               }
               else
               {
                  bug( "%s: Too many where_names", __FUNCTION__ );
                  fread_flagstring( fp );
               }
               fMatch = true;
               break;
            }
            break;
      }

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

/* Load in all the race files. */
void load_races( void )
{
   FILE *fp;
   const char *filename;
   int i;

   MAX_PC_RACE = 0;

   for( i = 0; i < MAX_RACE; i++ )
      race_table[i] = NULL;

   if( !( fp = fopen( RACE_LIST, "r" ) ) )
   {
      perror( RACE_LIST );
      exit( 1 );
   }

   for( ;; )
   {
      filename = feof( fp ) ? "$" : fread_word( fp );
      if( filename[0] == '$' || filename[0] == EOF )
         break;

      if( !load_race_file( filename ) )
         bug( "%s: Can't load race file: %s", __FUNCTION__, filename );
      else
         MAX_PC_RACE++;
   }
   fclose( fp );
   fp = NULL;
}

void save_races( void )
{
   int x;

   for( x = 0; x < MAX_PC_RACE; x++ )
      if( race_table[x] && race_table[x]->name )
         write_race_file( x );
}