rasputin/bin/
rasputin/cnf/
rasputin/doc/cwg/
rasputin/lib/
rasputin/lib/etc/
rasputin/lib/etc/boards/
rasputin/lib/house/
rasputin/lib/misc/
rasputin/lib/plralias/A-E/
rasputin/lib/plralias/F-J/
rasputin/lib/plralias/K-O/
rasputin/lib/plralias/P-T/
rasputin/lib/plralias/U-Z/
rasputin/lib/plralias/ZZZ/
rasputin/lib/plrfiles/
rasputin/lib/plrfiles/A-E/
rasputin/lib/plrfiles/F-J/
rasputin/lib/plrfiles/K-O/
rasputin/lib/plrfiles/P-T/
rasputin/lib/plrfiles/U-Z/
rasputin/lib/plrfiles/ZZZ/
rasputin/lib/plrobjs/
rasputin/lib/plrobjs/A-E/
rasputin/lib/plrobjs/F-J/
rasputin/lib/plrobjs/K-O/
rasputin/lib/plrobjs/P-T/
rasputin/lib/plrobjs/U-Z/
rasputin/lib/plrobjs/ZZZ/
rasputin/lib/plrvars/A-E/
rasputin/lib/plrvars/F-J/
rasputin/lib/plrvars/K-O/
rasputin/lib/plrvars/P-T/
rasputin/lib/plrvars/U-Z/
rasputin/lib/plrvars/ZZZ/
rasputin/lib/world/gld/
rasputin/lib/world/trg/
rasputin/src/
rasputin/src/doc/
/* ************************************************************************
*   File: class.c                                       Part of CircleMUD *
*  Usage: Source file for class-specific code                             *
*                                                                         *
*  All rights reserved.  See license.doc for complete information.        *
*                                                                         *
*  Copyright (C) 1993, 94 by the Trustees of the Johns Hopkins University *
*  CircleMUD is based on DikuMUD, Copyright (C) 1990, 1991.               *
************************************************************************ */

/*
 * This file attempts to concentrate most of the code which must be changed
 * in order for new classes to be added.  If you're adding a new class,
 * you should go through this entire file from beginning to end and add
 * the appropriate new special cases for your new class.
 */



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

CVSHEADER("$CVSHeader: cwg/rasputin/src/class.c,v 1.31 2005/05/29 18:36:38 zizazat Exp $");

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

extern void racial_ability_modifiers(struct char_data *ch);
extern void set_height_and_weight_by_race(struct char_data *ch);

extern struct spell_info_type spell_info[];

/* local functions */
void snoop_check(struct char_data *ch);
int parse_class(struct char_data *ch, char arg);
int num_attacks(struct char_data *ch, int offhand);
void roll_real_abils(struct char_data *ch);
void do_start(struct char_data *ch);
int backstab_dice(struct char_data *ch);
int invalid_class(struct char_data *ch, struct obj_data *obj);
int level_exp(int level);
byte object_saving_throws(int material_type, int type);
int load_levels();
void assign_auto_stats(struct char_data *ch);

/* Names first */

const char *class_abbrevs[] = {
  "Wi",
  "Cl",
  "Ro",
  "Fi",
  "Mo",
  "Pa",
  "Ex",
  "Ad",
  "Co",
  "Ar",
  "Wa",
  "\n"
};


/* Copied from the SRD under OGL, see ../doc/srd.txt for information */
const char *pc_class_types[] = {
  "Wizard",
  "Cleric",
  "Rogue",
  "Fighter",
  "Monk",
  "Paladin",
  "Expert",
  "Adept",
  "Commoner",
  "Aristrocrat",
  "Warrior",
  "\n"
};

/* Copied from the SRD under OGL, see ../doc/srd.txt for information */
const char *class_names[] = {
  "wizard",
  "cleric",
  "rogue",
  "fighter",
  "monk",
  "paladin",
  "artisan",
  "magi",
  "normal",
  "noble",
  "soldier",
  "\n"
};


/* The menu for choosing a class in interpreter.c: */
const char *class_display[NUM_CLASSES] = {
  "@B1@W) @MWizard\r\n",
  "@B2@W) @WCleric\r\n",
  "@B3@W) @YRogue\r\n",
  "@B4@W) @BFighter\r\n"
  "@B5@W) @BMonk\r\n"
  "@B6@W) @BPaladin\r\n"
  "Artisan NPC\r\n",
  "Magi NPC\r\n",
  "Normal NPC\r\n",
  "Noble NPC\r\n",
  "Soldier NPC\r\n",
};

#define Y   TRUE
#define N   FALSE

/* Some races copied from the SRD under OGL, see ../doc/srd.txt for information */
int class_ok_race[NUM_RACES][NUM_CLASSES] = {
  /*                 W, C, R, F, M, P, A, M, N, N, S */
  /* Human      */ { Y, Y, Y, Y, Y, Y, N, N, N, N, N },
  /* Elf        */ { Y, Y, Y, Y, Y, Y, N, N, N, N, N },
  /* Gnome      */ { Y, Y, Y, Y, Y, Y, N, N, N, N, N },
  /* Dwarf      */ { Y, Y, Y, Y, Y, Y, N, N, N, N, N },
  /* Half Elf   */ { Y, Y, Y, Y, Y, Y, N, N, N, N, N },
  /* Halfling   */ { Y, Y, Y, Y, Y, Y, N, N, N, N, N },
  /* Drow Elf   */ { Y, Y, Y, Y, Y, Y, N, N, N, N, N },
  /* Half Orc   */ { Y, Y, Y, Y, Y, Y, N, N, N, N, N },
  /* Animal     */ { N, N, N, N, N, N, N, N, N, N, N },
  /* Construct  */ { N, N, N, N, N, N, N, N, N, N, N },
  /* Demon      */ { N, N, N, N, N, N, N, N, N, N, N },
  /* Dragon     */ { N, N, N, N, N, N, N, N, N, N, N },
  /* Fish       */ { N, N, N, N, N, N, N, N, N, N, N },
  /* Giant      */ { N, N, N, N, N, N, N, N, N, N, N },
  /* Goblin     */ { N, N, N, N, N, N, N, N, N, N, N },
  /* Insect     */ { N, N, N, N, N, N, N, N, N, N, N },
  /* Orc        */ { N, N, N, N, N, N, N, N, N, N, N },
  /* Snake      */ { N, N, N, N, N, N, N, N, N, N, N },
  /* Troll      */ { N, N, N, N, N, N, N, N, N, N, N },
  /* Minotaur   */ { N, N, N, N, N, N, N, N, N, N, N },
  /* Kobold     */ { N, N, N, N, N, N, N, N, N, N, N },
  /* Mindflayer */ { N, N, N, N, N, N, N, N, N, N, N },
  /* Warhost    */ { N, N, N, N, N, N, N, N, N, N, N },
  /* Faerie     */ { N, N, N, N, N, N, N, N, N, N, N }
};

/* Adapted from the SRD under OGL, see ../doc/srd.txt for information */
int class_ok_align[9][NUM_CLASSES] = {
/*         M, C, T, W, M, P, A, M, N, N, S */
/* LG */ { Y, Y, Y, Y, Y, Y, Y, Y, Y, Y, Y },
/* NG */ { Y, Y, Y, Y, N, N, Y, Y, Y, Y, Y },
/* CG */ { Y, Y, Y, Y, N, N, Y, Y, Y, Y, Y },
/* LN */ { Y, Y, Y, Y, Y, N, Y, Y, Y, Y, Y },
/* NN */ { Y, Y, Y, Y, N, N, Y, Y, Y, Y, Y },
/* CN */ { Y, Y, Y, Y, N, N, Y, Y, Y, Y, Y },
/* LE */ { Y, Y, Y, Y, Y, N, Y, Y, Y, Y, Y },
/* NE */ { Y, Y, Y, Y, N, N, Y, Y, Y, Y, Y },
/* CE */ { Y, Y, Y, Y, N, N, Y, Y, Y, Y, Y }
};

/* Adapted from the SRD under OGL, see ../doc/srd.txt for information */
int favored_class[NUM_RACES] = {
/* -1 means highest class is considered favored */
/* Human      */ -1,
/* Elf        */ CLASS_WIZARD,
/* Gnome      */ CLASS_WIZARD,
/* Dwarf      */ CLASS_FIGHTER,
/* Half Elf   */ -1,
/* Halfling   */ CLASS_ROGUE,
/* Drow Elf   */ CLASS_WIZARD,
/* Half Orc   */ CLASS_FIGHTER,
/* Animal     */ -1,
/* Construct  */ -1,
/* Demon      */ -1,
/* Dragon     */ -1,
/* Fish       */ -1,
/* Giant      */ -1,
/* Goblin     */ -1,
/* Insect     */ -1,
/* Orc        */ -1,
/* Snake      */ -1,
/* Troll      */ -1,
/* Minotaur   */ -1,
/* Kobold     */ -1,
/* Mindflayer */ -1,
/* Warhost    */ -1,
/* Faerie     */ -1
};

/* Adapted from the SRD under OGL, see ../doc/srd.txt for information */
int prestige_classes[NUM_CLASSES] = {
/* WIZARD	*/ N,
/* CLERIC	*/ N,
/* ROGUE	*/ N,
/* FIGHTER	*/ N,
/* MONK		*/ N,
/* PALADIN	*/ N,
/* ARTISAN	*/ N,
/* MAGI		*/ N,
/* NORMAL	*/ N,
/* NOBLE	*/ N,
/* SOLDIER	*/ N
};

