WTFaq - Adding races...

Files needed:

  structs.h -- defining races, adding pfile stuff, etc
  utils.h -- get_race defines
  class.c -- race abrevations, race select, abilities
  constants.c -- race select, apply_race
  db.c -- get_race defines
  handler.c -- eq defines
  interpreter.c -- select race defines
Editors Note: Races are very complex, and this tutorial will only detail the simple aspects of race defines and newbie race selection.

First, let's open up the header (.h) files, as they are very important to the functioning of the program (.c) files.

OPEN EDIT FILE: structs.h
Search for

/* NPC classes (currently unused - feel free to implement!) */
#define CLASS_OTHER       0
#define CLASS_UNDEAD      1
#define CLASS_HUMANOID    2
#define CLASS_ANIMAL      3
#define CLASS_DRAGON      4
#define CLASS_GIANT       5
Right below it, put the following
/* PC races */              
#define RACE_UNDEFINED   -1 
#define RACE_HUMAN        0 
#define RACE_ELF          1 
#define RACE_GNOME        2 
#define RACE_FAIRY        3 

#define NUM_RACES         4 
Now search for
#define CON_DELCNF1      15             /* Delete confirmation 1        */
#define CON_DELCNF2      16             /* Delete confirmation 2        */
Right below it, put the following
#define CON_QRACE        17             /* Race?                        */ 
Now search for
#define ITEM_ANTI_WARRIOR  (1 << 15)    /* Not usable by warriors       */
#define ITEM_NOSELL        (1 << 16)    /* Shopkeepers won't touch it   */
Right below it, put the following
#define ITEM_ANTI_HUMAN    (1 << 17)    /* Not usable by Humans   */ 
#define ITEM_ANTI_ELF      (1 << 18)    /* Not usable by Elves    */ 
#define ITEM_ANTI_GNOME    (1 << 19)    /* Not usable by Gnomes   */ 
#define ITEM_ANTI_FAIRY    (1 << 20)    /* Not usable by Fairies  */ 
Now search for
#define APPLY_SAVING_BREATH    23       /* Apply to save throw: breath  */
#define APPLY_SAVING_SPELL     24       /* Apply to save throw: spells  */
Right below it, put the following
#define APPLY_RACE             25       /* Reserved                     */ 
Now search for
/* general player-related info, usually PC's and NPC's */
struct char_player_data {
   char *name;         /* PC / NPC s name (kill ...  )         */
   char *short_descr;  /* for NPC 'actions'                    */
   char *long_descr;   /* for 'look'                           */
   char *description;  /* Extra descriptions                   */
   char *title;        /* PC / NPC's title                     */
   byte sex;           /* PC / NPC's sex                       */
   byte class;         /* PC / NPC's class                     */
Right below it, between byte class and the next variable, put the following
   byte race;          /* PC / NPC's race                      */ 
Lastly search for
struct char_file_u {
   /* char_player_data */
   char name[MAX_NAME_LENGTH+1];
   char description[EXDSCR_LENGTH];
   char title[MAX_TITLE_LENGTH+1];
   byte sex;
   byte class;
Right below it, between byte class and the next variable, put the following
  byte race;

CLOSE EDIT FILE: structs.h
OPEN EDIT FILE: utils.h

Search for

#define GET_REAL_LEVEL(ch) \
   (ch->desc && ch->desc->original ? GET_LEVEL(ch->desc->original) : \
    GET_LEVEL(ch))

#define GET_CLASS(ch)   ((ch)->player.class)
Right below it, put the following
#define GET_RACE(ch)    ((ch)->player.race)  
Lastly search for
#define IS_THIEF(ch)            (!IS_NPC(ch) && \
                                (GET_CLASS(ch) == CLASS_THIEF))
#define IS_WARRIOR(ch)          (!IS_NPC(ch) && \
                                (GET_CLASS(ch) == CLASS_WARRIOR))
Right below it, put the following
#define IS_HUMAN(ch)            (!IS_NPC(ch) && \             
                                (GET_RACE(ch) == RACE_HUMAN)) 
                                                              
#define IS_ELF(ch)              (!IS_NPC(ch) && \             
                                (GET_RACE(ch) == RACE_ELF))   
                                                              
#define IS_GNOME(ch)            (!IS_NPC(ch) && \             
                                (GET_RACE(ch) == RACE_GNOME)) 
                                                              
#define IS_FAIRY(ch)            (!IS_NPC(ch) && \             
                                (GET_RACE(ch) == RACE_FAIRY)) 
CLOSE EDIT FILE: utils.h
Now, lets go on to the .C files, the core of it all.
OPEN EDIT FILE: class.c Search for
const char *pc_class_types[] = {
  "Magic User",
  "Cleric",
  "Thief",
  "Warrior",
  "\n"
  };
Then put the following below it
const char *race_abbrevs[] = {   
  "Hum",                         
  "Elf",                         
  "Gno",                         
  "Fai",                         
  "\n"                           
};                               
                                 
