// 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 // // @downloadURL https://update.greasyfork.icu/scripts/4022/monster%20manuel.user.js // @updateURL https://update.greasyfork.icu/scripts/4022/monster%20manuel.meta.js // ==/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 Name // in compact mode it's Name // 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 tags to eliminate false matches in random text. Harem guards // is an example. (why did I change this?) // if(monsterTables[i].innerHTML.indexOf(">" + monsterName + "") > -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 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 = " (factoids)"; ; 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 = " (no factoids)"; ; 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(); }