/* Adapted from the SRD under OGL, see ../doc/srd.txt for information */
/* -1 indicates no limit to the number of levels in this class under
 * epic rules */
int class_max_ranks[NUM_CLASSES] = {
/* WIZARD	*/ -1,
/* CLERIC	*/ -1,
/* ROGUE	*/ -1,
/* FIGHTER	*/ -1,
/* MONK		*/ -1,
/* PALADIN	*/ -1,
/* ARTISAN	*/ -1,
/* MAGI		*/ -1,
/* NORMAL	*/ -1,
/* NOBLE	*/ -1,
/* SOLDIER	*/ -1,
};


/*
 * The code to interpret a class letter -- used in interpreter.c when a
 * new character is selecting a class and by 'set class' in act.wizard.c.
 */

int parse_class(struct char_data *ch, char arg)
{
  int chclass = CLASS_UNDEFINED;

  switch (arg) {
  case '1': chclass = CLASS_WIZARD; break;
  case '2': chclass = CLASS_CLERIC; break;
  case '3': chclass = CLASS_ROGUE; break;
  case '4': chclass = CLASS_FIGHTER; break;
  case '5': chclass = CLASS_MONK; break;
  case '6': chclass = CLASS_PALADIN; break;
  default:  chclass = CLASS_UNDEFINED; break;
  }
  if (chclass >= 0 && chclass < NUM_CLASSES)
    if (!class_ok_race[(int)GET_RACE(ch)][chclass])
      chclass = CLASS_UNDEFINED;

  return (chclass);
}


/*
 * Is anything preventing this character from advancing in this class?
 */
int class_ok_general(struct char_data *ch, int whichclass)
{
  if (whichclass < 0 || whichclass >= NUM_CLASSES) {
    log("Invalid class %d in class_ok_general", whichclass);
    return 0;
  }
  if (!class_ok_race[(int)GET_RACE(ch)][whichclass])
    return 0;
  if (!class_ok_align[ALIGN_TYPE(ch)][whichclass])
    return 0;
  if (class_max_ranks[whichclass] > -1 &&
      GET_CLASS_RANKS(ch, whichclass) >= class_max_ranks[whichclass])
    return 0;
  switch (whichclass) {
  case CLASS_MONK:
    if (GET_CLASS_RANKS(ch, CLASS_MONK) && GET_CLASS(ch) != CLASS_MONK)
      return 0; /* You can't go back to monk if you were one and changed */
    else
  default:
    return 1; /* All other classes can be taken */
  }
}

/*
 * ...And the appropriate rooms for each guildmaster/guildguard; controls
 * which types of people the various guildguards let through.  i.e., the
 * first line shows that from room 3017, only WIZARDS are allowed
 * to go south.
 *
 * Don't forget to visit spec_assign.c if you create any new mobiles that
 * should be a guild master or guard so they can act appropriately. If you
 * "recycle" the existing mobs that are used in other guilds for your new
 * guild, then you don't have to change that file, only here.
 */
struct guild_info_type guild_info[] = {

/* Kortaal */
  { CLASS_WIZARD,	3017,	SCMD_EAST	},
  { CLASS_CLERIC,	3004,	SCMD_NORTH	},
  { CLASS_ROGUE,	3027,	SCMD_EAST	},
  { CLASS_FIGHTER,	3021,	SCMD_EAST	},

/* Brass Dragon */
  { -999 /* all */ ,	5065,	SCMD_WEST	},

/* this must go last -- add new guards above! */
  { -1, NOWHERE, -1}
};
/* 
 * These tables hold the various level configuration setting;
 * experience points, base hit values, character saving throws.
 * They are read from a configuration file (normally etc/levels)
 * as part of the boot process.  The function load_levels() at
 * the end of this file reads in the actual values.
 */
const char *config_sect[] = {
  "version",
  "experience",
  "vernum",
  "fortitude",
  "reflex",
  "will",
  "basehit",
  "\n"
};

#define CONFIG_LEVEL_VERSION	0
#define CONFIG_LEVEL_EXPERIENCE	1
#define CONFIG_LEVEL_VERNUM	2
#define CONFIG_LEVEL_FORTITUDE	3
#define CONFIG_LEVEL_REFLEX	4
#define CONFIG_LEVEL_WILL	5
#define CONFIG_LEVEL_BASEHIT	6

char level_version[READ_SIZE];
int level_vernum = 0;
int save_classes[SAVING_WILL][NUM_CLASSES];
int basehit_classes[NUM_CLASSES];
int exp_multiplier;

byte object_saving_throws(int material_type, int type)
{
  switch (type) {
  case SAVING_OBJ_IMPACT:
    switch (material_type) {
    case MATERIAL_GLASS:
      return 20;
    case MATERIAL_CERAMIC:
    case MATERIAL_WOOD:
      return 35;
    case MATERIAL_ORGANIC:
      return 40;
    case MATERIAL_IRON:
      return 60;
    case MATERIAL_LEATHER:
      return 70;
    case MATERIAL_SILVER:
    case MATERIAL_STEEL:
      return 85;
    case MATERIAL_MITHRIL:
    case MATERIAL_STONE:
      return 90;
    case MATERIAL_DIAMOND:
      return 95;
    default:
      return 50;
      break;
    }
  case SAVING_OBJ_HEAT:
    switch (material_type) {
    case MATERIAL_WOOL:
      return 15;
    case MATERIAL_PAPER:
      return 20;
    case MATERIAL_COTTON:
    case MATERIAL_SATIN:
    case MATERIAL_SILK:
    case MATERIAL_BURLAP:
    case MATERIAL_VELVET:
      return 25;
    case MATERIAL_ONYX:
    case MATERIAL_CURRENCY:
      return 45;
    case MATERIAL_GLASS:
      return 55;
    case MATERIAL_PLATINUM:
      return 75;
    case MATERIAL_ADAMANTINE:
      return 85;
    default:
      return 50;
      break;
    }
  case SAVING_OBJ_COLD:
    switch (material_type) {
    case MATERIAL_GLASS:
      return 35;
    case MATERIAL_ORGANIC:
    case MATERIAL_CURRENCY:
      return 45;
    default:
      return 50;
      break;
    }
  case SAVING_OBJ_BREATH:
    switch (material_type) {
    default:
      return 50;
      break;
    }
  case SAVING_OBJ_SPELL:
    switch (material_type) {
    default:
      return 50;
      break;
    }
  default:
    log("SYSERR: Invalid object saving throw type.");
    break;
  }
  /* Should not get here unless something is wrong. */
  return 100;
}


#define SAVE_MANUAL 0
#define SAVE_LOW 1
#define SAVE_HIGH 2

char *save_type_names[] = {
  "manual",
  "low",
  "high"
};

#define BASEHIT_MANUAL 0
#define BASEHIT_LOW     1
#define BASEHIT_MEDIUM  2
#define BASEHIT_HIGH    3

char *basehit_type_names[] = {
  "manual",
  "low",
  "medium",
  "high"
};

/*
 * This is for the new saving throws, that slowly go up as you level
 * If save_lev == 0, use the class tables
 * otherwise ignore the class and just determine the save for this level
 * of that type of save.
 */
int saving_throw_lookup(int save_lev, int chclass, int savetype, int level)
{
  if (level < 0) {
    log("SYSERR: Requesting saving throw for invalid level %d!", level);
    level = 0;
  }
  if (chclass >= NUM_CLASSES || chclass < 0) {
    log("SYSERR: Requesting saving throw for invalid class %d!", chclass);
    chclass = 0;
  }

  if (!save_lev)
    save_lev = save_classes[savetype][chclass];

  switch (save_lev) {
  case SAVE_MANUAL:
    log("Save undefined for class %s", pc_class_types[chclass]);
    return 0;
  case SAVE_LOW:
    return level / 3;
    break;
  case SAVE_HIGH:
    return (level / 2) + 2;
    break;
  default:
    log("Unknown save type %d in load_levels", save_lev);
    return 0;
    break;
  }
}


/* Base hit for classes and levels.
 * If hit_type == 0, use the class tables
 * otherwise ignore the class and just determine the base hit for this level
 * of that hit_type.
 */
int base_hit(int hit_type, int chclass, int level)
{
  if (level < 0 ) {
    log("SYSERR: Requesting base hit for invalid level %d!", level);
    level = 0;
  }
  if (chclass >= NUM_CLASSES || chclass < 0) {
    log("SYSERR: Requesting base hit for invalid class %d!", chclass);
    chclass = 0;
  }

  if (!hit_type)
    hit_type = basehit_classes[chclass];

  switch (hit_type) {
  case BASEHIT_MANUAL:
    log("Base hit undefined for class %s", pc_class_types[chclass]);
    return 0;
  case BASEHIT_LOW:
    return level / 2;
    break;
  case BASEHIT_MEDIUM:
    return level * 3 / 4;
    break;
  case BASEHIT_HIGH:
    return level;
    break;
  default:
    log("Unknown basehit type %d in load_levels", hit_type);
    return 0;
    break;
  }
}


/* Adapted from the SRD under OGL, see ../doc/srd.txt for information */
int num_attacks(struct char_data *ch, int offhand)
{
  int attk;
  if (offhand) {
    if (HAS_FEAT(ch, FEAT_GREATER_TWO_WEAPON_FIGHTING))
      return 3;
    if (HAS_FEAT(ch, FEAT_IMPROVED_TWO_WEAPON_FIGHTING))
      return 2;
    else
      return 1;
  }
  attk = (GET_ACCURACY_BASE(ch) + 4) / 5;
  attk = MIN(4, attk);
  attk = MAX(1, attk);
  return attk;
}


