antimarty birdform script

Version 0.3.1 - grant needed GM_ permissions, improve efficiency, track some KOL changes, move to Greasy Fork

// antimarty's bird form attack counter
// counts your uses of birdform attacks on your way to a glimmering roc feather in Kingdom of Loathing 
//
// mostly based on the antimarty fortune cookie script, which was mostly stolen
// script update code based on DrEvi1's hatrack helper, which credits Picklish
// support for wild hare, greatest pants, and organ grinder added by knitbone
//
// Released under the GPL license
// http://www.gnu.org/copyleft/gpl.html
//
// ==UserScript==
// @name	   antimarty birdform script
// @namespace	   antimarty
// @include	   *kingdomofloathing.com/*.php*
// @include	   *127.0.0.1:600*/*.php*
// @include	   *localhost:600*/*.php*
// @version		0.3.2
// @grant       GM_getValue
// @grant       GM_setValue
// @grant		GM_xmlhttpRequest
// 
// @description	   Version 0.3.1 - grant needed GM_ permissions, improve efficiency, track some KOL changes, move to Greasy Fork
//
// ==/UserScript==

// released versions:
// Version 0.2.7 - angry jung man, unconscious collective, gnome adventures.
// Version 0.2.6 - "new" Mr store fams (with only two weeks left in Mr Store)
// Version 0.2.5 - add support for stompin boots
// Version 0.2.4 - new navel run percentages, typo corrections, xenomorph, new update method
// Version 0.2.3 counts v-mask advs, hipster fights, greats pants runaways, organ grinder parts/pies, wild hare advs
// Version 0.2.2 handles generic drops more generically, handle multiround combat macros
// Version 0.2.0 counts floaty sand drops, squamous gibberer bonus adventures, game grid token drops
// Version 0.1.9 counts baby sandworm drops, hobo underling summons, mayfly summons
// Version 0.1.8 more robust reset on new day, , fix auto-update bug
// Version 0.1.7 fix navel ring runaway counting - only free runaways count
// Version 0.1.6 add navel ring, bandersnatch runaway counters
// Version 0.1.5 fix for mafia "recently seen" changes
// Version 0.1.3 fix for drop counts bug introduced in 0.1.2 
// Version 0.1.2 bugfix for drop counts when ascending
// Version 0.1.1 trivial update: update link opens in new window
// Version 0.1.0 add astral badger drops; check for updates
// Version 0.0.9 Count daily gong drops
// Version 0.0.8 bugfix for autoattacks
// Version 0.0.7 can manually clear and hide the counter
// Version 0.0.6 support compact mode
// version 0.0.5 another bug fix for "plural" opponents
// version 0.0.4 bug fix for "plural" opponents
// version 0.0.3 support for elemental attacks
// Version 0.0.1 initial beta

// Known bugs:

/* // (temporarily?) removed for move to greasy fork
var currentVersion = "0.3.1";
var scriptSite = "http://userscripts.org/scripts/show/28640"
// this is a small file autogenerated by userscripts.org from Userscript @ comments above, use to reduce bandwidth on version check
var scriptURL = "http://userscripts.org/scripts/source/28640.meta.js";  
 */
 
var ATTACK_NONE = -1;
var ATTACK_TALON_SLASH = 0;
var ATTACK_WING_BUFFET = 1;
var ATTACK_STATUE_TREATMENT = 2;
var ATTACK_THE_BIRD = 3;
var ATTACK_ANTARCTIC_FLAP = 4;
var ATTACK_RISE_FROM_ASHES = 5;
var ATTACK_FEAST_ON_CARRION = 6;

// can't use the symbolic names here, not set yet, I guess
// text is from You -> first comma or period
var attackTexts = {
	"You repeatedly slash your opponent with your razor-sharp talons,": 0, 
	"You repeatedly slash your opponents with your razor-sharp talons,": 0,  //  multiple oppenents like dread squad 
	"You furiously flap your wings,": 1,
	"You pretend that your opponent is a statue and do what comes naturally,": 2,  // seems to work even for multiple opponents
	"You give your opponent \"the bird.": 3,
	"You give your opponents \"the bird.": 3,  //  multiple oppenents like dread squad 
	"You flap your wings,": 4,
	"You fold your wings around you and burst into flames.": 5,
	"You start eating bits of your opponent's flesh,": 6,
	"You start eating bits of your opponents' flesh,": 6,
};	  

var attackSuffixes = {
	0: "_talon_slash", 
	1: "_wing_buffet",
	2: "_statue_treatment",
	3: "_the_bird",
	4: "_antarctic_flap",
	5: "_rise_from_ashes",
	6: "_feast_on_carrion",
};	  

// chance of a navel ring runaway after successful try x 
var navelPercentages = [ 100, 100, 100, 80, 80, 80, 50, 50, 50, 20, 20 ];

// turns til next pie on try x
var grinderTurns = [ 5, 10, 16, 23, 31, 50, 50, 50, 50, 50, 50 ];
var stogieTurns = [ 5, 5, 11, 18, 26, 45, 45, 45, 45, 45, 45 ];

