Mud20/accounts/
Mud20/accounts/c/
Mud20/accounts/f/
Mud20/accounts/k/
Mud20/accounts/s/
Mud20/accounts/t/
Mud20/area_current/
Mud20/area_current/newareas/
Mud20/bin/
Mud20/clans/
Mud20/gods/
Mud20/old-sources/
Mud20/player/
Mud20/player/a/del/
Mud20/player/b/
Mud20/player/b/bak/
Mud20/player/b/del/
Mud20/player/f/
Mud20/player/f/bak/
Mud20/player/f/del/
Mud20/player/k/
Mud20/player/k/bak/
Mud20/player/k/del/
Mud20/player/k/dmp/
Mud20/player/m/
Mud20/player/m/bak/
Mud20/player/o/
Mud20/player/o/bak/
Mud20/player/p/
Mud20/player/s/
Mud20/player/s/bak/
Mud20/player/s/del/
Mud20/player/t/
Mud20/player/t/del/
Mud20/player/v/
Mud20/public_html/
Mud20/races/
Mud20/skilltables/
__MACOSX/Mud20/accounts/
__MACOSX/Mud20/accounts/c/
__MACOSX/Mud20/accounts/f/
__MACOSX/Mud20/accounts/k/
__MACOSX/Mud20/accounts/s/
__MACOSX/Mud20/area_current/
__MACOSX/Mud20/area_current/core_areas/
__MACOSX/Mud20/area_current/helps/
__MACOSX/Mud20/area_current/newareas/
__MACOSX/Mud20/backups/
__MACOSX/Mud20/bin/
__MACOSX/Mud20/clans/
__MACOSX/Mud20/gods/
__MACOSX/Mud20/log/
__MACOSX/Mud20/old-sources/
__MACOSX/Mud20/player/
__MACOSX/Mud20/player/a/del/
__MACOSX/Mud20/player/b/
__MACOSX/Mud20/player/b/bak/
__MACOSX/Mud20/player/f/
__MACOSX/Mud20/player/f/bak/
__MACOSX/Mud20/player/f/del/
__MACOSX/Mud20/player/k/
__MACOSX/Mud20/player/k/bak/
__MACOSX/Mud20/player/k/del/
__MACOSX/Mud20/player/k/dmp/
__MACOSX/Mud20/player/m/
__MACOSX/Mud20/player/m/bak/
__MACOSX/Mud20/player/o/
__MACOSX/Mud20/player/o/bak/
__MACOSX/Mud20/player/p/
__MACOSX/Mud20/player/s/
__MACOSX/Mud20/player/s/bak/
__MACOSX/Mud20/player/t/del/
__MACOSX/Mud20/player/v/
__MACOSX/Mud20/public_html/
__MACOSX/Mud20/races/
__MACOSX/Mud20/skilltables/
/***************************************************************************
 * Mud20 1.0 by Todd H. Johnson (Kregor) a derivative of the Open Gaming   *
 * License by Wizards of the Coast. All comments referring to D20, OGL,    *
 * and SRD refer to the System Reference Document for the Open Gaming      *
 * system. Any inclusion of these derivatives must include credit to the   *
 * Mud20 system, the full and complete Open Gaming LIcense, and credit to  *
 * the respective authors. See ../doc/srd.txt for more information.        *
 *                                                                         *
 * Emud  2.2 by Igor van den Hoven, Michiel Lange, and Martin Bethlehem.   *
 *                                                                         *
 * MrMud 1.4 by David Bills, Dug Michael and Martin Gallwey                *
 *                                                                         *
 * Merc  2.1 Diku Mud improvments copyright (C) 1992, 1993 by Michael      *
 * Chastain, Michael Quan, and Mitchell Tse.                               *
 *                                                                         *
 * Original Diku Mud copyright (C) 1990 1991 by Sebastian Hammer,          *
 * Michael Seifert, Hans Henrik St{rfeld, Tom Madsen, and Katje Nyboe.     *
 ***************************************************************************/
 
/***************************************************************************
 * character.c - broken off from handler to deal with character-specific   *
 * handling functions - Kregor                                             *
 ***************************************************************************/

#include "mud.h"
#include <math.h>


/*
 *	Compute current stat of a character's abilities - Kregor.
 */
int get_curr_str( CHAR_DATA *ch )
{
	int stat;

	push_call("get_curr_str(%p)",ch);

	// nonabilities return as -1
	if ((stat = ch->perm_str) <= 0 || rspec_req(ch, RSPEC_INCORPOREAL))
	{
		pop_call();
		return -1;
	}
	if (race_table[get_race(ch)].nonability[STAT_STR])
	{
		pop_call();
		return -1;
	}
	
	stat += ch->mod_str;

	// size bonus/penalty
	stat += get_apply(ch, APPLY_SIZE) * 2;
	stat += get_apply(ch, APPLY_STR);
	
	/* Per SRD, stat penalties for exaustion */
	if (IS_EXHAUSTED(ch))
	{
		stat -= 6;
	}
	else if (IS_FATIGUED(ch))
	{
		stat -= 2;
	}

	// none of the above can lower stat past 1
	stat = UMAX(1, stat);
	
	stat += get_apply(ch, APPLY_STR_DAMAGE);
	stat += get_apply(ch, APPLY_STR_DRAIN);
	
	pop_call();
	return (UMAX(0, stat));
}

int get_curr_int( CHAR_DATA *ch )
{
	int stat;

	push_call("get_curr_int(%p)",ch);

	if ((stat = ch->perm_int) <= 0)
	{
		pop_call();
		return -1;
	}
	if (race_table[get_race(ch)].nonability[STAT_INT])
	{
		pop_call();
		return -1;
	}

	stat += ch->mod_int;
	stat += get_apply(ch, APPLY_INT);
	
	stat = UMAX(1, stat);
	
	stat += get_apply(ch, APPLY_INT_DAMAGE);
	stat += get_apply(ch, APPLY_INT_DRAIN);
	
	pop_call();
	return (UMAX(0, stat));
}

int get_curr_wis( CHAR_DATA *ch )
{
	int stat;

	push_call("get_curr_wis(%p)",ch);

	if ((stat = ch->perm_wis) <= 0)
	{
		pop_call();
		return -1;
	}
	if (race_table[get_race(ch)].nonability[STAT_WIS])
	{
		pop_call();
		return -1;
	}
	
	stat += ch->mod_wis;
	stat += get_apply(ch, APPLY_WIS);
	
	stat = UMAX(1,stat);

	stat += get_apply(ch, APPLY_WIS_DAMAGE);
	stat += get_apply(ch, APPLY_WIS_DRAIN);

	pop_call();
	return (UMAX(0, stat));
}

/*
 *	Retrieve character's current dexterity.
 */
int get_curr_dex( CHAR_DATA *ch )
{
	int stat;

	push_call("get_curr_dex(%p)",ch);

	if ((stat = ch->perm_dex) <= 0)
	{
		pop_call();
		return -1;
	}
	if (race_table[get_race(ch)].nonability[STAT_DEX])
	{
		pop_call();
		return -1;
	}
	
	if (ch->position <= POS_SLEEPING || (IS_AFFECTED(ch, AFF2_PARALYSIS) && !IS_AFFECTED(ch, AFF_FREEDOM)) || drunk_level(ch) >= DRUNK_TOTALLED)
	{
		pop_call();
		return 1;
	}
	
	stat += ch->mod_dex;
	stat -= get_apply(ch, APPLY_SIZE) * 2;
	
	if (is_affected(ch, gsn_iron_body))
		stat -= 6;
	stat += get_apply(ch, APPLY_DEX);

	/* Per SRD, stat penalties for exaustion or entanglement
	 * only the worst penalty applies.
	 */
	if (IS_EXHAUSTED(ch))
	{
		stat -= 6;
	}
	else if (IS_ENTANGLED(ch))
	{
		stat -= 4;
	}
	else if (IS_FATIGUED(ch))
	{
		stat -= 2;
	}
	// none of the above reduce below 1
	stat = UMAX(1,stat);
	
	stat += get_apply(ch, APPLY_DEX_DAMAGE);
	stat += get_apply(ch, APPLY_DEX_DRAIN);
	
	pop_call();
	return (UMAX(0, stat));
}

int get_curr_con( CHAR_DATA *ch )
{
	int stat;

	push_call("get_curr_con(%p)",ch);

	if ((stat = ch->perm_con) <= 0 || IS_UNDEAD(ch))
	{
		pop_call();
		return -1;
	}
	if (race_table[get_race(ch)].nonability[STAT_CON])
	{
		pop_call();
		return -1;
	}
	stat += ch->mod_con;
	stat += get_apply(ch, APPLY_CON);
	
	stat = UMAX(1, stat);
	
	stat += get_apply(ch, APPLY_CON_DAMAGE);
	stat += get_apply(ch, APPLY_CON_DRAIN);

	pop_call();
	return (UMAX(0, stat));
}