const char *pc_race_types[] = {  
  "Human",                       
  "Elf",                         
  "Gnome",                       
  "Fairy",                       
  "\n"
};
Now search for
"Select a class:\r\n"
"  [C]leric\r\n"
"  [T]hief\r\n"
"  [W]arrior\r\n"
"  [M]agic-user\r\n";
And put the following right below it
/* The menu for choosing a race in interpreter.c: */ 
const char *race_menu =                              
"\r\n"                                               
"Select a race:\r\n"                                 
"  [H]uman\r\n"                                      
"  [E]lf\r\n"
"  [G]nome\r\n"
"  [F]airy\r\n";
Now search for
  case 'w':
    return CLASS_WARRIOR;
    break;
  case 't':
    return CLASS_THIEF;
    break;
  default:
    return CLASS_UNDEFINED;
    break;
  }
}
And block copy the following below it
/*                                                                   
 * The code to interpret a race letter (used in interpreter.c when a 
 * new character is selecting a race).                               
 */                                                                  
int parse_race(char arg)                                             
{                                                                    
  arg = LOWER(arg);                                                  
                                                                     
  switch (arg) {                                                     
  case 'h':                                                          
    return RACE_HUMAN;                                               
    break;                                                           
  case 'e':                                                          
    return RACE_ELF;                                                 
    break;                                                           
  case 'g':                                                          
    return RACE_GNOME;                                               
    break;                                                           
  case 'f':                                                          
    return RACE_FAIRY;                                               
    break;                                                           
  default:                                                           
    return RACE_UNDEFINED;                                           
    break;                                                           
  }                                                                  
}                                                                    
Now search for
    case 't':
      return 4;
      break;
    case 'w':
      return 8;
      break;
    default:
      return 0;
      break;
  }
}
Then place the following below it
long find_race_bitvector(char arg)  {

  arg = LOWER(arg);                                                  
                                                                     
  switch (arg) {                                                     
    case 'h':                                                        
      return 1;                                                      
      break;                                                         
    case 'e':                                                        
      return 2;                                                      
      break;                                                         
    case 'g':                                                        
      return 4;                                                      
      break;                                                         
    case 'f':                                                        
      return 8;                                                      
      break;                                                         
    default:                                                         
      return 0;                                                      
      break;                                                         
  }                                                                  
}                                                                    
Now replace the description of void roll_real_abils(...) with this one
/*
 * Roll the 6 stats for a character... each stat is made of the sum of
 * the best 3 out of 4 rolls of a 6-sided die.  Each class then decides
 * which priority will be given for the best to worst stats.  Race also 
 * affects stats.                                                       
 */
Now search for
  case CLASS_WARRIOR:
    ch->real_abils.str = table[0];
    ch->real_abils.dex = table[1];
    ch->real_abils.con = table[2];
    ch->real_abils.wis = table[3];
    ch->real_abils.intel = table[4];
    ch->real_abils.cha = table[5];
    if (ch->real_abils.str == 18)
      ch->real_abils.str_add = number(0, 100);
    break;
  }
And directly<!> below it, put this
  switch (GET_RACE(ch)) {                       
  case RACE_HUMAN:                              
  ++ch->real_abils.con;                         
  break;                                        
  case RACE_ELF:                                
  ++ch->real_abils.dex;                         
  ++ch->real_abils.intel;                       
  --ch->real_abils.con;                         
  --ch->real_abils.str;                         
  break;                                        
  case RACE_GNOME:                              
  ch->real_abils.str+=3;                        
  --ch->real_abils.dex;                         
  --ch->real_abils.intel;                       
  --ch->real_abils.cha;                         
  break;                                        
  case RACE_FAIRY:                              
  ch->real_abils.dex+=2;                        
  ++ch->real_abils.wis;                         
  ++ch->real_abils.cha;                         
  ch->real_abils.str-=2;                        
  --ch->real_abils.con;                         
  break;                                        
 }                                              
You should have still left the end of the function the same though, like:
  ch->aff_abils = ch->real_abils;               
}                                               
Okay, now, at the very END of the file, add this function
int invalid_race(struct char_data *ch, struct obj_data *obj) {   
  if ((IS_OBJ_STAT(obj, ITEM_ANTI_HUMAN) && IS_HUMAN(ch)) ||     
      (IS_OBJ_STAT(obj, ITEM_ANTI_ELF)   && IS_ELF(ch)) ||       
      (IS_OBJ_STAT(obj, ITEM_ANTI_GNOME) && IS_GNOME(ch)) ||     
      (IS_OBJ_STAT(obj, ITEM_ANTI_FAIRY) && IS_FAIRY(ch)))       
        return 1;                                                
  else                                                           
        return 0;                                                
}                                                                
CLOSE EDIT FILE: class.c
OPEN EDIT FILE: constants.c

Search for