// all the random drops we count - just add a row to the "array" to add an item to count
// [in-game drop text, gm_counterName, gm_seenItFlag, our label, daily max]
var DROP_TEXT_IDX = 0;  // deprecated
var DROP_GM_VAR_IDX = 0;
var DROP_GM_FLAG_IDX = 1;
var DROP_LABEL_IDX = 2;
var DROP_DAILY_MAX_IDX = 3;
// there has to be a better way to make a 2 dimensional global array
// to add a "normal" drop item - just add a line here, rest should be automatic
var allDrops = {  // keyed to in-game text for the drop
	"astral mushroom":[ 			"_mushroomDrops", 	"_hasBadger", 	"Astral Mushroom Drops", 	"5"],
	"llama lama gong":[ 			"_gongDrops", 		"_hasLlama", 	"Llama Gong Drops", 		"5"],
	"tiny bottle of absinthe":[ 	"_absintheDrops", 	"_hasPixie", 	"Absinthe Drops", 			"5"],
	"disintegrating sheet music":["_musicDrops", 		"_hasTroll", 	"Sheet Music Drops", 		"?"],
	"Spooky Putty monster":[ 		"_puttyUses", 		"_hasPutty", 	"Putty Uses", 				"5"],
	"agua de vida":[ 				"_aguaDrops", 		"_hasSandworm", "Agua De Vida Drops", 		"5"],
	"Game Grid token":[ 			"_GameGridDrops", 	"_hasRogueProgram", "Game Grid Token Drops","5"],
	"transporter transponder":["_transponderDrops","_hasXenomorph", "Transponder Drops", 	"5"],
	// a zillion kinds of paste, oh well
	"beastly paste":["_pasteDrops","_hasBoots", "Boot Stomps", 	"7"],
	"bug paste":["_pasteDrops","_hasBoots", "Boot Stomps", 	"7"],
	"chlorophyll paste":["_pasteDrops","_hasBoots", "Boot Stomps", 	"7"],
	"cosmic paste":["_pasteDrops","_hasBoots", "Boot Stomps", 	"7"],
	"Crimbo paste":["_pasteDrops","_hasBoots", "Boot Stomps", 	"7"],
	"demonic paste":["_pasteDrops","_hasBoots", "Boot Stomps", 	"7"],
	"ectoplasmic paste":["_pasteDrops","_hasBoots", "Boot Stomps", 	"7"],
	"elemental paste":["_pasteDrops","_hasBoots", "Boot Stomps", 	"7"],
	"fishy paste":["_pasteDrops","_hasBoots", "Boot Stomps", 	"7"],
	"goblin paste":["_pasteDrops","_hasBoots", "Boot Stomps", 	"7"],
	"gooey paste":["_pasteDrops","_hasBoots", "Boot Stomps", 	"7"],
	"greasy paste":["_pasteDrops","_hasBoots", "Boot Stomps", 	"7"],
	"hippy paste":["_pasteDrops","_hasBoots", "Boot Stomps", 	"7"],
	"hobo paste":["_pasteDrops","_hasBoots", "Boot Stomps", 	"7"],
	"indescribably horrible paste":["_pasteDrops","_hasBoots", "Boot Stomps", 	"7"],
	"Mer-kin paste":["_pasteDrops","_hasBoots", "Boot Stomps", 	"7"],
	"oily paste":["_pasteDrops","_hasBoots", "Boot Stomps", 	"7"],
	"orc paste":["_pasteDrops","_hasBoots", "Boot Stomps", 	"7"],
	"penguin paste":["_pasteDrops","_hasBoots", "Boot Stomps", 	"7"],
	"pirate paste":["_pasteDrops","_hasBoots", "Boot Stomps", 	"7"],
	"slimy paste":["_pasteDrops","_hasBoots", "Boot Stomps", 	"7"],
	"strange paste":["_pasteDrops","_hasBoots", "Boot Stomps", 	"7"],
	// lobster in the crown of thrones will probably break this. And faxed clod hopper definitely does.
	"floaty sand":["_floatySandDrops","_hasLobster", "Floaty Sand Drops", 	"?"],
	"devilish folio":["_devilishFolioDrops","_hasKloop", "Devilish Folio Drops", 	"5"],
	"groose grease":["_grooseGreaseDrops","_hasGroose", "Groose Grease Drops", 	"5"],
	// TWO zillion kinds of siphoned spirits, oh well oh well
	// some names are subsets of others, and confuse this - e.g. the sloe comfortable zoo (on fire)
//	33":[beastly paste","_pasteDrops","_hasBoots", "Boot Stomps", 	"7"],
	"Zoodriver":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Sloe Comfortable Zoo":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Sloe Comfortable Zoo on Fire":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Grasshopper":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Locust":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Plague of Locusts":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Green Velvet":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Green Muslin":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Green Burlap":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Dark & Starry":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Black Hole":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Event Horizon":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Lollipop Drop":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Candy Alexander":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Candicaine":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Suffering Sinner":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Suppurating Sinner":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Sizzling Sinner":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Drac & Tan":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Transylvania Sling":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Shot of the Living Dead":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Firewater":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Earth and Firewater":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Earth, Wind and Firewater":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Caipiranha":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Flying Caipiranha":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Flaming Caipiranha":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Buttery Knob":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Slippery Knob":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Flaming Knob":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Humanitini":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"More Humanitini than Humanitini":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Oh, the Humanitini":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Red Dwarf":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Golden Mean":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Green Giant":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Fauna Libre":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Chakra Libre":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Aura Libre":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Mohobo":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Moonshine Mohobo":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Flaming Mohobo":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Great Old Fashioned":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Fuzzy Tentacle":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Crazymaker":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Punchplanter":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Doublepunchplanter":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Haymaker":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Cement Mixer":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Jackhammer":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Dump Truck":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Sazerorc":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Sazuruk-hai":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Flaming Sazerorc":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Herring Daiquiri":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Herring Wallbanger":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Herringtini":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Aye Aye":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Aye Aye, Captain":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Aye Aye, Tooth Tooth":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Slimosa":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Extra-slimy Slimosa":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Slimebite":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Drunken Philosopher":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Drunken Neurologist":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	"Drunken Astrophysicist":["_spiritDrops","_hasMedium", "Siphoned Spirits", 	"?"],
	// oh boy, 100 items
	"Rain-Doh box full of monster":[ 		"_Rain-DohUses", 		"_hasRain-Doh", 	"Rain-Doh Uses", 				"5"],
	"psychoanalytic jar":[ 		"_analyticJarDrops", 		"_hasJungMan", 	"Analytic Jar Drops", 				"1"],
	"Unconscious Collective Dream Jar":[ 		"_dreamJarDrops", 		"_hasCollective", 	"Dream Jar Drops", 				"5"],
	// buddy bjorn/crown of thrones will break these
	"grimstone mask":[ 		"_grimstoneMaskDrops", 		"_hasGrimGolem", 	"Grimstone Mask Drops", 				"1"],
	"grim fairy tale":[ 		"_grimFairyTaleDrops", 		"_hasGrimBrother", 	"Grim Fairy Tale Drops", 				"5"],
	"hot ashes":[ 		"_hotAshesDrops", 		"_hasGallopingGrill", 	"Hot Ashes Drops", 				"5"],
	"carrot nose":[ 		"_carrotNoseDrops", 		"_hasSnowSuit", 	"Carrot Nose Drops", 		"3"],
	
};

// second array for pie drops. although counting functions and combat displays are the same, account page displays should not loop
var pieDrops = {
	0:["liver and let pie",				"_PieDrops",		"_hasOrganGrinder", "Pie Drops",	"?"],
	1:["stomach turnover",				"_PieDrops",		"_hasOrganGrinder", "Pie Drops",	"?"],
	2:["piping organ pie",				"_PieDrops",		"_hasOrganGrinder", "Pie Drops",	"?"],
	3:["dead lights pie",				"_PieDrops",		"_hasOrganGrinder", "Pie Drops",	"?"],
	4:["throbbing organ pie",			"_PieDrops",		"_hasOrganGrinder", "Pie Drops",	"?"],
	5:["igloo pie",					"_PieDrops",		"_hasOrganGrinder", "Pie Drops",	"?"],
	6:["shoo-fish pie",				"_PieDrops",		"_hasOrganGrinder", "Pie Drops",	"?"],
	7:["badass pie",					"_PieDrops",		"_hasOrganGrinder", "Pie Drops",	"?"],
};

// pie part collecting text messages - don't track type for now
var pieParts = {  // do they change for multiple opponents?
	0:["a few choice bits to put in his grinder",	"_pieParts",		"_hasOrganGrinder", "Parts",	"?"],
	1:["smells like Betty, guv",					"_pieParts",		"_hasOrganGrinder", "Parts",	"?"],
	2:["burning his Longers and Lingers",			"_pieParts",		"_hasOrganGrinder", "Parts",	"?"],
	3:["the upper story on his Gregory",			"_pieParts",		"_hasOrganGrinder", "Parts",	"?"],
	4:["My Hampton has a funny feeling",			"_pieParts",		"_hasOrganGrinder", "Parts",	"?"],
	5:["catch his death of Boris",					"_pieParts",		"_hasOrganGrinder", "Parts",	"?"],
	6:["getting his Tony Blair wet",				"_pieParts",		"_hasOrganGrinder", "Parts",	"?"],
	7:["a bloody Pich and Toss",					"_pieParts",		"_hasOrganGrinder", "Parts",	"?"],
};	  

////////////////////////////////////////////////////////////////////////////////
// Based on a function taken from OneTonTomato's UpUp skill script
function GM_get(target, callback) {
   GM_xmlhttpRequest({
	  method: 'GET',
	  url: target,
	  onload:function(details) {
		 if( typeof callback=='function' ){
			callback(details.responseText);
		 }
	  }
   });
}

// Check for an updated script version
function CheckScriptVersion(data)
{
    // Preemptively set error, in case request fails...
    GM_setValue("webVersion", "Error")

	var m = data.match(/@version\s*([0-9.]+)/);
	if (m)
	{
		GM_setValue("webVersion", m[1]);
	}
}

////////////////////////////////////////////////////////////////////////////////
// parse the char sheet (not the sidepane) for the player name
function getPlayerNameFromCharsheet(data) {
	// it's an href with syntax something like 
	// showplayer.php?who=PlayerID">PlayerName</a>
	var playerName = /showplayer\.php\?who\=\d+\">([^<]+)<\/a/i.exec(data);  // sometimes this fails, don't know why
//	alert("got player name from charsheet: " + playerName);
	if(playerName)
		return playerName[1].toLowerCase();
	else
		return null;
}