int get_curr_cha( CHAR_DATA *ch )
{
	int stat;

	push_call("get_curr_cha(%p)",ch);

	if ((stat = ch->perm_cha) <= 0)
	{
		pop_call();
		return -1;
	}
	if (race_table[get_race(ch)].nonability[STAT_CHA])
	{
		pop_call();
		return -1;
	}
	stat += ch->mod_cha;
	stat += get_apply(ch, APPLY_CHA);
	
	stat = UMAX(1, stat);
	
	stat += get_apply(ch, APPLY_CHA_DAMAGE);
	stat += get_apply(ch, APPLY_CHA_DRAIN);

	pop_call();
	return (UMAX(0, stat));
}

/*
 * Get stat bonus - Based on SRD - Kregor
 */
int stat_bonus(bool fCurrent, CHAR_DATA *ch, int stat)
{
	int value, bonus;

	push_call("stat_bonus(%p,%p,%p)",fCurrent,ch,stat);
	
	switch(stat)
	{
		case APPLY_STR:
			if (IS_INCORPOREAL(ch))
			{
				value = ch->perm_dex;
			}
			else if (!fCurrent)
			{
				value = ch->perm_str;
			}
			else
			{
				value = get_curr_str(ch);
			}
			break;
		case APPLY_DEX:
			if (!fCurrent)
				value = ch->perm_dex;
			else
				value = get_curr_dex(ch);
			break;
		case APPLY_CON:
			if (IS_UNDEAD(ch) || race_type(ch) == RTYPE_CONSTRUCT)
			{
				value = 10;
			}
			else if (!fCurrent)
			{
				value = ch->perm_con;
			}
			else
			{
				value = get_curr_con(ch);
			}
			break;
		case APPLY_INT:
			if ((value = get_curr_int(ch)) < 0)
				value = 10;
			else if (!fCurrent)
				value = ch->perm_int;
			break;
		case APPLY_WIS:
			if (!fCurrent)
				value = ch->perm_wis;
			else
				value = get_curr_wis(ch);
			break;
		case APPLY_CHA:
			if (!fCurrent)
				value = ch->perm_cha;
			else
				value = get_curr_cha(ch);
			break;
	}
	
	if (value < 0)
	{
		pop_call();
		return 0;
	}

	bonus = ((int)(value / 2)) - 5;
	
	pop_call();
	return bonus;
}

/*
 * Reputation functions - Kregor
 */
int get_reputation( CHAR_DATA *ch )
{
	int value;

	push_call("get_reputation(%p)",ch);
	
	if (IS_NPC(ch))
	{
		pop_call();
		return 10;
	}

	value = ch->pcdata->reputation;

	if (IS_LAWFUL(ch))
		value += 1;
	if (IS_CHAOTIC(ch))
		value -= 1;
	if (IS_GOOD(ch))
		value += 1;
	if (IS_EVIL(ch))
		value -= 1;

	pop_call();
	return UMAX(1,value);
}

int reputation_bonus( CHAR_DATA *ch )
{
	int value, bonus;

	push_call("reputation_bonus(%p)",ch);
	
	value = get_reputation(ch);

	bonus = ((int)(value / 2)) - 5;
	
	pop_call();
	return bonus;
}

void gain_reputation( CHAR_DATA *ch, int amount )
{
	int value;

	push_call("gain_reputation(%p)",ch);
	
	if (IS_NPC(ch))
	{
		pop_call();
		return;
	}

	if (ch->pcdata->reputation + amount < 0)
		value = 0 - ch->pcdata->reputation;
	else
		value = amount;

	if (value < 0)
	{
		ch->pcdata->reputation = UMAX(0, ch->pcdata->reputation + value);
		ch_printf_color(ch, "{018}Word of your deeds costs you %d point%s of Reputation\n\r", value, value == -1 ? "" : "s");
	}
	else
	{
		ch->pcdata->reputation += value;
		ch_printf_color(ch, "{138}Word of your deeds gains you %d point%s of Reputation\n\r", value, value == 1 ? "" : "s");
	}
	pop_call();
	return;
}


/*
 * Returns race number for shapechange/poly chars
 * for things that shapechange/poly should NOT apply,
 * just use ch->race - Kregor
 */
int get_race(CHAR_DATA * ch)	
{
	int race;

	push_call("get_race(%p)",ch);

	if (IS_NPC(ch) && !ch->pIndexData->race)
	{
		pop_call();
		return RACE_NONE;
	}
	race = ch->race;
	race += get_apply(ch, APPLY_RACE);

	pop_call();
	return (race);
}

/*
 * Return a string for the faith rank of a ch.
 * Return generic string if none set for deity,
 * or None if agnostic - Kregor
 */
char *faith_rank(CHAR_DATA *ch)
{
	static char title[MAX_INPUT_LENGTH];

	if (!ch->god)
		strcpy(title, "none");
	else if (*god_table[ch->god].ranking[ch->pcdata->faith_rank] != '\0')
		strcpy(title, god_table[ch->god].ranking[ch->pcdata->faith_rank]);
	else
		strcpy(title, faith_ranks[ch->pcdata->faith_rank]);

	return title;
}
		
/*
 * Stat rolls and bonuses for SRD stat checks - Kregor
 */
int stat_mods( CHAR_DATA *ch, int stat )
{
	int mod, enc;

	push_call("stat_mods(%p)",ch);

	mod = stat_bonus(TRUE, ch, stat);
	enc = encumberance(ch) * 3;

	switch(stat)
	{
		default:
			break;
		case APPLY_STR:
			mod -= enc;
			if (learned(ch, gsn_divine_strength))
				mod += 1;
			break;
		case APPLY_DEX:	
			mod -= enc;
			if (IS_AFFECTED( ch, AFF2_FASCINATED))
				mod -= 4;
			break;
		case APPLY_CHA:
			if (learned(ch, gsn_divine_charm))
				mod += 1;
			break;
		case APPLY_WIS:
			if (IS_AFFECTED( ch, AFF2_FASCINATED))
				mod -= 4;
			break;
		case APPLY_CON:
			if (learned(ch, gsn_endurance))
				mod += 1;
			break;
	}

	mod += get_apply(ch, APPLY_COMP_SKILL);
	mod += get_apply(ch, APPLY_LUCK_SKILL);
	mod += get_apply(ch, APPLY_INS_SKILL);
	mod += get_apply(ch, APPLY_MOR_SKILL);
	
	mod += get_apply(ch, APPLY_LEVEL);

	if (IS_AFFECTED(ch, AFF2_SICKENED))
		mod -= 2;
	if (ch->fear_level > 0)
		mod -= UMIN(ch->fear_level, 3) * 2;
		
	switch(drunk_level(ch))
	{
		case DRUNK_SOBER:
			break;
		case DRUNK_TIPSY:
			mod -= 1;
			break;
		case DRUNK_MERRY:
			mod -= 2;
			break;
		case DRUNK_DRUNK:
			mod -= 4;
			break;
		case DRUNK_HAMMERED:
			mod -= 8;
			break;
		default:
			mod -= 16;
			break;
	}
	
	pop_call();
	return( mod );
}

/*
 * This variant rule allows for "skill-less" skill checks
 * for stat checks by adding a modifier based on class levels
 * if the stat is prime or secondary attribute for the class - Kregor
 */
int class_stat_bonus( CHAR_DATA *ch, int stat )
{
	int bonus, cnt;

	push_call("class_stat_bonus(%p)",ch);
	
	for (bonus = cnt = 0 ; cnt < MAX_CLASS ; cnt++)
	{
		if (!class_level(ch, cnt))
			continue;
		if (class_table[cnt].attr_prime == stat)
			bonus += class_level(ch, cnt) / 2;
		else if (class_table[cnt].attr_second == stat)
			bonus += class_level(ch, cnt) / 3;
	}
	pop_call();
	return bonus;
}

int str_roll( CHAR_DATA *ch )
{
	int roll;

	push_call("str_roll(%p)",ch);
	
	roll = dice(1,20);
	roll += stat_mods(ch, APPLY_STR);
	
	if (has_armor_penalty(ch))
		roll -= armor_check_penalty(ch);
		
	roll += class_stat_bonus(ch, APPLY_STR);

	pop_call();
	return( roll );
}

int dex_roll( CHAR_DATA *ch )
{
	int roll;

	push_call("dex_roll(%p)",ch);
	
	roll = dice(1,20);
	roll += stat_mods(ch, APPLY_DEX);

	if (has_armor_penalty(ch))
		roll -= armor_check_penalty(ch);

	roll += class_stat_bonus(ch, APPLY_DEX);

	pop_call();
	return( roll );
}

