// currently, only combat is supported as a base stat
var BaseStats = new Array(
	"reflex",
	"fortitude",
	"will",
	"combat",
/*	"melee",
	"ranged",
	"unarmed",
	"defense",
*/	"reputation"
);
function Character( p_aParams )
{
	if ( !p_aParams )
		p_aParams = new Array();
	
	this.name = "";
	this.size = "medium";
	this.abilityPointsSpent = 0;
	this.baseAbilities = { "STR" : 0, "DEX" : 0, "CON" : 0, "INT" : 0, "WIS" : 0, "CHA" : 0 }
	this.abilities = { "STR" : 0, "DEX" : 0, "CON" : 0, "INT" : 0, "WIS" : 0, "CHA" : 0 }
	this.coreAbility = "";
	this.background = new CharBackground();
	this.levels = new Array(); // array of CharLevels; index 0 = level 1
	this.roles = new Array();
	
	// level based
	this.baseStats = new Array();
	for ( var i = 0; i < BaseStats.length; i++ )
		this.baseStats[ BaseStats[i] ] = 0;
	
	// temp feats - used when selecting feats in order to allow simultaneous selection of a feat and its prerequisite
	this.tempFeats = new Array();
	
	// cannot be directly modified
	this.stats = 
	{
		"reflex" : 0,
		"fortitude" : 0,
		"will" : 0,
		"initiative" : 0,
		"combat" : 0,
		"melee" : 0,
		"ranged" : 0,
		"grapple" : 0,
		"unarmed" : 0,
		"speed" : 30,
		"defense" : 0,
		"parry" : 0,
		"dodge" : 0,
		"toughness" : 0,
		"unarmored toughness" : 0,
		"conviction" : 0,
		"reputation" : 0,
		"off-hand modifier" : -4,
		"dual wield modifier" : -6
	}
	
	// equipment
	this.armor = new CharArmor({ "id" : "unarmored", "proficient" : true });
	this.shield = new CharArmor({ "id" : "no shield", "proficient" : true });;
	this.mainWeapon = new CharWeapon({ "id" : "unarmed", "proficient" : true });
	this.offWeapon = null;
	this.rangedWeapon = null;
	
	this.armorProficiencies = new Array("unarmored", "no shield");
	this.weaponProficiencies = new Array().concat( WeaponLists["simple"].sort() );
	
	this.equipWeapon = CharEquipWeapon;
	this.getWeaponAttack = CharGetWeaponAttack;
	this.getWeaponDamage = CharGetWeaponDamage;
	this.getWeaponCritRange = CharGetWeaponCritRange;
	
	// skills
	this.skillPoints = 0;
	this.skillPointsSpent = 0;
	this.skills = new Array();
	for ( key in Skills )
	{
		// Skills[key].name + possible specialty is used in order to allow a skill with multiple specialties to be taken more than once
		if ( Skills[key].requiresTraining == false )
		{
			this.skills[ Skills[key].name ] = new CharSkill
			({
				"base skill id" : key,
				"name" : Skills[key].name,
				"key ability" : Skills[key].keyAbility,
				"requires training" : Skills[key].requiresTraining,
				"ranks" : 0,
				"total" : 0,
				"has specialties" : ( Skills[key].specialties.length > 0 )
			});
		}
	}
	
	this.setName = CharSetName;
	this.setSize = CharSetSize;
	this.modifyBaseAbility = CharModifyBaseAbility;
	this.modifyAbility = CharModifyAbility;
	this.modifyStat = CharModifyStat;
	this.modifySkillRanks = CharModifySkillRanks;
	this.modifySkillTotal = CharModifySkillTotal;
	this.setBackground = CharSetBackground;
	this.addInitialSkill = CharAddInitialSkill;
	this.addFeat = CharAddFeat;
	this.removeFeat = CharRemoveFeat;
	this.addBackgroundSkill = CharAddBackgroundSkill;
	this.addLevel = CharAddLevel;
	this.updateStats = CharUpdateStats;
	this.modifySkillPoints = CharModifySkillPoints;
	this.addNewSkill = CharAddNewSkill;
	this.modifySkillPointsSpent = CharModifySkillPointsSpent;
	this.getSkillBonusModifier = CharGetSkillBonusModifier;
	this.addBackgroundSkillPoints = CharAddBackgroundSkillPoints;
	
	this.getBaseStat = CharGetBaseStat;
	this.getStat = CharGetStat;
	this.getPowerRank = CharGetPowerRank;
	
	this.applyModifier = CharApplyModifier;
	
	this.equipArmor = CharEquipArmor;
	this.equipShield = CharEquipShield;
	
	this.getFeatSubtypes = CharGetFeatSubtypes;
}
function CharWeapon( p_aParams )
{
	this.id = p_aParams["id"];
	this.category = ( p_aParams["category"] ? p_aParams["category"] : Weapons[ this.id ].category );
	this.attack = ( p_aParams["attack"] ? p_aParams["attack"] : 0 );
	this.damage = ( p_aParams["damage"] ? p_aParams["damage"] : 0 );
	this.range = ( p_aParams["range"] ? p_aParams["range"] : Weapons[ this.id ].range );
	this.critRange = ( p_aParams["crit range"] ? p_aParams["crit range"] : Weapons[ this.id ].critRange );
	this.critBonus = ( p_aParams["crit bonus"] ? p_aParams["crit bonus"] : Weapons[ this.id ].critBonus );
	this.proficient = ( p_aParams["proficient"] ? p_aParams["proficient"] : false );
}
function CharArmor( p_aParams )
{
	this.id = p_aParams["id"];
	this.bonus = ( p_aParams["bonus"] ? p_aParams["bonus"] : Armors[ this.id ].bonus );
	this.penalty = ( p_aParams["penalty"] ? p_aParams["penalty"] : Armors[ this.id ].penalty );
	this.proficient = ( p_aParams["proficient"] ? p_aParams["proficient"] : false );
}
function CharBackground( p_aParams )
{
	if ( !p_aParams )
		p_aParams = new Array();
	
	this.id = ( p_aParams["id"] ? p_aParams["id"] : "" );
	this.skills = ( p_aParams["skills"] ? p_aParams["skills"] : new Array() ); // array of skill ids
	this.skillPoints = 0; // number of skill points assigned from the background due to unselected skills
	this.feats = ( p_aParams["feats"] ? p_aParams["feats"] : new Array() ); // array of CharFeats
	this.favoredFeats = ( p_aParams["favored feats"] ? p_aParams["favored feats"] : new Array() ); // array of CharFeats
	this.powers = ( p_aParams["powers"] ? p_aParams["powers"] : new Array() ); // array of CharPowers
}
function CharAddBackgroundSkillPoints( p_nSkillPoints )
{
	this.background.skillPoints += p_nSkillPoints;
	this.modifySkillPoints( p_nSkillPoints );
}
function CharLevel( p_aParams )
{
	this.role = p_aParams["role"]; // adept | expert | warrior
	this.abilities = ( p_aParams["abilities"] ? p_aParams["abilities"] : new Array() ); // array of CharAbilities
	this.skills = ( p_aParams["skills"] ? p_aParams["skills"] : new Array() ); // array of CharSkills
	this.feats = ( p_aParams["feats"] ? p_aParams["feats"] : new Array() ); // array of CharFeats
	this.powers = ( p_aParams["powers"] ? p_aParams["powers"] : new Array() ); // array of CharPowers
}
function CharAbilities( p_aParams )
{
	this.modifier = p_aParams["modifier"]; // usually 1
}
function CharSkill( p_aParams )
{
	this.baseSkillID = p_aParams["base skill id"];
	this.name = p_aParams["name"] + ( p_aParams["specialty"] ? " (" + p_aParams["specialty"] + ")" : "" ); // this is also the id for the purposes of the CharSkill
	this.keyAbility = p_aParams["key ability"];
	this.requiresTraining = p_aParams["requires training"];
	this.hasSpecialties = ( p_aParams["has specialties"] ? p_aParams["has specialties"] : false ); // does this skill have specialties (an instance of a specialty does not have specialties)
	this.armorPenalty = ( p_aParams["armor penalty"] ? p_aParams["armor penalty"] : Skills[ this.baseSkillID ].armorPenalty );
	this.ranks = ( p_aParams["ranks"] ? p_aParams["ranks"] : 0 ); // number of ranks gained
	this.total = ( p_aParams["total"] ? p_aParams["total"] : 0 ); // the total skill score including ranks, ability and miscellaneous modifiers
}
function CharFeat( p_aParams )
{
	this.id = p_aParams["id"];
	this.subtypes = ( p_aParams["subtypes"] ? p_aParams["subtypes"] : new Array() );
	this.restriction = ( p_aParams["restriction"] ? p_aParams["restriction"] : "" );
}
function CharPowers( p_aParams )
{
	this.id = p_aParams["id"];
	this.rank = this.getPowerRank( this.id );
}
function CharGetPowerRank( p_sPower )
{
	return this.getBaseStat( "power" );
}