////////////////////////////////////////////////////////////////////////////////
// get player name from the sidepane html
// code taken from kolpreviousnadventures script
function getPlayerNameFromCharpane() {
	var username = document.getElementsByTagName("b");
	if (!username || username.length < 1) return false;
	username = username[0];
	if (!username) return false;
	username = username.firstChild;
	if (!username) return false;
	// in full mode the link is <a><b>Name</b></a>
	// in compact mode it's <b><a>Name</a></b>
	// so have to handle this, and also can use it to tell
	// whether it's in compact mode or not.
	var fullmode = true;
	while (username && username.nodeType == 1)
	{
		username = username.firstChild;
		fullmode = false;
	}
	if (!username) return false;
	username = username.nodeValue;
	if (!username) return false;
	username = username.toLowerCase();
//	alert("found username " + username + ", fullmode: " + fullmode);
	return {'username': username, 'fullmode': fullmode};
}

function getDaysPlayed(data) {
	var dayCount = 0;
	if(data.indexOf("Days Played (this run)") >= 0) {
// alert("parsing datasheet for days for an ascended char");
		dayCount = /Days Played \(this run\)[^>]*>(<[^>]+>)*([\d,]+)/i.exec(data)[2];
	}
	else {
// alert("parsing datasheet for days for an UNascended char");
		dayCount = /Days Played[^>]*>(<[^>]+>)*([\d,]+)/i.exec(data)[2];
	}
// alert("found dayCount=" + dayCount);
	return parseInt(dayCount.replace(',',''),10);
}

////////////////////////////////////////////////////////////////////////////////
// watch for use of a birdform attack on the fight page
function  checkForBirdAttack(data) {
	var birdAttackFound = new Array;
	var attackText = data.match(/You (.*?)[\.\,]/gi);  // the .*? is the non-greedy version of .*

	if(attackText != null) {
		for ( var i = 0; i < attackText.length; ++i ){
			if(attackTexts[attackText[i]] != undefined) {
				birdAttackFound.push(attackTexts[attackText[i]]);
	// alert("found bird attack " + birdAttackFound + " (" + attackText[i] + ")");
//				break;
			}
		}
	}
	
	return birdAttackFound;
}

////////////////////////////////////////////////////////////////////////////////
// get monster name, need this to check for hipster
// the from putty doesn't work, need to make it persistent after 1st fight page
function getMonsterName(data) {
	var monsterName = /id=\"monname\">(.*?)<\/span>/i.exec(data);
	if( monsterName )
		monsterName = monsterName[1];
	else
		monsterName = "";
	
	var fromPutty = data.indexOf("You put the copied monster on the ground") != -1 ? true : false;

	return {'monsterName': monsterName, 'fromPutty': fromPutty};
}
	
function getRunAwayButton() {
	var allElements = document.getElementsByTagName("input");
	var button = null;

	for(var i=0; i < allElements.length; i++) {
		if(allElements[i].value == "Run Away" ) {  // if using mafia, will have some other text added, and fail (which we want)
			button = allElements[i];
			break;
		}	 
	}

	return button;
}

// also counts GA pants, peppermint parasol (partially!)
function checkForNavelRingRunaway(data) {
	// only counts as a runaway if succeeds (for purposes of success rate next time)
	if(data.indexOf( "you quickly float away" ) != -1
	|| data.indexOf( "your pants suddenly activate") != -1
	|| data.indexOf( "You hold up the parasol") != -1)
		return true;
	else
		return false;
}

function checkForSnatchRunaway(data) {
	if(data.indexOf( "the combat becomes a small dot" ) != -1 
	|| data.indexOf( "kicks you in the butt to speed your escape") != -1)
		return true;
	else
		return false;
}

function checkForUnderlingSummon(data) {
	if(data.indexOf( "A hobo runs up to you" ) != -1 )
		return true;
	else
		return false;
}

function checkForMayflySummon(data) {
	if(data.indexOf( "You open the little container full of mayfly bait" ) != -1 )
		return true;
	else
		return false;
}

function checkForGibbererAdv(data) {
	if(data.indexOf( "you feel time slow down" ) != -1 )
		return true;
	else
		return false;
}

function checkForGnomeAdv(data) {
	if(data.indexOf( "scrubs the mildew out of your grout" ) != -1 
	|| data.indexOf( "bundles your recycling for you" ) != -1 
	|| data.indexOf( "teaches you how to power-nap instead of sleeping all day" ) != -1 
	|| data.indexOf( "sharpens all your pencils and lines them up in a neat row" ) != -1 
	|| data.indexOf( "folds all your clean laundry" ) != -1 
	|| data.indexOf( "shows you how to shave a full minute off of your teeth brushing routine" ) != -1 
	|| data.indexOf( "organizes your sock drawer and alphabetizes your spice rack" ) != -1 
	|| data.indexOf( "hauls all of your scrap lumber to the dump" ) != -1 
	|| data.indexOf( "does all that tedious campsite cleaning you were going to do today" ) != -1 )
		return true;
	else
		return false;
}

function checkForHareAdv(data) {
	if(data.indexOf( "Two days slow, that's what it is" ) != -1 )
		return true;
	else
		return false;
}

function checkForVMaskAdv(data) {
	if(data.indexOf( "into last week. It saves you some time" ) != -1 )
		return true;
	else
		return false;
}

// only detect the initial page, so that don't keep re-detecting it
// as coded now, it's redundant to even look at the monster name, but maybe I'll fix it up later
function checkForHipsterAdv(data) {
	var monster = getMonsterName(data);
// alert("In checkForHipsterAdv, monster name is " + monster.monsterName + ", from putty: " + monster.fromPutty);
	if(monster.fromPutty == false) {
		if(monster.monsterName == "an angry bassist" && data.indexOf("a lanky dude with a knit cap") != -1
		|| monster.monsterName == "a blue-haired girl" && data.indexOf( "a girl with bright blue hair skates") != -1
		|| monster.monsterName == "an evil ex-girlfriend"  && data.indexOf( "A skinny woman in an over-sized sweater") != -1
		|| monster.monsterName == "a peeved roommate" && data.indexOf(  "an immaculately-dressed-and-coiffed guy") != -1
		|| monster.monsterName == "a random scenester" && data.indexOf( "A random hipster" ) != -1 )
			return true;
	}
	return false;
}

// return list of dropped items (as the bold html elements)
function getDrops(  ) {
	// limit our checks to first table in the content div  - allows us to skip extra "previously seen" repeats
	var allElements = document.getElementById("content_").getElementsByTagName("table")[0].getElementsByClassName("item");
	var drops = [];
	if(allElements) {
		for(var i=0; i < allElements.length; i++) {
			drops.push(allElements[i].getElementsByTagName("b")[0]);
		}
	}
	return drops;
}

function checkForDrop( dropText ) {
	// limit our checks to the content div  - allows us to skip extra "previously seen" repeats
	divElement = document.getElementById("content_");
	var allElements = divElement.getElementsByTagName("table")[0].getElementsByClassName("item");
	var dropElement = null;
	if(allElements) {
		for(var i=0; i < allElements.length; i++) {
			if(allElements[i].innerHTML.indexOf( dropText ) != -1 ) {
				dropElement = allElements[i].getElementsByTagName("b")[0];
				break;  // we're done, no matter what (? unless can get 2 of same drop, e.g. grim fairy tale from bjorn)
			}	 
		}
	}
	return dropElement;
}

function checkForPieDrop( dropText ) {
	// Look at all the <td> items, and return the one for the given drop text
	// in theory could check that it's a familiar message, there is a tag <!--familiarmessage-->
	var allElements = document.getElementsByTagName("td");
	// mafia's "previously seen" breaks this, now need to watch for previously seen and discount it
	var dropElement = null;

	for(var i=allElements.length-1; i >= 0; i--) {  // go backwards, to get lowest level one
		if(allElements[i].innerHTML.indexOf( dropText ) != -1 ) {
			dropElement = allElements[i];
			break;  // we're done, no matter what
		}	 
	}

	return dropElement;
}

