//
// Grim Tales/Universal Concepts Stat Block Generator
//
// Copyright 2005-2007
// Version 17
// by Jason 'Flynn' Kemp, http://www.geocities.com/flynnwd/
// with additions by Jason Spangler, http://www.wumple.com/
//

//
// Possible ideas for improvements:
//   Armor proficiency support
//
// Turn the following into arrays:
//   Races (name, size, movement, SA, SQ, Senses, base damage)
//   Stats 
//   Saves (Stat, feat bonuses, item bonuses)
//   Classes (BAB, Defense, HP, HD, SaveCalc, reputation)
//

// --------------------------------------------------------------------
// global objects
// --------------------------------------------------------------------

weapons = new Array();
armors = new Array();
weaponGroups = new Array();
usingWeaponGroups = new Boolean(true);

init();

/* initialize the data for the page, called onLoad */
function init()
{
	initializeWeapons();
	initializeWeaponGroups();
	initializeArmors();
}

// --------------------------------------------------------------------
// object prototypes, etc
// --------------------------------------------------------------------

// weapon object
function weapon(name, size, damage, threatRange, critMultiplier, melee, finesse)
{
	this.name = name;
	// 0=Fine, 1=Diminuative, 2=Tiny, 3=Small, 4=Medium,
	// 5=Large, 6=Huge, 7=Gargantuan, 8=Colossal
	this.size = size;
	this.damage = damage;
	this.threatRange = threatRange;
	this.critMultiplier = critMultiplier;
	this.melee = melee;
  // does weapon finesse apply to this weapon?
	this.finesse = finesse;
}

// create a single weapon object and add it to weapons[]
// this version supports finesse
function createWeapon2(name, size, damage, threatRange, critMultiplier, melee, finesse)
{
	weapons.push(new weapon(name, size, damage, threatRange, critMultiplier, melee, finesse));
}

// create a single weapon object and add it to weapons[]
function createWeapon(name, size, damage, threatRange, critMultiplier, melee)
{
	createWeapon2(name, size, damage, threatRange, critMultiplier, melee, false);
}

// create all weapons and add them to weapons[]
function initializeWeapons()
{
	// melee
	createWeapon('mancatcher',5,'1d4',20,2,true);
	createWeapon('greatsword',5,'2d6',19,2,true);
	createWeapon('bastard sword',4,'1d10',19,2,true);
	createWeapon('longsword',4,'1d8',19,2,true);
	createWeapon2('shortsword',3,'1d6',19,2,true, true);
	createWeapon2('short sword',3,'1d6',19,2,true, true);
	createWeapon('quarterstaff',5,'1d6',20,2,true);
	createWeapon2('punching dagger',2,'1d4',20,3,true, true);
	createWeapon2('spiked gauntlet',2,'1d4',20,2,true, true);
	createWeapon2('light mace',3,'1d6',20,2,true, true);
	createWeapon2('sickle',3,'1d6',20,2,true, true);
	createWeapon('heavy mace',4,'1d8',20,2,true);
	createWeapon('morningstar',4,'1d8',20,2,true);
	createWeapon2('handaxe',3,'1d6',20,3,true, true);
	createWeapon2('hand axe',3,'1d6',20,3,true, true);
	createWeapon('light lance',3,'1d6',20,3,true);
	createWeapon2('light pick',3,'1d4',20,4,true, true);
	createWeapon2('sap',3,'1d6',20,2,true, true);
	createWeapon('battleaxe',4,'1d8',20,3,true);
	createWeapon2('light flail',4,'1d8',20,2,true, true);
	createWeapon('heavy lance',4,'1d8',20,3,true);
	createWeapon('heavy pick',4,'1d6',20,4,true);
	createWeapon2('rapier',4,'1d6',18,2,true, true);
	createWeapon('scimitar',4,'1d6',18,2,true);
	createWeapon('warhammer',4,'1d8',20,3,true);
	createWeapon('falchion',5,'2d4',18,2,true);
	createWeapon('heavy flail',5,'1d10',19,2,true);
	createWeapon('glaive',5,'1d10',20,3,true);
	createWeapon('greataxe',5,'1d12',20,3,true);
	createWeapon('greatclub',5,'1d10',20,2,true);
	createWeapon('guisarme',5,'2d4',20,3,true);
	createWeapon('halberd',5,'1d10',20,3,true);
	createWeapon('longspear',5,'1d8',20,3,true);
	createWeapon('ranseur',5,'2d4',20,3,true);
	createWeapon('scythe',5,'2d4',20,4,true);
	createWeapon('halfling kama',2,'1d4',20,2,true);
	createWeapon2('kukri',3,'1d4',18,2,true, true);
	createWeapon('kama',3,'1d6',20,2,true);
	createWeapon('nunchaku',3,'1d6',20,2,true);
	createWeapon('siangham',3,'1d6',20,2,true);
	createWeapon('dwarven waraxe',4,'1d10',20,3,true);
	createWeapon('gnome hooked hammer',4,'1d6',20,3,true);
	createWeapon('orc double axe',5,'1d8',20,3,true);
	createWeapon2('spiked chain',5,'2d4',20,2,true, true);
	createWeapon('dire flail',5,'1d8',20,2,true);
	createWeapon('two-bladed sword',5,'1d8',19,2,true);
	createWeapon('dwarven urgrosh',5,'1d8',20,3,true);
	createWeapon2('whip',4,'1d3',20,2,true, true);

	// both ranged and melee
	createWeapon2('throwing axe',3,'1d6',20,2,true, true);
	createWeapon('throwing axe',3,'1d6',20,2,false);
	createWeapon2('light hammer',3,'1d4',20,2,true, true);
	createWeapon('light hammer',3,'1d4',20,2,false);
	createWeapon('trident',4,'1d8',20,2,true);
	createWeapon('trident',4,'1d8',20,2,false);
	createWeapon('pitchfork',4,'1d8',20,2,true);
	createWeapon('pitchfork',4,'1d8',20,2,false);
	createWeapon('shortspear',5,'1d8',20,3,true);
	createWeapon('shortspear',5,'1d8',20,3,false);
	createWeapon('halfspear',4,'1d6',20,3,true);
	createWeapon('halfspear',4,'1d6',20,3,false);
	createWeapon2('dagger',2,'1d4',19,2,true, true);
	createWeapon('dagger',2,'1d4',19,2,false);
	createWeapon('club',4,'1d6',20,2,true);
	createWeapon('club',4,'1d6',20,2,false);

	// ranged
	createWeapon('light crossbow',4,'1d8',19,2,false);
	createWeapon('dart',4,'1d4',20,2,false);
	createWeapon('heavy crossbow',4,'1d10',19,2,false);
	createWeapon('javelin',4,'1d6',20,2,false);
	createWeapon('hand crossbow',2,'1d4',19,2,false);
	createWeapon('shuriken',2,'1',20,2,false);
	createWeapon('repeating crossbow',4,'1d8',19,2,false);
	createWeapon('longbow',5,'1d8',20,3,false);
	createWeapon('shortbow',4,'1d6',20,3,false);
	createWeapon('sling',3,'1d4',20,2,false);
  // ranged crystal weapons (Samardan)
	createWeapon('dikres',3,'2d6',20,2,false);
	createWeapon('malkres',4,'2d8',20,2,false);
	createWeapon('kres',5,'2d10',20,2,false);
	createWeapon('grankres',6,'4d6',20,2,false);
  // melee crystal weapons (Samardan)
	createWeapon2('kres-dagger',2,'1d4',19,2,true, true);
	createWeapon2('dikresblade',2,'1d4',19,2,true, true);
	createWeapon2('kres-shortsword',4,'1d6',18,2,true, true);
	createWeapon2('malkresblade',4,'1d6',18,2,true, true);
	createWeapon2('kres-rapier',4,'1d6',18,2,true, true);
	createWeapon('kres-sword',4,'1d8',19,2,true);
	createWeapon('kres-longsword',4,'1d8',19,2,true);
	createWeapon('kresblade',4,'1d8',19,2,true);
	createWeapon('kres-bastardsword',4,'1d10',19,2,true);
	createWeapon('kres-greatsword',5,'2d6',19,2,true);
	createWeapon('grankresblade',5,'2d6',19,2,true);
}

// create one or more weapon group entry and add them to weaponGroups[]
// multiple group names are supported for the purpose of aliases
function createWeaponGroup(names, members)
{
  for (name in names)
	{
		weaponGroups[names[name]] = members;
	}
}