int con_roll( CHAR_DATA *ch )
{
	int roll;

	push_call("con_roll(%p)",ch);

	roll = dice(1,20);
	roll += stat_mods(ch, APPLY_CON);

	roll += class_stat_bonus(ch, APPLY_CON);

	pop_call();
	return( roll );
}

int int_roll( CHAR_DATA *ch )
{
	int roll;

	push_call("int_roll(%p)",ch);

	roll = dice(1,20);
	roll += stat_mods(ch, APPLY_INT);

	roll += class_stat_bonus(ch, APPLY_INT);

	pop_call();
	return( roll );
}

int wis_roll( CHAR_DATA *ch )
{
	int roll;

	push_call("wis_roll(%p)",ch);

	roll = dice(1,20);
	roll += stat_mods(ch, APPLY_WIS);

	roll += class_stat_bonus(ch, APPLY_WIS);

	pop_call();
	return( roll );
}

int cha_roll( CHAR_DATA *ch )
{
	int roll;

	push_call("cha_roll(%p)",ch);

	roll = dice(1,20);
	roll += stat_mods(ch, APPLY_CHA);

	roll += class_stat_bonus(ch, APPLY_CHA);

	pop_call();
	return( roll );
}

/*
 *	Retrieve a character's carry capacity.
 * (a mud thing, not SRD, but added SRD goodness) - Kregor
 * To discourage lugging too much inventory without a container or three
 */
int can_carry_n( CHAR_DATA *ch )
{
	int inv;
	
	push_call("can_carry_n(%p)",ch);

	inv = MAX_WEAR + ch->perm_dex + ch->level;
	
	if (learned(ch, gsn_packrat))
		inv += 2;
	
	if (IS_IMMORTAL(ch))
	{
		pop_call();
		return 1000;
	}
	switch (get_size(ch))
	{
		case SIZE_FINE:
			inv = inv / 3;
			break;
		
		case SIZE_DIMINUTIVE:
			inv = inv / 2;
			break;
		
		case SIZE_TINY:
			inv = inv * 2 / 3;
			break;		
		
		case SIZE_SMALL:
			inv = inv * 3 / 4;
			break;		
		
		case SIZE_LARGE:
			inv *= 2;
			break;		
		
		case SIZE_HUGE:
			inv *= 4;
			break;		
		
		case SIZE_GARGANTUAN:
			inv *= 6;
			break;		
		
		case SIZE_COLOSSAL:
			inv *= 8;
			break;		
	}
	if (quadruped(ch))
		inv *= 2;
	if (many_legged(ch))
		inv *= 4;
	if (is_affected(ch, gsn_floating_disc))
		inv *= 2;

	if (IS_NPC(ch))
	{
		if (IS_SET(ch->act, ACT_WEAK))
		{
			pop_call();
			return 1;
		}
	}
	pop_call();
	return inv;
}


/*
 * Carry weight tables under d20 SRD - Kregor
 */
const int max_carry_load[] =
{
  0,
  10,
  20,
  30,
  40,
  50,
  60,
  70,
  80,
  90,
  100,
  115,
  130,
  150,
  175,
  200,
  230,
  260,
  300,
  350,
  400,
  460,
  520,
  600,
  700,
  800,
  920,
  1040,
  1200,
  1400,
  1600,
};


int get_carry_w (CHAR_DATA *ch)
{
	OBJ_DATA *obj;
	int weight;
	
	push_call("get_carry_w(%p)",ch);
	
	for (weight = 0, obj = ch->first_carrying ; obj ; obj = obj->next_content)
	{
		if (obj->weight)
			weight += get_obj_weight(obj);
	}
	if (!IS_NPC(ch))
		weight += ch->gold / 500;
	
	pop_call();
	return weight;
}

/*
 * Retrieve a character's carry capacity.
 * Revised for SRD, and tenths of a pound - Kregor
 */
int can_carry_w( CHAR_DATA *ch )
{
	int stat, total, pounds;

	push_call("can_carry_w(%p)",ch);
	
	//incorporal beings can only carry incorporeal items, which also weigh 0
	if (IS_NPC(ch) && rspec_req(ch, RSPEC_INCORPOREAL))
	{
		pop_call();
		return 0;
	}

  stat = UMAX(0, UMIN(99, get_curr_str(ch)));
  
  if (learned(ch, gsn_packrat))
  	stat += 2;

  total = 1;
  while (stat > 30)
  {
    stat -= 10;
    total *= 4;
  }
  pounds = total * max_carry_load[stat];
	
	//to support 1/10ths of a pound
	pounds *= 10;
	
	if (many_legged(ch) || quadruped(ch))
	{
		switch (get_size(ch))
		{
			case SIZE_FINE:
				pounds = pounds / 4;
				break;
			case SIZE_DIMINUTIVE:
				pounds = pounds / 2;
				break;
			case SIZE_TINY:
				pounds = pounds * 3 / 4;
				break;		
			case SIZE_MEDIUM:
				pounds = pounds * 3 / 2;
				break;		
			case SIZE_LARGE:
				pounds *= 3;
				break;		
			case SIZE_HUGE:
				pounds *= 6;
				break;		
			case SIZE_GARGANTUAN:
				pounds *= 12;
				break;		
			case SIZE_COLOSSAL:
				pounds *= 24;
				break;		
		}
	}
	else
	{
		switch (get_size(ch))
		{
			case SIZE_FINE:
				pounds = pounds / 8;
				break;
			case SIZE_DIMINUTIVE:
				pounds = pounds / 4;
				break;
			case SIZE_TINY:
				pounds = pounds / 2;
				break;		
			case SIZE_SMALL:
				pounds = pounds * 3 / 4;
				break;		
			case SIZE_LARGE:
				pounds *= 2;
				break;		
			case SIZE_HUGE:
				pounds *= 4;
				break;		
			case SIZE_GARGANTUAN:
				pounds *= 8;
				break;		
			case SIZE_COLOSSAL:
				pounds *= 16;
				break;		
		}
	}
	
	if (many_legged(ch))
		pounds *= 8;
	else if (quadruped(ch))
		pounds *= 4;

	// add allowance for floating disc spell
	if (is_affected(ch, gsn_floating_disc))
		pounds += 1000 * get_affect_level(ch, gsn_floating_disc);

	if (IS_ACT(ch, ACT_WEAK))
		pounds = 10;

	pop_call();
	return pounds;
}

/* 
 * Height weight tables. Sadly only calculates
 * the standard player races. Should probably
 * re-do race tables for all races and call the
 * table with this function instead. - Kregor
 */