function CharSetName( p_sName )
{
	this.name = p_sName;
}
function CharSetSize( p_sNewSize )
{
	var sCurrentSize = this.size;
	
	// if there are no changes in size, do nothing
	if ( sCurrentSize == p_sNewSize )
		return;
	
	// comparing old and new modifiers and making proper adjustments
	var nCombat = Sizes[ p_sNewSize ].combat - ( Sizes[ sCurrentSize ].combat ? Sizes[ sCurrentSize ].combat : 0 );
	var nGrapple = Sizes[ p_sNewSize ].grapple - ( Sizes[ sCurrentSize ].grapple ? Sizes[ sCurrentSize ].grapple : 0 );
	var nToughness = Sizes[ p_sNewSize ].toughness - ( Sizes[ sCurrentSize ].toughness ? Sizes[ sCurrentSize ].toughness : 0 );
	var nStealth = Sizes[ p_sNewSize ].stealth - ( Sizes[ sCurrentSize ].stealth ? Sizes[ sCurrentSize ].stealth : 0 );
	
	// adding new modifiers
	this.modifyStat( "combat", nCombat );
	this.modifyStat( "grapple", nGrapple );
	this.modifyStat( "unarmored toughness", nToughness );
	this.modifySkillTotal( "Stealth", nStealth );
	
	this.size = p_sNewSize;
	
	fDisplaySize();
}
function CharModifyBaseAbility( p_sAbility, p_nModifier )
{
	if ( p_nModifier == 0 )
		return;
	
	this.baseAbilities[ p_sAbility ] += p_nModifier;
	this.abilityPointsSpent += p_nModifier;
	fDisplayAbilityPointsSpent();
	
	this.modifyAbility( p_sAbility, p_nModifier );
}
function CharModifyAbility( p_sAbility, p_nModifier )
{
	if ( p_nModifier == 0 )
		return;
	
	this.abilities[ p_sAbility ] += p_nModifier;
	
	// ability specific modifications
	switch ( p_sAbility )
	{
		case "STR":
			this.modifyStat( "grapple", p_nModifier );
			this.modifyStat( "parry", p_nModifier );
			
			if ( this.mainWeapon && Weapons[ this.mainWeapon.id ].addStrength )
			{
				this.mainWeapon.damage += p_nModifier;
				fDisplayWeapon( "main" );
			}
			if ( this.offWeapon && Weapons[ this.offWeapon.id ].addStrength )
			{
				this.offWeapon.damage += p_nModifier;
				fDisplayWeapon( "off" );
			}
			if ( this.rangedWeapon && Weapons[ this.rangedWeapon.id ].addStrength )
			{
				this.rangedWeapon.damage += p_nModifier;
				fDisplayWeapon( "ranged" );
			}
			break;
		case "DEX":
			this.modifyStat( "melee", p_nModifier );
			this.modifyStat( "defense", p_nModifier );
			this.modifyStat( "dodge", p_nModifier );
			this.modifyStat( "ranged", p_nModifier );
			this.modifyStat( "unarmed", p_nModifier );
			this.modifyStat( "initiative", p_nModifier );
			this.modifyStat( "reflex", p_nModifier );
			break;
		case "CON":
			this.modifyStat( "fortitude", p_nModifier );
			this.modifyStat( "unarmored toughness", p_nModifier );
			break;
		case "INT":
			if ( this.levels.length > 0 )
				this.modifySkillPoints( p_nModifier * ( this.levels.length + 3 ) );
			break;
		case "WIS":
			this.modifyStat( "will", p_nModifier );
			break;
	}
	
	// modifying skills
	for ( key in this.skills )
	{
		if ( this.skills[key].keyAbility == p_sAbility )
			this.modifySkillTotal( key, p_nModifier );
	}
	
	// apply modifications from feats that change based on ability scores
	var cFeats = this.background.feats;
	var cModifiers = null;
	for ( var i = 0; i < cFeats.length; i++ )
	{
		cModifiers = Feats[ cFeats[i].id ].modifiers;
		for ( var j = 0; j < cModifiers.length; j++ )
		{
			if ( cModifiers[j].modifierType == "ability" && cModifiers[j].modifier == p_sAbility )
			{
				switch ( cModifiers[j].type )
				{
					case "ability":
						this.modifyAbility( cModifiers[j].id, p_nModifier );
						break;
					case "stat":
						this.modifyStat( cModifiers[j].id, p_nModifier );
						break;
					case "skill":
						this.modifySkillTotal( cModifiers[j].id, p_nModifier );
						break;
				}
			}
		}
	}
	
	fDisplayAbility( p_sAbility );
}
function CharModifyStat( p_sStat, p_nModifier )
{
	Char.stats[ p_sStat ] += p_nModifier;
	
	switch ( p_sStat )
	{
		case "combat":
			this.modifyStat( "melee", p_nModifier );
			this.modifyStat( "defense", p_nModifier );
			this.modifyStat( "dodge", p_nModifier );
			this.modifyStat( "ranged", p_nModifier );
			this.modifyStat( "grapple", p_nModifier );
			this.modifyStat( "unarmed", p_nModifier );
			this.modifyStat( "parry", p_nModifier );
			break;
		// eventually work this out so that defense, melee, ranged, and unarmed have their own base stat instead of using "combat"
		case "defense":
			break;
		case "melee":
			if ( this.mainWeapon )
				this.mainWeapon.attack += p_nModifier;
			if ( this.offWeapon )
				this.offWeapon.attack += p_nModifier;
			fDisplayWeapon( "main" );
			fDisplayWeapon( "off" );
			break;
		case "ranged":
			if ( this.rangedWeapon )
				this.rangedWeapon.attack += p_nModifier;
			fDisplayWeapon( "ranged" );
			break;
		case "unarmed":
			break;
		case "unarmored toughness":
			this.modifyStat( "toughness", p_nModifier );
			break;
		case "dual wield modifier":
		case "off-hand modifier":
			if ( this.offWeapon )
			{
				this.mainWeapon.attack = this.getWeaponAttack( "main" );
				this.offWeapon.attack = this.getWeaponAttack( "off" );
				fDisplayWeapon( "main" );
				fDisplayWeapon( "off" );
			}
			return;
	}
	
	fDisplayStat( p_sStat );
}
// TO DO: make it so it correctly penalizes you if you are not proficient in the armor or shield you are wearing
// updates the full value (new - original)
function CharEquipArmor( p_sArmorID )
{
	var oArmor = new CharArmor({ "id" : p_sArmorID, "proficient" : fbInArray( this.armorProficiencies, p_sArmorID ) });
	this.modifyStat( "toughness", oArmor.bonus - this.armor.bonus );
	var nModifier = 0;
	
	// applying penalty
	for ( key in this.skills )
	{
		if ( this.skills[key].armorPenalty > 0 )
		{
			nModifier = oArmor.penalty - this.armor.penalty;
			if ( nModifier != 0 )
				this.modifySkillTotal( key, nModifier * this.skills[key].armorPenalty );
		}
	}
	
	// adjusting modifier if proficient with previous armor
	if ( this.armor.proficient && !oArmor.proficient )
		nModifier = oArmor.penalty;
	
	// applying penalty to attack if not proficient with armor
	if ( ( !this.armor.proficient || !oArmor.proficient ) && nModifier != 0 )
	{
		this.mainWeapon.attack += nModifier;
		fDisplayWeapon( "main" );
		
		if ( this.offWeapon )
		{
			this.offWeapon.attack += nModifier;
			fDisplayWeapon( "off" );
		}
		if ( this.rangedWeapon )
		{
			this.rangedWeapon.attack += nModifier;
			fDisplayWeapon( "ranged" );
		}
	}
	
	this.armor = oArmor;
	
	fDisplayArmor( p_sArmorID );
}
function CharEquipShield( p_sShieldID )
{
	var oShield = new CharArmor({ "id" : p_sShieldID, "proficient" : fbInArray( this.armorProficiencies, p_sShieldID ) });
	this.modifyStat( "dodge", oShield.bonus - this.shield.bonus );
	this.modifyStat( "parry", oShield.bonus - this.shield.bonus );
	var nModifier = 0;
	
	// applying penalty
	for ( key in this.skills )
	{
		if ( this.skills[key].armorPenalty > 0 )
		{
			nModifier = oShield.penalty - this.shield.penalty;
			if ( nModifier != 0 )
				this.modifySkillTotal( key, nModifier * this.skills[key].armorPenalty );
		}
	}
	
	// adjusting modifier if proficient with previous shield
	if ( this.shield.proficient && !oShield.proficient )
		nModifier = oShield.penalty;
	
	// applying penalty to attack if not proficient with shield
	if ( ( !this.shield.proficient || !oShield.proficient ) && nModifier != 0 )
	{
		this.mainWeapon.attack += nModifier;
		fDisplayWeapon( "main" );
		
		if ( this.offWeapon )
		{
			this.offWeapon.attack += nModifier;
			fDisplayWeapon( "off" );
		}
		if ( this.rangedWeapon )
		{
			this.rangedWeapon.attack += nModifier;
			fDisplayWeapon( "ranged" );
		}
	}
	
	this.shield = oShield;
	
	fDisplayShield( p_sShieldID );
}
function CharEquipWeapon( p_sLocation, p_sID )
{
	var oWeapon = null;
	if ( p_sID )
	{
		oWeapon = new CharWeapon
		({
			"id" : p_sID,
			"category" : Weapons[ p_sID ].category,
			"damage" : ( Weapons[ p_sID ].addStrength ? this.abilities["STR"] : 0 ) + Weapons[ p_sID ].damage,
			"proficient" : fbInArray( this.weaponProficiencies, p_sID )
		});
	}
	
	switch ( p_sLocation )
	{
		case "main":
			this.mainWeapon = oWeapon;
			this.mainWeapon.attack = this.getWeaponAttack( "main" );
			this.mainWeapon.damage = this.getWeaponDamage( "main" );
			this.mainWeapon.critRange = this.getWeaponCritRange( "main" );
			fDisplayWeapon( "main" );
			break;
		case "off":
			this.offWeapon = oWeapon;
			this.mainWeapon.attack = this.getWeaponAttack( "main" );
			if ( this.offWeapon )
			{
				this.offWeapon.attack = this.getWeaponAttack( "off" );
				this.offWeapon.damage = this.getWeaponDamage( "off" );
				this.offWeapon.critRange = this.getWeaponCritRange( "off" );
			}
			fDisplayWeapon( "main" );
			fDisplayWeapon( "off" );
			break;
		case "ranged":
			this.rangedWeapon = oWeapon;
			if ( this.rangedWeapon )
			{
				this.rangedWeapon.attack = this.getWeaponAttack( "ranged" );
				this.rangedWeapon.damage = this.getWeaponDamage( "ranged" );
				this.rangedWeapon.critRange = this.getWeaponCritRange( "ranged" );
			}
			fDisplayWeapon( "ranged" );
			break;
	}
}
function CharGetWeaponAttack( p_sLocation )
{
	var oWeapon = ( p_sLocation == "main" ? this.mainWeapon : ( p_sLocation == "ranged" ? this.rangedWeapon : this.offWeapon ) );
	var nBase = ( p_sLocation != "ranged" ? this.stats["melee"] : this.stats["ranged"] );
	var nProficiencyMod = ( oWeapon.proficient ? 0 : -4 ) + ( this.armor.proficient ? 0 : this.armor.penalty ) + ( this.shield.proficient ? 0 : this.shield.penalty );
	var nOffhandMod = ( p_sLocation != "off" ? 0 : this.stats["off-hand modifier"] );
	var nDualWeaponMod = ( p_sLocation != "ranged" ? ( !this.offWeapon ? 0 : this.stats["dual wield modifier"] + ( this.offWeapon.category == "light" ? 2 : 0 ) ) : 0 );
	var nFeatMod = 0;
	
	// looking for feats that impact the attack value
	for ( var i = 0; i < this.background.feats.length; i++ )
	{
		for ( var j = 0; j < Feats[ this.background.feats[i].id ].modifiers.length; j++ )
		{
			if ( Feats[ this.background.feats[i].id ].modifiers[j].type == "weapon" && Feats[ this.background.feats[i].id ].modifiers[j].id == "attack" && fbInArray( this.background.feats[i].subtypes, oWeapon.id ) )
				nFeatMod += Feats[ this.background.feats[i].id ].modifiers[j].modifier;
		}
	}
	for ( var i = 0; i < this.levels.length; i++ )
	{
		for ( var j = 0; j < this.levels[i].feats.length; j++ )
		{
			for ( var k = 0; k < Feats[ this.levels[i].feats[j].id ].modifiers.length; k++ )
			{
				if ( Feats[ this.levels[i].feats[j].id ].modifiers[k].type == "weapon" && Feats[ this.levels[i].feats[j].id ].modifiers[k].id == "attack" && fbInArray( this.levels[i].feats[j].subtypes, oWeapon.id ) )
					nFeatMod += Feats[ this.levels[i].feats[j].id ].modifiers[k].modifier;
			}
		}
	}
	
	return nBase + nProficiencyMod + nOffhandMod + nDualWeaponMod + nFeatMod;
}
function CharGetWeaponDamage( p_sLocation )
{
	var oWeapon = ( p_sLocation == "main" ? this.mainWeapon : ( p_sLocation == "ranged" ? this.rangedWeapon : this.offWeapon ) );
	var nBase = oWeapon.damage;
	var nAbilityMod = ( Weapons[ oWeapon.id ].addStrength ? this.abilities["STR"] : 0 );
	var nFeatMod = 0;
	
	// looking for feats that impact the damage value
	for ( var i = 0; i < this.background.feats.length; i++ )
	{
		for ( var j = 0; j < Feats[ this.background.feats[i].id ].modifiers.length; j++ )
		{
			if ( Feats[ this.background.feats[i].id ].modifiers[j].type == "weapon" && Feats[ this.background.feats[i].id ].modifiers[j].id == "damage" && fbInArray( this.background.feats[i].subtypes, oWeapon.id ) )
				nFeatMod += Feats[ this.background.feats[i].id ].modifiers[j].modifier;
		}
	}
	for ( var i = 0; i < this.levels.length; i++ )
	{
		for ( var j = 0; j < this.levels[i].feats.length; j++ )
		{
			for ( var k = 0; k < Feats[ this.levels[i].feats[j].id ].modifiers.length; k++ )
			{
				if ( Feats[ this.levels[i].feats[j].id ].modifiers[k].type == "weapon" && Feats[ this.levels[i].feats[j].id ].modifiers[k].id == "damage" && fbInArray( this.levels[i].feats[j].subtypes, oWeapon.id ) )
					nFeatMod += Feats[ this.levels[i].feats[j].id ].modifiers[k].modifier;
			}
		}
	}
	
	return nBase + nAbilityMod + nFeatMod;
}
function CharGetWeaponCritRange( p_sLocation )
{
	var oWeapon = ( p_sLocation == "main" ? this.mainWeapon : ( p_sLocation == "ranged" ? this.rangedWeapon : this.offWeapon ) );
	var nBase = oWeapon.critRange;
	var nFeatMod = 0;
	
	// looking for feats that impact the crit range value
	for ( var i = 0; i < this.background.feats.length; i++ )
	{
		for ( var j = 0; j < Feats[ this.background.feats[i].id ].modifiers.length; j++ )
		{
			if ( Feats[ this.background.feats[i].id ].modifiers[j].type == "weapon" && Feats[ this.background.feats[i].id ].modifiers[j].id == "critRange" && fbInArray( this.background.feats[i].subtypes, oWeapon.id ) )
			{
				if ( Feats[ this.background.feats[i].id ].modifiers[j].modifier == "crit range" ) // assumes that you must choose a subtype
					nFeatMod += Weapons[ this.background.feats[i].subtypes[0] ].critRange;
			}
		}
	}
	for ( var i = 0; i < this.levels.length; i++ )
	{
		for ( var j = 0; j < this.levels[i].feats.length; j++ )
		{
			for ( var k = 0; k < Feats[ this.levels[i].feats[j].id ].modifiers.length; k++ )
			{
				if ( Feats[ this.levels[i].feats[j].id ].modifiers[k].type == "weapon" && Feats[ this.levels[i].feats[j].id ].modifiers[k].id == "critRange" && fbInArray( this.levels[i].feats[j].subtypes, oWeapon.id ) )
				{
					if ( Feats[ this.levels[i].feats[j].id ].modifiers[k].modifier == "crit range" ) // assumes that you must choose a subtype
						nFeatMod += Weapons[ this.levels[i].feats[j].subtypes[0] ].critRange;
				}
			}
		}
	}
	
	return nBase + nFeatMod;
}
// does not include ranks
function CharGetSkillBonusModifier( p_sSkill )
{
	var nModifier = 0;
	
	// from skills
	nModifier += this.abilities[ this.skills[p_sSkill].keyAbility ];
	
	// from feats
	// TO DO: add modifiers for CharFeats
	var cFeats = this.background.feats;
	for ( var i = 0; i < cFeats.length; i++ )
	{
		var x = 0; // TO DO: remove stub
	}
	for ( var i = 0; i < this.levels.length; i++ )
	{
		cFeats = this.levels[i].feats;
		for ( var j = 0; j < cFeats.length; j++ )
		{
			var x = 0; // TO DO: remove stub
		}
	}
	
	// from armor
	if ( this.skills[p_sSkill].armorPenalty > 0 )
	{
		if ( this.armor )
			nModifier += this.skills[p_sSkill].armorPenalty * this.armor.penalty;
		if ( this.shield )
			nModifier += this.skills[p_sSkill].armorPenalty * this.shield.penalty;
	}
	
	return nModifier;
}
function CharAddNewSkill( p_sSkill )
{
	var sBaseSkillID = p_sSkill.toLowerCase();
	sBaseSkillID = ( p_sSkill.indexOf( "(" ) == -1 ? sBaseSkillID : sBaseSkillID.slice( 0, sBaseSkillID.indexOf( " (" ) ) );
	this.skills[ p_sSkill ] = new CharSkill
	({
		"base skill id" : sBaseSkillID,
		"name" : p_sSkill,
		"key ability" : Skills[ sBaseSkillID ].keyAbility,
		"requires training" : Skills[ sBaseSkillID ].requiresTraining
	});
	
	// adding all bonuses to the skill as it is newly added to the character's list
	// and thus has not had previous changes cascaded to it
	this.skills[ p_sSkill ].total = this.getSkillBonusModifier( p_sSkill );
	
	fDisplaySkills();
}
function CharModifySkillRanks( p_sSkill, p_nModifier )
{
	// if the character does not have ranks in the skill, set the rank to 0, then modify it
	if ( !this.skills[ p_sSkill ] )
		this.addNewSkill( p_sSkill );
	else if ( this.skills[ p_sSkill ].hasSpecialties == true ) // don't allow modification of core skills with specialties
	{
		alert( "This skill has specialties.\n\nYou may only increase the rank of a specialty, not of the base skill." );
		return;
	}
	
	var bProcessRequest = true;
	
	// ensuring ranks do not fall below 0
	if ( this.skills[ p_sSkill ].ranks == 0 && p_nModifier <= 0 )
		return;
	if ( this.skills[ p_sSkill ].ranks + p_nModifier < 0 )
		p_nModifier = this.skills[ p_sSkill ].ranks * -1;
	
	// checking to ensure ranks do not go over the max for the character's level
	var nMaxRanks = this.levels.length + 3;
	// setting it to 4 so that you don't get messages when applying background skills before adding a level
	if ( nMaxRanks < 4 )
		nMaxRanks = 4;
	if ( bProcessRequest && this.skills[ p_sSkill ].ranks <= nMaxRanks && this.skills[ p_sSkill ].ranks + p_nModifier > nMaxRanks )
	{
		if ( !confirm( "This would bring you above the maximum ranks for this character. Are you sure you wish to continue?" ) )
			bProcessRequest = false;
	}
	
	// checking to ensure there are skill points left to spend
	if ( bProcessRequest && this.skillPointsSpent <= this.skillPoints && this.skillPointsSpent + p_nModifier > this.skillPoints )
	{
		if ( !confirm( "This would bring you above your maximum allowed skill points. Are you sure you wish to continue?" ) )
			bProcessRequest = false;
	}
	
	if ( bProcessRequest )
	{
		this.skills[ p_sSkill ].ranks += p_nModifier;
		fDisplaySkillRank( p_sSkill );
		
		this.modifySkillPointsSpent( p_nModifier );
		this.modifySkillTotal( p_sSkill, p_nModifier );
	}
	
	if ( this.skills[ p_sSkill ].ranks == 0 && ( this.skills[ p_sSkill ].requiresTraining || p_sSkill.indexOf("(") != -1 ) )
	{
		delete this.skills[ p_sSkill ];
		fDisplaySkills();
	}
}
function CharModifySkillTotal( p_sSkill, p_nModifier )
{
	this.skills[ p_sSkill ].total += p_nModifier;
	
	fDisplaySkillTotal( p_sSkill );
}
function CharSetBackground( p_sNewBackground )
{
	var sCurrBackground = this.background.id;
	var cModifiers = null;
	var anModifiers = new Array();
	
	// comparing old and new modifiers and making proper adjustments
	if ( sCurrBackground )
	{
		// removing current ability modifiers
		cModifiers = Backgrounds[ sCurrBackground ].abilityModifiers;
		for ( key in cModifiers )
			anModifiers[key] = cModifiers[key] * -1;
		
		// removing bonus skills
		for ( var i = 0; i < this.background.skills.length; i++ )
		{
			this.modifySkillRanks( this.background.skills[i], -4 );
			this.modifySkillPoints( -4 );
		}
		if ( this.background.skillPoints != 0 )
			this.modifySkillPoints( this.background.skillPoints * -1 );
		this.background.skills = new Array();
		
		// removing background feat modifiers
		var cOldFeats = this.background.feats;
		for ( var i = 0; i < cOldFeats.length; i++ )
		{
			var cOldMods = Feats[ cOldFeats[i].id ].modifiers;
			for ( var j = 0; j < cOldMods.length; j++ )
				this.applyModifier( cOldMods[j], "remove", cOldFeats[i] );
		}
		
		// removing background feats
		this.background.feats = new Array();
		this.background.favoredFeats = new Array();
	}
	
	// re-setting background skill points
	this.background.skillPoints = 0;
	
	// adding new ability modifiers
	if ( p_sNewBackground )
	{
		cModifiers = Backgrounds[ p_sNewBackground ].abilityModifiers;
		for ( key in cModifiers )
			anModifiers[key] = cModifiers[key] + ( anModifiers[key] ? anModifiers[key] : 0 );
	}
	
	// making appropriate ability score modifications
	for ( key in anModifiers )
		Char.modifyAbility( key, anModifiers[key] );
	
	// setting the new size (and removing/adding appropriate modifiers)
	if ( p_sNewBackground )
		this.setSize( Backgrounds[ p_sNewBackground ].size );
	else
		this.setSize( "medium" );
	
	this.background.id = p_sNewBackground;
}