/*
 * Roll the 6 stats for a character... each stat is made of some combination
 * of 6-sided dice with various rolls discarded.  Each class then decides
 * which priority will be given for the best to worst stats.
 */
/* This character creation method is original */
void roll_real_abils(struct char_data *ch)
{
  int i, j, k, temp, temp2, total, count = 20;
  ubyte table[6];
  ubyte rolls[6];

  do {
    for (i = 0; i < 6; i++)
      table[i] = 0;
    for (i = 0; i < 6; i++) {
      for (j = 0; j < 6; j++)
        rolls[j] = rand_number(1, 6);

      switch (i) {
      case 0: /* We want one bad roll */
        /* Worst 3 out of 4 */
        temp = rolls[0] + rolls[1] + rolls[2] + rolls[3] -
               MAX(rolls[0], MAX(rolls[1], MAX(rolls[2], rolls[3])));
        break;
      case 5: /* We want one very good roll */
        /* Best 3 out of 4 + best of 2 */
        temp = rolls[0] + rolls[1] + rolls[2] + rolls[3] + rolls[4] + rolls[5] -
               (MIN(rolls[0], MIN(rolls[1], MIN(rolls[2], rolls[3]))) +
                MIN(rolls[4], rolls[5]));
        break;
      default: /* We want the rest to be 'better than average' */
        /* Best 3 out of 4 */
        temp = rolls[0] + rolls[1] + rolls[2] + rolls[3] -
               MIN(rolls[0], MIN(rolls[1], MIN(rolls[2], rolls[3])));
        break;
      }

      for (k = 0; k < 6; k++)
        if (table[k] < temp) {
	  temp2 = table[k];
	  table[k] = temp;
	  temp = temp2;
        }
    }

    switch (GET_CLASS(ch)) {
    case CLASS_WIZARD:
      ch->real_abils.intel = table[0];
      ch->real_abils.wis = table[1];
      ch->real_abils.dex = table[2];
      ch->real_abils.con = table[3];
      switch (GET_RACE(ch)) {
        case RACE_GNOME:
        case RACE_HALFLING:
        case RACE_HALF_ORC:
          ch->real_abils.cha = table[4];
          ch->real_abils.str = table[5];
          break;
        default:
          ch->real_abils.str = table[4];
          ch->real_abils.cha = table[5];
      }
      break;
    case CLASS_CLERIC:
      ch->real_abils.wis = table[0];
      switch (GET_RACE(ch)) {
        case RACE_DWARF:
        case RACE_HALF_ORC:
          ch->real_abils.str = table[1];
          ch->real_abils.cha = table[2];
          ch->real_abils.dex = table[4];
          ch->real_abils.intel = table[5];
        break;
        case RACE_HALFLING:
        case RACE_ELF:
          ch->real_abils.cha = table[1];
          ch->real_abils.str = table[2];
          ch->real_abils.dex = table[4];
          ch->real_abils.intel = table[5];
        default:
          ch->real_abils.cha = table[1];
          ch->real_abils.str = table[2];
          ch->real_abils.intel = table[4];
          ch->real_abils.dex = table[5];
        break;
      }
      ch->real_abils.con = table[3];
      break;
    case CLASS_ROGUE:
      ch->real_abils.dex = table[0];
      ch->real_abils.intel = table[1];
      ch->real_abils.con = table[2];
      ch->real_abils.str = table[3];
      ch->real_abils.cha = table[4];
      ch->real_abils.wis = table[5];
      break;
    case CLASS_FIGHTER:
      switch (GET_RACE(ch)) {
      case RACE_ELF:
        ch->real_abils.str = table[0];
        ch->real_abils.dex = table[1];
        ch->real_abils.con = table[2];
      case RACE_HALFLING:
        ch->real_abils.dex = table[0];
        ch->real_abils.con = table[1];
        ch->real_abils.str = table[2];
      default:
        ch->real_abils.str = table[0];
        ch->real_abils.con = table[1];
        ch->real_abils.dex = table[2];
        break;
      }
      ch->real_abils.cha = table[5];
      ch->real_abils.intel = table[4];
      ch->real_abils.wis = table[3];
      break;
      case CLASS_MONK:
        switch (GET_RACE(ch)) {
        case RACE_HALF_ORC:
          ch->real_abils.str = table[0];
          ch->real_abils.wis = table[1];
          ch->real_abils.dex = table[2];
        break;
        case RACE_GNOME:
        case RACE_ELF:
          ch->real_abils.dex = table[0];
          ch->real_abils.wis = table[1];
          ch->real_abils.str = table[2];
        break;
        default:
          ch->real_abils.wis = table[0];
          ch->real_abils.dex = table[1];
          ch->real_abils.str = table[2];
        break;
        }
        ch->real_abils.con = table[3];
        ch->real_abils.intel = table[4];
        ch->real_abils.cha = table[5];
        break;
      case CLASS_PALADIN:
        switch (GET_RACE(ch)) {
        case RACE_HALF_ORC:
          ch->real_abils.str = table[0];
          ch->real_abils.con = table[1];
          ch->real_abils.cha = table[2];
          ch->real_abils.wis = table[3];
          ch->real_abils.dex = table[4];
        break;
        case RACE_ELF:
          ch->real_abils.str = table[0];
          ch->real_abils.dex = table[1];
          ch->real_abils.wis = table[2];
          ch->real_abils.con = table[4];
          ch->real_abils.cha = table[3];
        case RACE_HALFLING:
          ch->real_abils.con = table[0];
          ch->real_abils.wis = table[1];
          ch->real_abils.cha = table[2];
          ch->real_abils.dex = table[4];
          ch->real_abils.str = table[3];
        break;
        default:
          ch->real_abils.str = table[0];
          ch->real_abils.con = table[1];
          ch->real_abils.wis = table[2];
          ch->real_abils.cha = table[3];
          ch->real_abils.dex = table[4];
          break;
        }
        ch->real_abils.intel = table[5];

      break;
    }

    racial_ability_modifiers(ch);
    ch->aff_abils = ch->real_abils;

    total = GET_STR(ch) / 2 + GET_CON(ch) / 2 + GET_WIS(ch) / 2 +
            GET_INT(ch) / 2 + GET_DEX(ch) / 2 + GET_CHA(ch) / 2;
    total -= 30;
    /*
     * If the character is not worth playing, reroll.
     * total == total ability_mod_value for all stats. If the totals for
     * ability mods would be 0 or less, the character sucks.
     * If the highest ability is 13, the character also sucks.
     * If the totals for ability mods would be higher than 12, the char
     * is just too powerful.
     * They get to keep the first acceptable character rolled; if no good
     * characters appear in {count} rerolls, they are stuck with the last
     * roll.
     */
  } while (--count && (total < 0 || table[0] < 14 || total > 12));

  if (!count)
    mudlog(NRM, ADMLVL_GOD, TRUE,
           "Unlucky new player %s has stat mods totalling %d and highest stat %d",
           GET_NAME(ch), total, table[0]);

  mudlog(CMP, ADMLVL_GOD, TRUE, "New player %s: %s %s. "
         "Str: %d Con: %d Int: %d Wis: %d Dex: %d Cha: %d mod total: %d",
         GET_NAME(ch), pc_race_types[GET_RACE(ch)], pc_class_types[GET_CLASS(ch)],
         GET_STR(ch), GET_CON(ch), GET_INT(ch), GET_WIS(ch), GET_DEX(ch),
         GET_CHA(ch), total);


  set_height_and_weight_by_race(ch);

  log("STR:[%2d] INT:[%2d] WIS:[%2d] DEX:[%2d] CON:[%2d] CHA:[%2d]", ch->real_abils.str, ch->real_abils.intel, ch->real_abils.wis, ch->real_abils.dex, ch->real_abils.con, ch->real_abils.cha);
}

/* Taken from the SRD under OGL, see ../doc/srd.txt for information */
int class_hit_die_size[NUM_CLASSES] = {
/* Wi */ 4,
/* Cl */ 8,
/* Ro */ 6,
/* Fi */ 10,
/* Mo */ 8,
/* Pa */ 10,
/* Ex */ 6,
/* Ad */ 6,
/* Co */ 4,
/* Ar */ 8,
/* Wa */ 8
};