void height_weight(CHAR_DATA * ch)
{
	int mod;

	push_call("height_weight(%p)",ch);

	ch->height = number_fuzzy(race_table[ch->race].height);
	ch->weight = number_fuzzy(race_table[ch->race].weight);

	if (ch->race == RACE_HUMAN && ch->sex == SEX_MALE)
	{
		mod = dice(2,10);
		ch->height = 58 + mod;
		ch->weight = 120 + (dice(2,4)*mod);
	}
	if (ch->race == RACE_HUMAN && ch->sex == SEX_FEMALE)
	{
		mod = dice(2,10);
		ch->height = 53 + mod;
		ch->weight = 85 + (dice(2,4)*mod);
	}
	if (IS_RACE(ch, RACE_DWARF) && ch->sex == SEX_MALE)
	{
		mod = dice(2,4);
		ch->height = 45 + mod;
		ch->weight = 130 + (dice(2,6)*mod);
	}
	if (IS_RACE(ch, RACE_DWARF) && ch->sex == SEX_FEMALE)
	{
		mod = dice(2,4);
		ch->height = 43 + mod;
		ch->weight = 100 + (dice(2,6)*mod);
	}
	if (IS_RACE(ch, RACE_ELF) && ch->sex == SEX_MALE)
	{
		mod = dice(2,6);
		ch->height = 53 + mod;
		ch->weight = 85 + (dice(1,6)*mod);
	}
	if (IS_RACE(ch, RACE_ELF) && ch->sex == SEX_FEMALE)
	{
		mod = dice(2,6);
		ch->height = 53 + mod;
		ch->weight = 80 + (dice(1,6)*mod);
	}
	if (IS_RACE(ch, RACE_GNOME) && ch->sex == SEX_MALE)
	{
		mod = dice(2,4);
		ch->height = 36 + mod;
		ch->weight = 40 + mod;
	}
	if (IS_RACE(ch, RACE_GNOME) && ch->sex == SEX_FEMALE)
	{
		mod = dice(2,4);
		ch->height = 34 + mod;
		ch->weight = 35 + mod;
	}
	if (IS_RACE(ch, RACE_HALFELF) && ch->sex == SEX_MALE)
	{
		mod = dice(2,8);
		ch->height = 55 + mod;
		ch->weight = 40 + (dice(2,4)*mod);
	}
	if (IS_RACE(ch, RACE_HALFELF) && ch->sex == SEX_FEMALE)
	{
		mod = dice(2,8);
		ch->height = 53 + mod;
		ch->weight = 35 + (dice(2,4)*mod);
	}
	if (IS_RACE(ch, RACE_HALFORC) && ch->sex == SEX_MALE)
	{
		mod = dice(2,12);
		ch->height = 58 + mod;
		ch->weight = 150 + (dice(2,6)*mod);
	}
	if (IS_RACE(ch, RACE_HALFORC) && ch->sex == SEX_FEMALE)
	{
		mod = dice(2,12);
		ch->height = 53 + mod;
		ch->weight = 110 + (dice(2,6)*mod);
	}
	if (IS_RACE(ch, RACE_HALFLING) && ch->sex == SEX_MALE)
	{
		mod = dice(2,4);
		ch->height = 32 + mod;
		ch->weight = 30 + mod;
	}
	if (IS_RACE(ch, RACE_HALFLING) && ch->sex == SEX_FEMALE)
	{
		mod = dice(2,4);
		ch->height = 30 + mod;
		ch->weight = 25 + mod;
	}
	if (ch->race == RACE_DROW && ch->sex == SEX_MALE)
	{
		mod = dice(2,4);
		ch->height = 53 + mod;
		ch->weight = 85 + (dice(1,4)*mod);
	}
	if (ch->race == RACE_DROW && ch->sex == SEX_FEMALE)
	{
		mod = dice(2,4);
		ch->height = 53 + mod;
		ch->weight = 80 + (dice(1,4)*mod);
	}
	if (ch->race == RACE_ORC && ch->sex == SEX_MALE)
	{
		mod = dice(2,12);
		ch->height = 60 + mod;
		ch->weight = 160 + (dice(2,8)*mod);
	}
	if (ch->race == RACE_ORC && ch->sex == SEX_FEMALE)
	{
		mod = dice(2,12);
		ch->height = 56 + mod;
		ch->weight = 130 + (dice(2,8)*mod);
	}
	pop_call();
	return;
}

/* factor applies and affects into height wieght and size - Kregor */
int get_height( CHAR_DATA *ch )
{
	int height;
	
	push_call("get_height(%p)",ch);
	
	height = ch->height;
	
	if (get_apply(ch, APPLY_SIZE) > 0)
		height *= get_apply(ch, APPLY_SIZE) * 2;
	else if (get_apply(ch, APPLY_SIZE) < 0)
		height /= abs(get_apply(ch, APPLY_SIZE)) * 2;
	
	pop_call();
	return height;
}
	
int get_weight( CHAR_DATA *ch )
{
	int weight;
	
	push_call("get_weight(%p)",ch);
	
	weight = ch->weight;
	
	if (get_apply(ch, APPLY_SIZE) > 0)
		weight *= get_apply(ch, APPLY_SIZE) * 2;
	else if (get_apply(ch, APPLY_SIZE) < 0)
		weight /= abs(get_apply(ch, APPLY_SIZE)) * 2;
	
	pop_call();
	return weight;
}
	
int get_size(CHAR_DATA * ch)	
{
	int size;

	push_call("get_size(%p)",ch);

	size = race_table[get_race(ch)].size;
	size += get_apply(ch, APPLY_SIZE);

	pop_call();
	return (URANGE(SIZE_FINE, size, SIZE_COLOSSAL));
}


/* calc the maximum hit points for ch */
int get_max_hit( CHAR_DATA *ch )
{
	int pts;
	
	push_call("get_max_hit(%p)",ch);

	pts = ch->max_hit + (stat_bonus(TRUE, ch, APPLY_CON) * ch->level);
	pts += get_apply(ch, APPLY_HIT);
	
	/* reworked toughness feat */
	if (learned(ch, gsn_toughness))
		pts += ch->level;
	if (learned(ch, gsn_secret_of_health))
		pts += ch->level;
		
	/* this causes a loop thru PET_DATA each
	 * time it's called, hopefully not too much
	 */
	if (has_familiar(ch, RACE_TOAD))
		pts += ch->level;
		
	/* -5 hp for every negative level per SRD */
	pts += get_apply(ch, APPLY_LEVEL) * 5;
	
	pts = UMAX(ch->level, pts);
	
	pop_call();
	return(pts);
}

/* calc the maximum move points for ch */
int get_max_move( CHAR_DATA *ch )
{
	int pts;
	
	push_call("get_max_move(%p)",ch);

	pts = stat_bonus(TRUE, ch, APPLY_CON) * ch->level + ch->max_move;
	pts += get_apply(ch, APPLY_MOVE);
	
	if (many_legged(ch) || quadruped(ch))
		pts *= 4;
	
	pts = UMAX(ch->level, pts);
	
	pop_call();
	return(pts);
}

/* no move = exhaustion */
bool IS_EXHAUSTED( CHAR_DATA *ch )
{
	push_call("IS_EXHAUSTED(%p)",ch);
	
	if (IS_AFFECTED(ch, AFF2_EXHAUSTED))
	{
		pop_call();
		return TRUE;
	}
	
	if (ch->move <= 0)
	{
		pop_call();
		return TRUE;
	}
	pop_call();
	return FALSE;
}

/* 1/3 of move = fatigued */
bool IS_FATIGUED( CHAR_DATA *ch )
{
	push_call("IS_FATIGUED(%p)",ch);
	
	/* If you're already worse, you're not this */
	if (IS_EXHAUSTED(ch))
	{
		pop_call();
		return FALSE;
	}

	if (IS_AFFECTED(ch, AFF2_FATIGUED))
	{
		pop_call();
		return TRUE;
	}
	
	if (ch->move < get_max_move(ch) / 3)
	{
		pop_call();
		return TRUE;
	}
	
	/* hungry and dehydrated = fatigue until sated */
	if (!IS_NPC(ch))
	{
		if (ch->pcdata->condition[COND_THIRST] <= 0
		|| ch->pcdata->condition[COND_FULL] <= 0)
		{
			pop_call();
			return TRUE;
		}
	}
	pop_call();
	return FALSE;
}

/* check for staggered condition */
bool IS_STAGGERED( CHAR_DATA *ch )
{
	push_call("IS_STAGGERED(%p)",ch);
	
	/* If you're out, you're not moving */
	if (!IS_AWAKE(ch))
	{
		pop_call();
		return FALSE;
	}

	if (ch->nonlethal == ch->hit || ch->hit <= 0)
	{
		pop_call();
		return TRUE;
	}
	
	if (ch->move <= 0)
	{
		pop_call();
		return TRUE;
	}
	
	if (!IS_AFFECTED(ch, AFF_FREEDOM))
	{
		if (IS_AFFECTED(ch, AFF2_STAGGERED) || IS_UNDERWATER(ch))
		{
			pop_call();
			return TRUE;
		}
	}
	pop_call();
	return FALSE;
}


int level_diff(CHAR_DATA *ch)
{
	int lev, cnt;
	int count = 0;
	int level1 = 0;
	int level2 = 0;

	push_call("level_diff(%p)",ch);

	if (IS_NPC(ch))
	{
		pop_call();
		return 0;
	}
	
	for (lev = 1 ; lev <= LEVEL_HERO ; lev++)
	{
		for (cnt = 0 ; cnt < CLASS_ARCANE_ARCHER ; cnt++)
		{
			if (favored_class(ch) == cnt)
				continue;
			if (lev == class_level(ch, cnt))
				level2 = lev;
		}
		if (level1 > 0 && level1+2 <= level2)
			count += 1;
		level1 = level2;
	}
	pop_call();
	return count;
}

int favored_class(CHAR_DATA *ch)
{
	int cnt = 0;
	int class = 0;

	push_call("favored_class(%p)",ch);

	class = CLASS_MONSTER;
	
	switch (ch->race)
	{
		case RACE_HUMAN:
		case RACE_HALFELF:
			for (cnt = 0 ; cnt < MAX_CLASS ; cnt++)
			{
				if (!ch->mclass[cnt])
				{
					continue;
				}
				if (ch->mclass[cnt] > class)
				{
					class = cnt;
				}
			}
			break;
		case RACE_DROW:
			if (ch->sex == SEX_MALE)
			{
				class = CLASS_WIZARD;
				break;
			}
			else
			{
				class = CLASS_FIGHTER;
				break;
			}
			break;
		case RACE_GNOME: /* bring back gnome illusionists! */
			if (get_school(ch) == SCHOOL_ILLUSION)
			{
				class = CLASS_WIZARD;
				break;
			}
			else
			{
				class = CLASS_BARD;
				break;
			}
			break;
		default:
			class = race_table[ch->race].favored_class;
			break;
	}
	pop_call();
	return class;
}