function CharAddBackgroundSkill( p_sSkill )
{
	this.background.skills.push( p_sSkill );
	if ( !this.skills[ p_sSkill ] )
		this.addNewSkill( p_sSkill );
	
	// adding 4 to the total skill points
	this.modifySkillPoints( 4 );
	
	// removing up to 4 ranks from the skill and adding them back to free points
	var nPointsRefunded = 0;
	if ( this.skills[ p_sSkill ].ranks > 0 )
	{
		var nPointsRefunded = ( this.skills[ p_sSkill ].ranks < 4 ? this.skills[ p_sSkill ].ranks : 4 );
		this.modifySkillRanks( p_sSkill, nPointsRefunded * -1 );
	}
	
	this.modifySkillRanks( p_sSkill, 4 );
}
// p_nLevel count starting at 0, so 0 = 1st level
function CharAddInitialSkill( p_sSkill )
{
	this.levels[0].skills.push( p_sSkill );
	if ( !this.skills[ p_sSkill ] )
		this.addNewSkill( p_sSkill );
	
	// adding 4 to the total skill points
	this.modifySkillPoints( 4 );
	
	// removing up to 4 ranks from the skill and adding them back to free points
	var nPointsRefunded = 0;
	if ( this.skills[ p_sSkill ].ranks > 0 )
	{
		var nPointsRefunded = ( this.skills[ p_sSkill ].ranks < 4 ? this.skills[ p_sSkill ].ranks : 4 );
		this.modifySkillRanks( p_sSkill, nPointsRefunded * -1 );
	}
	
	this.modifySkillRanks( p_sSkill, 4 );
}
// level	background | favored | 0 - 19 (count levels starting at 0, so 0 = 1st level)
function CharAddFeat( p_aParams )
{
	var cFeats = null; // the array to which the new feat gets appended
	
	if ( p_aParams["level"] == "temp feat" )
		cFeats = this.tempFeats;
	else if ( p_aParams["level"] == "background" )
		cFeats = this.background.feats;
	else if ( p_aParams["level"] == "favored" )
		cFeats = this.background.favoredFeats;
	else
		cFeats = this.levels[ p_aParams["level"] ].feats;
	
	// adding the feat to the proper array
	var oNewFeat = new CharFeat( { "id" : p_aParams["id"], "subtypes" : p_aParams["subtypes"], "restriction" : p_aParams["restriction"] } );
	if ( p_aParams["level"] != "temp feat" )
		cFeats.push( oNewFeat );
	else
		cFeats[ "i" + p_aParams["choice index"] ] = oNewFeat;
	
	// adding applicable modifiers
	var cMods = Feats[ p_aParams["id"] ].modifiers;
	for ( var i = 0; i < cMods.length; i++ )
		this.applyModifier( cMods[i], "apply", oNewFeat );
}
function CharRemoveFeat( p_aParams )
{
	var cOldFeats = null;
	var sIndex = "i" + p_aParams["choice index"];
	
	if ( p_aParams["level"] == "temp feat" )
	{
		cOldFeats = this.tempFeats;
		if ( !cOldFeats[ sIndex ] )
			return;
	}
	else if ( p_aParams["level"] == "background" )
		cOldFeats = this.background.feats;
	else if ( p_aParams["level"] == "favored" )
		cOldFeats = this.background.favoredFeats;
	else
		cOldFeats = this.levels[ p_aParams["level"] ].feats;
	
	var cOldMods = Feats[ cOldFeats[ sIndex ].id ].modifiers;
	
	// removing modifiers
	for ( var j = 0; j < cOldMods.length; j++ )
		this.applyModifier( cOldMods[j], "remove", cOldFeats[ sIndex ] );
	
	// removing the feat from the list
	delete cOldFeats[ sIndex ];
}
function CharAddLevel( p_aParams )
{
	var oLevel = new CharLevel( p_aParams );
	this.levels.push( oLevel );
	
	if ( this.roles[ oLevel.role ] > 0 )
		this.roles[ oLevel.role ]++;
	else
		this.roles[ oLevel.role ] = 1;
	
	this.updateStats();
	
	// skill points are only given past level 1
	// at level 1, characters select a given number of skills instead
	// that start at rank 4
	if ( this.levels.length == 1 )
		this.coreAbility = Roles[ oLevel.role ].coreAbility;
	else
	{
		var nSkillPoints = Roles[ oLevel.role ].skillPoints + this.abilities["INT"];
		if ( nSkillPoints < 1 )
			nSkillPoints = 1;
		this.modifySkillPoints( nSkillPoints );
	}
	
	fDisplayFeats(); // to display the core ability
	
	return oLevel;
}
function CharModifySkillPointsSpent( p_nModifier )
{
	this.skillPointsSpent += p_nModifier;
	fDisplaySkillPointsSpent();
}
function CharModifySkillPoints( p_nModifier )
{
	this.skillPoints += p_nModifier;
	fDisplaySkillPoints();
}