// from one ton tomato's mallsearch script
function getParent(el, pTagName) {
	if (el == null) {
		return null;
	} else if (el.nodeType == 1 && el.tagName.toLowerCase() == pTagName.toLowerCase()) {	// Gecko bug, supposed to be uppercase
		return el;
	} else {
		return getParent(el.parentNode, pTagName);
	}
}

function getPwdHash(data){
    var pwdHash = /pwdhash \= \"(.*?)\"/i.exec(data)[1];

// alert("got pwdHash: " + pwdHash);
	return pwdHash;
}

function getPlayerLevel(data) {
	var playerLevel = /Level (\d+)/i.exec(data);  // full mode
	if( !playerLevel)
		playerLevel = /Lvl\. (\d+)/i.exec(data);  // compact mode
	if( playerLevel)
		return parseInt(playerLevel[1],10);

	// normal level checks fail if astral spirit
	if(data.indexOf("Astral Spirit") != -1)
		return 0; // astral spirit
	else 
		return -1; // error
}

// check charpane data to see if wearing navel ring
function checkNavelRing(data) {
	if(data.indexOf("Omphaloskepsis") == -1)
		return false;
	else
		return true;
}

// check charpane data to see if Ode to Booze is active
function checkOde(data) {
	if(data.indexOf("Ode to Booze") == -1)
		return false;
	else
		return true;
}
	
// get familiar name (as used by the familiar gif) from charpane
// get weight, also
function getFamiliar(data, fullmode) {
// alert("in getFamiliar, data = " + data);
	// <img src="http://images.kingdomofloathing.com/itemimages/bandersnatch.gif"
	// <img src="/images/itemimages/stompboots.gif"
	var type = "";
	var weight = 0;
	var parsedData;
	if(fullmode == true) {
		parsedData = /itemimages\/([^\.]+)\.gif[^<]+<\/a>[^:]+<b>(\d+)<\/b> pound/i.exec(data);
	}
	else {
		parsedData = /itemimages\/([^\.]+)\.gif[^<]+<\/a><br>(\d+) lbs/i.exec(data);
	}
// alert("parsedData = " + parsedData);
	if(parsedData) {
		type = parsedData[1];
		weight = parseInt(parsedData[2]);
	}
// alert("found familiar type = " + type + ", weight = " + weight);	
	return { 'type' : type, 'weight': weight };
}

////////////////////////////////////////////////////////////////////////////////
// init all the tracking vars when entering birdform
function setBirdform(playerName) {
	GM_setValue(playerName+"_hasBirdform",true);

	GM_setValue(playerName + attackSuffixes[ATTACK_TALON_SLASH], 0); 
	GM_setValue(playerName + attackSuffixes[ATTACK_WING_BUFFET], 0); 
	GM_setValue(playerName + attackSuffixes[ATTACK_STATUE_TREATMENT], 0); 
	GM_setValue(playerName + attackSuffixes[ATTACK_THE_BIRD], 0); 
	GM_setValue(playerName + attackSuffixes[ATTACK_ANTARCTIC_FLAP], 0); 
	GM_setValue(playerName + attackSuffixes[ATTACK_RISE_FROM_ASHES], 0); 
	GM_setValue(playerName + attackSuffixes[ATTACK_FEAST_ON_CARRION], 0); 
}	

////////////////////////////////////////////////////////////////////////////////
// clear all the tracking vars when leaving birdform
function clearBirdform(playerName) {
	GM_setValue(playerName+"_hasBirdform", false);

	GM_setValue(playerName + attackSuffixes[ATTACK_TALON_SLASH], 0); 
	GM_setValue(playerName + attackSuffixes[ATTACK_WING_BUFFET], 0); 
	GM_setValue(playerName + attackSuffixes[ATTACK_STATUE_TREATMENT], 0); 
	GM_setValue(playerName + attackSuffixes[ATTACK_THE_BIRD], 0); 
	GM_setValue(playerName + attackSuffixes[ATTACK_ANTARCTIC_FLAP], 0); 
	GM_setValue(playerName + attackSuffixes[ATTACK_RISE_FROM_ASHES], 0); 
	GM_setValue(playerName + attackSuffixes[ATTACK_FEAST_ON_CARRION], 0); 
}	