/* calcs starting gold at character gen - Kregor */
int starting_gold ( CHAR_DATA * ch )
{
	int gold = 0;
	
	push_call("starting_gold(%p)",ch);
	
	switch (ch->class)
	{
		default:
			bug("starting_gold: unknown starting class.", 0);
			break;
		case CLASS_BARBARIAN:
			gold = dice(3, 6) * 1000;
			break;
		case CLASS_BARD:
			gold = dice(3, 6) * 1000;
			break;
		case CLASS_CLERIC:
			gold = dice(4, 6) * 1000;
			break;
		case CLASS_DRUID:
			gold = dice(2, 6) * 1000;
			break;
		case CLASS_FIGHTER:
			gold = dice(5, 6) * 1000;
			break;
		case CLASS_MONK:
			gold = dice(1, 6) * 1000;
			break;
		case CLASS_PALADIN:
			gold = dice(5, 6) * 1000;
			break;
		case CLASS_RANGER:
			gold = dice(4, 6) * 1000;
			break;
		case CLASS_ROGUE:
		case CLASS_EXPERT:
			gold = dice(4, 6) * 1000;
			break;
		case CLASS_SORCERER:
			gold = dice(2, 6) * 1000;
			break;
		case CLASS_WIZARD:
			gold = dice(2, 6) * 1000;
			break;
	}
	
	if (learned(ch, gsn_noble_birth))
		gold += 10000;

	pop_call();
	return gold;
}

/* Max new languages a CHAR can learn - Kregor */
int max_tongues(CHAR_DATA *ch)
{
	int max;

	push_call("max_tongues(%p)",ch);
	
	if (IS_NPC(ch))
	{
		pop_call();
		return 0;
	}
	
	max = stat_bonus(FALSE, ch, APPLY_INT);
	
	if (learned(ch, gsn_linguist))
		max += 2;
		
	max += class_level(ch, CLASS_LOREMASTER) / 4;

	pop_call();
	return max;
}


/* 
 * tally the current starting tongues for char besides
 * automatic ones - Kregor
 */
int count_tongues(CHAR_DATA *ch)
{
	int cnt, count;
	
	push_call("max_tongues(%p)",ch);
	
	if (IS_NPC(ch))
	{
		pop_call();
		return 0;
	}
	
	for (cnt = count = 0 ; cnt < MAX_LANG - 2 ; cnt++)
	{
		if (race_table[ch->race].speaks == 1 << cnt)
			continue;
		if (IS_SET(race_table[ch->race].understands, 1 << cnt))
			continue;
		if (IS_SET(ch->language, 1 << cnt))
			count++;
	}
	pop_call();
	return count;
}


/* tally number of bard songs learnable - Kregor */
int max_songs(CHAR_DATA *ch)
{
	int level;

	push_call("max_songs(%p)",ch);
	
	if ((level = multi_class_level(ch, gsn_bardic_song)) <= 0)
	{
		pop_call();
		return 0;
	}
	
	pop_call();
	return (level / 3);
}

int count_songs(CHAR_DATA *ch)
{
	int sn, count;
	
	push_call("count_songs(%p)",ch);
	
	if (IS_NPC(ch))
	{
		pop_call();
		return 0;
	}
	
	for (sn = count = 0 ; *skill_table[sn].name != '\0' ; sn++)
	{
		if (skill_table[sn].skilltype != FSKILL_BARDSONG)
			continue;
		if (learned(ch, sn))
			count++;
	}
	pop_call();
	return count;
}


/*
 * Compute saving throws.
 * d20 variant rule: added medium save progression.
 * d20 variant rule: Instead of computing saves per class, for
 * multiclass characters, total the levels of all classes in
 * each given save progression, and base the total bonus on
 * combined levels of each - Kregor
 */
int base_fort_save( CHAR_DATA *ch )
{
	float save;

	push_call("base_fort_save(%p)",ch);

	if (!IS_CLASSED(ch))
	{
		switch (race_type_table[race_type(ch)].fort_save)
		{
			case SAVE_HIGH:
				save = ch->level / 2 + 2;
				break;
			case SAVE_MED:
				save = ch->level / 2.5 + 1;
				break;
			default:
				save = ch->level / 3;
				break;
		}
		pop_call();
		return (save);
	}

// 	if (ch->level == 1)
// 	{
// 		switch (class_table[ch->class].fort_save)
// 		{
// 			case SAVE_HIGH:
// 				save = class_level(ch, ch->class) / 2 + 2;
// 				break;
// 			case SAVE_MED:
// 				save = class_level(ch, ch->class) / 2.5 + 1;
// 				break;
// 			default:
// 				save = class_level(ch, ch->class) / 3;
// 				break;
// 		}
// 		pop_call();
// 		return (save);
// 	}
// 		
	int save_hi, save_md, save_lo, cnt;

	for (cnt = save_hi = save_md = save_lo = 0 ; cnt < MAX_CLASS ; cnt++)
	{
		if (class_level(ch, cnt) <= 0)
			continue;

		if (cnt == CLASS_MONSTER)
		{
			if (race_type_table[race_type(ch)].fort_save == SAVE_HIGH)
				save_hi += class_level(ch, cnt);
			else if (race_type_table[race_type(ch)].fort_save == SAVE_MED)
				save_md += class_level(ch, cnt);
			else
				save_lo += class_level(ch, cnt);
			continue;
		}
		else
		{
			if (class_table[cnt].fort_save == SAVE_HIGH)
				save_hi += class_level(ch, cnt);
			else if (class_table[cnt].fort_save == SAVE_MED)
				save_md += class_level(ch, cnt);
			else
				save_lo += class_level(ch, cnt);
		}
	}
	save = 0;
	if (save_hi > 0)
		save += save_hi / 2 + 2;
	if (save_md > 0)
		save += save_md / 2.5 + 1;
	if (save_lo > 0)
		save += save_lo / 3;

	pop_call();
	return round(save);
}

int get_fort_save( CHAR_DATA *ch )
{
	int save;

	push_call("get_fort_save(%p)",ch);

	save = base_fort_save(ch);

	if ((is_affected(ch, gsn_bestow_grace) || learned(ch, gsn_divine_grace)) && stat_bonus(TRUE, ch, APPLY_CHA) > 0)
	{
		save += UMIN(stat_bonus(TRUE, ch, APPLY_CHA), multi_class_level(ch, gsn_divine_grace));
	}
	
	save += stat_bonus(TRUE, ch, APPLY_CON);

	save += get_apply(ch, APPLY_SAVING_FORT);
	save += combine_apply(ch, APPLY_COMP_FORT, APPLY_COMP_SAVES);
	save += combine_apply(ch, APPLY_INS_FORT, APPLY_INS_SAVES);
	save += combine_apply(ch, APPLY_LUCK_FORT, APPLY_LUCK_SAVES);
	save += combine_apply(ch, APPLY_MOR_FORT, APPLY_MOR_SAVES);
	save += get_apply(ch, APPLY_LEVEL);

	
	if (IS_AFFECTED(ch, AFF2_SICKENED))
		save -= 2;
	if (ch->fear_level > 0)
		save -= 2;

	if (has_familiar(ch, RACE_RODENT))
		save += 2;

	if (learned(ch, gsn_secret_of_stamina))
		save += 2;

	pop_call();
	return(save);
}

int base_refl_save( CHAR_DATA *ch )
{
	float save;

	push_call("base_refl_save(%p)",ch);

	if (!IS_CLASSED(ch))
	{
		switch (race_type_table[race_type(ch)].refl_save)
		{
			case SAVE_HIGH:
				save = ch->level / 2 + 2;
				break;
			case SAVE_MED:
				save = ch->level / 2.5 + 1;
				break;
			default:
				save = ch->level / 3;
				break;
		}
		pop_call();
		return (save);
	}

// 	if (ch->level == 1)
// 	{
// 		switch (class_table[ch->class].refl_save)
// 		{
// 			case SAVE_HIGH:
// 				save = class_level(ch, ch->class) / 2 + 2;
// 				break;
// 			case SAVE_MED:
// 				save = class_level(ch, ch->class) / 2.5 + 1;
// 				break;
// 			default:
// 				save = class_level(ch, ch->class) / 3;
// 				break;
// 		}
// 		pop_call();
// 		return (save);
// 	}
// 		
	int save_hi, save_md, save_lo, cnt;

	for (cnt = save_hi = save_md = save_lo = 0 ; cnt < MAX_CLASS ; cnt++)
	{
		if (class_level(ch, cnt) <= 0)
			continue;

		if (cnt == CLASS_MONSTER)
		{
			if (race_type_table[race_type(ch)].refl_save == SAVE_HIGH)
				save_hi += class_level(ch, cnt);
			else if (race_type_table[race_type(ch)].refl_save == SAVE_MED)
				save_md += class_level(ch, cnt);
			else
				save_lo += class_level(ch, cnt);
			continue;
		}
		else
		{
			if (class_table[cnt].refl_save == SAVE_HIGH)
				save_hi += class_level(ch, cnt);
			else if (class_table[cnt].refl_save == SAVE_MED)
				save_md += class_level(ch, cnt);
			else
				save_lo += class_level(ch, cnt);
		}
	}
	save = 0;
	if (save_hi > 0)
		save += save_hi / 2 + 2;
	if (save_md > 0)
		save += save_md / 2.5 + 1;
	if (save_lo > 0)
		save += save_lo / 3;

	pop_call();
	return round(save);
}

