var sl = {

// *********************
// INITIALIZING FUNCTION
// *********************

initialize:function () {
if(document.getElementById("spellLibApp")) {
	// Remove noscript restriction from application
	document.getElementById("spellLibApp").className = "nouser";
	sl.user_id = 0;
	// Set functions to all items
	sl.setUserMenuFunctions();
	sl.setTabFunctions();
	sl.setFilterFunctions();
	sl.setMetamagicFunctions();
	sl.setCharMenuFunctions();
	sl.setCharFormFunctions();
	// Prepare login forms
	sl.disableLoginForms(false);
	// Prepare character bar & form
	sl.disableCharLoad(true);
	sl.disableCharMenu(true);
	sl.disableCharForm(true);
	// Load the spell library file
	sl.loadSpellDoc();
}
},

loadSpellDoc:function () {
	var xmlDoc;
	if(window.XMLHttpRequest) {
		xmlDoc = new XMLHttpRequest();
	} else {
		alert("I'm sorry, but your browser does not support AJAX!");
		return;
	}
	xmlDoc.open("GET",sl.path + "SpellLibrary3_5.xml",true);
	xmlDoc.onreadystatechange = function () {
		if(xmlDoc.readyState == 4) {
			sl.spellXmlDoc = xmlDoc.responseXML;
			sl.fillSpellLibrary();
		}
	};
	xmlDoc.send(null);
},


// *****************************************
// GLOBAL VARIABLES, REFERENCES, & UTILITIES
// *****************************************

path:"",
spellXmlDoc:null,
user_id:0,
character:{
	id:0,
	profile:{
		name:"",
		abilities:{ Str:null,Dex:null,Con:null,Int:null,Wis:null,Cha:null },
		levels:{},
		feats:[],
		description:""
	},
	known:{},
	slots:{}
},

libMode:"browse",
libCls:"All",
libOptions:false,
detailMode:"concise",

playerClasses:["Brd","Clr","Drd","Pal","Rgr","Sor","Wiz"],
expandClsName:{ "Brd":"Bard","Clr":"Cleric","Drd":"Druid","Pal":"Paladin","Rgr":"Ranger","Sor":"Sorcerer","Wiz":"Wizard" },
wizSchools:["Abjuration","Conjuration","Divination","Enchantment","Evocation","Illusion","Necromancy","Transmutation"],
scnToSc:{ "Abjuration":0,"Conjuration":1,"Divination":2,"Enchantment":3,"Evocation":4,"Illusion":5,"Necromancy":6,"Transmutation":7,"Universal":8 },
scToScn:["Abjur","Conj","Div","Ench","Evoc","Illus","Necro","Trans","Univ"],
clrDomains:["Air","Animal","Chaos","Death","Destruction","Earth","Evil","Fire","Good","Healing","Knowledge","Law","Luck","Magic","Plant","Protection","Strength","Sun","Travel","Trickery","War","Water"],
featNames:["Empower Spell","Enlarge Spell","Extend Spell","Heighten Spell","Maximize Spell","Quicken Spell","Silent Spell","Still Spell","Widen Spell","Spell Focus","Greater Spell Focus"],

addEvent:function (obj,ev,fn) {
	if(obj.addEventListener) {
		obj.addEventListener(ev,fn,false);
	} else if(obj.attachEvent) {
		obj.attachEvent("on"+ev,fn);
	} else {
		var oldFn = obj["on"+ev];
		if(typeof(oldFn) == "function") {
			obj["on"+ev] = function () {
				oldFn();
				fn();
			};
		} else {
			obj["on"+ev] = fn;
		}
	}
},

convertLink:function (link,fn) {
	link.onclick = function (e) {
		fn();
		link.blur();
		return false;
	};
},

createSimpleElement:function (type,text) {
	var node = document.createElement(type);
	var child = document.createTextNode(text);
	node.appendChild(child);
	return node;
},

getDirectChildren:function (parent,tag) {
	var output = [];
	var children = parent.childNodes;
	for(var i=0;i<children.length;++i) {
		if(children[i].nodeName.toLowerCase() == tag.toLowerCase()) { output.push(children[i]); }
	}
	return output;
},

emptyElement:function (element) {
	while(element.childNodes.length > 0) {
		element.removeChild(element.childNodes[0]);
	}
},

numbersOnly:function (field,e) {
	if(!e) { e = window.event; }
	var key = (e.keyCode) ? e.keyCode : e.which;
	var keyValue = String.fromCharCode(key);
	var regExp = /[0-9]/;
	// Pass control keys (37-40 are arrow keys)
	if (key == null || key == 0 || key == 8 || key == 9 || key == 13 || key == 27 || key == 37 || key == 38 || key == 39 || key == 40) { return true; }
	// numbers
	else { return regExp.test(keyValue); }
},

isKnown:function (cls,name) {
	var found = false;
	if(cls in sl.character.known) {
		for(var n=0;n<sl.character.known[cls].length;++n) {
			if(sl.character.known[cls][n] == name) {
				found = true;
				break;
			}
		}
	}
	return found;
},

isCastable:function (cls,lv,sc,name) {
	var abil = 0;
	if(cls == "Wiz") {
		abil = sl.character.profile.abilities["Int"];
	} else if(cls == "Brd" || cls == "Sor") {
		abil = sl.character.profile.abilities["Cha"];
	} else {
		abil = sl.character.profile.abilities["Wis"];
	}
	if(sl.character.slots[cls] && sl.character.slots[cls].length > lv && abil >= (parseInt(lv) + 10)) {
		if(cls == "Wiz" || cls == "Sor" || cls == "Brd") {
			if(cls == "Wiz" && sl.character.profile.specialization != "None" && (sl.character.profile.prohibited[0] == sc || sl.character.profile.prohibited[1] == sc)) {
				return false;
			} else {
				return sl.isKnown(cls,name);
			}
		} else {
			return true;
		}
	} else {
		return false;
	}
},


// *****************
// STARTUP FUNCTIONS
// *****************

setUserMenuFunctions:function () {
	// Set functions for login & register forms
	document.forms["loginForm"].onsubmit = sl.login;
	document.forms["registerForm"].onsubmit = sl.register;
	// Utility functions for login & register menus
	var clickedOutside = function (id,e) {
		if(!e) { e = window.event; }
		var element = (e.target) ? e.target : e.srcElement;
		if(element != null) {
			if(element.nodeType == 3) { element = element.parentNode; }
		}
		while(element && element.id != "spellLibraryApp") {
			if(element.id == id) {
				return false;
			}
			element = element.parentNode;
		}
		return true;
	};
	// Set functions to menu links
	var loginLink = document.getElementById("menuLogin").getElementsByTagName("a")[0];
	sl.convertLink(loginLink,function (e) {
		document.getElementById("spellUserControls").className = "login";
		document.getElementById("spellLibApp").onmousedown = function (e) {
			if(clickedOutside("login",e) && clickedOutside("menuLogin",e)) {
				document.getElementById("spellUserControls").className = "";
				document.getElementById("spellLibApp").onmousedown = null;
			}
		};
	});
	var registerLink = document.getElementById("menuRegister").getElementsByTagName("a")[0];
	sl.convertLink(registerLink,function (e) {
		document.getElementById("spellUserControls").className = "register";
		document.getElementById("spellLibApp").onmousedown = function (e) {
			if(clickedOutside("register",e) && clickedOutside("menuRegister",e)) {
				document.getElementById("spellUserControls").className = "";
				document.getElementById("spellLibApp").onmousedown = null;
			}
		};
	});
	var logoutLink = document.getElementById("menuLogout").getElementsByTagName("a")[0];
	sl.convertLink(logoutLink,sl.logout);
},

setTabFunctions:function () {
	var links;
	// Set libMode tab links
	links = document.getElementById("libModeTabs").getElementsByTagName("a");
	for(var n=0;n<links.length;++n) {
		var mode = links[n].href.match(/#([A-Za-z]+)$/)[1];
		var scopePreserver = function (a) {
			return function () { sl.showLibMode(a,null); return false; };
		};
		sl.convertLink(links[n],scopePreserver(mode));
	}
	// Set libCls tab links
	links = document.getElementById("libClsTabs").getElementsByTagName("a");
	for(var n=0;n<links.length;++n) {
		var cls = links[n].href.match(/#([A-Za-z]{3})$/)[1];
		var scopePreserver = function (a) {
			return function () { sl.showLibCls(a); return false; };
		};
		sl.convertLink(links[n],scopePreserver(cls));
	}
},

setFilterFunctions:function () {
	var links;
	var id;
	var scopePreserver = function (a) {
		return function () { sl.chkBox(a); };
	};
	// Set functions for verbose option
	links = document.getElementById("verboseOption").getElementsByTagName("a");
	id = links[0].href.match(/#([A-Za-z]+)$/)[1];
	sl.convertLink(links[0],scopePreserver(id));
	// Set functions for level & school options
	links = document.getElementById("levelOption").getElementsByTagName("a");
	for(var n=0;n<links.length;++n) {
		id = "fltLv" + links[n].href.match(/#([0-9])$/)[1];
		sl.convertLink(links[n],scopePreserver(id));
	}
	links = document.getElementById("schoolOption").getElementsByTagName("a");
	for(var n=0;n<links.length;++n) {
		id = "fltSc" + links[n].href.match(/#([0-9])$/)[1];
		sl.convertLink(links[n],scopePreserver(id));
	}
	// Set functions for component options
	var boxes;
	var scopePreserver = function (a,b,c) {
		return function () { sl.chkComp(a,b,c); };
	};
	boxes = document.getElementById("reqComponentsOption").getElementsByTagName("input");
	links = document.getElementById("reqComponentsOption").getElementsByTagName("a");
	for(var n=0;n<links.length;++n) {
		id = links[n].href.match(/#([A-Za-z]{1,2})$/)[1];
		boxes[n].onclick = scopePreserver(id,true,true);
		sl.convertLink(links[n],scopePreserver(id,true,false));
	}
	boxes = document.getElementById("proComponentsOption").getElementsByTagName("input");
	links = document.getElementById("proComponentsOption").getElementsByTagName("a");
	for(var n=0;n<links.length;++n) {
		id = links[n].href.match(/#([A-Za-z]{1,2})$/)[1];
		boxes[n].onclick = scopePreserver(id,false,true);
		sl.convertLink(links[n],scopePreserver(id,false,false));
	}
	// Set functions for reset buttons
	document.getElementById("fltLvAll").onclick = function () { sl.chkAllLvs(false); };
	document.getElementById("fltScAll").onclick = function () { sl.chkAllScs(false); };
	document.getElementById("fltCompsAll").onclick = sl.chkAllComps;
	document.getElementById("fltClearSearch").onclick = sl.clearSearch;
	document.getElementById("fltReset").onclick = sl.resetFilter;
	// Set form submit function for toggling library options panel
	document.forms["filterForm"].onsubmit = function () {
		sl.libOptions = !sl.libOptions;
		if(!sl.libOptions) { sl.fillSpellLibrary(); }
		document.getElementById("optionsMode").value = (sl.libOptions) ? "Hide Options" : "Show Options";
		document.getElementById("spellLibrary").className = (sl.libOptions) ? "options" : sl.libMode;
		return false;
	};
},

setMetamagicFunctions:function () {
	var links = document.getElementById("applyMetamagic").getElementsByTagName("a");
	for(var n=0;n<links.length;++n) {
		var id = links[n].href.match(/#([A-Za-z]+)$/)[1];
		var scopePreserver = function (a) {
			return function () { document.getElementById(a).checked = !document.getElementById(a).checked; };
		};
		sl.convertLink(links[n],scopePreserver(id));
	}
},

setCharMenuFunctions:function () {
	// Set form submission function
	document.forms["controlForm"].onsubmit = function () { return sl.loadchar(false); };
	// Set button functions
	document.forms["controlForm"].elements["save"].onclick = function () { sl.savechar(false); };
	document.forms["controlForm"].elements["edit"].onclick = function () { sl.editchar(true); };
	document.forms["controlForm"].elements["delete"].onclick = sl.deletechar;
	document.forms["controlForm"].elements["new"].onclick = function () { sl.newchar(false,true); };
	// Set functions for resetting spell slots & showing spell controls
	document.getElementById("resetSlots").onclick = function () {
		if(confirm("Are you sure you want to reset all spell slots?")) {
			sl.character.slots = sl.calcSlots(sl.character.profile);
			sl.fillSpellSlots();
		}
	};
	sl.convertLink(document.getElementById("expandSpellControls"),function () {
		if(sl.detailMode == "concise") {
			sl.detailMode = "verbose";
			document.getElementById("expandSpellControls").childNodes[0].nodeValue = "\u25b2";
			document.getElementById("expandSpellControls").title = "Collapse Spell Options";
		} else {
			sl.detailMode = "concise";
			document.getElementById("expandSpellControls").childNodes[0].nodeValue = "\u25bc";
			document.getElementById("expandSpellControls").title = "Expand Spell Options";
		}
		document.getElementById("spellControls").className = sl.detailMode;
	});
},

setCharFormFunctions:function () {
	// Set form submission function
	document.forms["charForm"].onsubmit = function () { return sl.savechar(true); }
	// Set ability fields to numbers only
	var abils = ["Str","Dex","Con","Int","Wis","Cha"];
	var scopePreserver = function (a) { return function (e) { return sl.numbersOnly(a,e); }; };
	for(var i=0;i<=5;++i) {
		var field = document.forms["charForm"].elements["char"+abils[i]];
		field.onkeypress = scopePreserver(field);
	}
	// Set button functions
	document.forms["charForm"].elements["addCls"].onclick = function () { sl.addCls(false); };
	document.forms["charForm"].elements["addFeat"].onclick = function () { sl.addFeat(false,false); };
	document.forms["charForm"].elements["cancel"].onclick = function () { sl.editchar(false); };
	// Renumber form class entries to update included functions
	sl.renumberCls(0);
	// Register initial class entry value
	sl.selectCls(0,null,document.forms["charForm"].elements["charCls0"].value,false)
},


// ************************
// TAB NAVIGATION FUNCTIONS
// ************************

showLibMode:function (mode,cls) {
	document.getElementById("spellLibrary").className = mode;
	sl.libMode = mode;
	if(cls) {
		sl.showLibCls(cls);
	} else if(mode == "repertoire" && !sl.character.profile.levels[sl.libCls]) {
		for(var i in sl.character.profile.levels) {
			sl.showLibCls(i);
			break;
		}
	} else {
		sl.fillSpellLibrary();
	}
},

showLibCls:function (cls) {
	if(sl.libCls in sl.character.profile.levels) {
		document.getElementById(sl.libCls + "LibTab").className = "";
		document.getElementById(sl.libCls + "Slots").className = "";
	} else {
		document.getElementById(sl.libCls + "LibTab").className = "nocast";
	}
	document.getElementById(cls + "LibTab").className = "active";
	if(cls in sl.character.profile.levels) {
		document.getElementById(cls + "Slots").className = "active";
	}
	sl.libCls = cls;
	sl.fillSpellLibrary();
},

setTabsByChar:function () {
	// Establish whether character has given classes, and apply accordingly to tabs
	document.getElementById("AllLibTab").className = "nocast";
	var hasCls = false;
	for(var i=0;i<sl.playerClasses.length;++i) {
		var cls = sl.playerClasses[i];
		if(cls in sl.character.profile.levels) {
			document.getElementById(cls + "LibTab").className = "";
			hasCls = true;
		} else {
			document.getElementById(cls + "LibTab").className = "nocast";
		}
		document.getElementById(cls + "Slots").className = "";
	}
	// Initialize proper tabs
	if(!hasCls) {
		sl.libMode = "browse";
		document.getElementById("spellLibrary").className = "browse";
		sl.showLibCls(sl.libCls);
	} else {
		sl.showLibMode("repertoire");
		for(var cls in sl.character.profile.levels) {
			sl.showLibCls(cls);
			break;
		}
	}
},


// *************************
// USER MANAGEMENT FUNCTIONS
// *************************

disableLoginForms:function (disable) {
	for(var n=0;n<document.forms["loginForm"].getElementsByTagName("input").length;++n) {
		document.forms["loginForm"].getElementsByTagName("input")[n].disabled = disable;
	}
	for(var n=0;n<document.forms["registerForm"].getElementsByTagName("input").length;++n) {
		document.forms["registerForm"].getElementsByTagName("input")[n].disabled = disable;
	}
},

login:function () {
	// Get form information
	var uname = document.forms["loginForm"].elements["uname"].value;
	var pswd = document.forms["loginForm"].elements["pswd"].value;
	document.forms["loginForm"].elements["pswd"].value = "";
	sl.disableLoginForms(true);
	// Pass information as params to XMLHttpRequest
	var params = "uname=" + encodeURI(uname) + "&pswd=" + encodeURI(pswd) + "&sid=" + Math.random();
	var xmlhttp;
	if (window.XMLHttpRequest) {
		xmlhttp = new XMLHttpRequest();
	} else {
		alert("I'm sorry, but your browser does not support AJAX!");
		return false;
	}
	xmlhttp.open("POST",sl.path + "login.php",true);
	xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded");
	xmlhttp.setRequestHeader("Content-length",params.length);
	xmlhttp.setRequestHeader("Connection","close");
	xmlhttp.onreadystatechange = function () {
		if(xmlhttp.readyState == 4) {
			if(xmlhttp.responseText == "") {
				alert("Invalid username or password");
				sl.disableLoginForms(false);
			} else {
				// Set user_id
				document.getElementById("username").childNodes[0].nodeValue = uname;
				xmlhttp = JSON.parse(xmlhttp.responseText);
				sl.user_id = xmlhttp[0];
				// Check for existing character
				var char_id = parseInt(xmlhttp[1]);
				if(char_id != 0) {
					// If characters exist, fill character selection
					var select = document.forms["controlForm"].elements["charList"];
					sl.emptyElement(select);
					for(var i=2;i<xmlhttp.length;i+=2) {
						var option = sl.createSimpleElement("option",xmlhttp[i+1]);
						option.value = xmlhttp[i];
						if(option.value == char_id) { option.selected = true; }
						select.appendChild(option);
					}
					// Load most recent character
					sl.loadchar(char_id);
				} else {
					// Display new character creation if no saved characters exist
					sl.newchar(true,true);
				}
				sl.disableCharLoad(false);
				document.getElementById("spellUserControls").className = "";
				document.getElementById("spellLibApp").onmousedown = null;
				document.getElementById("spellLibApp").className = "user";
			}
		}
	};
	xmlhttp.send(params);
	return false;
},

register:function () {
	// Validate form & get entry values
	var valid = true;
	var valid_uname = /^\w{5,20}$/;
	var uname = document.forms["registerForm"].elements["uname"].value;
	if(!valid_uname.test(uname)) {
		if(uname.length < 5 || uname.length > 20) {
			document.forms["registerForm"].elements["uname"].parentNode.getElementsByTagName("span")[0].innerHTML = "Username must be 5-20 characters";
		} else {
			document.forms["registerForm"].elements["uname"].parentNode.getElementsByTagName("span")[0].innerHTML = "Username contains illegal characters";
		}
		valid = false;
	} else {
		document.forms["registerForm"].elements["uname"].parentNode.getElementsByTagName("span")[0].innerHTML = "";
	}
	var valid_pass = /^\S{5,20}$/;
	var pswd = document.forms["registerForm"].elements["pswd1"].value
	if(!valid_pass.test(pswd)) {
		if(pswd.length < 5 || pswd.length > 20) {
			document.forms["registerForm"].elements["pswd1"].parentNode.getElementsByTagName("span")[0].innerHTML = "Password must be 5-20 characters";
		} else {
			document.forms["registerForm"].elements["pswd1"].parentNode.getElementsByTagName("span")[0].innerHTML = "Password contains illegal characters";
		}
		valid = false;
	} else {
		document.forms["registerForm"].elements["pswd1"].parentNode.getElementsByTagName("span")[0].innerHTML = "";
	}
	if(pswd != document.forms["registerForm"].elements["pswd2"].value) {
		document.forms["registerForm"].elements["pswd2"].parentNode.getElementsByTagName("span")[0].innerHTML = "Password entries do not match!";
		valid = false;
	} else {
		document.forms["registerForm"].elements["pswd2"].parentNode.getElementsByTagName("span")[0].innerHTML = "";
	}
	var valid_email = new RegExp("^[\\w\\`\\~\\!\\#\\$\\%\\^\\&\\*\\-\\=\\+\\{\\}\\|\\'\\/\\?]+(\\.[\\w\\`\\~\\!\\#\\$\\%\\^\\&\\*\\-\\=\\+\\{\\}\\|\\'\\/\\?]+)*\\@\\w+(\\.\\w+)+$");
	var email = document.forms["registerForm"].elements["email"].value;
	if(!valid_email.test(email)) {
		document.forms["registerForm"].elements["email"].parentNode.getElementsByTagName("span")[0].innerHTML = "Invalid email address";
		valid = false;
	} else {
		document.forms["registerForm"].elements["email"].parentNode.getElementsByTagName("span")[0].innerHTML = "";
	}
	document.forms["registerForm"].elements["pswd1"].value = "";
	document.forms["registerForm"].elements["pswd2"].value = "";
	if(!valid) { return false; }
	sl.disableLoginForms(true);
	// If valid, pass information as params to XMLHttpRequest
	var params = "uname=" + encodeURI(uname) + "&email=" + encodeURI(email) + "&pswd=" + encodeURI(pswd) + "&sid=" + Math.random();
	var xmlhttp;
	if (window.XMLHttpRequest) {
		xmlhttp = new XMLHttpRequest();
	} else {
		alert("I'm sorry, but your browser does not support AJAX!");
		return false;
	}
	xmlhttp.open("POST",sl.path + "register.php",true);
	xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded");
	xmlhttp.setRequestHeader("Content-length",params.length);
	xmlhttp.setRequestHeader("Connection","close");
	xmlhttp.onreadystatechange = function () {
		if(xmlhttp.readyState == 4) {
			if(xmlhttp.responseText == "") {
				document.forms["registerForm"].elements["uname"].parentNode.getElementsByTagName("span")[0].innerHTML = "Username exists, please try again";
				sl.disableLoginForms(false);
			} else {
				// Set user_id
				document.getElementById("username").childNodes[0].nodeValue = uname;
				sl.user_id = parseInt(xmlhttp.responseText);
				sl.emptyElement(document.forms["controlForm"].elements["charList"]);
				sl.disableCharLoad(false);
				document.getElementById("spellUserControls").className = "";
				document.getElementById("spellLibApp").onmousedown = null;
				document.getElementById("spellLibApp").className = "user";
			}
		}
	};
	xmlhttp.send(params);
	return false;
},

logout:function () {
	if(confirm("Are you sure you want to sign off? Unsaved changes will be lost.")) {
		sl.user_id = 0;
		sl.emptyElement(document.forms["controlForm"].elements["charList"]);
		sl.disableCharLoad(true);
		sl.disableLoginForms(false);
		sl.newchar(true,false);
		sl.disableCharForm(true);
		document.getElementById("spellLibApp").className = "nouser";
		document.getElementById("username").childNodes[0].nodeValue = "(username)";
	}
	return false;
},


// ******************************
// CHARACTER MANAGEMENT FUNCTIONS
// ******************************

disableCharLoad:function (disable) {
	document.forms["controlForm"].elements["new"].disabled = disable;
	document.forms["controlForm"].elements["charList"].disabled = disable;
	document.forms["controlForm"].elements["load"].disabled = disable;
},

disableCharMenu:function (disable) {
	document.forms["controlForm"].elements["save"].disabled = disable;
	document.forms["controlForm"].elements["edit"].disabled = disable;
	document.forms["controlForm"].elements["delete"].disabled = disable;
	document.getElementById("resetSlots").disabled = disable;
},

disableCharForm:function (disable) {
	for(var n=0;n<document.forms["charForm"].elements.length;++n) {
		document.forms["charForm"].elements[n].disabled = disable;
	}
},

newchar:function (force,edit) {
	if(force || confirm("Unsaved changes to your character will be lost. Continue?")) {
		sl.character = {
			id:0,
			profile:{
				name:"",
				abilities:{ Str:null,Dex:null,Con:null,Int:null,Wis:null,Cha:null },
				levels:{},
				feats:[],
				description:""
			},
			known:{},
			slots:{}
		};
		sl.setTabsByChar();
		sl.clearSpellDetail();
		document.getElementById("charName").childNodes[0].nodeValue = "No Active Character";
		sl.disableCharMenu(true);
		sl.editchar(edit);
	}
},

loadchar:function (id) {
	// Confirm whether to load a new character
	var r = (sl.character.id != 0) ? confirm("Unsaved changes to the current character will be lost. Continue?") : true;
	if(!r) { return false; }
	// Obtain & check char_id
	var char_id = (id) ? id : document.forms["controlForm"].elements["charList"].value;
	char_id = parseInt(char_id);
	if(!char_id || char_id < 1) { alert("Error: cannot load character."); return false; }
	// Send request to server for character data associated with IDs
	var params = "user_id=" + sl.user_id + "&char_id=" + char_id + "&sid=" + Math.random();
	var xmlhttp;
	if (window.XMLHttpRequest) {
		xmlhttp = new XMLHttpRequest();
	} else {
		alert("I'm sorry, but your browser does not support AJAX!");
	}
	xmlhttp.open("POST",sl.path + "loadchar.php",true);
	xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded");
	xmlhttp.setRequestHeader("Content-length",params.length);
	xmlhttp.setRequestHeader("Connection","close");
	xmlhttp.onreadystatechange = function () {
		if(xmlhttp.readyState == 4) {
			if(xmlhttp.responseText == "") {
				alert("Error loading character");
			} else {
				xmlhttp = JSON.parse(xmlhttp.responseText);
				// Set character data
				sl.character.id = char_id;
				sl.character.profile = xmlhttp[0],
				sl.character.known = xmlhttp[1],
				sl.character.slots = xmlhttp[2]
				document.getElementById("charName").childNodes[0].nodeValue = sl.character.profile.name;
				// Build spell slots & initialize proper tabs
				sl.buildSpellSlots();
				sl.setTabsByChar();
				sl.clearSpellDetail();
				// Set character data to page
				sl.disableCharMenu(false);
				sl.editchar(false);
			}
		}
	};
	xmlhttp.send(params);
	return false;
},

savechar:function (saveForm) {
	// Build character profile from charForm
	var profile = {};
	var known = {};
	var slots = {};
	if(saveForm) {
		profile.name = document.forms["charForm"].elements["charName"].value;
		if(profile.name == "") { alert("Please give your character a name!"); return false; }
		profile.abilities = {
			Str:parseInt(document.forms["charForm"].elements["charStr"].value),
			Dex:parseInt(document.forms["charForm"].elements["charDex"].value),
			Con:parseInt(document.forms["charForm"].elements["charCon"].value),
			Int:parseInt(document.forms["charForm"].elements["charInt"].value),
			Wis:parseInt(document.forms["charForm"].elements["charWis"].value),
			Cha:parseInt(document.forms["charForm"].elements["charCha"].value)
		};
		profile.levels = {};
		var lvs = document.getElementById("lvList").getElementsByTagName("li").length;
		for(var i=0;i<lvs;++i) {
			profile.levels[document.forms["charForm"].elements["charCls"+i].value] = document.forms["charForm"].elements["charLv"+i].value;
			if(document.forms["charForm"].elements["charCls"+i].value == "Wiz") {
				profile.specialization = document.forms["charForm"].elements["charSpec"].value;
				if(document.forms["charForm"].elements["charSpec"].value != "None") {
					profile.prohibited = [];
					for(var j=0;j<=1;++j) {
						profile.prohibited[j] = document.forms["charForm"].elements["charProhib"+j].value;
					}
				}
			} else if(document.forms["charForm"].elements["charCls"+i].value == "Clr") {
				profile.domains = [];
				for(var j=0;j<=1;++j) {
					profile.domains[j] = document.forms["charForm"].elements["charDom"+j].value;
				}
			}
		}
		profile.feats = [];
		var fts = document.getElementById("featList").getElementsByTagName("li").length;
		for(var i=0;i<fts;++i) {
			profile.feats[i] = document.forms["charForm"].elements["charFeat"+i].value;
			if(profile.feats[i] == "Spell Focus" || profile.feats[i] == "Greater Spell Focus") {
				profile.feats[i] += " " + document.forms["charForm"].elements["charFocus"+i].value;
			}
		}
		profile.description = document.forms["charForm"].elements["charDescription"].value;
		// Adjust known spells to reflect changes to classes
		var known = {};
		for(var cls in profile.levels) {
			if(cls == "Brd" || cls == "Sor" || cls == "Wiz") {
				if(!sl.character.known[cls]) {
					known[cls] = [];
				} else {
					known[cls] = sl.character.known[cls];
				}
			}
		}
		// Calculate spell slots
		var slots = sl.calcSlots(profile);
	} else {
		profile = sl.character.profile;
		known = sl.character.known;
		slots = sl.character.slots;
	}
	var cname = encodeURI(JSON.stringify(profile.name));
	var cprof = encodeURI(JSON.stringify(profile));
	var cknown = encodeURI(JSON.stringify(known));
	var cslots = encodeURI(JSON.stringify(slots));
	var params = "user_id=" + sl.user_id + "&char_id=" + sl.character.id + "&cname=" + cname + "&cprof=" + cprof + "&cknown=" + cknown + "&cslots=" + cslots + "&sid=" + Math.random();
	var xmlhttp;
	if (window.XMLHttpRequest) {
		xmlhttp = new XMLHttpRequest();
	} else {
		alert("I'm sorry, but your browser does not support AJAX!");
	}
	xmlhttp.open("POST",sl.path + "savechar.php",true);
	xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded");
	xmlhttp.setRequestHeader("Content-length",params.length);
	xmlhttp.setRequestHeader("Connection","close");
	xmlhttp.onreadystatechange = function () {
		if(xmlhttp.readyState == 4) {
			if(xmlhttp.responseText == "") {
				alert("Error saving character");
			} else {
				// Set new character.id if necessary, and refresh character selection
				xmlhttp = JSON.parse(xmlhttp.responseText);
				if(xmlhttp[0] == 0) {
					alert("Error saving character: you already have a character entry by that name!");
				} else {
					alert("Character saved successfully!");
					if(saveForm) {
						// Set variable values
						sl.character.id = xmlhttp[0];
						sl.character.profile = profile;
						sl.character.known = known;
						sl.character.slots = slots;
						// Enable charMenu
						document.getElementById("charName").childNodes[0].nodeValue = sl.character.profile.name;
						var select = document.forms["controlForm"].elements["charList"];
						sl.emptyElement(select);
						for(var i=1;i<xmlhttp.length;i+=2) {
							var option = sl.createSimpleElement("option",xmlhttp[i+1]);
							option.value = xmlhttp[i];
							if(option.value == sl.character.id) { option.selected = true; }
							select.appendChild(option);
						}
						sl.disableCharMenu(false);
						// Build spell slots & initialize proper tabs
						sl.buildSpellSlots();
						sl.setTabsByChar();
						sl.clearSpellDetail();
						// Disable and hide charForm
						sl.disableCharForm(true);
						document.getElementById("spellLibMain").className = "char";
					}
				}
			}
		}
	};
	xmlhttp.send(params);
	return false;
},

editchar:function (edit) {
	// Enter name
	document.forms["charForm"].elements["charName"].value = sl.character.profile.name;
	// Enter ability scores
	for(var abil in sl.character.profile.abilities) {
		document.forms["charForm"].elements["char" + abil].value = sl.character.profile.abilities[abil];
	}
	// Enter classes and levels
	var entries = document.getElementById("lvList").getElementsByTagName("li");
	while(entries.length > 1) {
		sl.deleteCls(entries.length - 1);
	}
	var i = 0;
	for(var cls in sl.character.profile.levels) {
		var clsData1 = false;
		var clsData2 = false;
		var clsData3 = false;
		// While adding classes, add details for Wiz and Clr
		if(cls == "Wiz") {
			clsData1 = sl.character.profile.specialization;
			if(sl.character.profile.specialization != "None") {
				clsData2 = sl.character.profile.prohibited[0];
				clsData3 = sl.character.profile.prohibited[1];
			}
		} else if(cls == "Clr") {
			clsData1 = sl.character.profile.domains[0];
			clsData2 = sl.character.profile.domains[1];
		}
		if(i == entries.length) {
			sl.addCls(cls,sl.character.profile.levels[cls],clsData1,clsData2,clsData3);
		} else {
			document.forms["charForm"].elements["charLv"+i].value = sl.character.profile.levels[cls];
			document.forms["charForm"].elements["charCls"+i].value = cls;
			sl.selectCls(i,null,cls,clsData1,clsData2,clsData3);
		}
		++i;
	}
	if(i == 0) { sl.selectCls(0); }
	// Enter feats
	var feats = document.getElementById("featList").getElementsByTagName("li");
	while(feats.length > 0) {
		sl.deleteFeat(feats.length - 1);
	}
	for(var i=0;i<sl.character.profile.feats.length;++i) {
		var feat = sl.character.profile.feats[i];
		var focus = false;
		var n = sl.character.profile.feats[i].search(/Spell Focus/);
		if(n > -1) {
			focus = feat.substr(n+12);
			feat = feat.substr(0,n+11);
		}
		sl.addFeat(feat,focus);
	}
	// Enter description
	document.forms["charForm"].elements["charDescription"].value = sl.character.profile.description;
	// Display form
	sl.disableCharForm(!edit);
	document.getElementById("spellLibMain").className = (edit) ? "edit" : ((sl.character.id == 0) ? "nochar" : "char");
},

deletechar:function () {
	// See if there's a character to be deleted
	if(sl.character.id == 0) { return false; }
	// Ask permission to proceed
	if(!confirm("Are you sure you want to delete the profile for " + sl.character.profile.name + "?")) { return false; }
	var params = "user_id=" + sl.user_id + "&char_id=" + sl.character.id + "&sid=" + Math.random();
	var xmlhttp;
	if (window.XMLHttpRequest) {
		xmlhttp = new XMLHttpRequest();
	} else {
		alert("I'm sorry, but your browser does not support AJAX!");
	}
	xmlhttp.open("POST",sl.path + "deletechar.php",true);
	xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded");
	xmlhttp.setRequestHeader("Content-length",params.length);
	xmlhttp.setRequestHeader("Connection","close");
	xmlhttp.onreadystatechange = function () {
		if(xmlhttp.readyState == 4) {
			if(xmlhttp.responseText == "") {
				alert("Error deleting character");
			} else {
				// Refresh character selection list
				xmlhttp = JSON.parse(xmlhttp.responseText);
				var select = document.forms["controlForm"].elements["charList"];
				sl.emptyElement(select);
				for(var i=1;i<xmlhttp.length;i+=2) {
					var option = sl.createSimpleElement("option",xmlhttp[i+1]);
					option.value = xmlhttp[i];
					select.appendChild(option);
				}
				// Start new character
				sl.newchar(true,false);
			}
		}
	};
	xmlhttp.send(params);
	return false;
},


// ************************
// CHARACTER EDIT FUNCTIONS
// ************************

calcSlots:function (profile) {
	slots = {};
	for(var cls in profile.levels) {
		var chLv = profile.levels[cls];
		var abil;
		if(cls == "Brd" || cls == "Sor") { abil = "Cha"; }
		else if(cls == "Wiz") { abil = "Int"; }
		else { abil = "Wis"; }
		abil = profile.abilities[abil];
		var classSpells = [];
		if(cls == "Wiz" || cls == "Clr" || cls == "Drd") {
			for(var lv=0;lv<=9;++lv) {
				var lvLrn = (2*lv) - 1;
				if((lv == 0 && chLv >= 2) || (lv <= 9 && chLv == 20) || chLv >= (lvLrn + 6)) {
					if(cls != "Wiz") {
						if(lv == 0 && chLv >= 7) {
							classSpells[lv] = 6;
						} else if((lv == 0 && chLv >= 4) || chLv >= (lvLrn + 10)) {
							classSpells[lv] = 5;
						} else {
							classSpells[lv] = 4;
						}
					} else {
						classSpells[lv] = 4;
					}
				} else if((lv == 0 && chLv >= 1) || (lv <= 9 && chLv == 19) || chLv >= (lvLrn + 3)) {
					classSpells[lv] = 3;
				} else if(chLv >= (lvLrn + 1)) {
					classSpells[lv] = 2;
				} else if(chLv >= lvLrn) {
					classSpells[lv] = 1;
				} else {
					classSpells[lv] = undefined;
				}
			}
		} else if(cls == "Sor") {
			for(var lv=0;lv<=9;++lv) {
				var lvLrn = (2*lv);
				if(lv == 1 || lv == 0) { --lvLrn; }
				if((lv <= 9 && chLv >= 20) || chLv >= (lvLrn + 3)) {
					classSpells[lv] = 6;
				} else if(chLv >= (lvLrn + 2)) {
					classSpells[lv] = 5;
				} else if(chLv >= (lvLrn + 1)) {
					classSpells[lv] = 4;
				} else if(chLv >= lvLrn) {
					classSpells[lv] = 3;
				} else {
					classSpells[lv] = undefined;
				}
			}
		} else if(cls == "Pal" || cls == "Rgr") {
			for(var lv=1;lv<=4;++lv) {
				if((lv == 4 && chLv >= 20) || (lv <= 3 && chLv >= 19) || (lv == 1 && chLv >= 18)) {
					classSpells[lv] = 3;
				} else if((lv == 4 && chLv >= 19) || (lv == 3 && chLv >= 17) || (lv == 2 && chLv >= 16) || (lv == 1 && chLv >= 14)) {
					classSpells[lv] = 2;
				} else if((lv == 4 && chLv >= 15) || (lv == 3 && chLv >= 12) || (lv == 2 && chLv >= 10) || (lv == 1 && chLv >= 6)) {
					classSpells[lv] = 1;
				} else if((lv == 4 && chLv >= 14) || (lv == 3 && chLv >= 11) || (lv == 2 && chLv >= 8) || (lv == 1 && chLv >= 4)) {
					classSpells[lv] = 0;
				} else {
					classSpells[lv] = undefined;
				}
			}
		} else if(cls == "Brd") {
			for(var lv=0;lv<=6;++lv) {
				var lvLrn = (3*lv) - 2;
				if(lv <= 1) { ++lvLrn; }
				if(chLv >= (lv + 14)) {
					classSpells[lv] = 4;
				} else if((lv <= 6 && chLv >= 19) || chLv >= (lvLrn + 4)) {
					classSpells[lv] = 3;
				} else if(chLv >= (lvLrn + 2)) {
					classSpells[lv] = 2;
				} else if(chLv >= (lvLrn + 1)) {
					classSpells[lv] = 1;
				} else if(chLv >= lvLrn) {
					classSpells[lv] = 0;
				} else {
					classSpells[lv] = undefined;
				}
			}
		}
		var mod;
		if(abil%2 == 0) {
			mod = (abil - 10)/2;
		} else {
			mod = (abil - 11)/2;
		}
		var bonusSpells = [];
		for(var lv=0;lv<=9;++lv) {
			var b = Math.floor((mod - lv)/4 + 1);
			if(lv == 0 || b < 1) {
				bonusSpells[lv] = 0;
			} else {
				bonusSpells[lv] = b;
			}
		}
		slots[cls] = [];
		for(var lv=0;lv<=9;++lv) {
			if(classSpells[lv] != undefined) {
				slots[cls][lv] = [];
				if(cls == "Wiz" && profile.specialization != "None") {
					slots[cls][lv].push({ type:"specialization",status:"ready",spell:null });
				} else if(cls == "Clr" && lv > 0) {
					slots[cls][lv].push({ type:"domain",status:"ready",spell:null });
				}
				for(var n=0;n<classSpells[lv];++n) {
					slots[cls][lv].push({ type:"standard",status:"ready",spell:null });
				}
				for(var n=0;n<bonusSpells[lv];++n) {
					slots[cls][lv].push({ type:"bonus",status:"ready",spell:null });
				}
			}
		}
	}
	return slots;
},

moveUp:function (id,i) {
	var entries = document.getElementById(id).getElementsByTagName("li");
	if(i > 0 && i < entries.length) {
		var a = entries[i];
		var b = entries[i-1];
		document.getElementById(id).removeChild(a);
		document.getElementById(id).insertBefore(a,b);
		if(id == "lvList") { sl.renumberCls(i-1); }
		else if(id == "featList") { sl.renumberFeats(i-1); }
	}
},

addCls:function (setCls,setLv,clsData1,clsData2,clsData3) {
	var i = document.getElementById("lvList").getElementsByTagName("li").length
	if(i < 7) {
		var newEntry = document.getElementById("lvList").getElementsByTagName("li")[0].cloneNode(true);
		document.getElementById("lvList").appendChild(newEntry);
		sl.renumberCls(i);
		for(var j=0;j<i;++j) {
			var cls = document.forms["charForm"].elements["charCls"+j].value;
			sl.disableClsSelect(i,cls,true);
		}
		var select = document.forms["charForm"].elements["charCls"+i];
		if(setCls) {
			select.value = setCls;
			document.forms["charForm"].elements["charLv"+i].value = setLv;
		} else {
			for(var k=0;k<select.length;++k) {
				if(!select[k].disabled) { select[k].selected = true; break; }
			}
		}
		sl.selectCls(i,null,select.value,clsData1,clsData2,clsData3);
	} else {
		alert("No more spellcasting classes are available!");
	}
},

deleteCls:function (i) {
	var entries = document.getElementById("lvList").getElementsByTagName("li");
	if(entries.length > 1) {
		var cls = document.forms["charForm"].elements["charCls"+i].value;
		document.getElementById("lvList").removeChild(entries[i]);
		sl.renumberCls(i);
		for(var j=0;j<entries.length;++j) {
			sl.disableClsSelect(j,cls,false);
		}
	}
},

selectCls:function (i,oldCls,newCls,clsData1,clsData2,clsData3) {
	if(oldCls != newCls) {
		var entries = document.getElementById("lvList").getElementsByTagName("li");
		for(var j=0;j<entries.length;++j) {
			if(i != j) {
				if(oldCls != "") { sl.disableClsSelect(j,oldCls,false); }
				sl.disableClsSelect(j,newCls,true);
			}
		}
		var scopePreserver = function (a,b,c) { return function () { sl.selectCls(a,b,c.value); }; };
		var select = document.forms["charForm"].elements["charCls"+i];
		select.onchange = scopePreserver(i,newCls,select);
		var details = entries[i].getElementsByTagName("div")[0];
		sl.emptyElement(details);
		if(newCls == "Wiz") {
			// Create specialization school options
			var p = sl.createSimpleElement("p","Specialization: ");
			var select = document.createElement("select");
			select.name = "charSpec";
			var scopePreserver = function (a) {
				return function () { sl.selectSpec(a.value,false); };
			};
			select.onchange = scopePreserver(select);
			var noneOption = sl.createSimpleElement("option","None");
			noneOption.value = "None";
			select.appendChild(noneOption);
			for(var j=0;j<sl.wizSchools.length;++j) {
				var option = sl.createSimpleElement("option",sl.wizSchools[j]);
				option.value = sl.wizSchools[j];
				select.appendChild(option);
			}
			p.appendChild(select);
			details.appendChild(p);
			var prohibs = document.createElement("div");
			prohibs.className = "prohibs";
			details.appendChild(prohibs);
			// Set specialization school value
			if(clsData1) {
				select.value = clsData1;
			} else {
				select[0].selected = true;
			}
			sl.selectSpec(select.value,clsData2,clsData3);
		} else if(newCls == "Clr") {
			// Create domain options
			for(var d=0;d<=1;++d) {
				var p = sl.createSimpleElement("p","Domain " + (d+1) + ": ");
				var select = document.createElement("select");
				select.name = "charDom" + d;
				var scopePreserver = function (a,b) {
					return function () { sl.selectDom(a.value,b); };
				};
				select.onchange = scopePreserver(select,d);
				for(var j=0;j<sl.clrDomains.length;++j) {
					var option = sl.createSimpleElement("option",sl.clrDomains[j]);
					option.value = sl.clrDomains[j];
					select.appendChild(option);
				}
				p.appendChild(select);
				details.appendChild(p);
				// Set default values
				var dom = (clsData1) ? ((d == 0) ? clsData1 : clsData2) : false;
				if(dom) {
					select.value = dom;
				} else {
					select[d].selected = true;
				}
			}
			// Lock domain values
			for(var d=0;d<=1;++d) {
				sl.selectDom(document.forms["charForm"].elements["charDom"+d].value,d);
			}
		}
	}
},

disableClsSelect:function (i,cls,setTo) {
	var select = document.forms["charForm"].elements["charCls"+i];
	for(var n=0;n<select.length;++n) {
		if(select[n].value == cls) {
			select[n].disabled = setTo;
			break;
		}
	}
},

renumberCls:function (min) {
	var entries = document.getElementById("lvList").getElementsByTagName("li");
	var scopePreserver = function (a,b,c,d) {
		if(a == 0) {
			return function () { sl.moveUp("lvList",b); };
		} else if(a == 1) {
			return function () { sl.moveUp("lvList",b+1); };
		} else if(a == 2) {
			return function () { sl.deleteCls(b); };
		} else if(a == 3) {
			return function () { sl.selectCls(b,c,d.value,false); };
		}
	}
	for(var j=min;j<entries.length;++j) {
		for(var k=0;k<=2;++k) {
			sl.convertLink(entries[j].getElementsByTagName("a")[k],scopePreserver(k,j));
		}
		var selects = entries[j].getElementsByTagName("select");
		selects[0].name = "charLv" + j;
		selects[1].name = "charCls" + j;
		selects[1].onchange = scopePreserver(3,j,selects[1].value,selects[1]);
	}
},

selectSpec:function (sc,setProhib1,setProhib2) {
	var prohibs = document.forms["charForm"].elements["charSpec"].parentNode.parentNode.getElementsByTagName("div")[0];
	sl.emptyElement(prohibs);
	if(sc != "None") {
		// Create prohibited school options
		for(var d=0;d<=1;++d) {
			var p = sl.createSimpleElement("p","Prohibited School " + (d+1) + ": ");
			var select = document.createElement("select");
			select.name = "charProhib" + d;
			var scopePreserver = function (a,b) {
				return function () { sl.selectProhib(a.value,b); };
			};
			select.onchange = scopePreserver(select,d);
			for(var k=0;k<sl.wizSchools.length;++k) {
				if(sc != sl.wizSchools[k]) {
					var option = sl.createSimpleElement("option",sl.wizSchools[k]);
					option.value = sl.wizSchools[k];
					select.appendChild(option);
				}
			}
			p.appendChild(select);
			prohibs.appendChild(p);
			// Set default values
			var prohib = (setProhib1) ? ((d == 0) ? setProhib1 : setProhib2) : false;
			if(prohib) {
				select.value = prohib;
			} else {
				select[d].selected = true;
			}
		}
		// Lock in prohibited school values
		for(var d=0;d<=1;++d) {
			sl.selectProhib(document.forms["charForm"].elements["charProhib"+d].value,d);
		}
	}
},

selectProhib:function (sc,d) {
	var alt = (d == 0) ? 1 : 0;
	var options = document.forms["charForm"].elements["charProhib"+alt].getElementsByTagName("option");
	for(var n=0;n<options.length;++n) {
		if(options[n].value == sc) {
			options[n].disabled = true;
		} else {
			options[n].disabled = false;
		}
	}
},

selectDom:function (sc,d) {
	var alt = (d == 0) ? 1 : 0;
	var options = document.forms["charForm"].elements["charDom"+alt].getElementsByTagName("option");
	for(var n=0;n<options.length;++n) {
		if(options[n].value == sc) {
			options[n].disabled = true;
		} else {
			options[n].disabled = false;
		}
	}
},

addFeat:function (setFeat,setFocus) {
	var i = document.getElementById("featList").getElementsByTagName("li").length;
	if(i == 0) {
		var feat = document.createElement("li");
		// Add buttons
		var nav = document.createElement("p");
		nav.className = "nav";
		var moveUp = sl.createSimpleElement("a","\u2191");
		moveUp.href = "#";
		nav.appendChild(moveUp);
		var moveDown = sl.createSimpleElement("a","\u2193");
		moveDown.href = "#";
		nav.appendChild(moveDown);
		var deleteFeat = sl.createSimpleElement("a","X");
		deleteFeat.href = "#";
		nav.appendChild(deleteFeat);
		feat.appendChild(nav);
		// Add feat selection
		var p = sl.createSimpleElement("p","Feat known: ");
		var select = document.createElement("select");
		select.name = "charFeat" + i;
		for(var j=0;j<sl.featNames.length;++j) {
			var option = sl.createSimpleElement("option",sl.featNames[j]);
			option.value = sl.featNames[j];
			select.appendChild(option);
		}
		p.appendChild(select);
		feat.appendChild(p);
		// Add details div
		var details = document.createElement("div");
		details.className = "details";
		feat.appendChild(details);
		document.getElementById("featList").appendChild(feat);
		// Apply feat value
		sl.disableFeatSelect(i,"Greater Spell Focus",null);
		if(setFeat) {
			select.value = setFeat;
		} else {
			for(var k=0;k<select.length;++k) {
				if(!select[k].disabled) { select[k].selected = true; break; }
			}
		}
		sl.renumberFeats(i);
		sl.selectFeat(i,null,select.value,setFocus);
	} else if(i < 26) {
		var newEntry = document.getElementById("featList").getElementsByTagName("li")[0].cloneNode(true);
		document.getElementById("featList").appendChild(newEntry);
		sl.renumberFeats(i);
		for(var j=0;j<i;++j) {
			var feat = document.forms["charForm"].elements["charFeat"+j].value;
			sl.disableFeatSelect(i,feat,true);
		}
		sl.disableFeatSelect(i,"Greater Spell Focus",null);
		var select = document.forms["charForm"].elements["charFeat"+i];
		if(setFeat) {
			select.value = setFeat;
		} else {
			for(var k=0;k<select.length;++k) {
				if(!select[k].disabled) { select[k].selected = true; break; }
			}
		}
		sl.selectFeat(i,null,select.value,setFocus);
	} else {
		alert("No more feats are available!");
	}
},

deleteFeat:function (i) {
	var entries = document.getElementById("featList").getElementsByTagName("li");
	var feat = document.forms["charForm"].elements["charFeat"+i].value;
	var focus = (feat == "Spell Focus") ? document.forms["charForm"].elements["charFocus"+i].value : false;
	var greaterFocus = (feat == "Greater Spell Focus") ? document.forms["charForm"].elements["charFocus"+i].value : false;
	document.getElementById("featList").removeChild(entries[i]);
	sl.renumberFeats(i);
	for(var j=0;j<entries.length;++j) {
		sl.disableFeatSelect(j,feat,false);
		var thisFeat = document.forms["charForm"].elements["charFeat"+j].value;
		if(focus) {
			if(thisFeat == "Spell Focus") {
				sl.disableFocusSelect(j,focus,false);
			} else if(thisFeat == "Greater Spell Focus") {
				sl.disableFocusSelect(j,focus,true);
			}
		} else if(greaterFocus && thisFeat == "Greater Spell Focus") {
			for(var k=0;k<entries.length;++k) {
				if(document.forms["charForm"].elements["charFeat"+k].value == "Spell Focus") {
					if(document.forms["charForm"].elements["charFocus"+k].value == greaterFocus) {
						sl.disableFocusSelect(j,greaterFocus,false);
						break;
					}
				}
			}
		}
	}
},

selectFeat:function (i,oldFeat,newFeat,setFocus) {
	if(oldFeat != newFeat) {
		var entries = document.getElementById("featList").getElementsByTagName("li");
		for(var j=0;j<entries.length;++j) {
			if(i != j) {
				if(oldFeat) { sl.disableFeatSelect(j,oldFeat,false); }
				sl.disableFeatSelect(j,newFeat,true);
			}
			if(oldFeat == "Spell Focus" || newFeat == "Spell Focus") {
				sl.disableFeatSelect(j,"Greater Spell Focus",null);
			}
		}
		var scopePreserver = function (a,b,c) { return function () { sl.selectFeat(a,b,c.value); }; };
		var select = document.forms["charForm"].elements["charFeat"+i];
		select.onchange = scopePreserver(i,newFeat,select,false);
		var details = entries[i].getElementsByTagName("div")[0];
		sl.emptyElement(details);
		if(newFeat == "Spell Focus" || newFeat == "Greater Spell Focus") {
			// Create charFocus and append to form
			var p = sl.createSimpleElement("p","School: ");
			var select = document.createElement("select");
			select.name = "charFocus" + i;
			for(var j=0;j<sl.wizSchools.length;++j) {
				var option = sl.createSimpleElement("option",sl.wizSchools[j]);
				option.value = sl.wizSchools[j];
				if(newFeat == "Greater Spell Focus") { option.disabled = true; }
				select.appendChild(option);
			}
			p.appendChild(select);
			details.appendChild(p);
			sl.renumberFocus(i);
			// Set charFocus value, set onchange, and disable appropriate elements
			if(newFeat == "Greater Spell Focus") {
				for(var j=0;j<entries.length;++j) {
					if(i != j && document.forms["charForm"].elements["charFeat"+j].value == "Spell Focus") {
						var focus = document.forms["charForm"].elements["charFocus"+j].value;
						sl.disableFocusSelect(i,focus,false);
					}
				}
			}
			for(var j=0;j<entries.length;++j) {
				if(i != j && document.forms["charForm"].elements["charFeat"+j].value == newFeat) {
					var focus = document.forms["charForm"].elements["charFocus"+j].value;
					sl.disableFocusSelect(i,focus,true);
				}
			}
			if(setFocus) {
				select.value = setFocus;
			} else {
				for(var k=0;k<select.length;++k) {
					if(!select[k].disabled) { select[k].selected = true; break; }
				}
			}
			var greaterOnly = (newFeat == "Spell Focus") ? false : true;
			var scopePreserver = function (a,b,c,d) {
				return function () { sl.selectFocus(a,b,c.value,d); };
			};
			select.onchange = scopePreserver(i,select.value,select,greaterOnly);
			sl.selectFocus(i,null,select.value,greaterOnly);
		}
	}
},

disableFeatSelect:function (i,feat,setTo) {
	if(feat == "Spell Focus") {
		var a = 0;
		for(var j=0;j<document.getElementById("featList").getElementsByTagName("li").length;++j) {
			if(document.forms["charForm"].elements["charFeat"+j].value == "Spell Focus") { ++a; }
		}
		if(a < 8) { setTo = false; } else { setTo = true; }
	} else if(feat == "Greater Spell Focus") {
		var thisFeat = document.forms["charForm"].elements["charFeat"+i].value;
		var a = (thisFeat == "Spell Focus") ? -1 : 0;
		var b = (thisFeat == "Greater Spell Focus") ? -1 : 0;
		for(var j=0;j<document.getElementById("featList").getElementsByTagName("li").length;++j) {
			if(document.forms["charForm"].elements["charFeat"+j].value == "Spell Focus") { ++a; }
			else if(document.forms["charForm"].elements["charFeat"+j].value == "Greater Spell Focus") { ++b; }
		}
		if(a > 0 && b < a) { setTo = false; } else { setTo = true; }
	}
	var select = document.forms["charForm"].elements["charFeat"+i];
	for(var n=0;n<select.length;++n) {
		if(select[n].value == feat) {
			select[n].disabled = setTo;
			break;
		}
	}
},

renumberFeats:function (min) {
	var entries = document.getElementById("featList").getElementsByTagName("li");
	var scopePreserver = function (a,b,c,d) {
		if(a == 0) {
			return function () { sl.moveUp("featList",b); };
		} else if(a == 1) {
			return function () { sl.moveUp("featList",b+1); };
		} else if(a == 2) {
			return function () { sl.deleteFeat(b); };
		} else if(a == 3) {
			return function () { sl.selectFeat(b,c,d.value); };
		}
	}
	for(var j=min;j<entries.length;++j) {
		for(var k=0;k<=2;++k) {
			sl.convertLink(entries[j].getElementsByTagName("a")[k],scopePreserver(k,j));
		}
		var selects = entries[j].getElementsByTagName("select");
		selects[0].name = "charFeat" + j;
		selects[0].onchange = scopePreserver(3,j,selects[0].value,selects[0]);
		if(selects[1]) {
			sl.renumberFocus(j);
		}
	}
},

renumberFocus:function (i) {
	var selects = document.getElementById("featList").getElementsByTagName("li")[i].getElementsByTagName("select");
	var greaterOnly = (selects[0].value == "Spell Focus") ? false : true;
	selects[1].name = "charFocus" + i;
	var scopePreserver = function (a,b,c,d) { return function () { sl.selectFocus(a,b,c.value,d); }; };
	selects[1].onchange = scopePreserver(i,selects[1].value,selects[1],greaterOnly);
},

selectFocus:function (i,oldFocus,newFocus,greaterOnly) {
	if(oldFocus != newFocus) {
		var entries = document.getElementById("featList").getElementsByTagName("li");
		for(var j=0;j<entries.length;++j) {
			if(i != j) {
				if(!greaterOnly && document.forms["charForm"].elements["charFeat"+j].value == "Spell Focus") {
					if(oldFocus) { sl.disableFocusSelect(j,oldFocus,false); }
					sl.disableFocusSelect(j,newFocus,true);
				} else if(document.forms["charForm"].elements["charFeat"+j].value == "Greater Spell Focus") {
					if(oldFocus) { sl.disableFocusSelect(j,oldFocus,!greaterOnly); }
					sl.disableFocusSelect(j,newFocus,greaterOnly);
				}
			}
		}
		var scopePreserver = function (a,b,c,d) { return function () { sl.selectFocus(a,b,c.value,d); }; };
		var select = document.forms["charForm"].elements["charFocus"+i];
		select.onchange = scopePreserver(i,newFocus,select,greaterOnly);
	}
},

disableFocusSelect:function (i,focus,setTo) {
	var select = document.forms["charForm"].elements["charFocus"+i];
	for(var n=0;n<select.length;++n) {
		if(select[n].value == focus) {
			select[n].disabled = setTo;
			break;
		}
	}
},


// ***********************
// SPELL LIBRARY FUNCTIONS
// ***********************

fillSpellLibrary:function () {
	var x = sl.spellXmlDoc.getElementsByTagName("spell");
	var cls = sl.libCls;
	
	// Utility function for creating spell entries
	var newListEntry = function (i,name,known) {
		var newLi = document.createElement("li");
		if(known) { newLi.className = "known"; }
		var link = sl.createSimpleElement("a",name);
		link.href = "#" + i;
		var scopePreserver = function (a) {
			return function () { sl.showSpell(a); }
		}
		sl.convertLink(link,scopePreserver(i));
		newLi.appendChild(link);
		if(document.getElementById("showVerbose").checked) {
			var summary = x[i].getElementsByTagName("summary")[0].childNodes[0].nodeValue;
			newLi.appendChild(sl.createSimpleElement("span"," " + summary));
		}
		return newLi;
	};
	
	// Empty libList
	var libList = document.getElementById("libList");
	sl.emptyElement(libList);
	
	// Set libList class
	if(document.getElementById("showVerbose").checked) {
		libList.className = "verbose";
	} else {
		libList.className = "concise";
	}

	// Master function for generating spell list items according to class
	var listText = [];
	if(cls == "All") {
		// Sort spell list entries into array
		var list = [];
		for(var i=0;i<x.length;++i) {
			var name = x[i].getElementsByTagName("name")[0].childNodes[0].nodeValue;
			var sc = sl.scnToSc[x[i].getElementsByTagName("school")[0].childNodes[0].nodeValue];
			if(sl.passNameFilter(name) && sl.passScFilter(sc) && sl.passCompsFilter(x[i])) {
				list.push(newListEntry(i,name,false));
			}
		}
		// Print array into listText
		listText[0] = sl.createSimpleElement("h2","All Spells");
		listText[1] = document.createElement("ul");
		listText[1].className = "libNames";
		for(var n=0;n<list.length;++n) {
			listText[1].appendChild(list[n]);
		}
	} else if(cls == "Brd" || cls == "Clr" || cls == "Drd" || cls == "Pal" || cls == "Rgr") {
		// Sort spell list entries into array
		var list = [];
		for(var lv=0;lv<=9;++lv) {
			list[lv] = [];
		}
		for(var i=0;i<x.length;++i) {
			if(x[i].getElementsByTagName(cls)[0]) {
				var name = x[i].getElementsByTagName("name")[0].childNodes[0].nodeValue;
				if(sl.passNameFilter(name) && sl.passCompsFilter(x[i])) {
					var lv = x[i].getElementsByTagName(cls)[0].childNodes[0].nodeValue;
					var sc = sl.scnToSc[x[i].getElementsByTagName("school")[0].childNodes[0].nodeValue];
					if(sl.passLvFilter(lv) && sl.passScFilter(sc) && (sl.libMode == "browse" || sl.isCastable(cls,lv,sc,name))) {
						list[lv].push(newListEntry(i,name,sl.isKnown(cls,name)));
					}
				}
			}
		}
		// Print array into list
		var title = "";
		if(sl.libMode == "repertoire" && cls == "Brd") {
			title = "Known Bard Spells";
		} else {
			title = sl.expandClsName[cls] + " Spell List";
		}
		listText[0] = sl.createSimpleElement("h2",title);
		listText[1] = document.createElement("ul");
		listText[1].className = "libLevels";
		for(var lv=0;lv<=9;++lv) {
			if(list[lv].length != 0) {
				var lvLi = document.createElement("li");
				lvLi.appendChild(sl.createSimpleElement("h3","Level " + lv));
				var lvUl = document.createElement("ul");
				lvUl.className = "libNames";
				for(var n=0;n<list[lv].length;++n) {
					lvUl.appendChild(list[lv][n]);
				}
				lvLi.appendChild(lvUl);
				listText[1].appendChild(lvLi);
			}
		}
		if(cls == "Clr") {
			var list = {};
			var doms = [];
			if(sl.libMode == "browse") {
				doms = sl.clrDomains;
			} else if(sl.libMode == "repertoire") {
				doms = sl.character.profile.domains;
			}
			for(var d=0;d<doms.length;++d) {
				list[doms[d]] = [];
				for(var lv=0;lv<=9;++lv) {
					list[doms[d]][lv] = [];
				}
			}
			for(var i=0;i<x.length;++i) {
				for(var d in list) {
					if(x[i].getElementsByTagName(d)[0]) {
						var name = x[i].getElementsByTagName("name")[0].childNodes[0].nodeValue;
						if(sl.passNameFilter(name) && sl.passCompsFilter(x[i])) {
							var lv = x[i].getElementsByTagName(d)[0].childNodes[0].nodeValue;
							var sc = sl.scnToSc[x[i].getElementsByTagName("school")[0].childNodes[0].nodeValue];
							if(sl.passLvFilter(lv) && sl.passScFilter(sc) && (sl.libMode == "browse" || sl.isCastable(cls,lv,sc,name))) {
								list[d][lv].push(newListEntry(i,name,false));
							}
						}
					}
				}
			}
			// Print array into list
			listText[2] = sl.createSimpleElement("h2","Cleric Domain Spells");
			listText[3] = document.createElement("ul");
			listText[3].className = "libLevels";
			for(var d in list) {
				var n = 0;
				for(var lv=0;lv<=9;++lv) {
					if(list[d][lv].length != 0) { ++n; }
				}
				if(n > 0) {
					var dLi = document.createElement("li");
					dLi.appendChild(sl.createSimpleElement("h3",d + " Spells"));
					var dUl = document.createElement("ul");
					dUl.className = "libBlocks";
					for(var lv=1;lv<=9;++lv) {
						if(list[d][lv].length > 0) {
							var lvLi = document.createElement("li");
							lvLi.appendChild(sl.createSimpleElement("h4","Lv " + lv));
							var lvUl = document.createElement("ul");
							lvUl.className = "libNames";
							for(var n=0;n<list[d][lv].length;++n) {
								lvUl.appendChild(list[d][lv][n]);
							}
							lvLi.appendChild(lvUl);
							dUl.appendChild(lvLi);
						}
					}
					dLi.appendChild(dUl);
					listText[3].appendChild(dLi);
				}
			}
		}
	} else if(cls == "Sor" || cls == "Wiz") {
		// Sort spell list entries into array
		var list = [];
		for(var lv=0;lv<=9;++lv) {
			list[lv] = [];
			for(var sc=0;sc<=8;++sc) {
				list[lv][sc] = [];
			}
		}
		for(var i=0;i<x.length;++i) {
			if(x[i].getElementsByTagName(cls)[0]) {
				var name = x[i].getElementsByTagName("name")[0].childNodes[0].nodeValue;
				if(sl.passNameFilter(name) && sl.passCompsFilter(x[i])) {
					var lv = x[i].getElementsByTagName(cls)[0].childNodes[0].nodeValue;
					var sc = sl.scnToSc[x[i].getElementsByTagName("school")[0].childNodes[0].nodeValue];
					if(sl.passLvFilter(lv) && sl.passScFilter(sc) && (sl.libMode == "browse" || sl.isCastable(cls,lv,sc,name))) {
						list[lv][sc].push(newListEntry(i,name,sl.isKnown(cls,name)));
					}
				}
			}
		}
		// Print array into list
		var title = "";
		if(sl.libMode == "repertoire" && (cls == "Sor" || cls == "Wiz")) {
			title = "Known " + sl.expandClsName[cls] + " Spells";
		} else {
			title = sl.expandClsName[cls] + " Spell List";
		}
		listText[0] = sl.createSimpleElement("h2",title);
		listText[1] = document.createElement("ul");
		listText[1].className = "libLevels";
		for(var lv=0;lv<=9;++lv) {
			var n = 0;
			for(var sc=0;sc<=8;++sc) {
				if(list[lv][sc].length != 0) { ++n; }
			}
			if(n > 0) {
				var lvLi = document.createElement("li");
				lvLi.appendChild(sl.createSimpleElement("h3","Level " + lv));
				var lvUl = document.createElement("ul");
				lvUl.className = "libBlocks";
				for(var sc=0;sc<=8;++sc) {
					if(list[lv][sc].length != 0) {
						var scLi = document.createElement("li");
						scLi.appendChild(sl.createSimpleElement("h4",sl.scToScn[sc]));
						var scUl = document.createElement("ul");
						scUl.className = "libNames";
						for(var n=0;n<list[lv][sc].length;++n) {
							scUl.appendChild(list[lv][sc][n]);
						}
						scLi.appendChild(scUl);
						lvUl.appendChild(scLi);
					}
				}
				lvLi.appendChild(lvUl);
				listText[1].appendChild(lvLi);
			}
		}
	}
	
	// Append list results to libList
	for(var n=0;n<listText.length;++n) {
		libList.appendChild(listText[n]);
	}
},


// ************************
// LIBRARY OPTION FUNCTIONS
// ************************

passLvFilter:function (lv) {
	if(!document.getElementById("fltLv"+lv).checked) {
		return false;
	} else {
		return true;
	}
},

passScFilter:function (sc) {
	if(!document.getElementById("fltSc"+sc).checked) {
		return false;
	} else {
		return true;
	}
},

passCompsFilter:function (ref) {
	if(ref.getElementsByTagName("components")[0]) {
		var comps = ref.getElementsByTagName("components")[0].attributes;
		for(var n=0;n<comps.length;++n) {
			if(document.getElementById("flt"+comps[n].name+"req").checked && comps[n].value == "false") { return false; }
			if(document.getElementById("flt"+comps[n].name+"pro").checked && comps[n].value == "true") { return false; }
		}
	}
	return true;
},

passNameFilter:function (name) {
	var str = document.getElementById("fltSearchField").value.toLowerCase();
	if(str != "") {
		var quer = name.toLowerCase();
		if(quer.indexOf(str) == -1) { return false; }
	}
	return true;
},

chkBox:function (id) {
	var item = document.getElementById(id);
	item.checked = !item.checked;
},

chkAllLvs:function (reset) {
	var checkIt;
	if(reset || document.getElementById("fltLvAll").value == "Check All Levels") {
		checkIt = true;
		document.getElementById("fltLvAll").value = "Uncheck All Levels";
	} else {
		checkIt = false;
		document.getElementById("fltLvAll").value = "Check All Levels";
	}
	for(var lv=0;lv<=9;++lv) {
		document.getElementById("fltLv"+lv).checked = checkIt;
	}
},

chkAllScs:function (reset) {
	var checkIt;
	if(reset || document.getElementById("fltScAll").value == "Check All Schools") {
		checkIt = true;
		document.getElementById("fltScAll").value = "Uncheck All Schools";
	} else {
		checkIt = false;
		document.getElementById("fltScAll").value = "Check All Schools";
	}
	for(var sc=0;sc<=8;++sc) {
		document.getElementById("fltSc"+sc).checked = checkIt;
	}
},

chkComp:function (comp,req,onBox) {
	var id = "flt" + comp + ((req) ? "req" : "pro");
	var anti = "flt" + comp + ((req) ? "pro" : "req");
	var item = document.getElementById(id);
	if(!onBox) {
		item.checked = !item.checked;
	}
	if(item.checked) {
		document.getElementById(anti).checked = false;
	}
},

chkAllComps:function () {
	document.getElementById("fltVreq").checked = false;
	document.getElementById("fltSreq").checked = false;
	document.getElementById("fltMreq").checked = false;
	document.getElementById("fltFreq").checked = false;
	document.getElementById("fltDFreq").checked = false;
	document.getElementById("fltXPreq").checked = false;
	document.getElementById("fltVpro").checked = false;
	document.getElementById("fltSpro").checked = false;
	document.getElementById("fltMpro").checked = false;
	document.getElementById("fltFpro").checked = false;
	document.getElementById("fltDFpro").checked = false;
	document.getElementById("fltXPpro").checked = false;
},

clearSearch:function () {
	document.getElementById("fltSearchField").value = "";
},

resetFilter:function () {
	sl.chkAllLvs(true);
	sl.chkAllScs(true);
	sl.chkAllComps();
	sl.clearSearch();
},


// **********************
// SPELL DETAIL FUNCTIONS
// **********************

clearSpellDetail:function () {
	// Empty description
	document.getElementById("spellName").childNodes[0].nodeValue = "Spell Details";
	document.getElementById("spellSchool").childNodes[0].nodeValue = "(click on a spell to display)";
	sl.emptyElement(document.getElementById("spellDetailList"));
	document.getElementById("spellDescription").innerHTML = "";
	// Empty controls
	sl.emptyElement(document.getElementById("knownButtons"));
	sl.emptyElement(document.getElementById("prepButtons"));
	document.getElementById("applyMetamagic").getElementsByTagName("h4")[0].childNodes[0].nodeValue = "No applicable metamagic available";
	var metas = document.getElementById("applyMetamagic").getElementsByTagName("li");
	for(var i=0;i<metas.length;++i) {
		metas[i].className = "invalid";
	}
},

showSpell:function (i) {
	var x = sl.spellXmlDoc.getElementsByTagName("spell")[i];
	// Print spell name
	var name = x.getElementsByTagName("name")[0].childNodes[0].nodeValue;
	document.getElementById("spellName").childNodes[0].nodeValue = name;
	// Print school, subschools, and descriptors
	var school = x.getElementsByTagName("school")[0].childNodes[0].nodeValue;
	var subschool = "";
	if(x.getElementsByTagName("subschool").length > 0) {
		subschool = " (" + x.getElementsByTagName("subschool")[0].childNodes[0].nodeValue + ")";
	}
	var descriptors = "";
	if(x.getElementsByTagName("descriptor").length > 0) {
		descriptors += " [";
		d = x.getElementsByTagName("descriptor");
		for(var n=0;n<d.length;++n) {
			if(n > 0) { descriptors += ", "; }
			descriptors += d[n].childNodes[0].nodeValue;
		}
		descriptors += "]";
	}
	document.getElementById("spellSchool").childNodes[0].nodeValue = school + subschool + descriptors;
	// Get spellDetailList & empty it...
	var ul = document.getElementById("spellDetailList");
	sl.emptyElement(ul);
	
	// ...then refill spellDetailList
	levels="";
	var lvs = x.getElementsByTagName("levels")[0].childNodes;
	for(var n=0;n<lvs.length;++n) {
		if(lvs[n].nodeName != "Wiz" || x.getElementsByTagName("levels")[0].getElementsByTagName("Sor").length == 0) {
			if(levels != "") { levels += ", "; }
			var classy;
			if(lvs[n].nodeName == "Sor") {
				classy = "Sor/Wiz";
			} else {
				classy = lvs[n].nodeName;
			}
			levels += classy + " " + lvs[n].childNodes[0].nodeValue;
		}
	}
	var li = document.createElement("li");
	li.appendChild(sl.createSimpleElement("h4","Level"));
	li.appendChild(sl.createSimpleElement("p",levels));
	ul.appendChild(li);
	var components = x.getElementsByTagName("components")[0];
	if(components) {
		var comps = "";
		if(components.attributes.getNamedItem("V").value == "true") {
			comps += "V";
		}
		if(components.attributes.getNamedItem("S").value == "true") {
			if(comps != "") { comps += ", "; }
			comps += "S";
		}
		if(components.attributes.getNamedItem("M").value == "true") {
			if(comps != "") { comps += ", "; }
			comps += "M";
		}
		if(components.attributes.getNamedItem("F").value == "true") {
			if(comps != "") { comps += ", "; }
			comps += "F";
		}
		if(components.attributes.getNamedItem("DF").value == "true") {
			if(comps != "") {
				if(components.attributes.getNamedItem("M").value == "true" || components.attributes.getNamedItem("F").value == "true") {
					comps += "/";
				} else {
					comps += ", ";
				}
			}
			comps += "DF";
		}
		if(components.attributes.getNamedItem("XP").value == "true") {
			if(comps != "") { comps += ", "; }
			comps += "XP";
		}
		var li = document.createElement("li");
		li.appendChild(sl.createSimpleElement("h4","Components"));
		li.appendChild(sl.createSimpleElement("p",comps));
		ul.appendChild(li);
	}
	var castingtime = x.getElementsByTagName("castingtime")[0];
	if(castingtime) {
		var li = document.createElement("li");
		li.appendChild(sl.createSimpleElement("h4","Casting Time"));
		li.appendChild(sl.createSimpleElement("p",castingtime.childNodes[0].nodeValue));
		ul.appendChild(li);
	}
	var range = x.getElementsByTagName("range")[0];
	if(range) {
		var li = document.createElement("li");
		li.appendChild(sl.createSimpleElement("h4","Range"));
		li.appendChild(sl.createSimpleElement("p",range.childNodes[0].nodeValue));
		ul.appendChild(li);
	}
	var tae = x.getElementsByTagName("tae")[0];
	if(tae) {
		var li = document.createElement("li");
		li.appendChild(sl.createSimpleElement("h4",tae.attributes.getNamedItem("label").value));
		li.appendChild(sl.createSimpleElement("p",tae.childNodes[0].nodeValue));
		ul.appendChild(li);
	}
	var duration = x.getElementsByTagName("duration")[0];
	if(duration) {
		var li = document.createElement("li");
		li.appendChild(sl.createSimpleElement("h4","Duration"));
		li.appendChild(sl.createSimpleElement("p",duration.childNodes[0].nodeValue));
		ul.appendChild(li);
	}
	var savingthrow = x.getElementsByTagName("savingthrow")[0];
	if(savingthrow) {
		var li = document.createElement("li");
		li.appendChild(sl.createSimpleElement("h4","Saving Throw"));
		li.appendChild(sl.createSimpleElement("p",savingthrow.childNodes[0].nodeValue));
		ul.appendChild(li);
	}
	var spellresistance = x.getElementsByTagName("spellresistance")[0];
	if(spellresistance) {
		var li = document.createElement("li");
		li.appendChild(sl.createSimpleElement("h4","Spell Resistance"));
		li.appendChild(sl.createSimpleElement("p",spellresistance.childNodes[0].nodeValue));
		ul.appendChild(li);
	}
	// Print spellDescription
	document.getElementById("spellDescription").innerHTML = x.getElementsByTagName("description")[0].childNodes[0].nodeValue;
	
	sl.generateControls(i);
},

generateControls:function (i) {
	// Offer character-centric options for the given spell
	var x = sl.spellXmlDoc.getElementsByTagName("spell")[i];
	var name = x.getElementsByTagName("name")[0].childNodes[0].nodeValue;
	var sc = x.getElementsByTagName("school")[0].childNodes[0].nodeValue;
	var lvs = x.getElementsByTagName("levels")[0].childNodes;
	
	var addLearnButton = function (i,cls,known) {
		var newLi = document.createElement("li");
		var newButton = document.createElement("input");
		newButton.type = "button";
		if(cls == "Wiz") {
			if(known) {
				newButton.value = "Remove from spellbook";
				newButton.title = "Remove from spellbook";
			} else if(sl.character.profile.specialization != "None" && (sl.character.profile.prohibited[0] == sc || sl.character.profile.prohibited[1] == sc)) {
				newButton.value = "Prohibited spell";
				newButton.title = "Prohibited spell";
				newButton.className = "prohibited";
				newButton.disabled = true;
			} else {
				newButton.value = "Add to spellbook";
				newButton.title = "Add to spellbook";
			}
		} else {
			if(known) {
				newButton.value = "Forget as " + cls;
				newButton.title = "Forget as " + cls;
			} else {
				newButton.value = "Learn as " + cls;
				newButton.title = "Learn as " + cls;
			}
		}
		var scopePreserver = function(a,b,c) {
			return function () { sl.learn(a,b,c); };
		};
		newButton.onclick = scopePreserver(i,cls,!known);
		newLi.appendChild(newButton);
		document.getElementById("knownButtons").appendChild(newLi);
	};
	var addPrepButton = function (i,cls) {
		var newLi = document.createElement("li");
		var newButton = document.createElement("input");
		newButton.type = "button";
		if(cls == "Brd" || cls == "Sor") {
			newButton.value = "Cast as " + cls;
			newButton.title = "Cast as " + cls;
		} else {
			newButton.value = "Prepare as " + cls;
			newButton.title = "Prepare as " + cls;
		}
		var scopePreserver = function(a,b) {
			return function () { sl.prepSpell(a,b); };
		};
		newButton.onclick = scopePreserver(i,cls);
		newLi.appendChild(newButton);
		document.getElementById("prepButtons").appendChild(newLi);
	};
	
	sl.emptyElement(document.getElementById("knownButtons"));
	sl.emptyElement(document.getElementById("prepButtons"));
	
	var canCast = false;
	for(var cls in sl.character.profile.levels) {
		var lv = -1;
		for(var j=0;j<lvs.length;++j) {
			if(lvs[j].nodeName == cls) {
				lv = lvs[j].childNodes[0].nodeValue;
				break;
			}
		}
		if(lv > -1) {
			if(cls == "Brd" || cls == "Sor" || cls == "Wiz") {
				addLearnButton(i,cls,sl.isKnown(cls,name));
			}
			if(sl.isCastable(cls,lv,sc,name)) {
				addPrepButton(i,cls);
				canCast = true;
			}
		}
		if(cls == "Clr") {
			var lv = -1;
			for(var j=0;j<lvs.length;++j) {
				if(lvs[j].nodeName == sl.character.profile.domains[0]) {
					lv = parseInt(lvs[j].childNodes[0].nodeValue);
					break;
				}
			}
			for(var j=0;j<lvs.length;++j) {
				if(lvs[j].nodeName == sl.character.profile.domains[1]) {
					var d = parseInt(lvs[j].childNodes[0].nodeValue);
					if(lv == -1 || lv > d) { lv = d; }
					break;
				}
			}
			if(lv > -1) {
				if(sl.isCastable(cls,lv,sc,name)) {
					addPrepButton(i,"Domain");
					canCast = true;
				}
			}
		}
	}
	
	var comps = x.getElementsByTagName("components")[0];
	var metaId = {
		"Empower Spell":"metaEmpower",
		"Enlarge Spell":"metaEnlarge",
		"Extend Spell":"metaExtend",
		"Heighten Spell":"metaHeighten",
		"Maximize Spell":"metaMaximize",
		"Quicken Spell":"metaQuicken",
		"Silent Spell":"metaSilent",
		"Still Spell":"metaStill",
		"Widen Spell":"metaWiden"
	};
	var anyFeat = false;
	for(var i in metaId) {
		var featApply = false;
		if(canCast) {
			for(var j=0;j<sl.character.profile.feats.length;++j) {
				if(i == sl.character.profile.feats[j]) {
					if(i != "Silent Spell" && i != "Still Spell") {
						featApply = true;
						anyFeat = true;
					} else {
						if(comps) {
							if(i == "Silent Spell" && comps.attributes.getNamedItem("V").value == "true") {
								featApply = true;
								anyFeat = true;
							} else if(i == "Still Spell" && comps.attributes.getNamedItem("S").value== "true") {
								featApply = true;
								anyFeat = true;
							}
						} else {
							featApply = true;
							anyFeat = true;
						}
					} 
					break;
				}
			}
		}
		document.getElementById(metaId[i]).disabled = !featApply;
		document.getElementById(metaId[i]).parentNode.className = (featApply) ? "valid" : "invalid";
		document.getElementById(metaId[i]).checked = false;
	}
	document.getElementById("applyMetamagic").getElementsByTagName("h4")[0].childNodes[0].nodeValue = (anyFeat) ? "Apply metamagic:" : "No applicable metamagic available";
},

learn:function(i,cls,learn) {
	var name = sl.spellXmlDoc.getElementsByTagName("spell")[i].getElementsByTagName("name")[0].childNodes[0].nodeValue;
	if(learn) {
		sl.character.known[cls].push(name);
	} else {
		for(var n=0;n<sl.character.known[cls].length;++n) {
			if(sl.character.known[cls][n] == name) {
				sl.character.known[cls].splice(n,1);
				break;
			}
		}
	}
	sl.fillSpellLibrary();
	sl.generateControls(i);
},


// *********************
// SPELL SLOTS FUNCTIONS
// *********************

buildSpellSlots:function () {
	// Empty slotLists
	for(var k=0;k<sl.playerClasses.length;++k) {
		sl.emptyElement(document.getElementById(sl.playerClasses[k] + "SlotList"));
	}
	// Fill slotList
	for(var cls in sl.character.slots) {
		var list = document.getElementById(cls + "SlotList");
		for(var lv=0;lv<sl.character.slots[cls].length;++lv) {
			if(sl.character.slots[cls][lv] && sl.character.slots[cls][lv].length > 0) {
				var lvLi = document.createElement("li");
				lvLi.appendChild(sl.createSimpleElement("h3","Level " + lv));
				var lvUl = document.createElement("ul");
				lvUl.className = "slotBlock";
				for(var n=0;n<sl.character.slots[cls][lv].length;++n) {
					var slot = document.createElement("li");
					slot.id = cls + "Slot" + lv + "_" + n;
					var controls = document.createElement("ul");
					controls.className = "slotButtons";
					var buttons = ["Unprep","Cast","Recall","Exhaust","Refresh"];
					for(var m=0;m<buttons.length;++m) {
						var control = document.createElement("li");
						var button = document.createElement("input");
						button.type = "button";
						button.title = buttons[m];
						button.className = buttons[m].toLowerCase();
						var scopePreserver = function (a,b,c,d) {
							if(d == 0) {
								return function () { sl.unprepSpell(a,b,c); };
							} else if(d == 1) {
								return function () { sl.castSpell(a,b,c); };
							} else if(d == 2) {
								return function () { sl.recallSpell(a,b,c); };
							} else if(d == 3) {
								return function () { sl.exhaustSlot(a,b,c); };
							} else if(d == 4) {
								return function () { sl.refreshSlot(a,b,c); };
							}
						};
						button.onclick = scopePreserver(cls,lv,n,m);
						control.appendChild(button);
						controls.appendChild(control);
					}
					slot.appendChild(controls);
					var link = sl.createSimpleElement("a","");
					slot.appendChild(link);
					var saveDC = sl.createSimpleElement("span","");
					saveDC.className = "saveDC";
					slot.appendChild(saveDC);
					lvUl.appendChild(slot);
				}
				lvLi.appendChild(lvUl);
				list.appendChild(lvLi);
			}
		}
	}
	sl.fillSpellSlots();
},

fillSpellSlots:function () {
	for(var cls in sl.character.slots) {
		for(var lv=0;lv<sl.character.slots[cls].length;++lv) {
			if(sl.character.slots[cls][lv]) {
				for(var n=0;n<sl.character.slots[cls][lv].length;++n) {
					sl.fillSingleSlot(cls,lv,n);
				}
			}
		}
	}
},

fillSingleSlot:function (cls,lv,n) {
	var slot = document.getElementById(cls + "Slot" + lv + "_" + n);
	var status = (sl.character.slots[cls][lv][n].status == "ready" && sl.character.slots[cls][lv][n].spell == null) ? "empty" : sl.character.slots[cls][lv][n].status;
	slot.className = sl.character.slots[cls][lv][n].type + " " + status;
	if(sl.character.slots[cls][lv][n].spell) {
		var link = slot.getElementsByTagName("a")[0];
		link.childNodes[0].nodeValue = sl.character.slots[cls][lv][n].spell.name;
		link.href = "#" + sl.character.slots[cls][lv][n].spell.ref;
		var scopePreserver = function (a) {
			return function () {
				sl.showSpell(a);
			};
		};
		sl.convertLink(link,scopePreserver(sl.character.slots[cls][lv][n].spell.ref));
		slot.getElementsByTagName("span")[0].childNodes[0].nodeValue = sl.character.slots[cls][lv][n].spell.saveDC;
	} else {
		var link = slot.getElementsByTagName("a")[0];
		link.href = "#";
		link.childNodes[0].nodeValue = "(Empty)";
		sl.convertLink(link,function () {});
		slot.getElementsByTagName("span")[0].childNodes[0].nodeValue = "";
	}
},

prepSpell:function (i,cls) {
	var x = sl.spellXmlDoc.getElementsByTagName("spell")[i];
	
	var name = x.getElementsByTagName("name")[0].childNodes[0].nodeValue;
	var dom = false;
	var lv = -1;
	if(cls == "Domain") {
		cls = "Clr";
		dom = true;
		if(x.getElementsByTagName(sl.character.profile.domains[0])[0]) {
			lv = parseInt(x.getElementsByTagName(sl.character.profile.domains[0])[0].childNodes[0].nodeValue);
		}
		if(x.getElementsByTagName(sl.character.profile.domains[1])[0]) {
			var d = parseInt(x.getElementsByTagName(sl.character.profile.domains[1])[0].childNodes[0].nodeValue);
			if(lv == -1 || lv > d) { lv = d; }
		}
	} else {
		lv = parseInt(x.getElementsByTagName(cls)[0].childNodes[0].nodeValue);
	}
	var school = x.getElementsByTagName("school")[0].childNodes[0].nodeValue;
	var components = x.getElementsByTagName("components")[0];
	var minSlot = lv;
	var metamagic = [];
	if(document.getElementById("metaEmpower").checked) {
		metamagic.push("Empowered");
		minSlot += 2;
	}
	if(document.getElementById("metaEnlarge").checked) {
		metamagic.push("Enlarged");
		minSlot += 1;
	}
	if(document.getElementById("metaExtend").checked) {
		metamagic.push("Extended");
		minSlot += 1;
	}
	if(document.getElementById("metaHeighten").checked) {
		h = parseInt(document.getElementById("metaHeightenValue").value);
		metamagic.push("Heightened by " + h);
		minSlot += h;
		lv += h;
	}
	if(document.getElementById("metaMaximize").checked) {
		metamagic.push("Maximized");
		minSlot += 3;
	}
	if(document.getElementById("metaQuicken").checked) {
		metamagic.push("Quickened");
		minSlot += 4;
	}
	if(document.getElementById("metaSilent").checked && (!components || components.attributes.getNamedItem("V").value == "true")) {
		metamagic.push("Silent");
		minSlot += 1;
	}
	if(document.getElementById("metaStill").checked && (!components || components.attributes.getNamedItem("S").value == "true")) {
		metamagic.push("Stilled");
		minSlot += 1;
	}
	if(document.getElementById("metaWiden").checked) {
		metamagic.push("Widened");
		minSlot += 1;
	}
	
	var abil;
	if(cls == "Wiz") { abil = sl.character.profile.abilities["Int"]; }
	else if(cls == "Brd" || cls == "Sor") { abil = sl.character.profile.abilities["Cha"]; }
	else { abil = sl.character.profile.abilities["Wis"]; }
	abil = parseInt(abil);
	var mod;
	if(abil%2 == 0) {
		mod = (abil - 10)/2;
	} else {
		mod = (abil - 11)/2;
	}
	var focus = 0;
	for(var m=0;m<sl.character.profile.feats.length;++m) {
		if(sl.character.profile.feats[m] == "Spell Focus " + school || sl.character.profile.feats[m] == "Greater Spell Focus " + school) { focus += 1; }
	}
	var DC = 10 + lv + mod + focus;
	
	var spell = {};
	if(metamagic.length > 0) {
		spell.name = name + " (" + metamagic.join(", ") + ")";
	} else {
		spell.name = name;
	}
	spell.ref = i;
	spell.school = school;
	spell.minSlot = minSlot;
	spell.saveDC = "DC" + DC;
	
	var set = false;
	for(var lv=minSlot;lv<sl.character.slots[cls].length;++lv) {
		var min;
		var max;
		if((cls == "Clr" && lv > 0 && !dom) || (cls == "Wiz" && sl.character.profile.specialization != "None" && sl.character.profile.specialization != school)) {
			min = 1;
			max = sl.character.slots[cls][lv].length;
		} else if(cls == "Clr" && dom) {
			min = 0;
			max = 1;
		} else {
			min = 0;
			max = sl.character.slots[cls][lv].length;
		}
		for(var n=min;n<max;++n) {
			if(sl.character.slots[cls][lv][n].status == "ready" && sl.character.slots[cls][lv][n].spell == null) {
				sl.character.slots[cls][lv][n].spell = spell;
				if(cls == "Brd" || cls == "Sor") { sl.character.slots[cls][lv][n].status = "cast"; }
				sl.fillSingleSlot(cls,lv,n);
				sl.showLibMode("repertoire",cls);
				set = true;
				break;
			}
		}
		if(set) { break; }
	}
	if(!set) { alert("No available spell slots!"); }
},

unprepSpell:function (cls,lv,n) {
	// Check whether spell can be unprepped
	if(sl.character.slots[cls][lv][n].status != "ready") { return ; }
	// Set stored spell to null, write to slotList
	sl.character.slots[cls][lv][n].spell = null;
	// Fill in other spells as appropriate
	var filled = false;
	for(var sv=lv;sv<sl.character.slots[cls].length;++sv) {
		var min;
		var max;
		if(cls == "Clr") {
			if(sv > 0 && n == 0) {
				if(sv == lv) { min = n + 1; } else { min = 0; }
				max = 1;
			} else {
				if(sv == lv) { min = n + 1; } else { min = 1; }
				max = sl.character.slots[cls][sv].length;
			}
		} else {
			if(sv == lv) { min = n + 1; } else { min = 0; }
			max = sl.character.slots[cls][sv].length;
		}
		for(var sn=min;sn<max;++sn) {
			if(sl.character.slots[cls][sv][sn].status == "ready" && sl.character.slots[cls][sv][sn].spell != null) {
				if(sl.character.slots[cls][sv][sn].spell.minSlot <= lv) {
					if(cls != "Wiz" || n != 0 || sl.character.profile.specialization == "None" || sl.character.profile.specialization == sl.character.slots[cls][sv][sn].spell.school) {
						sl.character.slots[cls][lv][n].spell = sl.character.slots[cls][sv][sn].spell;
						sl.unprepSpell(cls,sv,sn);
						filled = true;
						break;
					}
				}
			}
		}
		if(filled) { break; }
	}
	sl.fillSingleSlot(cls,lv,n);
},

castSpell:function (cls,lv,n) {
	if(sl.character.slots[cls][lv][n].status == "ready" && sl.character.slots[cls][lv][n].spell != null) {
		sl.character.slots[cls][lv][n].status = "cast";
		document.getElementById(cls+"Slot"+lv+"_"+n).className = sl.character.slots[cls][lv][n].type + " cast";
	}
},

recallSpell:function (cls,lv,n) {
	if(sl.character.slots[cls][lv][n].status == "cast") {
		sl.character.slots[cls][lv][n].status = "ready";
		if(cls == "Brd" || cls == "Sor") {
			sl.unprepSpell(cls,lv,n);
		} else {
			document.getElementById(cls+"Slot"+lv+"_"+n).className = sl.character.slots[cls][lv][n].type + " ready";
		}
	}
},

exhaustSlot:function (cls,lv,n) {
	if(sl.character.slots[cls][lv][n].status == "ready") {
		sl.character.slots[cls][lv][n].status = "exhausted";
		document.getElementById(cls+"Slot"+lv+"_"+n).className = sl.character.slots[cls][lv][n].type + " exhausted";
	}
},

refreshSlot:function (cls,lv,n) {
	if(sl.character.slots[cls][lv][n].status == "exhausted") {
		if((cls == "Brd" || cls == "Sor") && sl.character.slots[cls][lv][n].spell != null) {
			sl.character.slots[cls][lv][n].status = "cast";
			document.getElementById(cls+"Slot"+lv+"_"+n).className = sl.character.slots[cls][lv][n].type + " cast";
		} else {
			sl.character.slots[cls][lv][n].status = "ready";
			if(sl.character.slots[cls][lv][n].spell == null) {
				sl.unprepSpell(cls,lv,n);
			} else {
				document.getElementById(cls+"Slot"+lv+"_"+n).className = sl.character.slots[cls][lv][n].type + " ready";
			}
		}
	}
},




yo:"yo"

};

// Append initialization function to page load
sl.addEvent(window,"load",sl.initialize);