/* Some initializations for characters, including initial skills */
void do_start(struct char_data *ch)
{
  struct obj_data *obj;

  GET_CLASS_LEVEL(ch) = 1;
  GET_HITDICE(ch) = 0;
  GET_LEVEL_ADJ(ch) = 0;
  GET_CLASS_NONEPIC(ch, GET_CLASS(ch)) = 1;
  GET_EXP(ch) = 1;

  SET_BIT_AR(PRF_FLAGS(ch), PRF_AUTOEXIT);

  if (GET_CLASS(ch) < 0 || GET_CLASS(ch) > NUM_CLASSES) {
    log("Unknown character class %d in do_start, resetting.", GET_CLASS(ch));
    GET_CLASS(ch) = 0;
  }

  set_title(ch, NULL);
  /* roll_real_abils(ch); */

  GET_MAX_HIT(ch)  = class_hit_die_size[GET_CLASS(ch)];
  GET_MAX_MANA(ch) = 100;
  GET_MAX_MOVE(ch) = 82;
  GET_GOLD(ch) = dice(3, 6) * 10;

  /* Derived from the SRD under OGL, see ../doc/srd.txt for information */
  switch (GET_RACE(ch)) {
  case RACE_HUMAN:
    SET_SKILL(ch, SKILL_LANG_COMMON, 1);
    break;
  case RACE_ELF:
    SET_BIT_AR(AFF_FLAGS(ch), AFF_INFRAVISION);
    SET_SKILL(ch, SKILL_LANG_COMMON, 1);
    SET_SKILL(ch, SKILL_LANG_ELVEN, 1);
    break;
  case RACE_HALF_ELF:
    SET_BIT_AR(AFF_FLAGS(ch), AFF_INFRAVISION);
    SET_SKILL(ch, SKILL_LANG_COMMON, 1);
    SET_SKILL(ch, SKILL_LANG_ELVEN, 1);
    break;
  case RACE_GNOME:
    SET_BIT_AR(AFF_FLAGS(ch), AFF_INFRAVISION);
    SET_SKILL(ch, SKILL_LANG_COMMON, 1);
    SET_SKILL(ch, SKILL_LANG_GNOME, 1);
    break;
  case RACE_DWARF:
    SET_BIT_AR(AFF_FLAGS(ch), AFF_INFRAVISION);
    SET_SKILL(ch, SKILL_LANG_COMMON, 1);
    SET_SKILL(ch, SKILL_LANG_DWARVEN, 1);
    break;
  case RACE_HALF_ORC:
    SET_BIT_AR(AFF_FLAGS(ch), AFF_INFRAVISION);
    SET_SKILL(ch, SKILL_LANG_COMMON, 1);
    break;
  case RACE_HALFLING:
    SET_SKILL(ch, SKILL_LANG_COMMON, 1);
    break;
  default:
    SET_SKILL(ch, SKILL_LANG_COMMON, 1);
    break;
  }

  SPEAKING(ch) = SKILL_LANG_COMMON;

  /* assign starting items etc...*/
  switch GET_CLASS(ch) {
    case CLASS_WIZARD:
      obj = read_object(1020, VIRTUAL);
      obj_to_char(obj, ch);
      break;
    case CLASS_CLERIC:
      obj = read_object(1000, VIRTUAL);
      obj_to_char(obj, ch);
      break;
    case CLASS_ROGUE:
      obj = read_object(1022, VIRTUAL);
      obj_to_char(obj, ch);
      break;
    case CLASS_MONK:
    case CLASS_PALADIN:
      GET_ETHIC_ALIGNMENT(ch) = 1000;
    default:
      break;
  }

  advance_level(ch, GET_CLASS(ch));
  mudlog(BRF, MAX(ADMLVL_IMMORT, GET_INVIS_LEV(ch)), TRUE, "%s advanced to level %d", GET_NAME(ch), GET_LEVEL(ch));

  GET_HIT(ch) = GET_MAX_HIT(ch);
  GET_MANA(ch) = GET_MAX_MANA(ch);
  GET_MOVE(ch) = GET_MAX_MOVE(ch);

  GET_COND(ch, THIRST) = 24;
  GET_COND(ch, FULL) = 24;
  GET_COND(ch, DRUNK) = 0;

  if (CONFIG_SITEOK_ALL)
    SET_BIT_AR(PLR_FLAGS(ch), PLR_SITEOK);

  ch->player_specials->olc_zone = NOWHERE;
}


/* Derived from the SRD under OGL, see ../doc/srd.txt for information */
int free_start_feats_mage[] = {
  FEAT_SIMPLE_WEAPON_PROFICIENCY,
  FEAT_SCRIBE_SCROLL,
  0
};

/* Derived from the SRD under OGL, see ../doc/srd.txt for information */
int free_start_feats_cleric[] = {
  FEAT_SIMPLE_WEAPON_PROFICIENCY,
  FEAT_ARMOR_PROFICIENCY_HEAVY,
  FEAT_ARMOR_PROFICIENCY_LIGHT,
  FEAT_ARMOR_PROFICIENCY_MEDIUM,
  FEAT_ARMOR_PROFICIENCY_SHIELD,
  0
};

/* Derived from the SRD under OGL, see ../doc/srd.txt for information */
int free_start_feats_rogue[] = {
  FEAT_SIMPLE_WEAPON_PROFICIENCY,
  FEAT_ARMOR_PROFICIENCY_LIGHT,
  0
};

/* Derived from the SRD under OGL, see ../doc/srd.txt for information */
int free_start_feats_warrior[] = {
  FEAT_SIMPLE_WEAPON_PROFICIENCY,
  FEAT_ARMOR_PROFICIENCY_HEAVY,
  FEAT_ARMOR_PROFICIENCY_LIGHT,
  FEAT_ARMOR_PROFICIENCY_MEDIUM,
  FEAT_ARMOR_PROFICIENCY_SHIELD,
  0
};

/* Derived from the SRD under OGL, see ../doc/srd.txt for information */
int free_start_feats_monk[] = {
  FEAT_SIMPLE_WEAPON_PROFICIENCY,
  FEAT_MARTIAL_WEAPON_PROFICIENCY,
  FEAT_EVASION,
  0
};

/* Derived from the SRD under OGL, see ../doc/srd.txt for information */
int *free_start_feats[] = {
 /* CLASS_WIZARD	*/ free_start_feats_mage,
 /* CLASS_CLERIC	*/ free_start_feats_cleric,
 /* CLASS_ROGUE		*/ free_start_feats_rogue,
 /* CLASS_FIGHTER	*/ free_start_feats_warrior,
 /* CLASS_MONK		*/ free_start_feats_monk,
 /* CLASS_PALADIN	*/ free_start_feats_warrior,
};


/*
 * This function controls the change to maxmove, maxmana, and maxhp for
 * each class every time they gain a level.
 */