function CharApplyModifier( p_oModifier, p_sMode, p_oFeat )
{
	if ( p_oFeat.restriction )
		return;
	
	var nModifier = 0;
	
	if ( !p_sMode )
		p_sMode = "apply";
	
	switch( p_oModifier.modifierType )
	{
		case "ability":
			nModifier = this.abilities[ p_oModifier.modifier ];
			break;
		case "array":
		case "numeric":
		case "string":
			nModifier = p_oModifier.modifier;
			break;
	}
	
	if ( p_oModifier.modifier == "crit range" ) // assumes that you must choose a subtype
		nModifier = Weapons[ p_oFeat.subtypes[0] ].critRange;
	
	if ( nModifier == 0 )
		return;
	
	if ( p_sMode == "remove" && p_oModifier.modifierType != "array" && p_oModifier.modifierType != "string" )
		nModifier *= -1;
	
	switch ( p_oModifier.type )
	{
		case "ability":
			this.modifyAbility( p_oModifier.id, nModifier );
			break;
		case "proficiency":
			// assumes each proficiency is addeded uniquely by one feat
			// assumes nothing other than the removal of those feats can remove a proficiency
			var oModifier = p_oModifier.modifier;
			
			if ( oModifier == "subtype" )
			{
				oModifier = new Array();
				for ( var i = 0; i < p_oFeat.subtypes.length; i++ )
					oModifier.push( p_oFeat.subtypes[i] );
			}
			
			if ( p_sMode == "apply" )
			{
				if ( p_oModifier.id == "weapon" )
				{
					this.weaponProficiencies = this.weaponProficiencies.concat( oModifier );
					this.weaponProficiencies.sort();
					
					// checking for proficiency
					if ( !this.mainWeapon.proficient && fbInArray( this.weaponProficiencies, this.mainWeapon.id ) )
					{
						this.mainWeapon.proficient = true;
						this.mainWeapon.attack += 4; // removing the non-proficiency penalty
						fDisplayWeapon( "main" );
					}
					if ( this.offWeapon && !this.offWeapon.proficient && fbInArray( this.weaponProficiencies, this.offWeapon.id ) )
					{
						this.offWeapon.proficient = true;
						this.offWeapon.attack += 4; // removing the non-proficiency penalty
						fDisplayWeapon( "off" );
					}
					if ( this.rangedWeapon && !this.rangedWeapon.proficient && fbInArray( this.weaponProficiencies, this.rangedWeapon.id ) )
					{
						this.rangedWeapon.proficient = true;
						this.rangedWeapon.attack += 4; // removing the non-proficiency penalty
						fDisplayWeapon( "ranged" );
					}
				}
				else // armor
				{
					this.armorProficiencies = this.armorProficiencies.concat( oModifier );
					this.armorProficiencies.sort();
					
					// checking for proficiency
					if ( !this.armor.proficient && fbInArray( oModifier, this.armor.id ) )
					{
						this.armor.proficient = true;
						
						this.mainWeapon.attack -= this.armor.penalty;
						fDisplayWeapon( "main" );
						if ( this.offWeapon )
						{
							this.offWeapon.attack -= this.armor.penalty;
							fDisplayWeapon( "off" );
						}
						if ( this.rangedWeapon )
						{
							this.rangedWeapon.attack -= this.armor.penalty;
							fDisplayWeapon( "ranged" );
						}
					}
					if ( !this.shield.proficient && fbInArray( oModifier, this.shield.id ) )
					{
						this.armor.proficient = true;
						
						this.mainWeapon.attack -= this.armor.penalty;
						fDisplayWeapon( "main" );
						if ( this.offWeapon )
						{
							this.offWeapon.attack -= this.armor.penalty;
							fDisplayWeapon( "off" );
						}
						if ( this.rangedWeapon )
						{
							this.rangedWeapon.attack -= this.armor.penalty;
							fDisplayWeapon( "ranged" );
						}
					}
				}
			}
			else // remove
			{
				var asNewProficiencies = new Array();
				var nModifierIndex = 0;
				oModifier.sort();
				
				if ( p_oModifier.id == "weapon" )
				{
					for ( var i = 0; i < this.weaponProficiencies.length && nModifierIndex < oModifier.length; i++ )
					{
						if ( this.weaponProficiencies[i] != oModifier[ nModifierIndex ] )
							asNewProficiencies.push( this.weaponProficiencies[i] )
						else
							nModifierIndex++;
					}
					this.weaponProficiencies = asNewProficiencies;
					
					// checking for proficiency
					if ( this.mainWeapon.proficient && !fbInArray( this.weaponProficiencies, this.mainWeapon.id ) )
					{
						this.mainWeapon.proficient = true;
						this.mainWeapon.attack -= 4; // removing the non-proficiency penalty
						fDisplayWeapon( "main" );
					}
					if ( this.offWeapon && this.offWeapon.proficient && !fbInArray( this.weaponProficiencies, this.offWeapon.id ) )
					{
						this.offWeapon.proficient = true;
						this.offWeapon.attack -= 4; // removing the non-proficiency penalty
						fDisplayWeapon( "off" );
					}
					if ( this.rangedWeapon && this.rangedWeapon.proficient && !fbInArray( this.weaponProficiencies, this.rangedWeapon.id ) )
					{
						this.rangedWeapon.proficient = true;
						this.rangedWeapon.attack -= 4; // removing the non-proficiency penalty
						fDisplayWeapon( "ranged" );
					}
				}
				else // armor
				{
					for ( var i = 0; i < this.armorProficiencies.length && nModifierIndex < oModifier.length; i++ )
					{
						if ( this.armorProficiencies[i] != oModifier[ nModifierIndex ] )
							asNewProficiencies.push( this.armorProficiencies[i] )
						else
							nModifierIndex++;
					}
					this.armorProficiencies = asNewProficiencies;
					
					// checking for proficiency
					if ( this.armor.proficient && !fbInArray( asNewProficiencies, this.armor.id ) )
					{
						this.armor.proficient = false;
						
						this.mainWeapon.attack += this.armor.penalty;
						fDisplayWeapon( "main" );
						if ( this.offWeapon )
						{
							this.offWeapon.attack += this.armor.penalty;
							fDisplayWeapon( "off" );
						}
						if ( this.rangedWeapon )
						{
							this.rangedWeapon.attack += this.armor.penalty;
							fDisplayWeapon( "ranged" );
						}
					}
					if ( this.shield.proficient && !fbInArray( asNewProficiencies, this.shield.id ) )
					{
						this.armor.proficient = false;
						
						this.mainWeapon.attack += this.armor.penalty;
						fDisplayWeapon( "main" );
						if ( this.offWeapon )
						{
							this.offWeapon.attack += this.armor.penalty;
							fDisplayWeapon( "off" );
						}
						if ( this.rangedWeapon )
						{
							this.rangedWeapon.attack += this.armor.penalty;
							fDisplayWeapon( "ranged" );
						}
					}
				}
			}
			return;
		case "stat":
			this.modifyStat( p_oModifier.id, nModifier );
			break;
		case "skill":
			// TO DO: add support for skill specialties
			if ( p_oModifier.id == "subtype" )
			{
				for ( var i = 0; i < p_oFeat.subtypes.length; i++ )
					this.modifySkillTotal( p_oFeat.subtypes[i], nModifier );
			}
			else
				this.modifySkillTotal( p_oModifier.id, nModifier );
			break;
		case "skill points":
			this.modifySkillPoints( nModifier );
			break;
		case "weapon":
			for ( var i = 0; i < p_oFeat.subtypes.length; i++ )
			{
				var sWeaponID = p_oFeat.subtypes[i];
				if ( this.mainWeapon.id == sWeaponID )
				{
					eval( "this.mainWeapon." + p_oModifier.id + " += nModifier;" );
					fDisplayWeapon( "main" );
				}
				if ( this.offWeapon && this.offWeapon.id == sWeaponID )
				{
					eval( "this.offWeapon." + p_oModifier.id + " += nModifier;" );
					fDisplayWeapon( "off" );
				}
				if ( this.rangedWeapon && this.rangedWeapon.id == sWeaponID )
				{
					eval( "this.rangedWeapon." + p_oModifier.id + " += nModifier;" );
					fDisplayWeapon( "ranged" );
				}
			}
			break;
	}
}