// create all weapon group entries
function initializeWeaponGroups()
{
	createWeaponGroup(['advanced weapons', 'advanced'], ['dikres', 'kres', 'malkres', 'grankres', 'kresblade', 'malkresblade', 'dikresblade', 'kres-dagger', 'kres-shortsword', 'kres-rapier', 'kres-sword', 'kres-longsword', 'kres-greatsword', 'kres-bastardsword']);
	createWeaponGroup(['archery weapons', 'archery'], ['shortbow', 'longbow', 'light crossbow', 'heavy crossbow', 'repeating crossbow', 'hand crossbow']);
	createWeaponGroup(['bladesman', 'blades'], ['dagger','short sword', 'shortsword', 'longsword', 'rapier', 'scimitar', 'bastard sword', 'greatsword', 'two-bladed sword', 'falchion']);
	createWeaponGroup(['close combat'], ['punching dagger', 'spiked gauntlet']);
	createWeaponGroup(['hafted weapons', 'hafted'], ['light mace', 'heavy mace', 'handaxe', 'hand axe', 'battleaxe', 'light pick', 'heavy pick', 'warhammer', 'greataxe', 'greatclub', 'dwarven waraxe', 'gnome hooked hammer', 'orc double axe', 'throwing axe', 'light hammer', 'club', 'sap', 'dwarven urgrosh']);
	createWeaponGroup(['heavy weapons', 'heavy'], ['none']); // TODO
	createWeaponGroup(['hinged weapons', 'hinged'], ['morningstar', 'light flail', 'heavy flail', 'spiked chain', 'dire flail', 'whip']);
	createWeaponGroup(['martial arts weapons', 'martial arts'], ['halfling kama', 'kukri', 'halfling nunchaku', 'halfling siangham', 'kama', 'nunchaku', 'siangham']);
	createWeaponGroup(['personal firearms'], ['none']); // TODO
	createWeaponGroup(['polearms'], ['mancatcher', 'quarterstaff', 'light lance', 'heavy lance', 'halberd', 'longspear', 'trident', 'pitchfork', 'shortspear', 'halfspear', 'glaive', 'guisarme', 'ranseur', 'scythe', 'sickle']);
	createWeaponGroup(['slings/thrown weapons'], ['dagger','sling', 'dart', 'javelin', 'shuriken', 'club', 'halfspear', 'shortspear', 'trident', 'light hammer', 'throwing axe', 'pitchfork']);

	// TODO: need to handle weapons that are both melee and ranged and need
	//   different weapon groups for each usage
}

// armor object
function armor(name, armorType, bonus, maxDexBonus)
{
	this.name = name;
	// 0 = armor, 1 = shield, 2 = deflect, 3 = natural
	this.armorType = armorType;
	this.bonus = bonus;
	// -1 means does not have a max Dex bonus
	this.maxDexBonus = maxDexBonus;
}

// create one armor entry and add it to armors[]
function createArmor(name, armorType, bonus, maxDexBonus)
{
	armors.push(new armor(name, armorType, bonus, maxDexBonus));
}

// create all armor entries and add them to armors[]
function initializeArmors()
{
	var armor = 0;
	var shield = 1;
	var deflect = 2;
	var natural = 3;

	// armor
	createArmor('bracers of armor',armor,0, -1);
	createArmor('padded',armor,1, 8);
	createArmor('leather',armor,2, 6);
	createArmor('studded leather',armor,3, 5);
	createArmor('chain shirt',armor,4, 4);
	createArmor('hide',armor,3, 4);
	createArmor('scale mail',armor,4, 3);
	createArmor('chainmail',armor,5, 2);
	createArmor('chain mail',armor,5, 2);
	createArmor('breastplate',armor,5, 3);
	createArmor('splint mail',armor,6, 0);
	createArmor('banded mail',armor,6, 1);
	createArmor('half-plate',armor,7, 0);
	createArmor('full plate',armor,8, 1);
	createArmor('mithral shirt',armor,4, 6);
	createArmor('elven chain',armor,5, 4);
	createArmor('dwarven plate',armor,8, 3);

	// shield
	createArmor('buckler',shield,1, -1);
	createArmor('small wooden shield',shield,1, -1);
	createArmor('small steel shield',shield,1, -1);
	createArmor('small metal shield',shield,1, -1);
	createArmor('small shield',shield,1, -1);
	createArmor('large wooden shield',shield,2, -1);
	createArmor('large steel shield',shield,2, -1);
	createArmor('large metal shield',shield,2, -1);
	createArmor('large shield',shield,2, -1);

	// deflection
	createArmor('ring of protection',deflect,0, -1);

	// natural 
	createArmor('amulet of natural armor',natural,0, -1);
}

//!
//! Find an item wpn in a string of items
//! Applies some basic rules to avoid false positives
//! returns index of wpn in items, or -1 if not found
//!
function findItem(wpn, items)
{
	var curSearchIndex = 0;

	var wpnIndex = -1;

	while (curSearchIndex != -1)
	{
		wpnIndex=items.indexOf(wpn, curSearchIndex);

		if(wpnIndex<0)
		{
			curSearchIndex = -1;
			break;
		}

		// Avoid finding weapons that are not present (ex: don't match
		//   "malkres" when looking for "kres")
		var checkIndex = wpnIndex - 1;

		var beforeTextOkay = false;

		// get the char to check if index is inside of string
		if (checkIndex >= 0)
		{
			var checkChar = items.charAt(checkIndex);

			// Needs a comma, blank, or "outside of string" in items[wpnIndex-1]
			if ((!isAlpha(checkChar)) && ((checkChar == ' ') || (checkChar == ',')))
			{
				// we have a good match in wpnIndex
				beforeTextOkay = true;
			}
		}
		else
		{
				// at beginning of string so before char okay
				beforeTextOkay = true;
		}

		// now check character after found weapon name
		// needs one of the following characters or conditions after name:
		// ' '
		// ','
		// '('
		// end of string
		var nameLength = wpn.length;
		var textLength = items.length;

		var afterIndex = wpnIndex + nameLength;

		var afterTextOkay = false;

		if (afterIndex < textLength)
		{
			var checkCharAfter = items.charAt(afterIndex);

			if ((checkCharAfter == ' ') || (checkCharAfter == ',') || (checkCharAfter == '('))
			{
				afterTextOkay = true;
			}
		}
		else
		{
			afterTextOkay = true;
		}

		// if both before text and after text okay, then we have a valid match
		if (beforeTextOkay && afterTextOkay)
		{
			break;
		}
		else
		{
			// advance next search past current character of string found
			//    in case this match is not a valid weapon match
			curSearchIndex = wpnIndex + 1;
		}
	}

	// we did not find the weapon (name by itself) so return -1
	if (curSearchIndex == -1)
	{
		wpnIndex = -1;
	}

	return wpnIndex;
}


//! object characterInput 
//!
//! This objects represents the user's input for the character
//!
function characterInput()
{
	this.chrName = '';
	this.chrRace = '';
	this.chrClasses = '';
	this.Str = new Number(10);
	this.Dex = new Number(10);
	this.Con = new Number(10);
	this.Int = new Number(10);
	this.Wis = new Number(10);
	this.Cha = new Number(10);
	this.chrHp = new Number(0);
	this.chrGender = '';
	this.chrAllegiance = '';
	this.feats = '';
	this.skills = '';
	this.talents = '';
	this.items = '';
	this.languages = '';
	this.notes = '';
	this.character = 0;
	this.format = 0;
}

function characterInput(chrName, chrRace, chrClasses, Str, Dex, Con, Int, Wis, Cha, chrHp, chrGender, chrAllegiance, feats, skills, talents, items, languages, notes, character, format)
{
	this.chrName = chrName;
	this.chrRace = chrRace;
	this.chrClasses = chrClasses;
	this.Str = new Number(Str);
	this.Dex = new Number(Dex);
	this.Con = new Number(Con);
	this.Int = new Number(Int);
	this.Wis = new Number(Wis);
	this.Cha = new Number(Cha);
	this.chrHp = new Number(chrHp);
	this.chrGender = chrGender;
	this.chrAllegiance = chrAllegiance;
	this.feats = feats;
	this.skills = skills;
	this.talents = talents;
	this.items = items;
	this.languages = languages;
	this.notes = notes;
	this.character = character;
	this.format = format;
}

//! object characterData
//!
//! This object represents the character's data
//! This data includes data from the user and data computed from user data
//!
function characterData()
{
	// methods
	this.createCharacterData = createCharacterData;

	// Set up default variables

	// data almost directly from character input
	this.chrName='Character';
	this.chrRace='human';
	this.chrClasses='Str1';
	this.Str=new Number(10);
	this.Dex=new Number(10);
	this.Con=new Number(10);
	this.Int=new Number(10);
	this.Wis=new Number(10);
	this.Cha=new Number(10);
	this.chrHp=new Number(8);
	this.chrGender='male';
	this.chrAllegiance='';
	this.feats='';
	this.skills='';
	this.talents='';
	this.items='';
	this.languages='';
	this.notes='';
	this.character = new Number(0);
	this.format = new Number(0);

	// derived data
	this.chrType='humanoid';
	this.chrSubType='human';
	this.chrCR='1';
	this.chrSize='medium';
	this.numSize=new Number(0);//ranges from -4 to +4
	this.chrSpace='5 ft.';
	this.chrReach='5 ft.';
	this.chrHD='1d8';
	this.numInit=new Number(0);
	this.chrInit='+0';
	this.chrSpd='30 ft.';

	this.ACLong='';
	this.atkM='';
	this.atkR='';
	this.SA='';
	this.SQ='';
	this.baseGrapple = new Number(0);
	this.repPoints = new Number(0);
	this.fort = new Number(0);
	this.ref = new Number(0);
	this.will = new Number(0);

	this.chrMas=new Number(10);
	this.chrDying=new Number(0);
	this.baseAtk=new Number(0);

	this.StrHero=new Number(1);
	this.DexHero=new Number(0);
	this.ConHero=new Number(0);
	this.IntHero=new Number(0);
	this.WisHero=new Number(0);
	this.ChaHero=new Number(0);
	
	this.StrHeroFirst=new Number(1);
	this.DexHeroFirst=new Number(0);
	this.ConHeroFirst=new Number(0);
	this.IntHeroFirst=new Number(0);
	this.WisHeroFirst=new Number(0);
	this.ChaHeroFirst=new Number(0);

	this.bestArmor='';
	this.bestShield='';
	this.bestNatural='';
	this.bestDeflect='';

	this.actPoints=new Number(5);
}