void advance_level(struct char_data *ch, int whichclass)
{
  struct levelup_data *llog;
  int add_hp, add_move = 0, add_mana = 0, add_prac = 1, add_ki = 0, add_train, i, j, ranks;
  int add_acc = 0, add_fort = 0, add_reflex = 0, add_will = 0;
  int add_gen_feats = 0, add_class_feats = 0;
  char buf[MAX_STRING_LENGTH];

  if (whichclass < 0 || whichclass >= NUM_CLASSES) {
    log("Invalid class %d passed to advance_level, resetting.", whichclass);
    whichclass = 0;
  }

  if (!CONFIG_ALLOW_MULTICLASS && whichclass != GET_CLASS(ch)) {
    log("Attempt to gain a second class without multiclass enabled for %s", GET_NAME(ch));
    whichclass = GET_CLASS(ch);
  }
  ranks = GET_CLASS_RANKS(ch, whichclass);

  CREATE(llog, struct levelup_data, 1);
  llog->next = ch->level_info;
  llog->prev = NULL;
  if (llog->next)
    llog->next->prev = llog;
  ch->level_info = llog;

  llog->skills = llog->feats = NULL;
  llog->type = LEVELTYPE_CLASS;
  llog->spec = whichclass;
  llog->level = GET_LEVEL(ch);

  /* Derived from the SRD under OGL, see ../doc/srd.txt for information */
  switch (ranks) {
  case 1:
    for (i = 0; (j = free_start_feats[whichclass][i]); i++) {
      HAS_FEAT(ch, j) = 1;
    }
    break;
  case 2:
    switch (whichclass) {
    case CLASS_ROGUE:
      HAS_FEAT(ch, FEAT_EVASION) = 1;
      break;
    case CLASS_MONK:
      HAS_FEAT(ch, FEAT_COMBAT_REFLEXES) = 1;
      break;
    }
    break;
  case 6:
    switch (whichclass) {
    case CLASS_MONK:
      HAS_FEAT(ch, FEAT_IMPROVED_DISARM) = 1;
      break;
    }
    break;
  case 9:
    switch (whichclass) {
    case CLASS_MONK:
      HAS_FEAT(ch, FEAT_IMPROVED_EVASION) = 1;
      break;
    }
    break;
  }

  if (GET_CLASS_LEVEL(ch) == 1 && GET_HITDICE(ch) < 2) { /* Filled in below */
      GET_HITDICE(ch) = 0;
      GET_MAX_HIT(ch) = 0;
      GET_ACCURACY_BASE(ch) = 0;
      GET_SAVE_BASE(ch, SAVING_FORTITUDE) = 0;
      GET_SAVE_BASE(ch, SAVING_REFLEX) = 0;
      GET_SAVE_BASE(ch, SAVING_WILL) = 0;
  }

  /* Derived from the SRD under OGL, see ../doc/srd.txt for information */
  if (GET_CLASS_LEVEL(ch) >= LVL_EPICSTART) { /* Epic character */
    if (GET_LEVEL(ch) % 2) {
      add_acc = 1;
    } else {
      add_fort = 1;
      add_reflex = 1;
      add_will = 1;
    }
  } else if (ranks == 1) { /* First level of a given class */
    add_fort = saving_throw_lookup(0, whichclass, SAVING_FORTITUDE, 1);
    add_reflex = saving_throw_lookup(0, whichclass, SAVING_REFLEX, 1);
    add_will = saving_throw_lookup(0, whichclass, SAVING_WILL, 1);
    add_acc = base_hit(0, whichclass, 1);
  } else { /* Normal level of a non-epic class */
    add_fort = saving_throw_lookup(0, whichclass, SAVING_FORTITUDE, ranks) -
               saving_throw_lookup(0, whichclass, SAVING_FORTITUDE, ranks - 1);
    add_reflex = saving_throw_lookup(0, whichclass, SAVING_REFLEX, ranks) -
                 saving_throw_lookup(0, whichclass, SAVING_REFLEX, ranks - 1);
    add_will = saving_throw_lookup(0, whichclass, SAVING_WILL, ranks) -
               saving_throw_lookup(0, whichclass, SAVING_WILL, ranks - 1);
    add_acc = base_hit(0, whichclass, ranks) - base_hit(0, whichclass, ranks - 1);
  }

  /* Derived from the SRD under OGL, see ../doc/srd.txt for information */
  if (ranks >= LVL_EPICSTART) { /* Epic class */
    j = ranks - 20;
    switch (whichclass) {
    case CLASS_ROGUE:
      if (!(j % 4))
        add_class_feats++;
      break;
    case CLASS_FIGHTER:
      if (!(j % 2))
        add_class_feats++;
      break;
    case CLASS_MONK:
      if (!(j % 5))
        add_class_feats++;
      break;
    case CLASS_NPC_EXPERT:
    case CLASS_NPC_ADEPT:
    case CLASS_NPC_COMMONER:
    case CLASS_NPC_ARISTOCRAT:
    case CLASS_NPC_WARRIOR:
      break;
    default:
      if (!(j % 3))
        add_class_feats++;
      break;
    }
  } else {
    switch (whichclass) {
    case CLASS_FIGHTER:
      if (ranks == 1 || !(ranks % 2))
        add_class_feats++;
      break;
    case CLASS_ROGUE:
      if (ranks > 9 && !(ranks % 3))
        add_class_feats++;
      break;
    case CLASS_WIZARD:
      if (!(ranks % 5))
        add_class_feats++;
      break;
    default:
      break;
    }
  }

  /* Derived from the SRD under OGL, see ../doc/srd.txt for information */
  switch (whichclass) {
  case CLASS_WIZARD:
    add_move = rand_number(0, 2);
    add_prac = 2 + ability_mod_value(GET_INT(ch));
    break;

  case CLASS_CLERIC:
    add_move = rand_number(0, 2);
    add_prac = 2 + ability_mod_value(GET_INT(ch));
    break;

  case CLASS_ROGUE:
    add_move = rand_number(1, 3);
    add_prac = 8 + ability_mod_value(GET_INT(ch));
    if (ranks % 2)
      HAS_FEAT(ch, FEAT_SNEAK_ATTACK) += 1;
    break;

  case CLASS_FIGHTER:
    add_move = rand_number(1, 3);
    add_prac = 2 + ability_mod_value(GET_INT(ch));
    break;

  case CLASS_MONK:
    add_move = rand_number(ranks, (int)(1.5 * ranks));
    add_prac = 4 + ability_mod_value(GET_INT(ch));
    add_ki = 10 + ability_mod_value(GET_WIS(ch));
    break;

  case CLASS_PALADIN:
    add_move = rand_number(1, 3);
    add_prac = 2 + ability_mod_value(GET_INT(ch));
    break;
  }

  /* Derived from the SRD under OGL, see ../doc/srd.txt for information */
  if (GET_LEVEL(ch) == 1 || !(GET_LEVEL(ch) % 3)) {
    add_gen_feats += 1;
  }

  /* Derived from the SRD under OGL, see ../doc/srd.txt for information */
  if (GET_RACE(ch) == RACE_HUMAN) /* Humans get 4 extra at level 1 and  */
    add_prac += 1;                /* 1 extra per level for adaptability */

  /* Derived from the SRD under OGL, see ../doc/srd.txt for information */
  i = ability_mod_value(GET_CON(ch));
  if (GET_LEVEL(ch) > 1) {
    j = rand_number(1, class_hit_die_size[whichclass]);
    add_hp = MAX(1, i + j);
  } else {
    j = class_hit_die_size[whichclass];
    add_hp = MAX(1, i + j);
    GET_MAX_HIT(ch) = 0; /* Just easier this way */
    add_prac *= 4;
  }
  llog->hp_roll = j;

  /* Derived from the SRD under OGL, see ../doc/srd.txt for information */
  add_train = (GET_LEVEL(ch) % 4) ? 0 : 1;
  if (add_train) {
    GET_TRAINS(ch) += add_train;
  }

  llog->mana_roll = add_mana;
  llog->move_roll = add_move;
  llog->ki_roll = add_ki;
  llog->add_skill = add_prac;
  add_prac = MAX(1, add_prac);
  GET_PRACTICES(ch, whichclass) += add_prac;
  GET_MAX_HIT(ch) += add_hp;
  GET_MAX_MOVE(ch) += add_move;
  GET_MAX_KI(ch) += add_ki;

  if (GET_CLASS_LEVEL(ch) >= LVL_EPICSTART) { /* Epic character */
    GET_EPIC_FEAT_POINTS(ch) += add_gen_feats;
    llog->add_epic_feats = add_gen_feats;
    GET_EPIC_CLASS_FEATS(ch, whichclass) += add_class_feats;
    llog->add_class_epic_feats = add_class_feats;
  } else {
    GET_FEAT_POINTS(ch) += add_gen_feats;
    llog->add_gen_feats = add_gen_feats;
    GET_CLASS_FEATS(ch, whichclass) += add_class_feats;
    llog->add_class_feats = add_class_feats;
  }

  if (GET_ADMLEVEL(ch) >= ADMLVL_IMMORT) {
    for (i = 0; i < 3; i++)
      GET_COND(ch, i) = (char) -1;
    SET_BIT_AR(PRF_FLAGS(ch), PRF_HOLYLIGHT);
  }

  sprintf(buf, "You gain: %d hit", add_hp);
  sprintf(buf, "%s, %d moves", buf, add_move);
  if (add_ki) {
    sprintf(buf, "%s, %d ki", buf, add_ki);
  }
  strcat(buf, ".\r\n");
  send_to_char(ch, "%s", buf);

  if (whichclass == CLASS_MONK) {
    buf[0] = 0;
    j = 0;
    for (i = 0; i < SKILL_TABLE_SIZE; i++)
      if (IS_SET(spell_info[i].skilltype, SKTYPE_ART))
        if (ranks >= spell_info[i].min_level[CLASS_MONK] && !GET_SKILL(ch, i)) {
          if (j)
            strncat(buf, ", ", sizeof(buf)-1);
          strncat(buf, spell_info[i].name, sizeof(buf)-1);
          SET_SKILL(ch, i, 1);
          j += 1;
        }
    if (j) {
      send_to_char(ch, "You gain the following abilit%s, usable via the \"art\" command:%s%s\r\n",
                   j > 1 ? "ies" : "y", j > 1 ? "\r\n" : " ", buf);
    }
  }
  llog->accuracy = add_acc;
  llog->fort = add_fort;
  llog->reflex = add_reflex;
  llog->will = add_will;
  GET_ACCURACY_BASE(ch) += add_acc;
  GET_SAVE_BASE(ch, SAVING_FORTITUDE) += add_fort;
  GET_SAVE_BASE(ch, SAVING_REFLEX) += add_reflex;
  GET_SAVE_BASE(ch, SAVING_WILL) += add_will;
  snoop_check(ch);
  save_char(ch);
}


/*
 * How many +1d6 dice does the character get for sneak attacks?
 */
/* Derived from the SRD under OGL, see ../doc/srd.txt for information */
int backstab_dice(struct char_data *ch)
{
  int dice;
  if (!IS_NPC(ch)) {
    dice = HAS_FEAT(ch, FEAT_SNEAK_ATTACK);
    if (dice < 0)
      return 0;
    return dice;
  } else {
    if (GET_CLASS(ch) == CLASS_ROGUE)
      return (GET_LEVEL(ch) + 1) / 2;
    else
      return 0;
  }
}


/*
 * invalid_class is used by handler.c to determine if a piece of equipment is
 * usable by a particular class, based on the ITEM_ANTI_{class} bitvectors.
 */
int invalid_class(struct char_data *ch, struct obj_data *obj)
{
  if (OBJ_FLAGGED(obj, ITEM_ANTI_WIZARD) && IS_WIZARD(ch))
    return TRUE;

  if (OBJ_FLAGGED(obj, ITEM_ANTI_CLERIC) && IS_CLERIC(ch))
    return TRUE;

  if (OBJ_FLAGGED(obj, ITEM_ANTI_FIGHTER) && IS_FIGHTER(ch))
    return TRUE;

  if (OBJ_FLAGGED(obj, ITEM_ANTI_ROGUE) && IS_ROGUE(ch))
    return TRUE;

  return FALSE;
}


/*
 * SPELLS AND SKILLS.  This area defines which spells are assigned to
 * which classes, and the minimum level the character must be to use
 * the spell or skill.
 */

void init_skill_race_classes(void)
{
  /* Human */
  skill_race_class(SKILL_LANG_COMMON, RACE_HUMAN, SKLEARN_BOOL);
  /* Elf */
  skill_race_class(SKILL_LANG_COMMON, RACE_ELF, SKLEARN_BOOL);
  skill_race_class(SKILL_LANG_ELVEN, RACE_ELF, SKLEARN_BOOL);
  /* Gnome */
  skill_race_class(SKILL_LANG_COMMON, RACE_GNOME, SKLEARN_BOOL);
  skill_race_class(SKILL_LANG_GNOME, RACE_GNOME, SKLEARN_BOOL);
  /* Dwarf */
  skill_race_class(SKILL_LANG_COMMON, RACE_DWARF, SKLEARN_BOOL);
  skill_race_class(SKILL_LANG_DWARVEN, RACE_DWARF, SKLEARN_BOOL);

}

