// antimarty's monster manuel helper - pop up monster info for the monster you are fighting
//
// Released under the GPL license
// http://www.gnu.org/copyleft/gpl.html
//
// ==UserScript==
// @name monster manuel
// @namespace antimarty
// @include *kingdomofloathing.com/fight.php*
// @include *127.0.0.1:600*/fight.php*
// @include *localhost:*/fight.php*
// @include *kingdomofloathing.com/questlog.php.php*
// @include *127.0.0.1:600*/questlog.php*
// @include *localhost:*/questlog.php*
// @version 0.1.7
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_xmlhttpRequest
//
// @description Version 0.1.7 - The [], spring break monsters
//
// ==/UserScript==
// released versions:
// Version 0.0.1 - first try
// Version 0.0.2 - fixes for special case monster names
// Version 0.0.3 - more fixes, add update link, general cleanup
// Version 0.0.4 - fernswarthy monster fixes
// Version 0.0.5 - slime tube and hobo monsters, haunted sorority monsters
// Version 0.0.6 - redo popup for non-mafia
// Version 0.0.7 - clean up some minor error handling stuff
// Version 0.0.8 - crimbo 2012 - taco elves and crimbokutown workers
// Version 0.0.9 - "The" Cray-kin, other jar of psychoses stuff, game inform monsters
// Version 0.1.0 - "The" Sierpinski Brothers, "The" Server, mafia wants a password
// Version 0.1.1 - password bug fix
// Version 0.1.2 - mostly undo the password bug fix, and a few things like angry angry bugbears
// Version 0.1.3 - add custom monster entry, then remove it. Plus more names-from-images.
// Version 0.1.4 - dreadsylvania monsters, fixes for GM/Firefox updates
// Version 0.1.5 - add junksprites, halloween monsters
// Known bugs:
// - shows all factoids for multiple monsters with the same name
/* var currentVersion = "0.1.6";
var scriptSite = "http://userscripts.org/scripts/show/150102"
// 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/150102.meta.js";
*/
////////////////////////////////////////////////////////////////////////////////
// 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 pane for the player name
// revised version! now taken directly from kolpreviousnadventures to handle compact mode
function getPlayerNameFromCharpane() {
var failed = {'username': "", 'fullmode': true};
if (!top.frames || !top.frames[0]) return failed;
var username = top.frames[0].document.getElementsByTagName("b");
if (!username || username.length < 1) return failed;
username = username[0];
if (!username) return failed;
username = username.firstChild;
if (!username) return failed;
// 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 failed;
username = username.nodeValue;
if (!username) return failed;
username = username.toLowerCase();
// alert("found username " + username + ", fullmode: " + fullmode);
return {'username': username, 'fullmode': fullmode};
}
// don't strip "The" (and maybe other stuff tbd) from the names of these
var invariableMonsterNames = [
"El Diablo",
"five skeleton invaders", // this one shares an image name with procedural stuff
"The Avatar of Sneaky Pete",
"The Bat in the Spats",
"The Beefhemoth",
"The Big Wisniewski",
"The Clownlord Beelzebozo",
"The Cray-Kin",
"The Frattlesnake",
"The Free Man",
"the ghost of Phil Bunion",
"the gunk",
"The Landscaper",
"The Large-Bellied Snitch",
"The Man",
"The Sierpinski brothers",
"The Server",
"The Snake With Like Ten Heads",
"The Terrible Pinch",
"The Thing with No Name",
"The Thorax",
"The Unkillable Skeleton"];
// for monsters with name generators, or easier to look at the image names
var monsterImages = {
"sororghost": "sexy sorority ghost",
"sororeton": "sexy sorority skeleton",
"sororpire": "sexy sorority vampire",
"sororwolf": "sexy sorority werewolf",
"sororbie": "sexy sorority zombie",
"coldhobo": "Cold hobo",
"hothobo": "Hot hobo",
"nhobo": "Normal hobo",
"slhobo": "Sleaze hobo",
"spookyhobo": "Spooky hobo",
"stenchhobo": "Stench hobo",
"elfhobo": "Hobelf",
"animelf1": "tiny-screwing animelf",
"animelf2": "plastic-extruding animelf",
"animelf3": "circuit-soldering animelf",
"animelf4": "quality control animelf",
"animelf5": "toy assembling animelf",
"beergolem": "X Bottles of Beer on a Golem",
"stonegolem": "X Stone Golem",
"dimhorror":" X-dimensional horror",
"hydra": "X-headed Hydra",
"earbeast": "Beast with X Ears",
"eyebeast": "Beast with X Eyes",
"fernghost": "Ghost of Fernswarthy's Grandfather",
"tacoelf_sign": "sign-twirling Crimbo elf",
"tacoelf_taco": "taco-clad Crimbo elf",
"tacoelf_cart": "tacobuilding elf",
"bigskeleton": "procedurally-generated skeleton",
"faq_miniboss": "Video Game Miniboss",
"faq_boss": "Video Game Boss",
"faq_": "Video Game Minion", // no convenient way to tell strong vs. weak vs. moderate
"bb_caveman": "angry cavebugbear", // and all the very very very... angry ones, too
"dvhotbear": "hot bugbear",
"dvcoldbear": "cold bugbear",
"dvspookybear": "spooky bugbear",
"dvsleazebear": "sleaze bugbear",
"dvstenchbear": "stench bugbear",
"dvhotghost": "hot ghost",
"dvcoldghost": "cold ghost",
"dvspookyghost": "spooky ghost",
"dvsleazeghost": "sleaze ghost",
"dvstenchghost": "stench ghost",
"dvhotskel": "hot skeleton",
"dvcoldskel": "cold skeleton",
"dvspookyskel": "spooky skeleton",
"dvsleazeskel": "sleaze skeleton",
"dvstenchskel": "stench skeleton",
"dvhotvamp": "hot vampire",
"dvcoldvamp": "cold vampire",
"dvspookyvamp": "spooky vampire",
"dvsleazevamp": "sleaze vampire",
"dvstenchvamp": "stench vampire",
"dvhotwolf": "hot werewolf",
"dvcoldwolf": "cold werewolf",
"dvspookywolf": "spooky werewolf",
"dvsleazewolf": "sleaze werewolf",
"dvstenchwolf": "stench werewolf",
"dvhotzom": "hot zombie",
"dvcoldzom": "cold zombie",
"dvspookyzom": "spooky zombie",
"dvsleazezom": "sleaze zombie",
"dvstenchzom": "stench zombie",
"shopteacher": "X-fingered Shop Teacher",
"js_bender": "junksprite bender",
"js_melter": "junksprite melter",
"js_sharpener": "junksprite sharpener",
"vandalkid": "vandal kid",
"paulblart": "suburban security civilian",
// sloppy seconds sundae, spring break sunken party
"ssd_cocktail": "Sloppy Seconds Cocktail",
"ssd_sundae": "Sloppy Seconds Sundae",
"ssd_burger": "Sloppy Seconds Burger",
"fun-gal": "Fun-Guy Playmate",
"srpainting": "ancestral Spookyraven portrait"
};
////////////////////////////////////////////////////////////////////////////////
// get monster name (monster manuel version)
function getMonsterName(data) {
var monsterName = /id=\"monname\"> *(.*?)<\/span>/i.exec(data);
if(monsterName)
monsterName = monsterName[1];
else
return "";
var imageName = /adventureimages\/(.*?)\.gif/i.exec(data);
if(imageName)
imageName = imageName[1];
else
return "";
// alert("initially found monster name: " + monsterName + " (image name: " + imageName + ")");
// strip off leading articles, etc from monster names as seen during fight
// maybe make this a list of prefixes, it's getting pretty long
if(invariableMonsterNames.indexOf(monsterName) == -1) {
if( monsterName.substring(0,2).toLowerCase()=="a ")
monsterName = monsterName.substring(2,monsterName.length);
else if( monsterName.substring(0,3).toLowerCase()=="an ")
monsterName = monsterName.substring(3,monsterName.length);
else if( monsterName.substring(0,5).toLowerCase()=="some ")
monsterName = monsterName.substring(5,monsterName.length);
else if( monsterName.substring(0,4).toLowerCase()=="the ")
monsterName = monsterName.substring(4,monsterName.length);
else if( monsterName.substring(0,3).toLowerCase()=="el ")
monsterName = monsterName.substring(3,monsterName.length);
else if( monsterName.substring(0,3).toLowerCase()=="la ")
monsterName = monsterName.substring(3,monsterName.length);
// translate special cases
if(monsterName.indexOf("'s butt") != -1)
monsterName = "[somebody else's butt]";
else if(monsterName.substring(0,6).toLowerCase()=="shadow" && monsterName.indexOf("Black Bubbles")==-1 && imageName.indexOf("faq_")==-1)
monsterName = "(shadow opponent)";
// all slime tube monsters are... "Slime Tube monster", ignore names, just check for the image
else if(/slime[1-5]/i.exec(imageName) != null )
monsterName = "Slime Tube monster";
// more monsters-from-images
// keep trailing numbers for anime elves (different monsters), strip otherwise (different images for same mob)
else if(/animelf[1-5]/i.exec(imageName) != null )
monsterName = monsterImages[imageName];
else {
var stripped = /(.*?)[0-9]+/i.exec(imageName);
if(stripped != null) imageName=stripped[1];
if(monsterImages[imageName] != undefined)
monsterName = monsterImages[imageName];
else if(imageName.substring(0,4).toLowerCase()=="faq_") // video game minions, how to tell strong vs. weak?
monsterName = monsterImages["faq_"];
}
}
// alert("returning monster name: " + monsterName + " (image name: " + imageName + ")");
return monsterName;
}
// 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);
}
}
// fill my popup window with a reduced version of manuels facts, for our monster only
function processManual(data){
var myDocument = document.createElement('div');
myDocument.innerHTML = data;
// could just get monsterName directly, this is a relic from earlier versions
var monsterName = /\&monster=(.+)/i.exec(decodeURIComponent(manuelURL));
if(monsterName) monsterName = monsterName[1];
// alert("monster name = " + monsterName);
// first find the table with our monster; find its parent; use that to find all the monster tables
if(monsterName) {
// strip out the javascript href change that loads the game main page
var allscripts = myDocument.getElementsByTagName("script");
allscripts[0].parentNode.removeChild(allscripts[0]);
var allElements = myDocument.getElementsByTagName("font");
var parentNode = 0;
var monsterTables =0;
var i;
for(i=0; i < allElements.length; i++) {
if(allElements[i].innerHTML.indexOf(monsterName) != -1) {
parentNode = getParent(allElements[i], "table");
parentNode = getParent(parentNode, "p");
monsterTables = parentNode.getElementsByTagName("table");
break;
}
}
// now delete all that parent's children, except our keeper monster
// monsterTables is a live list, so as we delete items in the doc, list items disappear...
if(monsterTables) {
for(i=0; i < monsterTables.length; ) {
// use the <font></font> tags to eliminate false matches in random text. Harem guards
// is an example. (why did I change this?)
// if(monsterTables[i].innerHTML.indexOf(">" + monsterName + "</font>") > -1) {
if(monsterTables[i].innerHTML.indexOf(">" + monsterName + "<") >= 0
|| monsterName=="Video Game Minion" && monsterTables[i].innerHTML.indexOf(">" + monsterName)>= 0) {
// our monster, skip it
i++;
}
else {
// not our monster, delete it (next target monster table will be at same index)
// there is also an <a> node in front of each monster
monsterTables[i].parentNode.removeChild(monsterTables[i].previousSibling);
monsterTables[i].parentNode.removeChild(monsterTables[i]);
}
}
}
// delete some junk stuff - other quests header info, back to campsite footer, etc
if(parentNode.parentNode) {
parentNode.parentNode.removeChild(parentNode.parentNode.firstChild);
parentNode.parentNode.removeChild(parentNode.parentNode.firstChild);
parentNode.parentNode.removeChild(parentNode.parentNode.firstChild);
parentNode.parentNode.removeChild(parentNode.parentNode.lastChild);
parentNode.parentNode.removeChild(parentNode.parentNode.lastChild);
}
/* // if new script version available, add a link
var webVer = GM_getValue("webVersion", "Error");
if (webVer != "Error" && webVer > currentVersion) { // this is actually a text string comparison, not numerical
var newElement = document.createElement('p');
newElement.style.fontSize = "x-small";
newElement.appendChild(document.createTextNode("New Monster Manuel script version " + webVer + " available: "));
var hrefElement = document.createElement('a');
hrefElement.setAttribute('href', scriptSite);
hrefElement.setAttribute('target', "_blank");
hrefElement.appendChild(document.createTextNode("here"));
newElement.appendChild(hrefElement);
parentNode.parentNode.appendChild(newElement);
}
*/
// and finally fill our window
myWindow.document.documentElement.innerHTML = myDocument.innerHTML;
myWindow.document.close();
}
}
// pop up an empty window for our factoid, and call something to fill it
// to do: figure out how to size this appropriately
var myWindow;
function manuelPopup()
{
myWindow = window.open(baseURL,'manuel','height=400,width=500,scrollbars=yes');
GM_get(baseURL + manuelURL, processManual);
}
// parse the charpane info for the password hash (use as a session ID)
function getPwdHash(data){
var pwdHash = /pwdhash \= \"(.*?)\"/i.exec(data); // the .*? is the non-greedy version of .*
if(pwdHash)
pwdHash = pwdHash[1];
else
pwdHash = "";
return pwdHash;
}
////////////////////////////////////////////////////////////////////////////////
function monsterNameEntry() {
var monsterName = prompt("Enter Monster Name to research:\n", "");
var firstChar = monsterName.toLowerCase().charAt(0) ;
if(firstChar < 'a' || firstChar > 'z') firstChar = '-';
manuelURL = "questlog.php?which=6&vl=" + firstChar + "&monster="+monsterName;
manuelPopup();
}
////////////////////////////////////////////////////////////////////////////////
// currently unused - not really helpful to search unless can cross first-letter sections
function processQuestPage() {
var entries = document.getElementsByTagName("b");
var i;
for(i=0; i < entries.length; i++) {
if(entries[i].innerHTML == "Other") {
// append our item here
var newElement = entries[i].parentNode.parentNode.appendChild(document.createElement('b'));
newElement.setAttribute("onmouseover", 'this.style.opacity="0.5"');
newElement.setAttribute("onmouseout", 'this.style.opacity="1"');
newElement.innerHTML = " Search";
newElement.addEventListener("click", monsterNameEntry);
}
}
}
////////////////////////////////////////////////////////////////////////////////
function processFight() {
var playerName = getPlayerNameFromCharpane().username;
var pwdHash = "";
// once per login, check if a new version is available
// why all this top level stuff sometimes fails at each level, who knows
if(playerName && top && top.frames[0] && top.frames[0].document && top.frames[0].document.documentElement) {
pwdHash = getPwdHash(top.frames[0].document.documentElement.innerHTML);
var oldPwdHash = GM_getValue(playerName + "_pwdHash", 0);
if(pwdHash != oldPwdHash) {
// new session
GM_setValue(playerName + "_pwdHash", pwdHash);
/* // check for a new version of script if none seen already
var webVer = GM_getValue("webVersion", "Error");
if(webVer == "Error" || webVer <= currentVersion)
GM_get(scriptURL, CheckScriptVersion);
*/
}
// clear any lingering flag that was set with empty player name by old script versions
GM_setValue("_hasManuel", false);
}
var monsterName = getMonsterName(document.body.innerHTML);
var firstChar = monsterName.toLowerCase().charAt(0) ;
if(firstChar < 'a' || firstChar > 'z') firstChar = '-';
// apparently mafia now wants a password
// yay, that change got reverted, and was causing problems anyway
// manuelURL = "questlog.php?which=6&vl=" + firstChar + "&pwd=" + pwdHash + "&monster="+monsterName;
manuelURL = "questlog.php?which=6&vl=" + firstChar + "&monster="+monsterName;
// find the monster's HTML element on the page; add our link
var monsterSpan = document.getElementById("monname");
var monsterTable = getParent(monsterSpan, "table");
if(monsterSpan && monsterTable) {
var newElement = document.createElement("FONT");
// make sure we have a factoid to popup - signalled by the attack/defense/hp display
// or by monster manuel saying something
if(monsterTable.innerHTML.indexOf("Enemy's Attack Power") != -1
|| document.body.innerHTML.indexOf("Monster Manuel") != -1 ){
if(playerName != "")
GM_setValue(playerName+"_hasManuel", true); // current monster has factoids, ergo player has manuel
newElement.innerHTML = "<font size=2> (<u>factoids</u>)</font>"; ;
newElement.setAttribute("onmouseover", 'this.style.opacity="0.5"');
newElement.setAttribute("onmouseout", 'this.style.opacity="1"');
newElement.setAttribute("id", 'manuel');
newElement.addEventListener("click", manuelPopup, true);
// always insert a factoid link if there are factoids
monsterSpan.parentNode.insertBefore(newElement, monsterSpan.nextSibling);
} else {
newElement.innerHTML = "<font size=2> (no factoids)</font>"; ;
newElement.setAttribute("onmouseout", 'this.style.opacity="0.5"');
newElement.setAttribute("id", 'manuel');
// no factoids, only insert the link if we know the player has manuel
if(GM_getValue(playerName+"_hasManuel", false) == true )
monsterSpan.parentNode.insertBefore(newElement, monsterSpan.nextSibling);
}
}
}
////////////////////////////////////////////////////////////////////////////////
// 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 manuelURL = "";
if (nodeBody) {
baseURL = nodeBody.baseURI.substring(0,nodeBody.baseURI.lastIndexOf('/')+1);
}
// our popup gets named after the originating window, which is fight.php, so don't process it
if(window.name != "manuel" && document.location.pathname.indexOf("fight.php") != -1 ) {
processFight();
}
else if(document.location.pathname.indexOf("questlog.php") != -1 ) {
// deprecated until I can figure out how to search across different first letters
// processQuestPage();
}