// given characterInput object x, set this characterData using x
// basically creates the full character data for creating a stat block
//    given the input from x
function createCharacterData(x)
{
	this.format = x.format;
	this.character = x.character;
	this.notes = x.notes;

	this.chrRace=x.chrRace;
	if(this.chrRace=='') this.chrRace='human';
	if(this.chrRace=='half orc') this.chrRace='half-orc';
	if(this.chrRace=='half elf') this.chrRace='half-elf';

	this.chrType='humanoid';
	this.chrSubType=this.chrRace;
	this.chrSize='medium';
	this.numSize=new Number(0);
	this.chrSpace='5 ft.';
	this.chrReach='5 ft.';

	if(this.chrRace=='gnome' || this.chrRace=='halfling')
	{
		this.chrSize='small';
		this.numSize=new Number(-1);
	};

	this.chrName=x.chrName;
	if(this.chrName=='') this.chrName='Character';

	if(x.chrGender.toLowerCase()=='female') this.chrGender='female'; else this.chrGender='male';

 this.chrClasses=x.chrClasses;
 if(this.chrClasses=='') this.chrClasses='Str1';

 this.chrAllegiance=x.chrAllegiance;
 if(this.chrAllegiance=='') this.chrAllegiance='--';

 this.Str=new Number(x.Str);
 this.Dex=new Number(x.Dex);
 this.Con=new Number(x.Con);
 this.Int=new Number(x.Int);
 this.Wis=new Number(x.Wis);
 this.Cha=new Number(x.Cha);

 this.chrHp=new Number(x.chrHp);

 this.feats=x.feats;
 this.skills=x.skills;
 this.talents=x.talents;
 this.items=x.items;
 this.languages=x.languages;

 // lower case versions for comparisons
 var Race = this.chrRace.toLowerCase();
 var Classes= this.chrClasses.toLowerCase();
 var Feats = this.feats.toLowerCase();
 var Talents = this.talents.toLowerCase();
 var Items = this.items.toLowerCase();

 // handle stats
 if(isNaN(this.Str)||this.Str==''||this.Str==0) {
  this.Str=10;
 }
 if(Race=='gnome') this.Str=this.Str-2;
 if(Race=='half-orc') this.Str=this.Str+2;
 if(Race=='piljanan') this.Str=this.Str+2;
 if(Race=='halfling') this.Str=this.Str-2;
 if(Race=='kelshan') this.Str=this.Str+2;
 if(isNaN(this.Dex)||this.Dex==''||this.Dex==0) {
  this.Dex=10;
 }
 if(Race=='elf') this.Dex=this.Dex+2;
 if(Race=='halfling') this.Dex=this.Dex+2;
 if(isNaN(this.Con)||this.Con==''||this.Con==0) {
  this.Con=10;
 }
 if(Race=='dwarf') this.Con=this.Con+2;
 if(Race=='elf') this.Con=this.Con-2;
 if(Race=='gnome') this.Con=this.Con+2;
 if(Race=='piljanan') this.Con=this.Con+2;
 if(Race=='kelshan') this.Con=this.Con+2;
 if(isNaN(this.Int)||this.Int==''||this.Int==0) {
  this.Int=10;
 }
 if(Race=='half-orc') this.Int=this.Int-2;
 if(Race=='piljanan') this.Int=this.Int-2;
 if(isNaN(this.Wis)||this.Wis==''||this.Wis==0) {
  this.Wis=10;
 }
 if(isNaN(this.Cha)||this.Cha==''||this.Cha==0) {
  this.Cha=10;
 }
 if(Race=='dwarf') this.Cha=this.Cha-2;
 if(Race=='half-orc') this.Cha=this.Cha-2;
 if(Race=='piljanan') this.Cha=this.Cha-2;
 if(Race=='kelshan') this.Cha=this.Cha-4;

 // calculate class levels and CR
 this.StrHero=new Number(gc(Classes,'str'));
 this.StrHero=Math.max(this.StrHero,gc(Classes,'strong hero'));
 this.StrHeroFirst=isFirstClass(Classes, 'str', 'strong hero');;

 this.DexHero=new Number(gc(Classes,'dex'));
 this.DexHero=Math.max(this.DexHero,gc(Classes,'fast hero'));
 this.DexHeroFirst=isFirstClass(Classes, 'dex', 'fast hero');;

 this.ConHero=new Number(gc(Classes,'con'));
 this.ConHero=Math.max(this.ConHero,gc(Classes,'tough hero'));
 this.ConHeroFirst=isFirstClass(Classes, 'con', 'tough hero');;

 this.IntHero=new Number(gc(Classes,'int'));
 this.IntHero=Math.max(this.IntHero,gc(Classes,'smart hero'));
 this.IntHeroFirst=isFirstClass(Classes, 'int', 'smart hero');;

 this.WisHero=new Number(gc(Classes,'wis'));
 this.WisHero=Math.max(this.WisHero,gc(Classes,'dedicated hero'));
 this.WisHeroFirst=isFirstClass(Classes, 'wis', 'dedicated hero');

 this.ChaHero=new Number(gc(Classes,'cha'));
 this.ChaHero=Math.max(this.ChaHero,gc(Classes,'charismatic hero'));
 this.ChaHeroFirst=isFirstClass(Classes, 'cha', 'charismatic hero');

 var cLevel=new Number(this.StrHero+this.DexHero+this.ConHero+this.IntHero+this.WisHero+this.ChaHero);
 this.chrCR=cLevel;
 if(cLevel==0) {
  this.StrHero=1;
  cLevel=1;
  this.chrCR=1;
 }
 if(this.talents=='') this.chrCR=this.chrCR-1;

 // calculate base attack bonus
 this.baseAtk=new Number(0);
 this.baseAtk+=this.StrHero;
 this.baseAtk+=Math.floor(this.DexHero*.75);
 this.baseAtk+=Math.floor(this.ConHero*.75);
 this.baseAtk+=Math.floor(this.IntHero*.5);
 this.baseAtk+=Math.floor(this.WisHero*.75);
 this.baseAtk+=Math.floor(this.ChaHero*.5);

	// calculate best AC values
	var armor=new Number(0);
	var shield=new Number(0);
	var deflect=new Number(0);
	var natural=new Number(0);
  var maxDex = new Number(100);

	for (var i = 0; i < armors.length; i++)
	{
		var a = armors[i];

		var bestACreturn;

		switch (a.armorType)
		{
			case 0:
				bestACreturn = bestAC(a, armor, Items, maxDex, this.bestArmor);
				armor = bestACreturn.bestBonus;
				this.bestArmor = bestACreturn.bestText;
				maxDex = bestACreturn.maxDexBonus;
				break
			case 1:
				bestACreturn = bestAC(a, shield, Items, maxDex, this.bestShield);
				shield = bestACreturn.bestBonus;
				this.bestShield = bestACreturn.bestText;
				maxDex = bestACreturn.maxDexBonus;
				break
			case 2:
				bestACreturn = bestAC(a, deflect, Items, maxDex, this.bestDeflect);
				deflect = bestACreturn.bestBonus;
				this.bestDeflect = bestACreturn.bestText;
				maxDex = bestACreturn.maxDexBonus;
				break
			case 3:
				bestACreturn = bestAC(a, natural, Items, maxDex, this.bestNatural);
				natural = bestACreturn.bestBonus;
				this.bestNatural = bestACreturn.bestText;
				maxDex = bestACreturn.maxDexBonus;
				break
		}
	}

 var DexAC=new Number(Math.min(maxDex,Math.floor(this.Dex/2-5)));

 // calculate class defense
 var defense=new Number(0);
 if(this.StrHero>0) defense+=Math.floor(1.4+0.4*(this.StrHero+0)/1);
 if(this.DexHero>0) defense+=Math.floor(3  +0.5*(this.DexHero+0)/1);
 if(this.ConHero>0) defense+=Math.floor(1.4+0.4*(this.ConHero+0)/1);
 if(this.IntHero>0) defense+=Math.floor(        (this.IntHero+1)/3);
 if(this.WisHero>0) defense+=Math.floor(1.4+0.4*(this.WisHero+0)/1);
 if(this.ChaHero>0) defense+=Math.floor(        (this.ChaHero+1)/3);

 var classDef=new Number(1);
 classDef=new Number(Math.min(maxDex,defense));

 // calculate standard AC, touch AC, and flat-footed AC
 var AC=new Number(10);
 var tAC=new Number(10);
 var ffAC=new Number(10);

 AC=AC+armor+shield+natural+deflect+DexAC-this.numSize+classDef;
 tAC=tAC+deflect+DexAC-this.numSize+classDef;
 ffAC=ffAC+armor+shield+natural+deflect-this.numSize+classDef;

 this.ACLong=''+AC+'';
 if(armor+shield+natural+deflect+classDef>0||DexAC!=0||numSize!=0) {
  this.ACLong+=' (';
  if(armor>0) this.ACLong+='+'+armor+' '+this.bestArmor+', ';
  if(DexAC!=0) {
   if(DexAC>0) this.ACLong+='+';
   this.ACLong+=DexAC+' Dex, ';
  }
  if(this.numSize!=0) {
   temp=new Number(-1*this.numSize);
   if(temp>0) this.ACLong+='+';
   this.ACLong+=temp+' size, ';
  }
  if(classDef>0) this.ACLong+='+'+classDef+' class, ';
  if(deflect>0) this.ACLong+='+'+deflect+' '+this.bestDeflect+', ';
  if(shield!=0) this.ACLong+='+'+shield+' '+this.bestShield+', ';
  if(natural!=0) this.ACLong+='+'+natural+' '+this.bestNatural+', '
  this.ACLong=left(this.ACLong,this.ACLong.length-2)+')';
 }
 this.ACLong+=', touch '+tAC+', flat-footed '+ffAC;

 // calculate grapple bonus
 var j=new Number(Math.floor(this.Str/2-5)+(this.numSize*4));
 if(Feats.indexOf('improved grapple')>=0) j=j+4;
 this.baseGrapple=new Number(this.baseAtk+j);

 // create class string
 this.chrClasses='';
 var tempDesc='Hero';
 if(this.talents=='') tempDesc='Ordinary';
 
 if(this.StrHero>0) this.chrClasses+='Strong '+tempDesc+' '+this.StrHero+'/';
 if(this.DexHero>0) this.chrClasses+='Fast '+tempDesc+' '+this.DexHero+'/';
 if(this.ConHero>0) this.chrClasses+='Tough '+tempDesc+' '+this.ConHero+'/';
 if(this.IntHero>0) this.chrClasses+='Smart '+tempDesc+' '+this.IntHero+'/';
 if(this.WisHero>0) this.chrClasses+='Dedicated '+tempDesc+' '+this.WisHero+'/';
 if(this.ChaHero>0) this.chrClasses+='Charismatic '+tempDesc+' '+this.ChaHero+'/';

 this.chrClasses=left(this.chrClasses,this.chrClasses.length-1);

 // calculate saving throws

 this.fort=new Number(0);
 this.ref=new Number(0);
 this.will=new Number(0);

 if(this.StrHero>0) this.fort+=Math.floor(1.2+0.4*this.StrHero);
 if(this.ConHero>0) this.fort+=Math.floor(1.2+0.4*this.ConHero);
 if(this.WisHero>0) this.fort+=Math.floor(1.2+0.4*this.WisHero);
 if(this.ChaHero>0) this.fort+=Math.floor(1.2+0.4*this.ChaHero);
 if(this.DexHero>0) this.fort+=Math.floor(this.DexHero/3);
 if(this.IntHero>0) this.fort+=Math.floor(this.IntHero/3);
 if(Feats.indexOf('great fortitude')>=0) this.fort=this.fort+2;
 this.fort+=Math.floor(this.Con/2-5);
 if(Race=='halfling') this.fort+=1;
 if(Talents.indexOf('aura of grace')>=0) this.fort+=Math.floor(this.Cha/2-5);

 if(this.DexHero>0) this.ref+=Math.floor(1.2+0.4*this.DexHero);
 if(this.ChaHero>0) this.ref+=Math.floor(1.2+0.4*this.ChaHero);
 if(this.StrHero>0) this.ref+=Math.floor(this.StrHero/3);
 if(this.ConHero>0) this.ref+=Math.floor(this.ConHero/3);
 if(this.IntHero>0) this.ref+=Math.floor(this.IntHero/3);
 if(this.WisHero>0) this.ref+=Math.floor(this.WisHero/3);
 if(Feats.indexOf('lightning reflexes')>=0) this.ref+=2;
 this.ref+=Math.floor(this.Dex/2-5);
 if(Race=='halfling') this.ref+=1;
 if(Talents.indexOf('aura of grace')>=0) this.ref+=Math.floor(this.Cha/2-5);

 if(this.IntHero>0) this.will+=Math.floor(1.2+0.4*this.IntHero);
 if(this.WisHero>0) this.will+=Math.floor(1.2+0.4*this.WisHero);
 if(this.StrHero>0) this.will+=Math.floor(this.StrHero/3);
 if(this.ConHero>0) this.will+=Math.floor(this.ConHero/3);
 if(this.DexHero>0) this.will+=Math.floor(this.DexHero/3);
 if(this.ChaHero>0) this.will+=Math.floor(this.ChaHero/3);
 if(Feats.indexOf('iron will')>=0) this.will+=2;
 this.will+=Math.floor(this.Wis/2-5);
 if(Race=='halfling') this.will+=1;
 if(Talents.indexOf('aura of grace')>=0) this.will+=Math.floor(this.Cha/2-5);

 var j=Items.indexOf('cloak of resistance');
 if(j>=0) {
  j=new Number(Items.charAt(j-2));
  if(!isNaN(j)) {
   this.fort+=j;
   this.ref+=j;
   this.will+=j;
  }
 }

 // calculate hit points (HP)
 var numToughness=0;
 var pos=Feats.indexOf('toughness');
 if(pos>=0) {
  numToughness=1;
  if(Feats.length>=pos+'toughness ('.length) {
   numToughness=Feats.charAt(pos+'toughness ('.length);
  }
  if(isNaN(numToughness)) numToughness=1;
 }

 // if hit points are not pre-set, compute them
 if(this.chrHp<cLevel) 
 {
  var newHp = new Number(0);
  mod=Math.floor(this.Con/2-5);
  newHp=new Number(0);
  
  j=new Number(this.IntHero+this.WisHero+this.ChaHero);
  newHp+=j*hpAve(6,mod, this.character);
  j=new Number(this.StrHero+this.DexHero);
  newHp+=j*hpAve(8,mod, this.character);
  j=new Number(this.ConHero);
  newHp+=j*hpAve(10,mod, this.character);
  
  newHp+=3*numToughness;
  
  //j=new Number(Math.floor(this.chrHp+0.5));
  //newHp+==new Number(j); 
 
 // characters get full (not average) HP their first level
 if (this.character != 0)
 {
	// get first character level and hp mod
	// hpAve(4) = 3, so add 1
	// hpAve(6) = 4, so add 2
	// hpAve(8) = 5, so add 3
	// hpAve(10) = 6, so add 4
	// hpAve(12) = 7, so add 5
	
	var firstLevelHPMod = 0;
	
	if (this.IntHeroFirst || this.WisHeroFirst || this.ChaHeroFirst)
	{
		firstLevelHPMod = 2;
	}
	if (this.StrHeroFirst || this.DexHeroFirst)
	{
		firstLevelHPMod = 3;
	}
	if (this.ConHeroFirst)
	{
		firstLevelHPMod = 4;
	}

	// add the first level HP mod to max out first level HP	
	newHp += firstLevelHPMod;
 }
 
 // d20 rounds down
 this.chrHp = Math.floor(newHp);
 }

 // calculate hit dice (HD)
 this.chrHD='';
 j=new Number(this.IntHero+this.WisHero+this.ChaHero);
 if(j>0) this.chrHD=this.chrHD+j+'d6+';
 j=new Number(this.StrHero+this.DexHero);
 if(j>0) this.chrHD=this.chrHD+j+'d8+';
 j=new Number(this.ConHero);
 if(j>0) this.chrHD=this.chrHD+j+'d10+';
 this.chrHD=left(this.chrHD,this.chrHD.length-1);
 j=new Number(Math.floor(this.Con/2-5)*cLevel);
 j=j+3*numToughness;
 if(j>0) this.chrHD=this.chrHD+'+';  
 if(j!=0) this.chrHD=this.chrHD+j;

 // calculate massive damage threshold (Mas)
 var numIDT=0;
 pos=Feats.indexOf('improved damage threshold');
 if(pos>=0) {
  numIDT=1;
  if(Feats.length>=pos+'improved damage threshold ('.length) {
    numIDT=Feats.charAt(pos+'improved damage threshold ('.length);
  }
  if(isNaN(numIDT)) numIDT=1;
 }
 this.chrMas = new Number(this.Con + 3*numIDT);

 // calculate dying damage theshold (Dying)
 //j=new Number(Math.floor(Con/2-5)*cLevel);
 j=new Number(Math.floor(this.Con/2-5));
 j=j+1;
 if(j<1) j=1;
 this.chrDying = new Number(j);

 // calculate initiative (Init)
 var numIR=0;
 pos=Talents.indexOf('improved reaction');
 if(pos>=0) {
  numIR=1;
  if(Talents.length>=pos+'improved reaction ('.length) {
    numIR=Talents.charAt(pos+'improved reaction ('.length);
  }
  if(isNaN(numIR)) numIR=1;
 }

 var numInit=Math.floor((this.Dex/2-5)+(2*numIR));
 pos=Feats.indexOf('improved initiative');
 if(pos>=0) {
  numInit=numInit+4;
 }

 this.chrInit='';
 if(numInit>=0) this.chrInit+='+';
 this.chrInit+=numInit;
 if(Math.floor(this.Dex/2-5)!=0||Feats.indexOf('improved initiative')>=0||numIR>0) {
  this.chrInit+=' (';
  if(Math.floor(this.Dex/2-5)!=0) {
    if (Math.floor(this.Dex/2-5)>0) this.chrInit+='+';
    this.chrInit+=Math.floor(this.Dex/2-5)+' Dex';
  }
  if(Math.floor(this.Dex/2-5)!=0&&Feats.indexOf('improved initiative')>=0) this.chrInit+=', ';
  if(Feats.indexOf('improved initiative')>=0) this.chrInit+='+4 feat';
  if((Math.floor(this.Dex/2-5)!=0||Feats.indexOf('improved initiative')>=0)&&numIR>0) this.chrInit+=', ';
  if(numIR>0) {
    var j=new Number(numIR*2);
    this.chrInit+='+'+j+' talent';
  };
  this.chrInit+=')';
 }

 // calculate speed
 var j=new Number(30);
 if(Race=='dwarf') j=j-10;
 if(Race=='gnome') j=j-10;
 if(Race=='halfling') j=j-10;
 var numIM=0;
 pos=Talents.indexOf('increased speed');
 if(pos>=0) {
  numIM=1;
  if(Talents.length>=pos+'increased speed ('.length) {
    numIM=Talents.charAt(pos+'increased speed ('.length);
  }
  if(isNaN(numIM)) numIM=1;
 }
 var numSpd = new Number(j+numIM*5);
 this.chrSpd = numSpd + ' ft.';

 // list special attacks
 this.SA='';
 if(Race=='dwarf') this.SA=SA+'+1 attack vs Orcs and Goblinoids, ';
 if(Race=='gnome') this.SA+'+1 attack vs Kobolds and Goblinoids, ';
 if(Race=='halfling') this.SA+'+1 attack with thrown weapons and slings, ';
 if(Race=='kelshan') this.SA+'1d4 claws, prehensile tail, ';
 if(this.SA!='') this.SA=left(this.SA,this.SA.length-2);

 // list senses
 this.chrSenses='';
 if(Race=='dwarf') this.chrSenses+='darkvision, ';
 if(Race=='elf') this.chrSenses+='low-light vision, ';
 if(Race=='gnome') this.chrSenses+='low-light vision, ';
 if(Race=='half-elf') this.chrSenses+='low-light vision, ';
 if(Race=='half-orc') this.chrSenses+='darkvision, ';
 if(this.chrSenses!='') this.chrSenses=left(this.chrSenses,this.chrSenses.length-2);

 // list special qualities
 SQ='';
 if(Race=='dwarf') this.SQ+='+4 dodge vs Giants, movement unrestricted by armor, +2 on saves vs poisons and magic, stability, stonecunning, ';
 if(Race=='elf') this.SQ+='+2 on saves vs enchantments, sleep immunity, ';
 if(Race=='gnome') this.SQ+='+4 dodge vs Giants, +2 on saves vs illusions, spell-like abilities, ';
 if(Race=='half-elf') this.SQ+='+2 on saves vs enchantments, sleep immunity, ';
 if(Race=='halfling') this.SQ+='+2 morale on saves vs fear, +1 on all saves, ';
 if(Race=='kelshan') this.SQ+='+2 to Reputation for infamy checks only, ';
 if(this.SQ!='') this.SQ=left(this.SQ,this.SQ.length-2);

 // calculate reputation
 this.repPoints=new Number(0);
 if(this.StrHero>0) this.repPoints+=Math.floor((this.StrHero-1)/4);
 if(this.DexHero>0) this.repPoints+=Math.floor(this.DexHero/3);
 if(this.ConHero>0) this.repPoints+=Math.floor(this.ConHero/3);
 if(this.IntHero>0) this.repPoints+=Math.floor((this.IntHero+2)/3);
 if(this.WisHero>0) this.repPoints+=Math.floor((this.WisHero+2)/3);
 if(this.ChaHero>0) this.repPoints+=Math.floor((this.ChaHero+5)/3);
 pos=Feats.indexOf('low profile');
 if(pos>=0) {
  this.repPoints=this.repPoints-3;
 }
 pos=Feats.indexOf('renown');
 if(pos>=0) {
  this.repPoints=this.repPoints+3;
 }

 // calculate action points
 this.actPoints=new Number(5+Math.floor((this.StrHero+this.DexHero+this.ConHero+this.IntHero+this.WisHero+this.ChaHero)/2));
 if(Talents=='') this.actPoints=new Number(0);

 // calculate melee attacks
 var bShield = false;
 if (this.bestShield != '')
 {
    bShield = true;
 }

// 0=Fine, 1=Diminuative, 2=Tiny, 3=Small, 4=Medium,
// 5=Large, 6=Huge, 7=Gargantuan, 8=Colossal

	atkM='';
	atkR='';

	for (var i = 0; i < weapons.length; i++)
	{
		var w = weapons[i];
		var weaponText = buildAttackText(w.name, w.size, w.damage, w.threatRange, w.critMultiplier, w.melee, w.finesse, Items, Feats, Talents, bShield, this.baseAtk, this.numSize, this.Str, this.Dex);
		if (w.melee == true)
		{
			this.atkM += weaponText;
		}
		else
		{
			this.atkR += weaponText;
		}
	}

 // unarmed attacks
 var uAtkMod=new Number(0);
 if(Feats.indexOf('weapon finesse')<0) {
  uAtkMod=Math.floor(this.Str/2-5);
 } else {
  uAtkMod=Math.floor(this.Dex/2-5);
 }
 uAtkMod=uAtkMod+this.baseAtk+this.numSize;
 if(Feats.indexOf('weapon focus (unarmed strike)')>=0) uAtkMod=uAtkMod+1;
 if(Feats.indexOf('greater weapon focus (unarmed strike)')>=0) uAtkMod=uAtkMod+1;
 if(Feats.indexOf('brawl')>=0) uAtkMod=uAtkMod+1;
 if(Feats.indexOf('improved brawl')>=0) uAtkMod=uAtkMod+1;

 var uDmg=Math.floor(this.Str/2-5);
 if(Talents.indexOf('weapon specialization (unarmed strike)')>=0) uDmg=uDmg+2;
 if(Talents.indexOf('greater weapon specialization (unarmed strike)')>=0) uDmg=uDmg+2;
 if(Talents.indexOf('zen strike')>=0) uDmg=uDmg+Math.floor(Wis/2-5);

 // unarmed 0..n melee smash talents
 var numMS=0;
 var pos=Talents.indexOf('melee smash');
 if(pos>=0) {
  numMS=1;
  if(Talents.length>=pos+'melee smash ('.length) {
    numMS=Talents.charAt(pos+'melee smash ('.length);
  }
  if(isNaN(numMS)) numMS=1;
 }
 if(numMS>0) uDmg=uDmg+(1*numMS);

 // unarmed threat range
 var uThreat=new Number(20);
 if(Feats.indexOf('improved combat martial arts')>=0) uThreat=uThreat-1;
 if(Talents.indexOf('improved critical (unarmed strike)')>=0) uThreat=uThreat-1;

 // unarmed crit multiplier
 var uCrit=new Number(2);
 if(Feats.indexOf('advanced combat martial arts')>=0) uCrit=uCrit+1;
 if(Feats.indexOf('greater unarmed strike')>=0) uCrit=uCrit+1;
 if(Feats.indexOf('improved knockout punch')>=0) uCrit=uCrit+1;

 // unarmed base damage
 var uBaseDmg='1d3';
 if(Race=='kelshan') uBaseDmg='1d4';
 if(Feats.indexOf('combat martial arts')>=0) uBaseDmg='1d4';
 if(Feats.indexOf('brawl')>=0) uBaseDmg='1d6';
 if(Feats.indexOf('improved brawl')>=0) uBaseDmg='1d8';

 numLW=0;
 pos=Talents.indexOf('living weapon');
 if(pos>=0) {
  numLW=1;
  if(Talents.length>=pos+'living weapon ('.length) {
    numLW=Talents.charAt(pos+'living weapon ('.length);
  }
  if(isNaN(numLW)) numLW=1;
 }
 if(numLW>0) {
  if(numLW==1) uBaseDmg='1d6';
  if(numLW==2) uBaseDmg='1d8';
  if(numLW==3) uBaseDmg='1d10';
  if(numLW==4) uBaseDmg='2d6';
  if(numLW>4) uBaseDmg='2d8';
 }

 this.atkM+=attack(true,this.numSize+2,false,0,'unarmed strike', this.baseAtk,uAtkMod,uBaseDmg,uDmg,uThreat,uCrit,5,false,4);

 this.atkM=left(this.atkM,this.atkM.length-5); //Remove trailing ", or "
 this.atkR=left(this.atkR,this.atkR.length-5); //Remove trailing ", or "
}