int get_refl_save( CHAR_DATA *ch )
{
	int save, plus;
	OBJ_DATA *shield;

	push_call("get_refl_save(%p)",ch);

	save = base_refl_save(ch);

	switch(drunk_level(ch))
	{
		case DRUNK_SOBER:
			break;
		case DRUNK_TIPSY:
			save -= 1;
			break;
		case DRUNK_MERRY:
			save -= 2;
			break;
		case DRUNK_DRUNK:
			save -= 4;
			break;
		case DRUNK_HAMMERED:
			save -= 8;
			break;
		default:
			save -= 16;
			break;
	}

	if ((is_affected(ch, gsn_bestow_grace) || learned(ch, gsn_divine_grace)) && stat_bonus(TRUE, ch, APPLY_CHA) > 0)
	{
		save += UMIN(stat_bonus(TRUE, ch, APPLY_CHA), multi_class_level(ch, gsn_divine_grace));
	}
	
	if ((plus = multi_class_level(ch, gsn_grace)) >= 4)
	{
		shield = get_eq_char(ch, WEAR_SHIELD);
		
		if (armor_type_worn(ch) == ARMOR_NONE && !shield)
		{
			save += (plus / 4) * 2;
		}
	}
	
	save += stat_bonus(TRUE, ch, APPLY_DEX);

	if (get_monk_style(ch) == STYLE_INVISIBLE_EYE && class_level(ch, CLASS_MONK) >= 12)
		save += 2;

	save += get_apply(ch, APPLY_SAVING_REFL);
	save += combine_apply(ch, APPLY_COMP_REFL, APPLY_COMP_SAVES);
	save += combine_apply(ch, APPLY_INS_REFL, APPLY_INS_SAVES);
	save += combine_apply(ch, APPLY_LUCK_REFL, APPLY_LUCK_SAVES);
	save += combine_apply(ch, APPLY_MOR_REFL, APPLY_MOR_SAVES);
	save += get_apply(ch, APPLY_LEVEL);
	
	// armor penalty applies to non-proficient armor
	if (has_armor_penalty(ch))
		save -= armor_check_penalty(ch);
	
	if (IS_AFFECTED(ch, AFF2_SICKENED))
		save -= 2;
	if (ch->fear_level > 0)
		save -= 2;

	if (IS_AFFECTED(ch, AFF2_FASCINATED))
		save -= 4;

	if (has_familiar(ch, RACE_WEASEL))
		save += 2;

	if (learned(ch, gsn_secret_of_avoidance))
		save += 2;

	pop_call();
	return(save);
}

int base_will_save( CHAR_DATA *ch )
{
	float save;

	push_call("base_will_save(%p)",ch);

	if (!IS_CLASSED(ch))
	{
		switch (race_type_table[race_type(ch)].will_save)
		{
			case SAVE_HIGH:
				save = ch->level / 2 + 2;
				break;
			case SAVE_MED:
				save = ch->level / 2.5 + 1;
				break;
			default:
				save = ch->level / 3;
				break;
		}
		pop_call();
		return (save);
	}

// 	if (ch->level == 1)
// 	{
// 		switch (class_table[ch->class].will_save)
// 		{
// 			case SAVE_HIGH:
// 				save = class_level(ch, ch->class) / 2 + 2;
// 				break;
// 			case SAVE_MED:
// 				save = class_level(ch, ch->class) / 2.5 + 1;
// 				break;
// 			default:
// 				save = class_level(ch, ch->class) / 3;
// 				break;
// 		}
// 		pop_call();
// 		return (save);
// 	}
// 		
	int save_hi, save_md, save_lo, cnt;

	for (cnt = save_hi = save_md = save_lo = 0 ; cnt < MAX_CLASS ; cnt++)
	{
		if (class_level(ch, cnt) <= 0)
			continue;

		if (cnt == CLASS_MONSTER)
		{
			if (race_type_table[race_type(ch)].will_save == SAVE_HIGH)
				save_hi += class_level(ch, cnt);
			else if (race_type_table[race_type(ch)].will_save == SAVE_MED)
				save_md += class_level(ch, cnt);
			else
				save_lo += class_level(ch, cnt);
			continue;
		}
		else
		{
			if (class_table[cnt].will_save == SAVE_HIGH)
				save_hi += class_level(ch, cnt);
			else if (class_table[cnt].will_save == SAVE_MED)
				save_md += class_level(ch, cnt);
			else
				save_lo += class_level(ch, cnt);
		}
	}
	save = 0;
	if (save_hi > 0)
		save += save_hi / 2 + 2;
	if (save_md > 0)
		save += save_md / 2.5 + 1;
	if (save_lo > 0)
		save += save_lo / 3;

	pop_call();
	return round(save);
}

int get_will_save( CHAR_DATA *ch )
{
	int save;

	push_call("get_will_save(%p)",ch);

	save = base_will_save(ch);

	if ((is_affected(ch, gsn_bestow_grace) || learned(ch, gsn_divine_grace)) && stat_bonus(TRUE, ch, APPLY_CHA) > 0)
	{
		save += UMIN(stat_bonus(TRUE, ch, APPLY_CHA), multi_class_level(ch, gsn_divine_grace));
	}
	
	save += stat_bonus(TRUE, ch, APPLY_WIS);
	
	save += get_apply(ch, APPLY_SAVING_WILL);
	save += combine_apply(ch, APPLY_COMP_WILL, APPLY_COMP_SAVES);
	save += combine_apply(ch, APPLY_INS_WILL, APPLY_INS_SAVES);
	save += combine_apply(ch, APPLY_LUCK_WILL, APPLY_LUCK_SAVES);
	save += combine_apply(ch, APPLY_MOR_WILL, APPLY_MOR_SAVES);
	save += get_apply(ch, APPLY_LEVEL);
	
	if (learned(ch, gsn_secret_of_resolve))
		save += 2;
	if (IS_AFFECTED(ch, AFF2_SICKENED))
		save -= 2;
	if (ch->fear_level > 0)
		save -= 2;

	pop_call();
	return(save);
}

/*
 * Return the flag for sorcerer bloodline - Kregor
 */
int get_bloodline( CHAR_DATA *ch )
{
	int num = -1;

	push_call("get_bloodline(%p)",ch);

	if (!class_level(ch, CLASS_SORCERER))
	{
		pop_call();
		return -1;
	}

	if (IS_NPC(ch))
		num = BLOODLINE_DRACONIC;
	else if (ch->learned[gsn_aberrant_bloodline])
		num = BLOODLINE_ABERRANT;
	else if (ch->learned[gsn_abyssal_bloodline])
		num = BLOODLINE_ABYSSAL;
	else if (ch->learned[gsn_arcane_bloodline])
		num = BLOODLINE_ARCANE;
	else if (ch->learned[gsn_celestial_bloodline])
		num = BLOODLINE_CELESTIAL;
	else if (ch->learned[gsn_destined_bloodline])
		num = BLOODLINE_DESTINED;
	else if (ch->learned[gsn_air_bloodline])
		num = BLOODLINE_AIR;
	else if (ch->learned[gsn_fire_bloodline])
		num = BLOODLINE_FIRE;
	else if (ch->learned[gsn_earth_bloodline])
		num = BLOODLINE_EARTH;
	else if (ch->learned[gsn_water_bloodline])
		num = BLOODLINE_WATER;
	else if (ch->learned[gsn_fey_bloodline])
		num = BLOODLINE_FEY;
	else if (ch->learned[gsn_infernal_bloodline])
		num = BLOODLINE_INFERNAL;
	else if (ch->learned[gsn_undead_bloodline])
		num = BLOODLINE_UNDEAD;

	pop_call();
	return num;
}
		
/*
 * Return the flag for combat style
 * NPCs without a style set default to d20 originals - Kregor
 */