////////////////////////////////////////////////////////////////////////////////
// check for a birdform attack, increment proper count if see one
// also check for all the various drops, and navel ring/bandersnatch runaways
function processFight() {
	var attacksFound = checkForBirdAttack(document.body.innerHTML);
	var playerName = GM_getValue("currentPlayer");
	var attack = attacksFound.pop();
	while(attack != null) {
		var hasBirdform = GM_getValue(playerName+"_hasBirdform",false);
		
		// force birdform if it's not set (e.g. used the llama gong from mafia cli but playing manually)
		if(hasBirdform == false) {
			setBirdform(playerName);
		}
		
		// increment the proper attack count
		var attackCount = GM_getValue(playerName+attackSuffixes[attack],0);
		GM_setValue(playerName+attackSuffixes[attack],attackCount+1);
		attack = attacksFound.pop();
	}
	
	// check for all the standard drops, and decorate them
	{	
		var drops = getDrops();
		var i;
		for(i in drops) {
			var dropText = drops[i].innerHTML;
// alert("checking drop " + dropText + " against list");
			if(allDrops[dropText] != null) {
				var dropCount = GM_getValue(playerName+allDrops[dropText][DROP_GM_VAR_IDX],0)+1;
//		alert("new drops value is " + dropCount);
				GM_setValue(playerName+allDrops[dropText][DROP_GM_VAR_IDX],dropCount);
				GM_setValue(playerName+allDrops[dropText][DROP_GM_FLAG_IDX], true);  // a one time flag, never reset. Easier than checking terrarium!

				// kind of bogus, but the best I could come up with
				if(allDrops[dropText][DROP_GM_FLAG_IDX]=="_hasPutty" && GM_getValue(playerName+"_hasRain-Doh")==true) {
					var dropsLeft = Math.min(5, Math.max(0,6 - GM_getValue(playerName+"_Rain-DohUses",0)));
					allDrops[dropText][DROP_DAILY_MAX_IDX] = dropsLeft.toString();
				}
				else if(allDrops[dropText][DROP_GM_FLAG_IDX]=="_hasRain-Doh" && GM_getValue(playerName+"_hasPutty")==true) {
					var dropsLeft = Math.min(5, Math.max(0,6 - GM_getValue(playerName+"_puttyUses",0)));
					allDrops[dropText][DROP_DAILY_MAX_IDX] = dropsLeft.toString();
				}					
				
				// add a bit of html to decorate the drop
				var newElement = document.createElement("FONT");
				newElement.innerHTML = "<font size=0>" + " (" + dropCount + "/" + allDrops[dropText][DROP_DAILY_MAX_IDX] + ")" + "</font>";
				drops[i].parentNode.insertBefore(newElement, drops[i].nextSibling);
			}
		}
	}

	// check for organ grinder drops, and decorate them
	if(GM_getValue(playerName+"_usingGrinder", false)) {
		var i;
		var gotPie = false;
		//pies
		for(i in pieDrops) {
			var dropElement = checkForDrop(pieDrops[i][DROP_TEXT_IDX]);
			if(dropElement != null) {
				var drops = GM_getValue(playerName+pieDrops[i][DROP_GM_VAR_IDX],0)+1;
				GM_setValue(playerName+pieDrops[i][DROP_GM_VAR_IDX],drops);
				GM_setValue(playerName+pieDrops[i][DROP_GM_FLAG_IDX], true);  // a one time flag, never reset. Easier than checking terrarium!
				gotPie = true;
				
				// add a bit of html to decorate the drop
				var gturns = drops + 1 <= grinderTurns.length ? grinderTurns[drops] : grinderTurns[grinderTurns.length - 1];
				var sturns = drops + 1 <= stogieTurns.length ? stogieTurns[drops] : stogieTurns[stogieTurns.length - 1];
				var newElement = document.createElement("FONT");
				var tempText = "<font size=0><br />" + "Pie #" + drops + ". Next pie needs " + gturns + " parts";
				if(sturns != gturns) 
					tempText += ", or " + sturns + " with stogie.</font>";
				else
					tempText += ".</font>";
				newElement.innerHTML = tempText;
				dropElement.parentNode.insertBefore(newElement, dropElement.nextSibling);

				// set pie part counter back to 0 when a pie drops
				GM_setValue(playerName+pieParts[i][DROP_GM_VAR_IDX],0);

				break; // can only get one pie per fight
			}
		}
		// pie parts - don't bother if grinder handed us a pie this turn
		if(!gotPie) {
			for(i in pieParts) {
				var dropElement = checkForPieDrop(pieParts[i][DROP_TEXT_IDX]);
				if(dropElement != null) {
					var parts = GM_getValue(playerName+pieParts[i][DROP_GM_VAR_IDX],0)+1;
					GM_setValue(playerName+pieParts[i][DROP_GM_VAR_IDX],parts);
					GM_setValue(playerName+pieParts[i][DROP_GM_FLAG_IDX], true);  // a one time flag, never reset. Easier than checking terrarium!

					// add a bit of html to decorate the drop
					var pies = GM_getValue(playerName+pieDrops[i][DROP_GM_VAR_IDX],0);
					var gturns = pies < grinderTurns.length ? grinderTurns[pies] : grinderTurns[grinderTurns.length - 1];
					var sturns = pies < stogieTurns.length ? stogieTurns[pies] : stogieTurns[stogieTurns.length - 1];
					var tempText = "<font size=0>" + " (part " + parts + ", next pie at " + gturns;
					if(sturns != gturns) 
						tempText += ", or " + sturns + " with stogie)</font>";
					else
						tempText += ")</font>";
					dropElement.innerHTML += tempText;
					
					break; // can only get one part per fight
				}
			}
		}
	}

	// we branch out into counting navel ring and bandersnatch runaways and more
	if(checkForNavelRingRunaway(document.body.innerHTML)) {
		var count = GM_getValue(playerName+"_navelRingRunaways",0);
		GM_setValue(playerName+"_navelRingRunaways",count+1);
	}
	
	// now includes stomping boots runaways
	if(checkForSnatchRunaway(document.body.innerHTML)) {
		var count = GM_getValue(playerName+"_snatchRunaways",0);
		GM_setValue(playerName+"_snatchRunaways",count+1);
	}

	if(checkForUnderlingSummon(document.body.innerHTML)) {
		var count = GM_getValue(playerName+"_underlingSummons",0);
		GM_setValue(playerName+"_underlingSummons",count+1);
		GM_setValue(playerName+"_hasFrippery", true);  // one time flag
	}
	
	if(checkForGibbererAdv(document.body.innerHTML)) {
		var count = GM_getValue(playerName+"_gibbererAdv",0);
		GM_setValue(playerName+"_gibbererAdv",count+1);
		GM_setValue(playerName+"_hasGibberer", true);  // one time flag
		// stick something on the screen to indicate how many this it?
	}

	if(checkForGnomeAdv(document.body.innerHTML)) {
		var count = GM_getValue(playerName+"_gnomeAdv",0);
		GM_setValue(playerName+"_gnomeAdv",count+1);
		GM_setValue(playerName+"_hasGnome", true);  // one time flag
		// stick something on the screen to indicate how many this it?
	}

	if(checkForHareAdv(document.body.innerHTML)) {
		var count = GM_getValue(playerName+"_HareAdv",0);
		GM_setValue(playerName+"_HareAdv",count+1);
		GM_setValue(playerName+"_hasHare", true);  // one time flag
		// stick something on the screen to indicate how many this it?
	}

	if(checkForVMaskAdv(document.body.innerHTML)) {
		var count = GM_getValue(playerName+"_VMaskAdv",0);
		GM_setValue(playerName+"_VMaskAdv",count+1);
		GM_setValue(playerName+"_hasVMask", true);  // one time flag
		// stick something on the screen to indicate how many this it?
	}

	if(checkForHipsterAdv(document.body.innerHTML)) {
		var count = GM_getValue(playerName+"_hipsterAdv",0);
		GM_setValue(playerName+"_hipsterAdv",count+1);
		GM_setValue(playerName+"_hasHipster", true);  // one time flag
		// stick something on the screen to indicate how many this it?
	}
	
	// update skill menu with remaining summon underilng info
	if(GM_getValue(playerName+"_hasFrippery", false)){
		var allElements = document.getElementsByTagName("option");
		for(var i=0; i < allElements.length; i++) {
			if(allElements[i].value == "7052" ) {
				var remaining = 5 - GM_getValue(playerName+"_underlingSummons",0);
				allElements[i].innerHTML = allElements[i].innerHTML.replace("Points)",
					"Points) (" + remaining + "/5 left)");   
				break;
			}	 
		}
	}
		
	if(checkForMayflySummon(document.body.innerHTML)) {
		var count = GM_getValue(playerName+"_mayflySummons",0);
		GM_setValue(playerName+"_mayflySummons",count+1);
		GM_setValue(playerName+"_hasMayflies", true);  // one time flag
	}
	
	// update skill menu with remaining summon mayflies info
	if(GM_getValue(playerName+"_hasMayflies", false)){
		var allElements = document.getElementsByTagName("option");
		for(var i=0; i < allElements.length; i++) {
			if(allElements[i].value == "7024" ) {
				var remaining = 30 - GM_getValue(playerName+"_mayflySummons",0);
				allElements[i].innerHTML = allElements[i].innerHTML.replace("Points)",
					"Points) (" + remaining + "/30 left)");   
				break;
			}	 
		}
	}
		
	// add some info to the run away button
	// currently doesn't work for Greatest American Pants - how to tell that I'm wearing them?
	var runAwayButton  = getRunAwayButton();
	if(runAwayButton) {
// alert("found run away button, decorating");	
		var newElement = document.createElement("FONT");
		
		if(GM_getValue(playerName + "_haveOde", false) && GM_getValue(playerName + "_usingBandersnatch", false)
		|| GM_getValue(playerName + "_usingBoots", false)){
			var count = GM_getValue(playerName+"_snatchRunaways", 0);
			var max = Math.floor(GM_getValue(playerName + "_familiarWeight",0) / 5);
			newElement.innerHTML = "<font size=0>" + " \'snatch/boots: " + Math.max(max-count, 0) + "/" + max + " left</font>";
			runAwayButton.parentNode.insertBefore(newElement, runAwayButton.nextSibling);
		} 
		else if (GM_getValue(playerName + "_usingNavelRing", false)) {
			var count = GM_getValue(playerName+"_navelRingRunaways",0);
			var runPercent = count + 1 <= navelPercentages.length ? navelPercentages[count] : navelPercentages[navelPercentages.length - 1];
// alert("adding navel ring info - runaways = " + count + ", percent = " + runPercent);
			newElement.innerHTML = "<font size=0>" + " (\Navel: " + runPercent + "%)</font>";
			runAwayButton.parentNode.insertBefore(newElement, runAwayButton.nextSibling);
		}
	}

	// find and label the runaway button in the combat bar, if any 
	// this it broken - CAB is filling the action bar -after- the script runs, so all the buttons are blank when we search
	{
		var allElements = document.getElementsByTagName("img");
		var button = null;

		for(var i=0; i < allElements.length; i++) {
			if(allElements[i].title == "Run Away" ) {
				button = allElements[i];
				break;
			}	 
		}
		if(button) {
			var theTable = getParent(button,'table');
			// first row is label, 2nd is buttons, 3rd is labels that we want to edit
			var rows = theTable.getElementsByTagName("tr");
			var buttons = rows[1].getElementsByTagName("td");
			var labels = rows[2].getElementsByTagName("td");
			for(var i=0; i < buttons.length; i++) {
				if(buttons[i].innerHTML.indexOf("Run Away") != -1) {
					// edit the corresponding label
					if(GM_getValue(playerName + "_haveOde", false) && GM_getValue(playerName + "_usingBandersnatch", false)){
						var count = GM_getValue(playerName+"_snatchRunaways", 0);
						var max = Math.floor(GM_getValue(playerName + "_familiarWeight",0) / 5);
						labels[i].innerHTML = Math.max(max-count,0) + " left";
					} 
					else if (GM_getValue(playerName + "_usingNavelRing", false)) {
						var count = GM_getValue(playerName+"_navelRingRunaways",0);
						var runPercent = count + 1 <= navelPercentages.length ? navelPercentages[count] : navelPercentages[navelPercentages.length - 1];
						labels[i].innerHTML = "(" + runPercent + "%)";
					}
				}
			}
		}
	}
}	