// given character input in form, create a characterInput object and return it
function createCharacterInputFromForm(form)
{
  var newForm = 0;
  var newCharacter = 0;

	// set value of hack format and character variables for stat block 
	//   type and character persistance
	if (form.newformat.checked)
	{
		newForm = 1;
	}
	if (form.character.checked)
	{
		newCharacter = 1;
	}

	newCharacterInput = new characterInput(
		form.chrName.value,
		form.chrRace.value,
		form.chrClasses.value,
		form.Str.value,
		form.Dex.value,
		form.Con.value,
		form.Int.value,
		form.Wis.value,
		form.Cha.value,
		form.chrHp.value,
		form.chrGender.value,
		form.chrAllegiance.value,
		form.feats.value,
		form.skills.value,
		form.talents.value,
		form.items.value,
		form.languages.value,
		form.notes.value,
		newCharacter,
		newForm
		);

	return newCharacterInput;
}

// given a characterInput object, fill the form with the information from it
function fillFormFromCharacterInput(input, form)
{
	form.chrName.value = input.chrName;
	form.chrRace.value = input.chrRace;
	form.chrClasses.value = input.chrClasses;
	form.Str.value = input.Str;
	form.Dex.value = input.Dex;
	form.Con.value = input.Con;
	form.Int.value = input.Int;
	form.Wis.value = input.Wis;
	form.Cha.value = input.Cha;
	form.chrHp.value = input.chrHp;
	form.chrGender.value = input.chrGender;
	form.chrAllegiance.value = input.chrAllegiance;
	form.feats.value = input.feats;
	form.skills.value = input.skills;
	form.talents.value = input.talents;
	form.items.value = input.items;
	form.languages.value = input.languages;
	form.notes.value = input.notes;

	// handle stat block type
	if (input.format != 0)
	{
		form.newformat.checked = true;
	}
	else
	{
		form.newformat.checked = false;
	}
	
	// handle character type
	if (input.character != 0)
	{
		form.character.checked = true;
	}
	else
	{
		form.character.checked = false;
	}
}