int get_monk_style( CHAR_DATA *ch )
{
	int num = 0;

	push_call("get_monk_style(%p)",ch);

	if (!class_level(ch, CLASS_MONK))
	{
		pop_call();
		return 0;
	}

	if (ch->learned[gsn_style_cobra_strike])
		num = STYLE_COBRA_STRIKE;
	else if (ch->learned[gsn_style_denying_stance])
		num = STYLE_DENYING_STANCE;
	else if (ch->learned[gsn_style_hand_and_foot])
		num = STYLE_HAND_AND_FOOT;
	else if (ch->learned[gsn_style_invisible_eye])
		num = STYLE_INVISIBLE_EYE;
	else if (ch->learned[gsn_style_passive_way])
		num = STYLE_PASSIVE_WAY;
	else if (ch->learned[gsn_style_sleeping_tiger])
		num = STYLE_SLEEPING_TIGER;
	else if (ch->learned[gsn_style_undying_way])
		num = STYLE_UNDYING_WAY;
	else if (IS_NPC(ch))
		num = STYLE_HAND_AND_FOOT;

	pop_call();
	return num;
}
		
int get_ranger_style( CHAR_DATA *ch )
{
	int num = 0;

	push_call("get_ranger_style(%p)",ch);

	if (!class_level(ch, CLASS_RANGER))
	{
		pop_call();
		return 0;
	}

	if (ch->learned[gsn_style_melee])
		num = STYLE_MELEE;
	else if (ch->learned[gsn_style_archery])
		num = STYLE_ARCHERY;
	else if (ch->learned[gsn_style_horseback])
		num = STYLE_HORSEBACK;
	else if (ch->learned[gsn_style_skirmish])
		num = STYLE_SKIRMISHING;
	else if (ch->learned[gsn_style_strongarm])
		num = STYLE_STRONGARM;
	else if (ch->learned[gsn_style_throwing])
		num = STYLE_THROWING;
	else if (IS_NPC(ch))
		num = STYLE_MELEE;

	pop_call();
	return num;
}
		
/*
 * Return the flag for wizard school - Kregor
 */
int get_school( CHAR_DATA *ch )
{
	int num = -1;

	push_call("get_school(%p)",ch);

	if (!class_level(ch, CLASS_WIZARD))
	{
		pop_call();
		return -1;
	}

	if (ch->learned[gsn_school_univ])
		num = SCHOOL_UNIVERSAL;
	else if (ch->learned[gsn_school_abj])
		num = SCHOOL_ABJURATION;
	else if (ch->learned[gsn_school_conj])
		num = SCHOOL_CONJURATION;
	else if (ch->learned[gsn_school_div])
		num = SCHOOL_DIVINATION;
	else if (ch->learned[gsn_school_ench])
		num = SCHOOL_ENCHANTMENT;
	else if (ch->learned[gsn_school_evoc])
		num = SCHOOL_EVOCATION;
	else if (ch->learned[gsn_school_illus])
		num = SCHOOL_ILLUSION;
	else if (ch->learned[gsn_school_necro])
		num = SCHOOL_NECROMANCY;
	else if (ch->learned[gsn_school_trans])
		num = SCHOOL_TRANSMUTATION;

	pop_call();
	return num;
}
		
/*
 * Return clerical domains - Kregor
 */
bool has_domain( CHAR_DATA *ch, int dom )
{
	push_call("has_domain(%p)",ch);

	if (!class_level(ch, CLASS_CLERIC))
	{
		pop_call();
		return FALSE;
	}

	if (IS_NPC(ch))
	{
		if (ch->god && (god_table[ch->god].domain1 == dom || (class_level(ch, CLASS_CLERIC) > 10 && god_table[ch->god].domain2 == dom)))
		{
			pop_call();
			return TRUE;
		}
		pop_call();
		return FALSE;
	}

	pop_call();
	return ch->pcdata->domain[dom];
}
		
/*
 * is cleric apotheosized? - Kregor
 */
bool domain_apotheosis( CHAR_DATA *ch, int dom )
{
	push_call("domain_apotheosis(%p)",ch);

	if (class_level(ch, CLASS_CLERIC) < 20)
	{
		pop_call();
		return FALSE;
	}

	pop_call();
	return has_domain(ch, dom);
}
		
/*
 * check sorcerer capstone. - Kregor
 */
bool bloodline_capstone( CHAR_DATA *ch, int bloodline )
{
	push_call("bloodline_capstone(%p)",ch);

	if (class_level(ch, CLASS_SORCERER) < 20)
	{
		pop_call();
		return FALSE;
	}

	pop_call();
	return (get_bloodline(ch) == bloodline);
}
		
/*
 * check arcane mastery. - Kregor
 */
bool arcane_mastery( CHAR_DATA *ch, int school )
{
	push_call("arcane_mastery(%p)",ch);

	if (class_level(ch, CLASS_WIZARD) < 20)
	{
		pop_call();
		return FALSE;
	}

	pop_call();
	return (get_school(ch) == school);
}
		
/*
 * Extensively reworked to return ranks in skills,
 * Activate race and class abilities at a given level,
 * and return classes a spell is known in - Kregor
 */
int learned( CHAR_DATA *ch, int sn )
{
	int cnt, rank;

	push_call("learned(%p,%p)",ch,sn);
	
	if (ch == NULL)
	{
		pop_call();
		return 0;
	}
	
	if (!is_string(skill_table[sn].name))
	{
		pop_call();
		return 0;
	}
	
	/*
	 * NPC skill ranks: check for hard-set first, then
	 * assign ranks based on class, fall back to racial skills.
	 */
	if (IS_NPC(ch) && skill_table[sn].skilltype == FSKILL_SKILL)
	{
		if ((rank = ch->learned[sn]) <= 0)
		{
			if (IS_CLASSED(ch))
			{
				if ((cnt = multi(ch, sn)) != -1)
				{
					rank = class_level(ch, cnt);
				}
				else
				{
					rank = 0;
				}
			}
			if (get_race(ch) > RACE_NONE && race_skill(ch, sn) > rank)
			{
				rank = race_skill(ch, sn);				
			}
		}
		pop_call();
		return UMAX(0, number_fuzzy(rank));
	}

	if (skill_table[sn].skilltype == FSKILL_SPELL)
	{
		int class = ch->learned[sn];
		
		/*
		 * NPC spells are known and cast at highest casting class
		 */
		if (IS_NPC(ch))
		{
			if ((class = multi(ch, sn)) != -1)
			{
				pop_call();
				return (1 << class);
			}
			pop_call();
			return 0;
		}	
		if (opposing_domain(ch, sn))
		{
			pop_call();
			return 0;
		}
		if (domain_skill(ch, sn))
		{
			SET_SHIFT(class, CLASS_CLERIC);
		}		
		if (bloodline_skill(ch, sn))
		{
			SET_SHIFT(class, CLASS_SORCERER);
		}
		for (cnt = 0 ; cnt < MAX_CLASS ; cnt++)
		{
			if (class_level(ch, cnt) <= 0)
				continue;
			if (IS_GOD(ch))
			{
				SET_SHIFT(class, cnt);
			}
			if (cnt == CLASS_WIZARD && opposing_school(ch, sn)) // wizards can't have opposing schools
				continue;
			if (ch->learned[gsn_shadow_casting] && IS_SET(skill_table[sn].spell_desc, SDESC_LIGHT))
				continue;
			if (cnt == CLASS_WIZARD && skill_table[sn].skill_level[cnt] == 0) // automatic cantrips
				SET_SHIFT(class, cnt);			
				
			if (cnt == CLASS_BLACKGUARD && !IS_EVIL(ch)) // non evil blackguards lose spells
				continue;

			if (cnt == CLASS_PALADIN && (!IS_GOOD(ch) || !IS_LAWFUL(ch))) // non LG pallys lose spells
				continue;

			if (cnt == CLASS_PALADIN && (!IS_NPC(ch) && ch->pcdata->reputation < 9)) // Pally reputation matters!
				continue;

			if (class_table[cnt].attr_prime == APPLY_WIS)
			{
				if (max_spell_circle(ch, cnt) >= skill_table[sn].skill_level[cnt])
				{
					SET_SHIFT(class, cnt);
				}
			}
			else if (skill_table[sn].skill_level[cnt] == 0)
			{
				SET_SHIFT(class, cnt);
			}
		}
		pop_call();
		return class;
	}
	
	if (skill_table[sn].skilltype == FSKILL_ABILITY)
	{
		if (class_ability(ch, sn) || race_skill(ch, sn))
		{
			pop_call();
			return (UMAX(1,ch->learned[sn]));
		}
	}
	
	if (skill_table[sn].skilltype == FSKILL_RACEATTACK && race_skill(ch, sn))
	{
		pop_call();
		return (UMAX(1,ch->learned[sn]));
	}
	
	if (skill_table[sn].skilltype == FSKILL_BARDSONG)
	{
		if (multi_class_level(ch, gsn_bardic_song) >= skill_table[sn].skill_level[CLASS_BARD])
		{
			pop_call();
			return (UMAX(1,ch->learned[sn]));
		}
	}
	
	if (skill_table[sn].skilltype == FSKILL_FEAT)
	{
		// give imp. unarmed strike to clerics of deities w/ unarmed strike as fave weapon - Kregor
		if (sn == gsn_imp_unarmed_strike)
		{
			if (class_level(ch, CLASS_CLERIC) && ch->god && god_table[ch->god].favored_weapon == WEAPON_TYPE_WEAPON)
			{
				pop_call();
				return (UMAX(1,ch->learned[sn]));
			}
		}
		if (class_ability(ch, sn) || race_skill(ch, sn))
		{
			pop_call();
			return (UMAX(1,ch->learned[sn]));
		}
	}

	/* Weapon proficiencies by class */
	if (skill_table[sn].skilltype == FSKILL_WEAPON)
	{
		int skill = ch->learned[sn];

		if (skill_table[sn].spell_desc == WEAPON_CLASS_SIMPLE
		&& sn != gsn_unarmed_strike
		&& (ch->learned[gsn_weapon_prof_simple]
		|| class_ability(ch, gsn_weapon_prof_simple)))
		{
			SET_BIT(skill, WSKILL_PROFICIENT);
		}
		if (skill_table[sn].spell_desc == WEAPON_CLASS_MARTIAL
		&& (ch->learned[gsn_weapon_prof_martial]
		|| class_ability(ch, gsn_weapon_prof_martial)))
		{
			SET_BIT(skill, WSKILL_PROFICIENT);
		}
		if (skill_table[sn].spell_desc == WEAPON_CLASS_EXOTIC
		&& (ch->learned[gsn_weapon_prof_exotic]
		|| class_ability(ch, gsn_weapon_prof_exotic)))
		{
			SET_BIT(skill, WSKILL_PROFICIENT);
		}
		if (class_ability(ch, sn) || race_skill(ch, sn))
		{
			SET_BIT(skill, WSKILL_PROFICIENT);
		}
		if (sn == gsn_unarmed_strike)
		{
			if (class_level(ch, CLASS_CLERIC) && ch->god && god_table[ch->god].favored_weapon == WEAPON_TYPE_WEAPON)
			{
				SET_BIT(skill, WSKILL_PROFICIENT);
				if (has_domain(ch, DOMAIN_WAR))
				{
					SET_BIT(skill, WSKILL_SPECIALIZED);
				}
			}
			else if (class_ability(ch, gsn_imp_unarmed_strike) || ch->learned[gsn_imp_unarmed_strike]
			|| class_ability(ch, gsn_martial_arts) || ch->learned[gsn_martial_arts])
			{
				SET_BIT(skill, WSKILL_PROFICIENT);
			}
		}
		pop_call();
		return skill;
	}
	
	if ((sn == gsn_evasion || sn == gsn_imp_evasion) && IS_ACT(ch, ACT_FAMILIAR))
	{
		pop_call();
		return (UMAX(1,ch->learned[sn]));
	}

	if ((sn == gsn_evasion || sn == gsn_imp_evasion) && IS_ACT(ch, ACT_COMPANION|ACT_WARHORSE))
	{
		if (ch->level > 11 && sn == gsn_imp_evasion)
		{
			pop_call();
			return (UMAX(1,ch->learned[sn]));
		}
		if (ch->level > 4 && sn == gsn_evasion)
		{
			pop_call();
			return (UMAX(1,ch->learned[sn]));
		}
	}

	if (ch->learned[sn] <= 0)
	{
		pop_call();
		return 0;
	}
	pop_call();
	return (ch->learned[sn]);
}