/* Derived from the SRD under OGL, see ../doc/srd.txt for information */
void init_skill_classes(void)
{
  /* MAGES */
  skill_class(SKILL_HANDLE_ANIMAL, CLASS_WIZARD, SKLEARN_CANT);
  skill_class(SKILL_CRAFT_ALCHEMY, CLASS_WIZARD, SKLEARN_CLASS);
  skill_class(SKILL_CONCENTRATION, CLASS_WIZARD, SKLEARN_CLASS);
  skill_class(SKILL_DECIPHER_SCRIPT, CLASS_WIZARD, SKLEARN_CANT);
  skill_class(SKILL_SPELLCRAFT, CLASS_WIZARD, SKLEARN_CLASS);

  /* CLERICS */
  skill_class(SKILL_HANDLE_ANIMAL, CLASS_CLERIC, SKLEARN_CANT);
  skill_class(SKILL_CONCENTRATION, CLASS_CLERIC, SKLEARN_CLASS);
  skill_class(SKILL_DECIPHER_SCRIPT, CLASS_CLERIC, SKLEARN_CANT);
  skill_class(SKILL_HEAL, CLASS_CLERIC, SKLEARN_CLASS);
  skill_class(SKILL_DIPLOMACY, CLASS_CLERIC, SKLEARN_CLASS);
  skill_class(SKILL_SPELLCRAFT, CLASS_CLERIC, SKLEARN_CLASS);

  /* THIEVES */
  skill_class(SKILL_HANDLE_ANIMAL, CLASS_ROGUE, SKLEARN_CANT);
  skill_class(SKILL_USE_MAGIC_DEVICE, CLASS_ROGUE, SKLEARN_CLASS);
  skill_class(SKILL_TUMBLE, CLASS_ROGUE, SKLEARN_CLASS);
  skill_class(SKILL_BALANCE, CLASS_ROGUE, SKLEARN_CLASS);
  skill_class(SKILL_BLUFF, CLASS_ROGUE, SKLEARN_CLASS);
  skill_class(SKILL_CLIMB, CLASS_ROGUE, SKLEARN_CLASS);
  skill_class(SKILL_DECIPHER_SCRIPT, CLASS_ROGUE, SKLEARN_CLASS);
  skill_class(SKILL_SPOT, CLASS_ROGUE, SKLEARN_CLASS);
  skill_class(SKILL_DISABLE_DEVICE, CLASS_ROGUE, SKLEARN_CLASS);
  skill_class(SKILL_DISGUISE, CLASS_ROGUE, SKLEARN_CLASS);
  skill_class(SKILL_ESCAPE_ARTIST, CLASS_ROGUE, SKLEARN_CLASS);
  skill_class(SKILL_APPRAISE, CLASS_ROGUE, SKLEARN_CLASS);
  skill_class(SKILL_FORGERY, CLASS_ROGUE, SKLEARN_CLASS);
  skill_class(SKILL_HIDE, CLASS_ROGUE, SKLEARN_CLASS);
  skill_class(SKILL_INTIMIDATE, CLASS_ROGUE, SKLEARN_CLASS);
  skill_class(SKILL_JUMP, CLASS_ROGUE, SKLEARN_CLASS);
  skill_class(SKILL_LISTEN, CLASS_ROGUE, SKLEARN_CLASS);
  skill_class(SKILL_GATHER_INFORMATION, CLASS_ROGUE, SKLEARN_CLASS);
  skill_class(SKILL_DIPLOMACY, CLASS_ROGUE, SKLEARN_CLASS);
  skill_class(SKILL_PERFORM, CLASS_ROGUE, SKLEARN_CLASS);
  skill_class(SKILL_OPEN_LOCK, CLASS_ROGUE, SKLEARN_CLASS);
  skill_class(SKILL_SENSE_MOTIVE, CLASS_ROGUE, SKLEARN_CLASS);
  skill_class(SKILL_SEARCH, CLASS_ROGUE, SKLEARN_CLASS);
  skill_class(SKILL_MOVE_SILENTLY, CLASS_ROGUE, SKLEARN_CLASS);
  skill_class(SKILL_SLEIGHT_OF_HAND, CLASS_ROGUE, SKLEARN_CLASS);
  skill_class(SKILL_SWIM, CLASS_ROGUE, SKLEARN_CLASS);

  /* FIGHTERS */
  skill_class(SKILL_HANDLE_ANIMAL, CLASS_FIGHTER, SKLEARN_CANT);
  skill_class(SKILL_CLIMB, CLASS_FIGHTER, SKLEARN_CLASS);
  skill_class(SKILL_DECIPHER_SCRIPT, CLASS_FIGHTER, SKLEARN_CANT);
  skill_class(SKILL_SURVIVAL, CLASS_FIGHTER, SKLEARN_CANT);
  skill_class(SKILL_JUMP, CLASS_FIGHTER, SKLEARN_CLASS);
  skill_class(SKILL_RIDE, CLASS_FIGHTER, SKLEARN_CLASS);
  skill_class(SKILL_SWIM, CLASS_FIGHTER, SKLEARN_CLASS);
  skill_class(SKILL_USE_ROPE, CLASS_FIGHTER, SKLEARN_CLASS);

  /* MONKS */
  skill_class(SKILL_USE_MAGIC_DEVICE, CLASS_MONK, SKLEARN_CANT);
  skill_class(SKILL_DECIPHER_SCRIPT, CLASS_MONK, SKLEARN_CANT);
  skill_class(SKILL_TUMBLE, CLASS_MONK, SKLEARN_CLASS);
  skill_class(SKILL_HANDLE_ANIMAL, CLASS_MONK, SKLEARN_CANT);
  skill_class(SKILL_BALANCE, CLASS_MONK, SKLEARN_CLASS);
  skill_class(SKILL_CLIMB, CLASS_MONK, SKLEARN_CLASS);
  skill_class(SKILL_CONCENTRATION, CLASS_MONK, SKLEARN_CLASS);
  skill_class(SKILL_DIPLOMACY, CLASS_MONK, SKLEARN_CLASS);
  skill_class(SKILL_ESCAPE_ARTIST, CLASS_MONK, SKLEARN_CLASS);
  skill_class(SKILL_HIDE, CLASS_MONK, SKLEARN_CLASS);
  skill_class(SKILL_JUMP, CLASS_MONK, SKLEARN_CLASS);
  skill_class(SKILL_LISTEN, CLASS_MONK, SKLEARN_CLASS);
  skill_class(SKILL_MOVE_SILENTLY, CLASS_MONK, SKLEARN_CLASS);
  skill_class(SKILL_PERFORM, CLASS_MONK, SKLEARN_CLASS);
  skill_class(SKILL_SWIM, CLASS_MONK, SKLEARN_CLASS);

  /* PALADINS */
  skill_class(SKILL_HANDLE_ANIMAL, CLASS_PALADIN, SKLEARN_CANT);
  skill_class(SKILL_DECIPHER_SCRIPT, CLASS_PALADIN, SKLEARN_CANT);
  skill_class(SKILL_USE_MAGIC_DEVICE, CLASS_PALADIN, SKLEARN_CANT);
  skill_class(SKILL_CONCENTRATION, CLASS_PALADIN, SKLEARN_CLASS);
  skill_class(SKILL_DIPLOMACY, CLASS_PALADIN, SKLEARN_CLASS);
  skill_class(SKILL_USE_ROPE, CLASS_PALADIN, SKLEARN_CLASS);
  skill_class(SKILL_HEAL, CLASS_PALADIN, SKLEARN_CLASS);
  skill_class(SKILL_RIDE, CLASS_PALADIN, SKLEARN_CLASS);
}


/* Function to return the exp required for each class/level */
int level_exp(int level)
{
  switch (level) {
  case 0:
    return 0;
  case 1:
    return 1;
  case 2:
    return exp_multiplier;
  default:
    return exp_multiplier * (level * (level - 1)) / 2;
  }
  return 0;
}


/* Derived from the SRD under OGL, see ../doc/srd.txt for information */
sbyte ability_mod_value(int abil)
{
  return ((int)(abil / 2)) - 5;
}

/* Derived from the SRD under OGL, see ../doc/srd.txt for information */
sbyte dex_mod_capped(const struct char_data *ch)
{
  sbyte mod;
  struct obj_data *armor;
  mod = ability_mod_value(GET_DEX(ch));
  armor = GET_EQ(ch, WEAR_BODY);
  if (armor && GET_OBJ_TYPE(armor) == ITEM_ARMOR) {
    mod = MIN(mod, GET_OBJ_VAL(armor, VAL_ARMOR_MAXDEXMOD));
  }
  return mod;
}

int cabbr_ranktable[NUM_CLASSES];

int comp_rank(const void *a, const void *b)
{
  int first, second;
  first = *(int *)a;
  second = *(int *)b;
  return cabbr_ranktable[second] - cabbr_ranktable[first];
}