// --------------------------------------------------------------------
// characterData related functions
// --------------------------------------------------------------------

// get character class information
function gc (classes, j) {
 var out='';
 if(classes.indexOf(j)<0) {
  out=0;
 } else {
  d=classes.charAt(classes.indexOf(j)+j.length+1);
  if(d==' '||d==','||d=='/') out+=classes.charAt(classes.indexOf(j)+j.length);
  else {
    out+=classes.charAt(classes.indexOf(j)+j.length)+d;

    // look for second character of 2 digit class level
    e=classes.charAt(classes.indexOf(j)+j.length+2);
    if(e==' '||e==','||e=='/') { }
    else out+=e;
    }
 }
 if(isNaN(out)) out=0;
 return out;
}

// is this class the first class (level 1) for a character?
function isFirstClass(classes, name1, name2)
{
	// name must start at index 0
	// possible improvement: ignore beginning white space
	if(classes.indexOf(name1)==0)
	{
		return 1;
	}
	if(classes.indexOf(name2)==0)
	{
		return 1;
	}
	
	return 0;
}

function hpAve(die, mod, isCharacter)
{
	var hp = 0;
	
	if (isCharacter)
	{
		hp = die/2+mod+1;
	}
	else
	{
		hp = die/2+mod+0.5;
	}
		
	// no 0 or negative hp points
	if (hp < 1)
	{
		hp = 1;
	}
		
	return hp;
}

