antimarty birdform script

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

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// 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();
}