////////////////////////////////////////////////////////////////////////////////
// watch choice.php for the arrival of bird form, 
// and for the the talk-to-the-llama that signals the end of birdform
function processChoice() {
	if ( (pos = document.body.innerHTML.indexOf( "Form of...Bird!" )) != -1 ) {
		// we are starting a new birdform session
		var playerName = GM_getValue( "currentPlayer");

		setBirdform(playerName);

		// trigger char pane refresh to make counter visible
		top.frames[0].location.reload();
	}	
	else if ( (pos = document.body.innerHTML.indexOf( "The llama looks deep into your eyes" )) != -1 ) { 
		// we are done
		// what if we miss this for some reason - maybe the user did it in mafia or something -
		// we don't want birdform forever. Maybe a manual clear button?
		var playerName = GM_getValue( "currentPlayer");

		clearBirdform(playerName);

		// trigger char pane refresh to make counter visible
		top.frames[0].location.reload();
	}
}

function manualClearCounter() {
	var playerName = getPlayerNameFromCharpane().username;
	GM_setValue("currentPlayer", playerName);  // store for other functions that need to know who's playing

	if( confirm("Clear and hide birdform counter?")) {
		clearBirdform(playerName);

		// trigger char pane refresh to make counter visible - will this work?
		top.frames[0].location.reload();
	}
}

// clear the counters if a new day or ascension
function zeroDropVars(playerName){
	// all the generic ones
	for(var drop in allDrops) {
		GM_setValue(playerName+allDrops[drop][DROP_GM_VAR_IDX], 0);
	}
	
	// and the "special" ones that aren't in the array	
	GM_setValue(playerName+"_underlingSummons",0);
	GM_setValue(playerName+"_mayflySummons",0);
	GM_setValue(playerName+"_navelRingRunaways",0);
	GM_setValue(playerName+"_snatchRunaways",0);
	GM_setValue(playerName+"_gibbererAdv",0);
	GM_setValue(playerName+"_gnomeAdv",0);
	GM_setValue(playerName+"_HareAdv",0);
	GM_setValue(playerName+"_VMaskAdv",0);
	GM_setValue(playerName+"_hipsterAdv",0);
	GM_setValue(playerName+"_PieDrops",0);
	GM_setValue(playerName+"_pieParts",0);
}

////////////////////////////////////////////////////////////////////////////////
// callback function to process the main charsheet if we find ourselves in a 
// new session, to see if it is a new day
function processCharsheet(data) {
	var playerName = getPlayerNameFromCharsheet(data);
	GM_setValue("currentPlayer", playerName);
	
	var prevDayCount = GM_getValue(playerName+"_dayCount",-1);
	var dayCount = getDaysPlayed(data);
//	alert("got daycount: " + dayCount + " for " + playerName);

	if(isNaN(dayCount)) {
// alert("processCharsheet - unable to parse dayCount, aborting");
		return;	 // hopefully will try again with more success, can't continue
	}
		
	GM_setValue(playerName+"_dayCount", dayCount);
	GM_setValue(playerName + "_checkForReset", false); // says we succeeded, don't try again
	
	// if a new day, zero the current gong drop count
	if( dayCount != prevDayCount ) {  // current dayCount is more, less (ascended) or prevDayCount was -1 are all valid
		zeroDropVars(playerName);
	}
}

// add a charsheet row for a given daily drop
function addDropRow(lastTR, dropText, dropCount, maxDrops) {
		var newTR = document.createElement("tr");
		var newElement = document.createElement("td");
		newElement.appendChild(document.createTextNode(dropText));
		newElement.align = "right";
		newTR.appendChild(newElement);

		newElement = document.createElement("td");
		newElement.align = "left";
		newElement.style.fontWeight = "bold";
		
		newElement.appendChild(document.createTextNode(dropCount + (maxDrops != "" ? "/" + maxDrops : "")));

		newTR.appendChild(newElement);
		lastTR.parentNode.insertBefore(newTR, lastTR.nextSibling);

		return newTR;
}

function checkForHardcore(data) {
	if ( (pos = data.indexOf( "You are in Hardcore mode" )) != -1 ) 
		return true;
	else
		return false;
}