// returns { bestBonus, bestText, maxDexBonus }
function bestAC(a, bestBonus, items, maxDexBonus, bestText) {

 var name = a.name;
 var bonus = a.bonus;

 var e=0; // enchancement
 var j=findItem(name, items);
 // if item not in items, return current best
 if(j<0) return { bestBonus : bestBonus, bestText : bestText, maxDexBonus : maxDexBonus };

 // get enhancement
 if(j-2>=0) {
  e=items.charAt(j-2);
  if(isNaN(e)) e=0;
 }

 // get masterwork
 var mw=false;
 j=items.indexOf(name)-3;
 if(j>=0) {
  j=items.charAt(j)+items.charAt(j+1);
  if(j=='mw') mw=true;
 }

 // compute total bonus
 j=new Number(e);
 j=j+bonus;

 // if not as good as current best bonus, return current
 if(j<bestBonus)
   return { bestBonus : bestBonus, bestText : bestText, maxDexBonus : maxDexBonus };

 // otherwise better, so build best string
 var n=name;
 if(mw) n='mw '+n;
 // temp disable italic for enhancement bonus since HTML embedded in text
 //    does not work now
 // if(e>0) n='<i>'+n+'</i>';
 bestText=n;

 // change max dex bonus if one exists for the chosen armor
 if (a.maxDexBonus != -1)
 {
   maxDexBonus = a.maxDexBonus;
 }

 // return new best bonus
 return { bestBonus : j, bestText : bestText, maxDexBonus : maxDexBonus };
}

function buildAttackText(wpn,wpnSize,baseDmg,threat,crit,melee,finesse, items, feats, talents, bShield, baseAtk, numSize, Str, Dex) {

	var wpnIndex = findItem(wpn, items);

	// we did not find the weapon (name by itself) so return blank
	if (wpnIndex == -1)
	{
		return '';
	}

 var wPos=wpnIndex-2;
 var wpnPlus=new Number(0);
 if(wPos>=0) {
  wpnPlus=new Number(items.charAt(wPos));
  if(wpnPlus<1||isNaN(wpnPlus)) wpnPlus=0;
 }

 var mw=false;
 if(wPos>=1) {
  if(items.substring(wPos-1,wPos+1)=='mw') mw=true;
 }

 var atkB=new Number(0);
 // check weapon finesse for this weapon
 if((melee&&feats.indexOf('weapon finesse')>=0&&finesse) || (!melee)) {
   atkB=Math.floor(Dex/2-5);
 } else {
   atkB=Math.floor(Str/2-5);
 }

 atkB=atkB+baseAtk+wpnPlus+numSize;
 if(mw) atkB=atkB+1;

 // use weapon groups or individual weapon feats, as configured
 if (usingWeaponGroups)
 {
   var userProficient = false;

   /* attack bonus feats for weapon groups */
   // for each weapon group
   for (weaponGroup in weaponGroups)
   {
     // check if weapon is in weapon group
     // var weaponIndex = weaponGroups[weaponGroup].indexOf(wpn);
     //var weaponIndex = -1;
     var weaponIndex = searchArray(weaponGroups[weaponGroup], wpn);
   
     // if so, if we have feat for that weapon group then add bonuses
     if (weaponIndex != -1)
     {
       if(feats.indexOf('weapon group ('+weaponGroup+')')>=0) userProficient=true;
       if(feats.indexOf('weapon focus ('+weaponGroup+')')>=0) atkB=atkB+1;
       if(feats.indexOf('greater weapon focus ('+weaponGroup+')')>=0) atkB=atkB+1;
     }
   }

   if (userProficient == false)
   {
      // non-proficiency imposes a -4 penatly on attack rolls
      atkB=atkB-4;
   }
 }
 else
 {
   /* attack bonus feats for individual weapons */
   if(feats.indexOf('weapon focus ('+wpn+')')>=0) atkB=atkB+1;
   if(feats.indexOf('greater weapon focus ('+wpn+')')>=0) atkB=atkB+1;
 }


 if(melee) {
  //Use for later
 } else {
  if(talents.indexOf('zen focus')>=0) atkB=atkB+Math.floor(Wis/2-5);

  /* handle 0..n ranged accuracy talents */
  var numRangedAccuracy=0;
  var pos=talents.indexOf('ranged accuracy');
  if(pos>=0) {
    numRangedAccuracy=1;
    if(talents.length>=pos+'ranged accuracy ('.length) {
      numRangedAccuracy=talents.charAt(pos+'ranged accuracy ('.length);
    }
    if(isNaN(numRangedAccuracy)) numRangedAccuracy=1;
    atkB=atkB+(1*numRangedAccuracy);
  }
  /* end handle 0..n ranged accuracy talents */
 }

 var dmgMod=new Number(wpnPlus);

 if(melee) {

  /* handle 0..n melee smash talents */
  var numMS=0;
  var pos=talents.indexOf('melee smash');
  if(pos>=0) {
   numMS=1;
   if(talents.length>=pos+'melee smash ('.length) {
     numMS=talents.charAt(pos+'melee smash ('.length);
   }
   if(isNaN(numMS)) numMS=1;
   dmgMod=dmgMod+(1*numMS);
  }
  /* end handle 0..n melee smash talents */

  if(((numSize+4)>wpnSize)||(bShield==true)) {
   dmgMod=dmgMod+Math.floor(Str/2-5);
  } else {
   if(Str>10) {
     dmgMod=dmgMod+Math.floor(1.5*(Math.floor(Str/2-5)));
   } else {
    dmgMod=dmgMod+Math.floor(Str/2-5);
   }
  }
 } else {
  if(Str<10) {
   dmgMod=dmgMod+Math.floor(Str/2-5);
  }
 }

 // use weapon groups or individual weapon feats, as configured
 if (usingWeaponGroups)
 {
   /* damage and threat feats for each weapon group */
   // for each weapon group
   for (weaponGroup in weaponGroups)
   {
     // check if weapon is in weapon group
     //var weaponIndex = weaponGroups[weaponGroup].indexOf(wpn);
     //var weaponIndex = -1;
     var weaponIndex = searchArray(weaponGroups[weaponGroup], wpn);
     
     // if so, if we have feat for that weapon group add bonuses
     if (weaponIndex != -1)
     {
       if(talents.indexOf('weapon specialization ('+weaponGroup+')')>=0) dmgMod=dmgMod+2;
       if(talents.indexOf('greater weapon specialization ('+weaponGroup+')')>=0) dmgMod=dmgMod+2;
       if(talents.indexOf('improved critical ('+weaponGroup+')')>=0) threat=threat-1;
     }
   }
 }
 else
 {
   /* damage and threat feats for individual weapons */
   if(talents.indexOf('weapon specialization ('+wpn+')')>=0) dmgMod=dmgMod+2;
   if(talents.indexOf('greater weapon specialization ('+wpn+')')>=0) dmgMod=dmgMod+2;
   if(talents.indexOf('improved critical ('+wpn+')')>=0) threat=threat-1;
 } 

 return attack(melee,wpnSize,mw,wpnPlus,wpn,baseAtk,atkB,baseDmg,dmgMod,threat,crit,5,false,4);
}

function attack(melee,wpnSize,mw,wpnPlus,wpn,baseAtk,atkB,baseDmg,dmgMod,threat,crit,iteration,flurry,maxIt) {

 var out='';

 if(flurry) {
   // TODO: calculate flurry progression
 }

 if(mw) out+='mw ';
 if(wpnPlus>0) out+='+'+wpnPlus+' '+wpn; else out+=wpn;
 out+=' ';

 for(i=0;i<Math.min(baseAtk,maxIt*iteration)||i==0;i=i+iteration) {
  if(i>0) out+='/';
  if((atkB-i)>=0) out+='+';
  out+=(atkB-i);
 }

 if(melee) out+=' melee'; else out+=' ranged';
 out+=' ('+baseDmg;
 if(dmgMod>0) out+='+';
 if(dmgMod!=0) out+=dmgMod;
 if(threat!=20||crit!=2) {
  out+='/crit ';
  if(threat!=20) out+=threat+'-20';
  if(threat!=20&&crit!=2) out+='/';
  if(crit!=2) out+='x'+crit;
 }

 out+='), or ';
 return out;
}