/* Derived from the SRD under OGL, see ../doc/srd.txt for information */
char *class_desc_str(struct char_data *ch, int howlong, int wantthe)
{
  static char str[MAX_STRING_LENGTH];
  char *ptr = str;
  int i, rank, j;
  int rankorder[NUM_CLASSES];
  char *buf, *buf2, *buf3;

  if (wantthe)
    ptr += sprintf(str, "the ");

  if (howlong) {
    buf2 = buf = buf3 = "";
    if (howlong == 2) {
      buf3 = " ";
      if (GET_CLASS_LEVEL(ch) >= LVL_EPICSTART)
        ptr += sprintf(ptr, "Epic ");
    }
    for (i = 0; i < NUM_CLASSES; i++) {
      cabbr_ranktable[i] = GET_CLASS_RANKS(ch, i);
      rankorder[i] = i;
    }
    rankorder[0] = GET_CLASS(ch); /* we always want primary class first */
    rankorder[GET_CLASS(ch)] = 0;
    qsort((void *)rankorder, NUM_CLASSES, sizeof(int), comp_rank);
    for (i = 0; i < NUM_CLASSES; i++) {
      rank = rankorder[i];
      if (cabbr_ranktable[rank] == 0)
        continue;
      ptr += snprintf(ptr, sizeof(str) - (ptr - str), "%s%s%s%s%s%d", buf, buf2, buf,
                      (howlong == 2 ? pc_class_types : class_abbrevs)[rank], buf3,
                      cabbr_ranktable[rank]);
      buf2 = "/";
      if (howlong == 2)
        buf = " ";
    }
    return str;
  } else {
    rank = GET_CLASS_RANKS(ch, GET_CLASS(ch));
    j = GET_CLASS(ch);
    for (i = 0; i < NUM_CLASSES; i++)
      if (GET_CLASS_RANKS(ch, i) > rank) {
        j = i;
        rank = GET_CLASS_RANKS(ch, j);
      }
    rank = GET_CLASS_RANKS(ch, GET_CLASS(ch));
    snprintf(ptr, sizeof(str) - (ptr - str), "%s%d%s", class_names[GET_CLASS(ch)],
             rank, GET_LEVEL(ch) == rank ? "" : "+");
    return str;
  }
}


int total_skill_levels(struct char_data *ch, int skill)
{
  int i = 0, j, total = 0;
  for (i = 0; i < NUM_CLASSES; i++) {
    j = 1 + GET_CLASS_RANKS(ch, i) - spell_info[skill].min_level[i];
    if (j > 0)
     total += j;
  }
  return total;
}


int load_levels()
{
  FILE *fp;
  char line[READ_SIZE], sect_name[READ_SIZE] = { '\0' }, *ptr;
  int  linenum = 0, tp, cls, sect_type = -1;

  if (!(fp = fopen(LEVEL_CONFIG, "r"))) {
    log("SYSERR: Could not open level configuration file, error: %s!", 
         strerror(errno));
    return -1;
  }

  for (tp = 0; tp < NUM_CLASSES; tp++) {
    for (cls = 0; cls <= SAVING_WILL; cls++) {
      save_classes[cls][tp] = 0;
    }
    basehit_classes[tp] = 0;
  }

  for (;;) {
    linenum++;
    if (!fgets(line, READ_SIZE, fp)) {  /* eof check */
      log("SYSERR: Unexpected EOF in file %s.", LEVEL_CONFIG);
      return -1;
    } else if (*line == '$') { /* end of file */
      break;
    } else if (*line == '*') { /* comment line */
      continue;
    } else if (*line == '#') { /* start of a section */
      if ((tp = sscanf(line, "#%s", sect_name)) != 1) {
        log("SYSERR: Format error in file %s, line number %d - text: %s.", 
             LEVEL_CONFIG, linenum, line);
        return -1;
      } else if ((sect_type = search_block(sect_name, config_sect, FALSE)) == -1) {
          log("SYSERR: Invalid section in file %s, line number %d: %s.", 
              LEVEL_CONFIG, linenum, sect_name);
          return -1;
      }
    } else {
      if (sect_type == CONFIG_LEVEL_VERSION) {
        if (!strncmp(line, "Suntzu", 6)) {
          log("SYSERR: Suntzu %s config files are not compatible with rasputin", LEVEL_CONFIG);
          return -1;
        } else
          strcpy(level_version, line); /* OK - both are READ_SIZE] */
      } else if (sect_type == CONFIG_LEVEL_VERNUM) {
	level_vernum = atoi(line);
      } else if (sect_type == CONFIG_LEVEL_EXPERIENCE) {
        tp = atoi(line);
        exp_multiplier = tp;
      } else if ((sect_type >= CONFIG_LEVEL_FORTITUDE && sect_type <= CONFIG_LEVEL_WILL) ||
                 sect_type == CONFIG_LEVEL_BASEHIT) {
        for (ptr = line; ptr && *ptr && !isdigit(*ptr); ptr++);
        if (!ptr || !*ptr || !isdigit(*ptr)) {
          log("SYSERR: Cannot find class number in file %s, line number %d, section %s.", 
              LEVEL_CONFIG, linenum, sect_name);
          return -1;
        }
        cls = atoi(ptr);
        for (; ptr && *ptr && isdigit(*ptr); ptr++);
        for (; ptr && *ptr && !isdigit(*ptr); ptr++);
        if (ptr && *ptr && !isdigit(*ptr)) {
          log("SYSERR: Non-numeric entry in file %s, line number %d, section %s.", 
              LEVEL_CONFIG, linenum, sect_name);
          return -1;
        }
        if (ptr && *ptr) /* There's a value */
          tp = atoi(ptr);
        else {
          log("SYSERR: Need 1 value in %s, line number %d, section %s.", 
              LEVEL_CONFIG, linenum, sect_name);
          return -1;
        }
        if (cls < 0 || cls >= NUM_CLASSES) {
          log("SYSERR: Invalid class number %d in file %s, line number %d.", 
              cls, LEVEL_CONFIG, linenum);
          return -1;
        } else {
          if (sect_type == CONFIG_LEVEL_BASEHIT)
            basehit_classes[cls] = tp;
          else
            save_classes[SAVING_FORTITUDE + sect_type - CONFIG_LEVEL_FORTITUDE][cls] = tp;
        }
      } else {
        log("Unsupported level config option");
      }
    }
  }
  fclose(fp);

  for (cls = 0; cls < NUM_CLASSES; cls++)
    log("Base hit for class %s: %s", class_names[cls], basehit_type_names[basehit_classes[cls]]);

  for (cls = 0; cls < NUM_CLASSES; cls++)
    log("Saves for class %s: fort=%s, reflex=%s, will=%s", class_names[cls],
        save_type_names[save_classes[SAVING_FORTITUDE][cls]],
        save_type_names[save_classes[SAVING_REFLEX][cls]],
        save_type_names[save_classes[SAVING_WILL][cls]]);

  return 0;
}


/* Derived from the SRD under OGL, see ../doc/srd.txt for information */
int highest_skill_value(int level, int type)
{
  switch (type) {
    case SKLEARN_CROSSCLASS:
      return (level + 3) / 2;
    case SKLEARN_CLASS:
      return level + 3;
    case SKLEARN_BOOL:
      return 1;
    case SKLEARN_CANT:
    default:
      return 0;
  }
}


/* This returns the number of classes that should be penalized. */
/* Derived from the SRD under OGL, see ../doc/srd.txt for information */
int exp_penalty(struct char_data *ch)
{
  int high, highclass, i, fcl, pen;
  fcl = favored_class[GET_RACE(ch)]; /* This class is skipped for penalties */
  if (fcl == -1) { /* -1 means find highest and use it as 'favored' class */
    for (high = fcl = i = 0; i < NUM_CLASSES; i++) {
      if (GET_CLASS_RANKS(ch, i) > high) {
        fcl = i;
        high = GET_CLASS_RANKS(ch, i);
      }
    }
  }
  for (high = highclass = i = 0; i < NUM_CLASSES; i++) {
    /* Favored class and prestige classes don't count */
    if (i == fcl || prestige_classes[i])
      continue;
    if (GET_CLASS_RANKS(ch, i) > high) {
      highclass = i;
      high = GET_CLASS_RANKS(ch, i);
    }
  }
  /*
   * OK, we know their favored class and highest class, now we can figure
   * out the penalty
   */
  pen = 0;
  for (i = 0; i < NUM_CLASSES; i++) {
    if (i == fcl || prestige_classes[i] || !GET_CLASS_RANKS(ch, i))
      continue;
    if (GET_CLASS_RANKS(ch, i) < (high - 1))
      pen++;
  }
  return pen;
}

/*
 * -20% for each class 2 or more less than highest class, ignoring favored
 * class and any prestige classes
 */
/* Derived from the SRD under OGL, see ../doc/srd.txt for information */
int calc_penalty_exp(struct char_data *ch, int gain)
{
  int pen;
  int a = 1, b = 1;
  for (pen = exp_penalty(ch); pen > 0; pen--) {
    a *= 4;
    b *= 5;
  }
  gain *= a;
  gain /= b;
  return gain;
}


int size_scaling_table[NUM_SIZES][4] = {
/*                   str       dex     con  nat arm */
/* Fine		*/ { -10,	-2,	-2,	0 },
/* Diminutive	*/ { -10,	-2,	-2,	0 },
/* Tiny		*/ { -8,	-2,	-2,	0 },
/* Small	*/ { -4,	-2,	-2,	0 },
/* Medium	*/ { 0,		0,	0,	0 },
/* Large	*/ { 8,		-2,	4,	2 },
/* Huge		*/ { 16,	-4,	8,	5 },
/* Gargantuan	*/ { 24,	-4,	12,	9 },
/* Colossal	*/ { 32,	-4,	16,	14 }
};


/*
 * Assign stats to a mob based on race/class/size/level
 */