// updates level-based stats
function CharUpdateStats()
{
	var key = null;
	var nOldStat = null;
	var nNewStat = null;
	
	for ( var i = 0; i < BaseStats.length; i++ )
	{
		key = BaseStats[i];
		nOldStat = this.baseStats[ key ];
		nNewStat = this.getBaseStat( key );
		this.modifyStat( key, nNewStat - nOldStat );
		this.baseStats[ key ] = nNewStat;
	}
	
	this.stats["conviction"] = this.getBaseStat( "conviction" );
	fDisplayStat( "conviction" );
}
function CharGetStat( p_sStat )
{
	return this.stats[ p_sStat ];
}
function CharGetBaseStat( p_sStat )
{
	var nValue = 0;
	var nLevelValue = 0;
	
	// 1st level bonus
	var key = this.levels[0].role;
	if ( Roles[ key ].statProgressions[ p_sStat ] && Roles[key].statProgressions[ p_sStat ].bonusApplication == "first only" )
		nValue += Roles[key].statProgressions[ p_sStat ].bonus;
	
	// role level value
	for ( key in this.roles )
	{
		if ( Roles[key].statProgressions[ p_sStat ] )
		{
			if ( Roles[key].statProgressions[ p_sStat ].bonusApplication == "every role" )
				nValue += Roles[key].statProgressions[ p_sStat ].bonus;
			
			nLevelValue = Roles[ key ].statProgressions[ p_sStat ].rate * this.roles[key];
			switch ( Roles[key].statProgressions[p_sStat].rounding )
			{
				case "round down":
					nLevelValue = Math.floor( nLevelValue );
					break;
				case "round off":
					nLevelValue = Math.round( nLevelValue );
					break;
				case "round up":
					nLevelValue = Math.ceil( nLevelValue );
					break;
			}
		}
		nValue += nLevelValue;
	}
	
	// specific values
	switch ( p_sStat )
	{
		case "conviction":
			var oProgression = ConvictionProgressions["round up plus two"];
			var nLevelValue = this.levels.length * oProgression.rate;
			nValue += oProgression.bonus;
			switch ( oProgression.rounding )
			{
				case "round down":
					nValue += Math.floor( nLevelValue );
					break;
				case "round off":
					nValue += Math.round( nLevelValue );
					break;
				case "round up":
					nValue += Math.ceil( nLevelValue );
					break;
			}
			break;
	}
	
	return nValue;
}
function CharGetFeatSubtypes( p_sFeatID )
{
	var asSubtypes = new Array();
	
	for ( var i = 0; i < this.background.feats.length; i++ )
	{
		if ( this.background.feats[i].id == p_sFeatID && this.background.feats[i].subtypes.length > 0 )
			asSubtypes = asSubtypes.concat( this.background.feats[i].subtypes );
	}
	for ( var i = 0; i < this.levels.length; i++ )
	{
		for ( var j = 0; j < this.levels[i].feats; j++ )
		{
			if ( this.levels[i].feats[j].id == p_sFeatID && this.levels[i].feats[j].subtypes.length > 0 )
				asSubtypes = asSubtypes.concat( this.levels[i].feats[j].subtypes );
		}
	}
	
	return asSubtypes;
}