function left(s,len) {
 var o='';
 var l=0;
 for(l=0;l<len;l++) o=o+s.charAt(l);
 return o;
}

function right(s,len) {
 var o='';
 var l=0;
 for(l=1;l<=len;l++) o=s.charAt(s.length-l)+o;
 return o;
}

// --------------------------------------------------------------------
// stat rolling functions
// --------------------------------------------------------------------

function rollStats(pointBuy)
{
 var x=document.sbGen;

 var l_aAttributes = new Array(6);
 var l_aCosts = new Array(20);
 var l_nPoints = pointBuy;
 var l_nAttribute = 0;

 /* Set the ability costs */
 l_aCosts[1] = 1;
 l_aCosts[2] = 1;
 l_aCosts[3] = 1;
 l_aCosts[4] = 1;
 l_aCosts[5] = 1;
 l_aCosts[6] = 1;
 l_aCosts[7] = 1;
 l_aCosts[8] = 1;
 l_aCosts[9] = 1;
 l_aCosts[10] = 1;
 l_aCosts[11] = 1;
 l_aCosts[12] = 1;
 l_aCosts[13] = 1;
 l_aCosts[14] = 1;
 l_aCosts[15] = 2;
 l_aCosts[16] = 2;
 l_aCosts[17] = 3;
 l_aCosts[18] = 3;
 l_aCosts[19] = 9999;

 /* Set all abilities to the lowest score of 8 */
 for( var i = 0; i < 6; i = i + 1 )
 {
  l_aAttributes[i] = 8;
 } 


 /* Spend the points */
 while( l_nPoints > 0 )
 {
  /* Randomly choose an attribute */
  l_nAttribute = Math.floor( Math.random() * 6 );
   
  /* Check if we have enough points to increase that attribute by 1 */
  if( l_aCosts[l_aAttributes[l_nAttribute]+1] <= l_nPoints )
  {
   /* Purchase the attribute */
   l_aAttributes[l_nAttribute] = l_aAttributes[l_nAttribute] + 1;
   l_nPoints = l_nPoints - l_aCosts[l_aAttributes[l_nAttribute]];
  }
 } 

 var Str=new Number(l_aAttributes[0]);
 var Dex=new Number(l_aAttributes[1]);
 var Con=new Number(l_aAttributes[2]);
 var Int=new Number(l_aAttributes[3]);
 var Wis=new Number(l_aAttributes[4]);
 var Cha=new Number(l_aAttributes[5]);

 var chrRace = x.chrRace.value.toLowerCase();

 if(chrRace=='gnome') Str=Str-2;
 if(chrRace=='piljanan') Str=Str+2;
 if(chrRace=='half-orc') Str=Str+2;
 if(chrRace=='kelshan') Str=Str+2;
 if(chrRace=='halfling') Str=Str-2;
 if(chrRace=='elf') Dex=Dex+2;
 if(chrRace=='halfling') Dex=Dex+2;
 if(chrRace=='dwarf') Con=Con+2;
 if(chrRace=='piljanan') Con=Con+2;
 if(chrRace=='kelshan') Con=Con+2;
 if(chrRace=='elf') Con=Con-2;
 if(chrRace=='gnome') Con=Con+2;
 if(chrRace=='half-orc') Int=Int-2;
 if(chrRace=='piljanan') Int=Int-2;
 if(chrRace=='dwarf') Cha=Cha-2;
 if(chrRace=='half-orc') Cha=Cha-2;
 if(chrRace=='piljanan') Cha=Cha-2;
 if(chrRace=='kelshan') Cha=Cha-4;

 x.Str.value=Str;
 x.Dex.value=Dex;
 x.Con.value=Con;
 x.Int.value=Int;
 x.Wis.value=Wis;
 x.Cha.value=Cha;

 display();
}

// --------------------------------------------------------------------
// Display related functions
// --------------------------------------------------------------------

/* adds an old style stat block "section" to element */
/* HTML: "<p><em>title</em>text</p>" */
function getOldResultsSectionDOM(element, title, text)
{
  var pElementB = document.createElement('p');
  var emElementB = document.createElement('em');
  var textElementB = document.createTextNode(title);
  emElementB.appendChild(textElementB);
  pElementB.appendChild(emElementB);
  var textElementB2 = document.createTextNode(text);
  pElementB.appendChild(textElementB2);
  element.appendChild(pElementB);
}

/* adds an old style stat block "section" to element */
/* HTML: "<p><em>title</em>: text</p>" */
function getOldResultsBlockDOM(element, title, text)
{
  var colontext = new String(': ');
  colontext = colontext + text;
  getOldResultsSectionDOM(element, title, colontext);
}

/* get old stat block format and add to element via DOM */
function getOldResultsDOM(element, character)
{
  //Start of old format

  // do core section

  // core stats
  var results = new String(character.chrGender);
  results=results+" ";
  results=results+character.chrRace+ " ";
  results=results+character.chrClasses;
  results=results+"; CR ";
  if(character.chrCR>0) {
   results=results+character.chrCR;
  } else {
   results=results+"1/2";
  }
  results=results+"; ";
  results=results+character.chrSize;
  results=results+" ";
  results=results+character.chrType;
  results=results+" [";
  results=results+character.chrSubType;
  results=results+"]; HD ";
  results=results+character.chrHD;
  results=results+"; hp ";
  results=results+character.chrHp;
  results=results+"; Mas ";
  results=results+character.chrMas;
  results=results+", Dying -";
  results=results+character.chrDying;
  results=results+", Dead -";
  results=results+character.Con;
  results=results+"; Init ";
  results=results+character.chrInit;
  results=results+"; Spd ";
  results=results+character.chrSpd;
  results=results+"; AC ";
  results=results+character.ACLong;
  results=results+"; BAB ";
  if (character.baseAtk>=0) results=results+"+";
  results=results+character.baseAtk;
  results=results+"; Grp ";
  if (character.baseGrapple>=0) results=results+"+";
  results=results+character.baseGrapple;
  if((character.atkM!='')||(character.atkR!='')) {
   results=results+"; Atk: ";
   if(character.atkM!='') {
    results=results+character.atkM;
   }
   if((character.atkM!='')&&(character.atkR!='')) results=results+", or ";
   if(character.atkR!='') {
    results=results+character.atkR;
   }
  }
  results=results+"; Space/Reach ";
  results=results+character.chrSpace;
  results=results+"/";
  results=results+character.chrReach;
  results=results+"; ";
  if(character.SA!='') results=results+"SA "+character.SA+"; ";
  if((character.SQ!='')||(character.atkR!='')) {
   results=results+"SQ ";
   if(character.chrSenses!='') {
    results=results+character.chrSenses;
   }
   if((character.SQ!='')&&(character.chrSenses!='')) results=results+", ";
   if(character.SQ!='') {
    results=results+character.SQ;
   }
   results=results+"; ";
  }
  results=results+"AL ";
  results=results+character.chrAllegiance;
  results=results+"; AP ";
  results=results+character.actPoints;
  results=results+"; Rep ";
  if (character.repPoints>=0) results=results+"+";
  results=results+character.repPoints;
  results=results+"; SV Fort ";
  if (character.fort>=0) results=results+"+";
  results=results+character.fort;
  results=results+", Ref ";
  if (character.ref>=0) results=results+"+";
  results=results+character.ref;
  results=results+", Will ";
  if (character.will>=0) results=results+"+";
  results=results+character.will;
  results=results+"; ";
 
  results=results+"Str "+character.Str+", ";
  results=results+"Dex "+character.Dex+", ";
  results=results+"Con "+character.Con+", ";
  results=results+"Int "+character.Int+", ";
  results=results+"Wis "+character.Wis+", ";
  results=results+"Cha "+character.Cha+".";
  
  // character name is the title section
  getOldResultsBlockDOM(element, character.chrName, results);

  // do skills and feats sections
  results = new String(character.skills);
  results += "; "+character.feats+".";
  getOldResultsBlockDOM(element, 'Skills & Feats', results);
  
  if(character.talents!='') {
   getOldResultsBlockDOM(element, 'Talents', character.talents);
  }

  getOldResultsBlockDOM(element, 'Possessions', character.items);

  if(character.languages!='') {
   getOldResultsBlockDOM(element, 'Languages', character.languages);
  }
  
  if(character.notes!='') {
   getOldResultsBlockDOM(element, 'Notes', character.notes);
  }

  //end of regular format
}

/* add a non-breaking space "&nbsp;" to element */
function addNonBreakingSpace(element)
{
  /* from http://www.jdstiles.com/dhtml/dhtmlfaqs.html */
  var nbsp = document.createTextNode( "\u00A0" );		
  element.appendChild( nbsp );
}

/* add a "<br>" to element */
function addBR(element)
{
  var newElement = document.createElement('br');
  element.appendChild(newElement);
}

/* add a "<strong>text</strong>" to element */
function addStrongText(element, text)
{
  var sElement = document.createElement('strong');
  var textElement = document.createTextNode(text);
  sElement.appendChild(textElement);
  element.appendChild(sElement);
}

/* add text to element */
function addText(element, text)
{
  var textElement = document.createTextNode(text);
  element.appendChild(textElement);
}