/* Original algorithm */
void assign_auto_stats(struct char_data *ch)
{
  struct abil_data *ab;
  int sz, extra, tmp;

  if (!ch)
    return;

  ab = &ch->real_abils;
 
  ab->intel = ab->wis = ab->str = ab->con = ab->dex = ab->cha = 10;
  racial_ability_modifiers(ch);

  sz = get_size(ch);
  
  ab->str += size_scaling_table[sz][0];
  ab->dex += size_scaling_table[sz][1];
  ab->con += size_scaling_table[sz][2];

  extra = GET_LEVEL(ch) / 3; /* For PC's it's every 4, we want to make it compensate */

  switch (GET_CLASS(ch)) {
  case CLASS_WIZARD:
    tmp = (extra + 1) / 2;
    extra -= tmp;
    ab->intel += tmp;
    tmp = extra / 2;
    extra -= tmp;
    ab->con += tmp;
    if (ab->dex > ab->str)
      ab->dex += extra;
    else
      ab->str += extra;
    break;
  case CLASS_CLERIC:
    tmp = (extra + 1) / 2;
    extra -= tmp;
    ab->wis += tmp;
    tmp = extra / 2;
    extra -= tmp;
    ab->con += tmp;
    ab->cha += extra;
    break;
  case CLASS_ROGUE:
    tmp = (extra + 1) / 2;
    extra -= tmp;
    ab->dex += tmp;
    tmp = extra / 2;
    extra -= tmp;
    ab->intel += tmp;
    if (ab->con > ab->str)
      ab->con += extra;
    else
      ab->str += extra;
    break;
  case CLASS_MONK:
    tmp = (extra + 1) / 2;
    extra -= tmp;
    ab->wis += tmp;
    tmp = extra / 2;
    extra -= tmp;
    ab->con += tmp;
    if (ab->str > ab->dex)
      ab->str += extra;
    else
      ab->dex += extra;
    break;
  case CLASS_PALADIN:
    tmp = extra / 2;
    extra -= tmp;
    if (ab->str > ab->dex)
      ab->str += tmp;
    else
      ab->dex += tmp;
    tmp = (extra + 1) / 2;
    extra -= tmp;
    ab->con += tmp;
    if (ab->wis > ab->cha)
      ab->wis += extra;
    else
      ab->cha += extra;
    break;
  case CLASS_FIGHTER:
  default:
    tmp = extra / 2;
    extra -= tmp;
    ab->con += tmp;
    if (ab->dex > ab->str)
      ab->dex += extra;
    else
      ab->str += extra;
    break;
  }

  ch->aff_abils = ch->real_abils;
  return;
}

/* Derived from the SRD under OGL, see ../doc/srd.txt for information */
time_t birth_age(struct char_data *ch)
{
  int cltype;
  struct aging_data *aging;
  int tmp;

  switch (GET_CLASS(ch)) {
  case CLASS_WIZARD:
  case CLASS_CLERIC:
  case CLASS_MONK:
    cltype = 2;
    break;
  case CLASS_FIGHTER:
  case CLASS_PALADIN:
    cltype = 1;
    break;
  case CLASS_ROGUE:
  default:
    cltype = 0;
    break;
  }
  aging = racial_aging_data + GET_RACE(ch);

  tmp = aging->adult + dice(aging->classdice[cltype][0], aging->classdice[cltype][1]);
  tmp *= SECS_PER_MUD_YEAR;
  tmp += rand_number(0, SECS_PER_MUD_YEAR);

  return tmp;
}

time_t max_age(struct char_data *ch)
{
  struct aging_data *aging;
  size_t tmp;

  if (ch->time.maxage)
    return ch->time.maxage - ch->time.birth;

  aging = racial_aging_data + GET_RACE(ch);

  tmp = aging->venerable + dice(aging->maxdice[0], aging->maxdice[1]);
  tmp *= SECS_PER_MUD_YEAR;
  tmp += rand_number(0, SECS_PER_MUD_YEAR);

  return tmp;
}

/*
 * Returns:
 * 0 on failure
 * 1 on raising normal class level
 * 2 on raising epic class level
 * 3 on raising both
 */
int raise_class_only(struct char_data *ch, int cl, int v)
{
  if (cl < 0 || cl >= NUM_CLASSES)
    return 0;
  if (GET_CLASS_LEVEL(ch) + v < LVL_EPICSTART) {
    GET_CLASS_NONEPIC(ch, cl) += v;
    return 1;
  }
  if (GET_CLASS_LEVEL(ch) >= LVL_EPICSTART - 1) {
    GET_CLASS_EPIC(ch, cl) += v;
    return 2;
  }
  GET_CLASS_NONEPIC(ch, cl) += (v -= (LVL_EPICSTART - (1 + GET_CLASS_LEVEL(ch))));
  GET_CLASS_EPIC(ch, cl) += v;
  return 3;
}

const int class_feats_wizard[] = {
  FEAT_SPELL_MASTERY,
  FEAT_BREW_POTION,
  FEAT_CRAFT_MAGICAL_ARMS_AND_ARMOR,
  FEAT_CRAFT_ROD,
  FEAT_CRAFT_STAFF,
  FEAT_CRAFT_WAND,
  FEAT_CRAFT_WONDEROUS_ITEM,
  FEAT_FORGE_RING,
  FEAT_SCRIBE_SCROLL,
  FEAT_EMPOWER_SPELL,
  FEAT_ENLARGE_SPELL,
  FEAT_EXTEND_SPELL,
  FEAT_HEIGHTEN_SPELL,
  FEAT_MAXIMIZE_SPELL,
  FEAT_QUICKEN_SPELL,
  FEAT_SILENT_SPELL,
  FEAT_STILL_SPELL,
  FEAT_WIDEN_SPELL,
  FEAT_UNDEFINED
};

/*
 * Rogues follow opposite logic - they can take any feat in place of these,
 * all of these are abilities that are not normally able to be taken as
 * feats. Most classes can ONLY take from these lists for their class
 * feats.
 */
const int class_feats_rogue[] = {
  FEAT_IMPROVED_EVASION,
  FEAT_CRIPPLING_STRIKE,
  FEAT_DEFENSIVE_ROLL,
  FEAT_IMPROVED_EVASION,
  FEAT_OPPORTUNIST,
  FEAT_SKILL_MASTERY,
  FEAT_SLIPPERY_MIND,
  FEAT_UNDEFINED
};

const int class_feats_fighter[] = {
  FEAT_BLIND_FIGHT,
  FEAT_CLEAVE,
  FEAT_COMBAT_EXPERTISE,
  FEAT_COMBAT_REFLEXES,
  FEAT_DEFLECT_ARROWS,
  FEAT_DODGE,
  FEAT_WEAPON_PROFICIENCY_BASTARD_SWORD,
  FEAT_EXOTIC_WEAPON_PROFICIENCY,
  FEAT_FAR_SHOT,
  FEAT_GREAT_CLEAVE,
  FEAT_GREATER_TWO_WEAPON_FIGHTING,
  FEAT_GREATER_WEAPON_FOCUS,
  FEAT_GREATER_WEAPON_SPECIALIZATION,
  FEAT_IMPROVED_BULL_RUSH,
  FEAT_IMPROVED_CRITICAL,
  FEAT_IMPROVED_DISARM,
  FEAT_IMPROVED_FEINT,
  FEAT_IMPROVED_GRAPPLE,
  FEAT_IMPROVED_INITIATIVE,
  FEAT_IMPROVED_OVERRUN,
  FEAT_IMPROVED_PRECISE_SHOT,
  FEAT_IMPROVED_SHIELD_BASH,
  FEAT_IMPROVED_SUNDER,
  FEAT_IMPROVED_TRIP,
  FEAT_IMPROVED_TWO_WEAPON_FIGHTING,
  FEAT_IMPROVED_UNARMED_STRIKE,
  FEAT_MANYSHOT,
  FEAT_MOBILITY,
  FEAT_MOUNTED_ARCHERY,
  FEAT_MOUNTED_COMBAT,
  FEAT_POINT_BLANK_SHOT,
  FEAT_POWER_ATTACK,
  FEAT_PRECISE_SHOT,
  FEAT_QUICK_DRAW,
  FEAT_RAPID_RELOAD,
  FEAT_RAPID_SHOT,
  FEAT_RIDE_BY_ATTACK,
  FEAT_SHOT_ON_THE_RUN,
  FEAT_SNATCH_ARROWS,
  FEAT_SPIRITED_CHARGE,
  FEAT_SPRING_ATTACK,
  FEAT_STUNNING_FIST,
  FEAT_TRAMPLE,
  FEAT_TWO_WEAPON_DEFENSE,
  FEAT_TWO_WEAPON_FIGHTING,
  FEAT_WEAPON_FINESSE,
  FEAT_WEAPON_FOCUS,
  FEAT_WEAPON_SPECIALIZATION,
  FEAT_WHIRLWIND_ATTACK,
  FEAT_UNDEFINED
};

const int no_class_feats[] = {
  FEAT_UNDEFINED
};

const int *class_bonus_feats[NUM_CLASSES] = {
/* WIZARD		*/ class_feats_wizard,
/* CLERIC		*/ no_class_feats,
/* ROGUE		*/ class_feats_rogue,
/* FIGHTER		*/ class_feats_fighter,
/* MONK			*/ no_class_feats,
/* PALADIN		*/ no_class_feats,
/* NPC_EXPERT		*/ no_class_feats,
/* NPC_ADEPT		*/ no_class_feats,
/* NPC_COMMONER		*/ no_class_feats,
/* NPC_ARISTOCRAT	*/ no_class_feats,
/* NPC_WARRIOR		*/ no_class_feats
};