/* CON_x */
const char *connected_types[] = {
  "Playing",
  "Disconnecting",
  "Get name",
  "Confirm name",
  "Get password",
  "Get new PW",
  "Confirm new PW",
  "Select sex",
  "Select class",
  "Reading MOTD",
  "Main Menu",
  "Get descript.",
  "Changing PW 1",
  "Changing PW 2",
  "Changing PW 3",
  "Self-Delete 1",
  "Self-Delete 2",
And add the following below it
  "Select race",
Lastly search for
/* APPLY_x */
const char *apply_types[] = {
  "NONE",
  "STR",
  "DEX",
  "INT",
  "WIS",
  "CON",
  "CHA",
  "CLASS",
  "LEVEL",
  "AGE",
  "CHAR_WEIGHT",
  "CHAR_HEIGHT",
  "MAXMANA",
  "MAXHIT",
  "MAXMOVE",
  "GOLD",
  "EXP",
  "ARMOR",
  "HITROLL",
  "DAMROLL",
  "SAVING_PARA",
  "SAVING_ROD",
  "SAVING_PETRI",
  "SAVING_BREATH",
  "SAVING_SPELL",
And add the following below it
  "RACE",

CLOSE EDIT FILE: constant.c
OPEN EDIT FILE: db.c

Search for

    mob_proto[i].player.sex = t[2];

    mob_proto[i].player.class = 0;
Then put this after it
    mob_proto[i].player.race = 0;  
Now search for
  GET_SEX(ch) = st->sex;
  GET_CLASS(ch) = st->class;
Then add the following below it
  GET_RACE(ch) = st->race;              
Lastly search for
  st->sex = GET_SEX(ch);
  st->class = GET_CLASS(ch);
Then put this below it
  st->race = GET_RACE(ch);
CLOSE EDIT FILE: db.c
OPEN EDIT FILE: handler.c

Search for

  case APPLY_SAVING_SPELL:
    GET_SAVE(ch, SAVING_SPELL) += mod;
    break;
Put the following below it
  case APPLY_RACE:              
    /* ??? GET_RACE(ch) += mod; */
    break;
Now search for
void equip_char(struct char_data * ch, struct obj_data * obj, int pos)
{
  int j;
  int invalid_class(struct char_data *ch, struct obj_data *obj);
And add this below it
  int invalid_race(struct char_data *ch, struct obj_data *obj);  
Lastly search for the following
  if ((IS_OBJ_STAT(obj, ITEM_ANTI_EVIL) && IS_EVIL(ch)) ||
      (IS_OBJ_STAT(obj, ITEM_ANTI_GOOD) && IS_GOOD(ch)) ||
      (IS_OBJ_STAT(obj, ITEM_ANTI_NEUTRAL) && IS_NEUTRAL(ch)) ||
      invalid_class(ch, obj)) {
and REPLACE the entire thing with the following:
  if ((IS_OBJ_STAT(obj, ITEM_ANTI_EVIL) && IS_EVIL(ch)) ||
      (IS_OBJ_STAT(obj, ITEM_ANTI_GOOD) && IS_GOOD(ch)) ||
      (IS_OBJ_STAT(obj, ITEM_ANTI_NEUTRAL) && IS_NEUTRAL(ch)) ||
      invalid_class(ch, obj) || invalid_race(ch, obj)) {   
CLOSE EDIT FILE: handler.c
OPEN EDIT FILE: interpreter.c

Search for

  extern const char *class_menu;
Then add this below it
  extern const char *race_menu;   
Now, search for (its right below it)
  sh_int load_room;

  int load_char(char *name, struct char_file_u * char_element);
  int parse_class(char arg);
And add this...
  int parse_race(char arg);     
Now replace the following two CASE statements for the ones that already exist (THIS IS VERY IMPORTANT!) completely erase old case CON_QCLASS, and add this one plus the additional new case CON_QRACE at the end of it...
  case CON_QCLASS:
    if ((GET_CLASS(d->character) = parse_class(*arg)) == CLASS_UNDEFINED) {
      SEND_TO_Q("\r\nThat's not a class.\r\nClass: ", d);
      return;
    }
    SEND_TO_Q(race_menu, d);         
    SEND_TO_Q("\r\nRace: ", d);      
    STATE(d) = CON_QRACE;            
    break;                           

  case CON_QRACE:                                                        
    if ((GET_RACE(d->character) = parse_race(*arg)) == CLASS_UNDEFINED) {
      SEND_TO_Q("\r\nThat's not a race.\r\nRace: ", d);                  
      return;                                                            
    }                                                                    
                                                                         
    if (GETPFILEPOS(d->character) < 0)
      GETPFILEPOS(d->character) = create_entry(GET_NAME(d->character);
    init_char(d->character);
    save_char(d->character, NOWHERE);
    SEND_TO_Q(motd, d);                                                  
    SEND_TO_Q("\r\n\n*** PRESS RETURN: ", d);                            
    STATE(d) = CON_RMOTD;                                                
                                                                         
    sprintf(buf, "%s [%s] new player.", GET_NAME(d->character), d->host);
    mudlog(buf, NRM, LVL_IMMORT, TRUE);                                  
    break;                                                               
CLOSE EDIT FILE: interpreter.c

- Nick C.