// edit the charsheet display with the gongs-dropped-today info
function processCharsheetDisplay() {
	// maybe we should only print these if they actually have that familiar, or 
	// if the count is > 0
	// on the other hand, this is also where we post the update-available link, so we
	// probably want the gong drops no matter what.
	
	// as a sanity check thing, could call processCharSheet() here
	// if it resets the count to zero after something has already dropped - is that better than nothing?
	
	var playerName = getPlayerNameFromCharsheet(document.body.innerHTML);
	GM_setValue("currentPlayer", playerName);

	var inHardcore = checkForHardcore(document.body.innerHTML);
	
	// stick it after the "Days Played (this run)" line (but "this run" doesn't appear for unascended chars)
	var allElements = document.getElementsByTagName("td");
	var targetElement = null; // where to insert our info
	for(var i=0; i < allElements.length; i++) {
		if(allElements[i].innerHTML.indexOf( "Days Played" ) != -1 
			&& allElements[i].innerHTML.indexOf( "Turns Played" ) == -1  // awkward way to ensure we don't get some parent container with the right text 
			&& allElements[i].innerHTML.indexOf( "(total)" ) == -1 ) {   // ensure that it's not Days Played (total)
			targetElement = allElements[i];
			break;
		}	 
	}
	if(targetElement != null) {
		// add a row for each drop we are tracking

		// "generic" ones first, then the special ones we track in some other way
		var lastTR = targetElement.parentNode;
		var lastDrop = "";
		for(var drop in allDrops) {
			if(GM_getValue(playerName+allDrops[drop][DROP_GM_FLAG_IDX], false)) {
				// so can filter out repeats on same counter (e.g. pastes)
				if(lastDrop != allDrops[drop][DROP_GM_VAR_IDX]) {  
					lastDrop = allDrops[drop][DROP_GM_VAR_IDX];
					var dropCount = GM_getValue(playerName+allDrops[drop][DROP_GM_VAR_IDX],0);

					// kind of bogus, but the best I could come up with
					if(allDrops[drop][DROP_GM_FLAG_IDX]=="_hasPutty" && GM_getValue(playerName+"_hasRain-Doh")==true) {
						var dropsLeft = Math.min(5, 6 - GM_getValue(playerName+"_Rain-DohUses",0));
						allDrops[drop][DROP_DAILY_MAX_IDX] = dropsLeft.toString();
					}
					else if(allDrops[drop][DROP_GM_FLAG_IDX]=="_hasRain-Doh" && GM_getValue(playerName+"_hasPutty")==true) {
						var dropsLeft = Math.min(5, 6 - GM_getValue(playerName+"_puttyUses",0));
						allDrops[drop][DROP_DAILY_MAX_IDX] = dropsLeft.toString();
					}					
					
					lastTR = addDropRow(lastTR, allDrops[drop][DROP_LABEL_IDX] + " Today:", dropCount, allDrops[drop][DROP_DAILY_MAX_IDX]);
				}
			}
		}

		// these aren't in the all-drops array, since they require some custom coding or another
		if( GM_getValue(playerName+"_hasOrganGrinder", false)) {
			var pies = GM_getValue(playerName+"_PieDrops",0);
			var parts = GM_getValue(playerName+"_pieParts",0);
			lastTR = addDropRow(lastTR, "Pie Drops Today:", pies + " pie" + (pies != 1 ? "s" : "") 
			     + ", " + parts + " part" + (parts != 1 ? "s" : ""), "");
		}

		if(!inHardcore && GM_getValue(playerName+"_hasFrippery", false)) {
			var summonses = GM_getValue(playerName+"_underlingSummons",0);
			lastTR = addDropRow(lastTR, "Hobo Underling Summons Today:", summonses, 5);
		}
		
		if(!inHardcore && GM_getValue(playerName+"_hasMayflies", false)) {
			var summonses = GM_getValue(playerName+"_mayflySummons",0);
			lastTR = addDropRow(lastTR, "Mayfly Summons Today:", summonses, 30);
		}
		
		if(!inHardcore && GM_getValue(playerName+"_hasNavelRing", false)) {
			var navelRingRunaways = GM_getValue(playerName+"_navelRingRunaways",0);
			lastTR = addDropRow(lastTR, "Navel Ring/GAP Runaways Today:", navelRingRunaways, "");
		}
		
		if(GM_getValue(playerName+"_hasSnatch", false)) {
			var snatchRunaways = GM_getValue(playerName+"_snatchRunaways",0);
			lastTR = addDropRow(lastTR, "Bander/Boots Runaways Today:", snatchRunaways, "");
		}
		
		if(GM_getValue(playerName+"_hasGibberer", false)) {
			var advs = GM_getValue(playerName+"_gibbererAdv",0);
			lastTR = addDropRow(lastTR, "Gibberer Bonus Adventures Today:", advs, "");
		}

		if(GM_getValue(playerName+"_hasGnome", false)) {
			var advs = GM_getValue(playerName+"_gnomeAdv",0);
			lastTR = addDropRow(lastTR, "Gnome Bonus Adventures Today:", advs, "");
		}

		if(GM_getValue(playerName+"_hasHare", false)) {
			var hadvs = GM_getValue(playerName+"_HareAdv",0);
			lastTR = addDropRow(lastTR, "Wild Hare Bonus Adventures Today:", hadvs, "");
		}

		if(GM_getValue(playerName+"_hasVMask", false)) {
			var advs = GM_getValue(playerName+"_VMaskAdv",0);
			lastTR = addDropRow(lastTR, "V-mask Bonus Adventures Today:", advs, "");
		}

		if(GM_getValue(playerName+"_hasHipster", false)) {
			var advs = GM_getValue(playerName+"_hipsterAdv",0);
			lastTR = addDropRow(lastTR, "Hipster Fights Today:", advs, "");
		}
	}

	// throw in something to tell user if new script version is available
/* 	{
		var message;
		var href = null;
		var webVer = GM_getValue("webVersion");
		if (webVer != "Error" && webVer > currentVersion) {  // this is actually a text string comparison, not numerical
			newTR = document.createElement("tr");
			newElement = document.createElement("td");
			newElement.appendChild(document.createTextNode("New birdform script version " + webVer + " available:"));
			newElement.align = "right";
			newElement.style.fontSize = "x-small";
			newTR.appendChild(newElement);
	
			newElement = document.createElement("td");
			newElement.align = "left";
			newElement.style.fontWeight = "bold";
			newElement.style.fontSize = "x-small";

			var hrefElement = document.createElement("a");
			hrefElement.setAttribute('href', scriptSite);
			hrefElement.setAttribute('target', "_blank");
			hrefElement.appendChild(document.createTextNode("here"));
			newElement.appendChild(hrefElement);

			newTR.appendChild(newElement);
			lastTR.parentNode.insertBefore(newTR, lastTR.nextSibling);
		}
	}
 */	
}