/* add "<strong>title</strong>text" to element */
function addHeaderedText(element, title, text)
{
   addStrongText(element, title);
   addText(element, text);
}

/* add "<strong>title</strong>text<br>" to element */
function addStrongLine(element, title, text)
{
   var results = new String(' ');
   results+=text;
   addHeaderedText(element, title, results);
   addBR(element);
}

/* get new stat block format and add to element via DOM */
function getNewResultsDOM(element, character)
{
  // character name and CR
  var pElementA = document.createElement('p');
  element.appendChild(pElementA);

  var sElementA = document.createElement('strong');
  var emElementA = document.createElement('em');

  addText(emElementA, character.chrName);

  addNonBreakingSpace(emElementA);
  addNonBreakingSpace(emElementA);
  addNonBreakingSpace(emElementA);

  var results = new String('CR ');

  if(character.chrCR>0) {
   results += character.chrCR;
  } else {
   results += "1/2";
  }

  addText(emElementA, results);

  sElementA.appendChild(emElementA);
  pElementA.appendChild(sElementA);

  addBR(pElementA);

  // gender and class
  var results = new String('');
  results=results+character.chrGender+" ";
  results=results+character.chrRace+ " ";
  results=results+character.chrClasses;
  addText(pElementA, results);

  addBR(pElementA);

  // size, type, and subtype
  var results = new String('');
  results=results+character.chrSize;
  results=results+" ";
  results=results+character.chrType;
  results=results+" [";
  results=results+character.chrSubType;
  results=results+"]";
  addText(pElementA, results);

  addBR(pElementA);

  // init and senses
  addStrongText(pElementA, 'Init');

  var results = new String(' ');
  results=results+character.chrInit;
  addText(pElementA, results);

  if (character.chrSenses!='') {
   addText(pElementA, '; ');
   addStrongText(pElementA, 'Senses');
   var results = new String(' ');
   results=results+character.chrSenses;
   //results=results+"; Listen +xx, Spot +xx";
   addText(pElementA, results);
  }

  addBR(pElementA);

  // langauges
  if (character.languages!='') {
   addStrongLine(pElementA, 'Languages', character.languages);
  }

  // allegiances
  addStrongLine(pElementA, 'Allegiances', character.chrAllegiance);

  // action points and reputation
  addStrongText(pElementA, 'Action Points');
  var results = new String(' ');
  results += character.actPoints;
  results += '; ';
  addText(pElementA, results);

  addStrongText(pElementA, 'Reputation');
  var results = new String(' ');
  if (character.repPoints>=0) results=results+"+";
  results=results+character.repPoints;
  addText(pElementA, results);

  // AC
  var pElementB = document.createElement('p');
  element.appendChild(pElementB);
  //results=results+"; (list defenses here, if any)";
  addStrongLine(pElementB, 'AC', character.ACLong);

  var results = new String(' ');
  results=results+character.chrHp;
  results=results+" (";
  results=results+character.chrHD;
  results=results+" HD); ";
  addHeaderedText(pElementB, 'hp', results);

  var results = new String(' ');
  results=results+character.chrMas;
  results=results+", ";
  addHeaderedText(pElementB, 'Mas', results);

  var results = new String(' -');
  results=results+character.chrDying;
  results=results+", ";
  addHeaderedText(pElementB, 'Dying', results);

  var results = new String(' -');
  results=results+character.Con;
  addHeaderedText(pElementB, 'Dead', results);

  //results=results+"; fast healing (if any)";
  //results=results+"; <strong>DR</strong> xx/xx (if any)";
  addBR(pElementB);

  //results=results+"<strong>Immune</strong> (list here, if any)<br>";
  //results=results+"<strong>Resist</strong> (list here, if any); <strong>SR</strong> xx (if any)<br>";

  var results = new String(' ');
  if (character.fort>=0) results=results+"+";
  results=results+character.fort;
  results=results+", ";
  addHeaderedText(pElementB, 'Fort', results);

  var results = new String(' ');
  if (character.ref>=0) results=results+"+";
  results=results+character.ref;
  results=results+", ";
  addHeaderedText(pElementB, 'Ref', results);

  var results = new String(' ');
  if (character.will>=0) results=results+"+";
  results=results+character.will;
  addHeaderedText(pElementB, 'Will', results);
  addBR(pElementB);

  //results=results+"<strong>Weakness</strong> (list here, if any)";

  var pElementC = document.createElement('p');
  element.appendChild(pElementC);

  addStrongLine(pElementC, 'Speed', character.chrSpd);

  if((character.atkM!='')||(character.atkR!='')) {
   addStrongLine(pElementC, 'Attacks', '');

   if(character.atkM!='') {
    var results = new String('Melee ');
    results=results+character.atkM;
    addText(pElementC, results);
    addBR(pElementC);
   }
   if(character.atkR!='') {
    var results = new String('Ranged ');
    results=results+character.atkR;
    addText(pElementC, results);
    addBR(pElementC);
   }
  }

  var results = new String(' ');
  results=results+character.chrSpace;
  results=results+"; "
  addHeaderedText(pElementC, 'Space', results);

  var results = new String(' ');
  results=results+character.chrReach;
  addHeaderedText(pElementC, 'Reach', results);

  addBR(pElementC);

  var results = new String(' ');
  if (character.baseAtk>=0) results=results+"+";
  results=results+character.baseAtk;
  results=results+"; ";
  addHeaderedText(pElementC, 'BAB', results);

  var results = new String(' ');
  if (character.baseGrapple>=0) results=results+"+";
  results=results+character.baseGrapple;
  addHeaderedText(pElementC, 'Grp', results);

  addBR(pElementC);

  if(character.SA!='') addStrongLine(pElementC, 'Special Atks', character.SA);
  addStrongLine(pElementC, 'Combat Gear', character.items);

  var pElementD = document.createElement('p');
  element.appendChild(pElementD);

  var results = new String('');
  results=results+"Str "+character.Str+", ";
  results=results+"Dex "+character.Dex+", ";
  results=results+"Con "+character.Con+", ";
  results=results+"Int "+character.Int+", ";
  results=results+"Wis "+character.Wis+", ";
  results=results+"Cha "+character.Cha;
  addStrongLine(pElementD, 'Abilities', results);

  if(character.SQ!='') addStrongLine(pElementD, 'Special Qualities', character.SQ);

  addStrongLine(pElementD, 'Feats', character.feats);
  addStrongLine(pElementD, 'Skills', character.skills);
  if(character.talents!='') {
   addStrongLine(pElementD, 'Talents', character.talents);
  }
  
  if(character.notes!='') {
   var pElementE = document.createElement('p');
   element.appendChild(pElementE);
   addStrongLine(pElementE, 'Notes', character.notes);
  }

  //End of new stat block format

  return results;
}

/* add the stat block to the DOM node element */
function getResultsDOM(element)
{
	/* fill characterInput from form data */
	var myCharacterInput = createCharacterInputFromForm(document.sbGen);

	/* now compute characterData from characterInput */
	myCharacterData = new characterData();
	myCharacterData.createCharacterData(myCharacterInput);

	if (myCharacterData.format)
	{
		getNewResultsDOM(element, myCharacterData);
	}
	else
	{
		getOldResultsDOM(element, myCharacterData);
	}
}

/* Display the character block using DOM */
function display()
{
	// create a DIV element, using the variable eDIV as a reference to it
	var eDIV = document.createElement("div");

	//use the setAttribute method to assign it an id
	eDIV.setAttribute("id","CharacterBlock");

	getResultsDOM(eDIV);

	// append your newly created DIV element to an already existing element.
	var parentElement = document.getElementById("results");

	// remove current block
	var blockNode = document.getElementById("CharacterBlock");

	// if the node does not exist, add it
	if (blockNode == null)
	{
		parentElement.appendChild(eDIV);
	}
	// otherwise replace the existing node
	else
	{
		parentElement.replaceChild(eDIV, blockNode);
	}
}

// --------------------------------------------------------------------
// Persistance functions
// --------------------------------------------------------------------

/* set variables on object from encoded URL */
function setVarsFromURL(url, object)
{
	var urlArray = url.split('?');

	/* stop if no variables */
	if (urlArray.length <= 1)
	{
		return false;
	}
   
	var varArray = urlArray[1].split('&');

	for(var x=0; x<varArray.length; x++)
	{
		if (varArray[x] != '')
		{
			var tmp = varArray[x].split('=');
			var tmp0 = unescape(tmp[0]);
			var tmp1 = unescape(tmp[1]);

			object[tmp0] = tmp1;
		}
	}

	return true;
}

/* get encoded URL that includes variable names and values for decoding */
function getVarsURL(url, object)
{
	var varStr = new String(url + '?');

	for(x in object)
	{
		varStr += escape(x) + '=' + escape(object[x]);
		// TODO only on non-last
		varStr += '&';
	}

	return varStr;
}

/* change URL to encode our variables */
function link()
{
	/* fill characterInput from form data */
	var myCharacterInput = createCharacterInputFromForm(document.sbGen);

	/* get URL without arguments */
	var linkloc = document.location.href.split('?')[0];

	/* create URL with arguments */
	var url = getVarsURL(linkloc, myCharacterInput);

	document.location = url;
}

/* get our settings for the stat block from the URL */
function getStatBlockVars()
{
	var url = document.location.href;

  var myCharacterInput = new characterInput();

	var bSet = setVarsFromURL(url, myCharacterInput);

	if (bSet)
	{
  	// copy myCharacterInput to form document.sbGen
		fillFormFromCharacterInput(myCharacterInput, document.sbGen);
	}
}