/*
 *	Calculate the effective armor type worn by a character.
 */
int armor_type_worn( CHAR_DATA *ch )
{
	return ch->armor_type;
}

/*
 * Check to see if an armor check penalty for non-proficiency applies - Kregor 4/7/07
 */
bool has_armor_penalty(CHAR_DATA * ch)
{
	push_call("has_armor_penalty(%p)",ch);
	
	if (armor_type_worn(ch) >= ARMOR_HEAVY)
	{
		if (!learned(ch, gsn_armor_proficiency_heavy))
		{
			pop_call();
			return TRUE;
		}
	}
	else if (armor_type_worn(ch) >= ARMOR_MEDIUM)
	{
		if (!learned(ch, gsn_armor_proficiency_medium))
		{
			pop_call();
			return TRUE;
		}
	}
	if (armor_type_worn(ch) >= ARMOR_LIGHT)
	{
		if (!learned(ch, gsn_armor_proficiency_light))
		{
			pop_call();
			return TRUE;
		}
	}
	pop_call();
	return FALSE;
}

/*
 *	return effective max dex bonus.
 */
int dex_bonus_max( CHAR_DATA *ch )
{
	int max = ch->dex_max;

	push_call("dex_bonus_max(%p)",ch);

	switch (encumberance(ch))
	{
		case VERY_ENCUMBERED:
			max = UMIN(max, 1);
			break;
		case ENCUMBERED:
			max = UMIN(max, 3);
			break;
	}

	pop_call();
	return (int)(max);
}
			
/*
 * Pet and familiar functions - Kregor
 */
int get_pets( CHAR_DATA *ch)
{
	PET_DATA *pet;
	int cnt = 0;

	push_call("get_pets(%p)",ch);

	for (pet = mud->f_pet ; pet ; pet = pet->next)
	{
		if (pet->ch->master == ch)
		{
			if (IS_ACT(pet->ch, ACT_PET))
			{
				cnt++;
			}
		}
	}
	pop_call();
	return( cnt );
}

int get_pet_levels( CHAR_DATA *ch)
{
	PET_DATA *pet;
	int cnt = 0;

	push_call("get_pet_levels(%p)",ch);

	for (pet = mud->f_pet ; pet ; pet = pet->next)
	{
		if (pet->ch->master == ch)
		{
			if (IS_ACT(pet->ch, ACT_PET))
			{
				cnt += pet->ch->level;
			}
		}
	}
	pop_call();
	return( cnt );
}

// total follower levels a char may have - Kregor
int max_pet_levels( CHAR_DATA *ch)
{
	int levels;

	push_call("max_pet_levels(%p)",ch);

	levels = ch->level;
	if (learned(ch, gsn_leadership))
		levels = levels * 3 / 2;
	levels += stat_bonus(FALSE, ch, APPLY_CHA);

	pop_call();
	return( levels );
}

bool has_familiar(CHAR_DATA *ch, int race)
{
	push_call("has_familiar(%p)",ch);

	if (IS_NPC(ch))
	{
		pop_call();
		return FALSE;
	}
	if (ch->pcdata->familiar == race)
	{
		pop_call();
		return TRUE;
	}
	pop_call();
	return FALSE;
}
					
bool is_master( CHAR_DATA *ch, CHAR_DATA *master )
{
	push_call("is_master(%p,%p)",ch,master);
	
	if ((IS_ACT(ch, ACT_PET) || IS_AFFECTED (ch, AFF_DOMINATE)) && ch->master == master)
	{
		pop_call();
		return TRUE;
	}
	pop_call();
	return FALSE;
}

/*
 * snarf familiar or companion for a char
 */
CHAR_DATA *get_familiar(CHAR_DATA *ch)
{
	PET_DATA *pet;

	push_call("get_familiar(%p)",ch);

	for (pet = mud->f_pet ; pet ; pet = pet->next)
	{
		if (pet->ch->master == ch)
		{
			if (IS_SET(pet->ch->act, ACT_FAMILIAR))
			{
				pop_call();
				return pet->ch;
			}
		}
	}
	pop_call();
	return NULL;
}
					
/*
 * snarf familiar or companion for a char
 */
CHAR_DATA *get_warhorse(CHAR_DATA *ch)
{
	PET_DATA *pet;

	push_call("get_familiar(%p)",ch);

	for (pet = mud->f_pet ; pet ; pet = pet->next)
	{
		if (pet->ch->master == ch)
		{
			if (IS_SET(pet->ch->act, ACT_WARHORSE))
			{
				pop_call();
				return pet->ch;
			}
		}
	}
	pop_call();
	return NULL;
}
					
CHAR_DATA *get_companion(CHAR_DATA *ch)
{
	PET_DATA *pet;

	push_call("get_familiar(%p)",ch);

	for (pet = mud->f_pet ; pet ; pet = pet->next)
	{
		if (pet->ch->master == ch)
		{
			if (IS_SET(pet->ch->act, ACT_COMPANION))
			{
				pop_call();
				return pet->ch;
			}
		}
	}
	pop_call();
	return NULL;
}