///////////////////////////////////////////////////////////////////////////////
// update display information - this function must be called when we are in the char sidepane
function updateCharacterPane() {
	var a = getPlayerNameFromCharpane();
	var playerName = a.username;
	var fullmode = a.fullmode;
// alert("in updateCharacterPane; player name = " + playerName);
	if( playerName == null )  // not sure why we sometimes see this, but doesn't seem to be at critical times
		return;
	
	GM_setValue("currentPlayer", playerName);  // store for other functions that need to know who's playing

	// if astral plane, need to reset counters
	// getPlayerLevel() returns 0 for astral plane
	var playerLevel = getPlayerLevel(document.documentElement.innerHTML);
	if(playerLevel == 0) {
// alert("detected astral plane; zeroing counters");		
		// clear the counters, no point in doing anything else
		zeroDropVars(playerName);
		clearBirdform(playerName);
		return;
	}

	// check the free runaway prereqs - actual processing is done on the fight page
	var usingNavelRing = checkNavelRing(document.documentElement.innerHTML);
	GM_setValue(playerName + "_usingNavelRing", usingNavelRing);
	if(usingNavelRing) GM_setValue(playerName+"_hasNavelRing", true);  // a one time flag, never reset.

	var familiar = getFamiliar(document.documentElement.innerHTML, fullmode);
	var usingBandersnatch = familiar.type == "bandersnatch" ? true: false;
	GM_setValue(playerName + "_usingBandersnatch", usingBandersnatch);
	if(usingBandersnatch) GM_setValue(playerName+"_hasSnatch", true);  // a one time flag, never reset. Easier than checking terrarium!

	var usingGrinder = familiar.type == "organgoblin" ? true: false;
	GM_setValue(playerName + "_usingGrinder", usingGrinder);
	if(usingGrinder) GM_setValue(playerName+"_hasOrganGrinder", true);  // a one time flag, never reset. Easier than checking terrarium!
	
	var usingBoots = familiar.type == "stompboots" ? true: false;
	GM_setValue(playerName + "_usingBoots", usingBoots);
	if(usingBoots) GM_setValue(playerName+"_hasBoots", true);  // a one time flag, never reset. Easier than checking terrarium!
	
	GM_setValue(playerName + "_familiarWeight", familiar.weight);
	GM_setValue(playerName + "_haveOde", checkOde(document.documentElement.innerHTML));
	
//	alert("using navel ring: " + usingNavelRing + ", usingBandersnatch: " + usingBandersnatch + ", weight = " + weight + ", have ode: " + haveOde);
	
	// If have birdform, display the count
	var hasBirdform = GM_getValue(playerName+"_hasBirdform",false);

	// check the session ID to see if we are still in the same session
	// nb can't use document.body, doesn't include the initial javascript stuff
	var pwdHash = getPwdHash(document.documentElement.innerHTML);
	var oldPwdHash = GM_getValue(playerName + "_pwdHash", 0);
	GM_setValue(playerName + "_pwdHash", pwdHash);
	
	if(pwdHash != oldPwdHash) {
		// set flag to request check for new day; clear when check succeeds (sometimes takes more than 1 try)
		GM_setValue(playerName + "_checkForReset", true);

/* 		// check for a new version of script if none seen already (asynch call, will run in parallel)
		var webVer = GM_getValue("webVersion", "Error");
		if(webVer == "Error" || webVer <= currentVersion)  // **BUG** fails forever if webVer gets set to a value higher than currentVersion
			GM_get(scriptURL, CheckScriptVersion);
 */	
	}
	
	if(GM_getValue(playerName + "_checkForReset", false) == true) {
		// need to check if it is a new da - asynch call, runs in parallel
		GM_get(baseURL + charSheet, processCharsheet);
	}
		
	if(hasBirdform) {
		// build up display text for total physical (two attacks) and elemental (one attack each)
		// always display physical. Others if > 0
		var count = GM_getValue(playerName + attackSuffixes[ATTACK_TALON_SLASH], 0) 
			  + GM_getValue(playerName + attackSuffixes[ATTACK_WING_BUFFET], 0); 
		var displayText = "\<font color=\"black\"\>\<b\>" + count + "\</b\>\</font\>";
		
		count = GM_getValue(playerName + attackSuffixes[ATTACK_STATUE_TREATMENT], 0);
		if(count > 0) {
			displayText = displayText + "<font color=\"green\">+<b>" + count + "</b></font>";
		}
		
		count = GM_getValue(playerName + attackSuffixes[ATTACK_THE_BIRD], 0);
		if(count > 0) {
			displayText = displayText + "<font color=\"blueviolet\">+<b>" + count + "</b></font>";
		}
		
		count = GM_getValue(playerName + attackSuffixes[ATTACK_ANTARCTIC_FLAP], 0);
		if(count > 0) {
			displayText = displayText + "<font color=\"blue\">+<b>" + count + "</b></font>";
		}
		
		count = GM_getValue(playerName + attackSuffixes[ATTACK_RISE_FROM_ASHES], 0);
		if(count > 0) {
			displayText = displayText + "<font color=\"red\">+<b>" + count + "</b></font>";
		}
		
		count = GM_getValue(playerName + attackSuffixes[ATTACK_FEAST_ON_CARRION], 0);
		if(count > 0) {
			displayText = displayText + "<font color=\"gray\">+<b>" + count + "</b></font>";
		}

		if(fullmode) {
			var newElement = document.createElement("FONT");
			newElement.innerHTML = "<b>"
			  + "<font size=2>Bird Attacks: " 
			  + displayText
			  + "</font></b><br><br>"; ;
			newElement.setAttribute("onmouseover", 'this.style.opacity="0.5"');
			newElement.setAttribute("onmouseout", 'this.style.opacity="1"');
			newElement.setAttribute("id", 'birdAttackCounter');
			newElement.addEventListener("click", manualClearCounter, true);

			var elements = document.getElementsByTagName( "FONT" );
			for ( var i = 0; i < elements.length; ++i ){
				if ( elements[i].innerHTML.indexOf( "Last Adventure" ) != -1 ){
					// insert ours before this one
					elements[i].parentNode.insertBefore(newElement,elements[i]);
					break;
				}
			}
		}
		else { // compact mode - different layout, make a table row for our display
			var newTR = document.createElement('tr');

			var newElement = document.createElement("td");
			newElement.appendChild(document.createTextNode("Bird:"));
			newElement.align = "right";
			newTR.appendChild(newElement);
			
			newElement = document.createElement("td");
			newElement.setAttribute("onmouseover", 'this.style.opacity="0.5"');
			newElement.setAttribute("onmouseout", 'this.style.opacity="1"');
			newElement.setAttribute("id", 'birdAttackCounter');
			newElement.addEventListener("click", manualClearCounter, true);
			var newFontElement = document.createElement("FONT");
			newFontElement.innerHTML = "<b><font size=2>" + displayText + "</font></b>";
			newElement.appendChild(newFontElement);
			newElement.align = "left";
			newTR.appendChild(newElement);
	
			var elements = document.getElementsByTagName( "TR" );
			var done = false;
			// if the last adventures script is running, insert before, else append to table
			for ( var i = 1; i < elements.length; ++i ){
				// normally "Adv", might be "Last Adventures" if that script is running
				if ( elements[i].innerHTML.indexOf( "Last Adventures" ) != -1 ){
					// insert ours before this one - experiment, back up one more
					elements[i].parentNode.insertBefore(newTR,elements[i-1]);
					done = true;
					break;
				}
			}
			if(!done) { // normal, no last adv script
				for ( var i = 0; i < elements.length; ++i ){
					 if ( elements[i].innerHTML.indexOf( "Adv" ) != -1 ){
						// insert ours at end of the table in compact mode
						elements[i].parentNode.appendChild(newTR);
						break;
					 }
				}
			}
		}
	}
	
	// duplicate functionality added in 0.2.3 for displaying mayfly counter on the charpane
	var mayflycount = GM_getValue(playerName+"_mayflySummons",0);
	var hasmayflies = GM_getValue(playerName+"_hasMayflies", false);
	if(hasmayflies && mayflycount > 0 && mayflycount < 30) {
			var mayflytext = "Mayfly Summons: " + mayflycount + "/30";

		if(fullmode) {
			var newElement = document.createElement("FONT");
			newElement.innerHTML = "<b><font size=2> " 
			  + mayflytext
			  + "</font></b><br>"; ;

			var elements = document.getElementsByTagName( "FONT" );
			for ( var i = 0; i < elements.length; ++i ){
				if ( elements[i].innerHTML.indexOf( "Last Adventure" ) != -1 ){
					// insert ours before this one
					elements[i].parentNode.insertBefore(newElement,elements[i]);
					break;
				}
			}
		}
		else { // compact mode - different layout, make a table row for our display
			var newTR = document.createElement('tr');

			var newElement = document.createElement("td");
			newElement.appendChild(document.createTextNode("Mayfly: "));
			newElement.align = "right";
			newTR.appendChild(newElement);
			
			newElement = document.createElement("td");
			var newFontElement = document.createElement("FONT");
			newFontElement.innerHTML = "<b><font size=2>" + mayflycount + "/30</font></b>";
			newElement.appendChild(newFontElement);
			newElement.align = "left";
			newTR.appendChild(newElement);
	
			var elements = document.getElementsByTagName( "TR" );
			var done = false;
			// if the last adventures script is running, insert before, else append to table
			for ( var i = 1; i < elements.length; ++i ){
				// normally "Adv", might be "Last Adventures" if that script is running
				if ( elements[i].innerHTML.indexOf( "Last Adventures" ) != -1 ){
					// insert ours before this one - experiment, back up one more
					elements[i].parentNode.insertBefore(newTR,elements[i-1]);
					done = true;
					break;
				}
			}
			if(!done) { // normal, no last adv script
				for ( var i = 0; i < elements.length; ++i ){
					 if ( elements[i].innerHTML.indexOf( "Adv" ) != -1 ){
						// insert ours at end of the table in compact mode
						elements[i].parentNode.appendChild(newTR);
						break;
					 }
				}
			}
		}
	}

}   

////////////////////////////////////////////////////////////////////////////////
// main prog, just call the proper routine if we are on a pane we care about
var nodeBody   = document.getElementsByTagName("body").item(0);
var baseURL	   = "";
var charSheet = "charsheet.php";

if (nodeBody) {
   baseURL = nodeBody.baseURI.substring(0,nodeBody.baseURI.lastIndexOf('/')+1);
}

if(document.location.pathname.indexOf("fight.php") > 0 ) {
	// detect talon slashes and wingbuffets here
	processFight();
}
else if ( document.location.pathname.indexOf("charpane.php") > 0 ) {
	// update display
	updateCharacterPane();
}
else if ( document.location.pathname.indexOf("choice.php") > 0 ) {
	// this is where we detect that we got birdform,
	// and/or that we are done, and now talking to the llama
	processChoice();
}
else if ( document.location.pathname.indexOf("charsheet.php") > 0 ) {
	// tell the user how many gongs have dropped today...
	processCharsheetDisplay();
}