// antimarty's fortune cookie script, based in part on: // csemaj's KoL Script // Copyright (c) 2007, James Cammarata // Based on code written by Byung Kim (Tard) http://kol.dashida.com and OneTonTomato's scripts // toggle preference code from lukifer's mrscript // http://www.noblesse-oblige.org/lukifer/scripts/ // script update code based on DrEvi1's hatrack helper, which credits Picklish // fixes for lag combatting inventory screens by Firvagor // now using Charon's account options tab code // Released under the GPL license // http://www.gnu.org/copyleft/gpl.html // // ==UserScript== // @name antimarty fortune cookie script // @namespace antimarty // @include *kingdomofloathing.com/*.php* // @include *127.0.0.1:600*/*.php* // @include *localhost:600*/*.php* // @include localhost:18481/*.php* // @version 0.5.8 // @grant GM_getValue // @grant GM_setValue // @grant GM_xmlhttpRequest // // @description Kingdom of Loathing fortune cookie tracker // @downloadURL https://update.greasyfork.icu/scripts/4019/antimarty%20fortune%20cookie%20script.user.js // @updateURL https://update.greasyfork.icu/scripts/4019/antimarty%20fortune%20cookie%20script.meta.js // ==/UserScript== // released versions: // Version 0.5.7 - clan speakeasy, new topmenu frame rearrangement, gm grant stuff, move to Greasy Fork // Version 0.5.5 - kolhs semis; red fox; bram the stoker // Version 0.5.4 - new undersea SRs // Version 0.5.3 - new giant castle SRs, fix oxygenarian detection // Version 0.5.2 - updated for new level 8 and level 9 semis, and typo for friars // Version 0.5.1 - new hidden temple semi rare // Version 0.5.0 - add last location info to char pane if no popups // Version 0.4.9 - bug fix for deep fat friars' adventure name // Version 0.4.8 - bug fix for auto-attack combat macros, change version check // Version 0.4.7 - add disable popups option as FF4 workaround // Version 0.4.6 - bug fix for noncombat semis // Version 0.4.5 - Show initial bracket for new ascension, bug fix for puttied/faxed/arrow'd SR monsters // Version 0.4.4 - ascii, kge fights from fax/putty/camera/arrowing aren't semis; support new options page // Version 0.4.3 - add elf alley semi // Version 0.4.2 - add the pixel stopwatch // Version 0.4.1, use the turnsplayed native counter // Version 0.4.0, display a predicted cookie window if no cookie eaten // Version 0.3.9, support for billiards room semi // Version 0.3.8, sanity check numbers when user loads charsheet // Version 0.3.7, misc bug fixes // Version 0.3.6, fixes (courtesy Firvagor) for crimbo anti-lag changes to inventory screens; clear counter after semi-rare // Version 0.3.5, filter impossible cookie values; check for updates automatically // Version 0.3.4, support for hobopolis adventures // Version 0.3.3, bugfixes encore // Version 0.3.2, bugfixes // Version 0.3.1, bugfixes // Version 0.3.0, remember most recent semi-rare. Re-fix bugs re-introduced in 0.2.6 // for unascended chars and compact mode, thanks to bad version control. // Version 0.2.6, reduced overhead, and bug fixes // Version 0.2.5, compact mode support and support for unascended chars // Version 0.2.3, improved sanity checker // Version 0.2.0, includes sanity checker // Version 0.1.1, bugfixes // Version 0.1.0 initial beta // Known bugs: // doesn't work in the kolmafia relay browser if you have this option set: // [] Force results to reload inline for [use] links // ... because the results page never reloads, so greasemonkey scripts don't run // may not save data correctly if Firefox doesn't exit cleanly (Greasemonkey issue) // basic logic to do this without retrieving character pane data: // - store an internal counter for adventures used // - look at the adventures remaining // - every time the pane is refreshed: // - if the remaining adventures decreases, decrease the counter by that amount // - if stays the same, do nothing // - if increases, do nothing // warn when hit zero // // read char sheet as a sanity check when the counter doesn't seem to be // decrementing normally // (temporarily?) unused, with move to Greasy Fork /* var currentVersion = "0.5.7"; var scriptSite = "http://userscripts.org/scripts/show/13180" // this is autogenerated by userscripts.org from Userscript @ comments above, use to reduce bandwidth on version check var scriptURL = "http://userscripts.org/scripts/source/13180.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 version number of script on the web function CheckScriptVersion(data) { // alert("inside version check"); // 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 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 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 false; username = username.nodeValue; if (!username) return false; username = username.toLowerCase(); // alert("found username " + username + ", fullmode: " + fullmode); return {'username': username, 'fullmode': fullmode}; } //////////////////////////////////////////////////////////////////////////////// // 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 var playerName = /showplayer\.php\?who\=\d+\">([^<]+)<\/a/i.exec(data); // sometimes this fails, don't know why if(playerName) return playerName[1].toLowerCase(); else return null; } //////////////////////////////////////////////////////////////////////////////// // parse the char sheet (not pane) for the total adventure count // taken from the csemaj cookie script // the "(this run)" only appears if you have ascended function getTurnsPlayed(data) { var turncount = "0"; if(data.indexOf("Turns Played (this run)") >= 0) { // alert("parsing datasheet for turns for an ascended char"); turncount = /Turns Played \(this run\)[^>]*>(<[^>]+>)*([\d,]+)/i.exec(data)[2]; } else { // alert("parsing datasheet for turns for an UNascended char"); turncount = /Turns Played[^>]*>(<[^>]+>)*([\d,]+)/i.exec(data)[2]; } // alert("found turncount=" + turncount); return parseInt(turncount.replace(',',''),10); } 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); } // parse the charpane info for the password hash (use as a session ID) function getPwdHash(data){ var pwdHash = /pwdhash \= \"(.*?)\"/i.exec(data)[1]; // the .*? is the non-greedy version of .* // alert("got pwdHash: " + pwdHash); return pwdHash; } // parse the charpane info for the turnsplayed counter // updates at the end of the adventure, not during the fight function getTurnsplayedVar(data){ var turnsplayed = /turnsthisrun \= (\d+)/i.exec(data)[1]; return parseInt(turnsplayed); } //////////////////////////////////////////////////////////////////////////////// // parse the main page for the name of the (noncombat) adventure - assuming // it's the next bolded thing after "Adventure Results:" function getAdventureName(data) { var adventureName = ""; if ( data.indexOf( "Adventure Results:" ) != -1 ) { // kol mafia can put up stuff that requires this check adventureName = /Results:<\/b>.*?(.*?)<\/b>/i.exec(data)[1]; } // the hobopolis rares are choice adventures, not normal noncombats else if(document.location.pathname.indexOf("choice.php") != -1) { // look for adventure name as something in bold on blue background adventureName = /bgcolor=\"blue\">(.*?)<\/b>/i.exec(data)[1]; } // check for known broken adventures if(adventureName == "") { if(data.indexOf( "There once was a bleary-eyed cyclops" ) != -1 ){ adventureName = "The Bleary-Eyed Cyclops"; } } // alert("got adventure name: " + adventureName); return adventureName; } //////////////////////////////////////////////////////////////////////////////// // parse the main page for the name of the adventure function getMonsterName(data) { var monsterName = /id=\"monname\"> *(.*?)<\/span>/i.exec(data); if(monsterName) monsterName = monsterName[1].toLowerCase(); else return {'monsterName': "", 'fromPutty': false}; // don't count monsters from putty/fax/camera/arrowing var fromPutty = data.indexOf("You put the copied monster on the ground") != -1 ? true : false; if(!fromPutty) fromPutty = data.indexOf("You gaze at the photocopy") != -1 ? true : false; if(!fromPutty) fromPutty = data.indexOf("You reach down to open up the camera") != -1 ? true : false; if(!fromPutty) fromPutty = data.indexOf("hear a wolf whistle from behind you") != -1 ? true : false; if(!fromPutty) fromPutty = data.indexOf("You play back the recording") != -1 ? true : false; if(!fromPutty) fromPutty = data.indexOf("%%") != -1 ? true : false; // some sort of error we see a lot with copied monsters // alert("got monster name: *" + monsterName + "*; from putty = " + fromPutty); return {'monsterName': monsterName, 'fromPutty': fromPutty}; } // process a noncombat result for semi-rareness var semis = { noncombats: { "Play Misty For Me" : "the Haunted Kitchen", "Like the Sunglasses, But Less Comfortable" : "the Haunted Library", "The Pilsbury Doughjerk" : "the Haunted Pantry", "The Bleary-Eyed Cyclops" : "the Limerick Dungeon", // I don't see the title appear when I get this adv. "In the Still of the Alley" : "the Sleazy Back Alley", "Natural Selection" : "the Goatlet", "Not Quite as Cold as Ice" : "the Lair of the Ninja Snowmen", "Prior to Always" : "a Battlefield", "How Does He Smell?" : "the Batrat and Ratbat Burrow", "All The Rave" : "the Castle Top Floor", "Le Chauve-Souris du Parfum" : "Guano Junction", "Hands On" : "the Harem", "You Can Top Our Desserts, But You Can't Beat Our Meats" : "the Laboratory", "Rokay, Raggy!" : "the Menagerie Level 2", "A Menacing Phantom" : "the Misspelled Cemetery", "Lunchboxing" : "the Outskirts of Cobb's Knob", "Filth, Filth, and More Filth" : "South of the Border", "It's The Only Way To Be Sure" : "the Deep Fat Friars", "It's the Only Way to be Sure" : "Pandamonium slums", "Two Sizes Too Small" : "the Hidden City", "Some Bricks Do, In Fact, Hang in the Air" : "the Inexplicable Door", "Blaaargh! Blaaargh!" : "the Spooky Forest", "Monty of County Crisco" : "Whitey's Grove", "The Latest Sorcerous Developments" : "the Hippy Camp (pre-war)", "Sand in the Vaseline" : "the Orcish Frat House (pre-war)", "Yo Ho Ho and a Bottle of Whatever This Is" : "the Obligatory Pirate's Cove", "A Tight Squeeze" : "BurnBarrel Blvd.", "Cold Comfort" : "Exposure Esplanade", "Juicy!" : "the Heap", "Flowers for You" : "the Ancient Hobo Burial Ground", "Maybe It's a Sexy Snake!" : "the Purple Light District", "What a Tosser" : "Elf Alley", "A Shark's Chum" : "The Haunted Billiards Room", "The Time This Fire" : "Vanya's Castle Chapel", "Oh, There Have it Gone" : "A-Boo Peek", "Synecdoche, Twin Peak" : "Twin Peak", "It's a Gas Gas Gas" : "Oil Peak", "Fit and Finish" : "the Castle Basement", "Ahead of the Game" : "the Castle Ground Floor", "Razor, Scooter" : "the Dive Bar", "Deeps Impact" : "the Briny Deeps", "The Haggling" : "the Brinier Deepers", "Camera On, James" : "the Wreck of the Edgar Fitzsimmons", "Dragon the Line" : "Madness Reef", "A Drawer of Chests" : "the Mer-Kin Outpost", "Through the Locking Glass" : "the Hallowed Halls", "Clay Is Great, But Leather Is Bether" : "Ye Olde Medievale Villagee", "Where There's Smoke...": "The Copperhead Club", "Methinks the Protesters Doth Protest Too Little": "A Mob of Zeppelin Protesters", // fake ones for testing // "A Dash of Boulder" : "the Dungeons of Doom - Boulder", // "Meat Score!" : "the Treasury - Meat Score", }, combats: { // use lowercase "some bad ascii art" : "the Valley Beyond The Orc Chasm", "a knob goblin elite guard captain" : "the Cobb's Knob Kitchens", "a knob goblin embezzler" : "the Treasury", "a c. h. u. m. chieftain" : "a Maze of Sewer Tunnels", "baa'baa'bu'ran" : "the Hidden Temple", "a 7-foot dwarf foreman" : "Itznotyerzitz Mine", "a moister oyster" : "An Octopus's Garden", "françois verte, art teacher" : "Art Class", // some encoding issue with this one "x-fingered shop teacher" : "Shop Class", // need to resolve the X (1-11) "mrs. k, the chemistry teacher" : "Chemistry class", "the red fox" : "The Red Zeppelin", "bram the stoker" : "The Haunted Boiler Room", "a full-length mirror" : "The Haunted Storage Room", // fake ones for testing // "a knob goblin elite guardsman" : "the Treasury", // "a swarm of killer bees" : "the Dungeons of Doom", }, }; function clearCounters(playerName) { GM_setValue(playerName+"_lastSemiTurn",-1); GM_setValue(playerName+"_lastSemiLocation",""); GM_setValue(playerName+"_luckyTurn1", -1); GM_setValue(playerName+"_luckyTurn2", -1); GM_setValue(playerName+"_luckyTurn3", -1); } function checkForNoncombatSemi(data) { theAdv = getAdventureName(document.body.innerHTML); // alert("in checkForNoncombatSemi(), adv name = " + theAdv); // alert("semis.noncombats[theAdv] = " + semis.noncombats[theAdv]); if(semis.noncombats[theAdv] != undefined) { var playerName = GM_getValue("currentPlayer"); var turncount = GM_getValue(playerName+"_turncount", 0); // var turncount = getTurnsplayedVar(top.frames[0].document); // alert("Found semirare adv \'" + theAdv + "\' in area " + semis.noncombats[theAdv] + " on tuncount " + turncount + " for player " + playerName); // found a semi, clear counters // this might backfire if using the counter as a generic countdown for other purposes, e.g. wossname tracking clearCounters(playerName); GM_setValue(playerName+"_lastSemiTurn",turncount+1); // the turncount var isn't updated yet GM_setValue(playerName+"_allowBracket",true); GM_setValue(playerName+"_lastSemiLocation",semis.noncombats[theAdv]); // trigger char pane refresh with the new info top.frames[0].location.reload(); } } function checkForCombatSemi(data) { var monster = getMonsterName(document.body.innerHTML); theAdv = monster.monsterName; if(monster.fromPutty == false && semis.combats[theAdv] != undefined) { var playerName = GM_getValue("currentPlayer"); var turncount = GM_getValue(playerName+"_turncount", 0); // var turncount = getTurnsplayedVar(top.frames[0].document); // alert("Found semi rare adv \'" + theAdv + "\' in area " + semis.combats[theAdv] + " - for player " + playerName + " on turn " + turncount ); // found a semi, clear counters // this might backfire if using the counter as a generic countdown for other purposes, e.g. wossname tracking clearCounters(playerName); GM_setValue(playerName+"_lastSemiTurn",turncount+1); // the turncount var isn't updated yet GM_setValue(playerName+"_allowBracket",true); GM_setValue(playerName+"_lastSemiLocation",semis.combats[theAdv]); // trigger char pane refresh with the new info top.frames[0].location.reload(); } } //////////////////////////////////////////////////////////////////////////////// // callback function to process the main charsheet as a sanity check after // something questionable was spotted, or just on a new session // **** mostly deprecated **** function sanityCheckCharsheet(data) { // alert("entering sanity check"); var playerName = getPlayerNameFromCharsheet(data); GM_setValue("currentPlayer", playerName); var turncount = getTurnsPlayed(data); // total turns played if(isNaN(turncount) ) { // alert("sanityCheckCharsheet - unable to parse either turncount (" + turncount + ") or remaining adv count (" + remainingAdventureCount + "), aborting"); return; // hopefully will try again with more success, can't continue } GM_setValue(playerName+"_turncount", turncount); // I'll try to keep this up to date // if the previous turncount makes no sense, zero it. After ascending would be an example var lastSemiTurn = GM_getValue(playerName+"_lastSemiTurn",-1); if( lastSemiTurn > turncount+1) { GM_setValue(playerName+"_lastSemiTurn",-1); GM_setValue(playerName+"_lastSemiLocation",""); } var prevDayCount = GM_getValue(playerName+"_dayCount",-1); var dayCount = getDaysPlayed(data); if(!isNaN(dayCount)) { if(dayCount < prevDayCount) { // must have ascended GM_setValue(playerName+"_lastSemiTurn",-1); GM_setValue(playerName+"_lastSemiLocation",""); } GM_setValue(playerName+"_dayCount",dayCount); } // set a flag is we are oxy, since it affects the cookie spacing if( data.match("Oxygenarian")) GM_setValue(playerName+"_isOxy", true); else GM_setValue(playerName+"_isOxy", false); } //////////////////////////////////////////////////////////////////////////////// // watch for the lucky numbers result on the main page function processMain(doc) { var countdown1 = -1; var countdown2 = -1; var countdown3 = -1; var pos = -1; // alert("in processMain()"); ///////NEW STUFF (Firvagor) - changed the if line to use the div instead of the body, since the results, and a hidden field is made inside the div so infinite looping doesn't occur////////// //if the hidden field is found, don't do anything if (doc.getElementById("cookiedone")!=null){return;} var playerName = GM_getValue( "currentPlayer"); var divElement = doc.getElementById("effdiv"); var bqElement = doc.getElementsByTagName("blockquote"); // if(bqElement[0]) // alert("processMain: speakeasy quote = " + bqElement[0]); if ( divElement ) { // alert("found divElement"); doc.getElementById("effdiv").innerHTML += ""; if((pos = doc.getElementById("effdiv").innerHTML.indexOf( "Lucky numbers:" )) != -1 ) { // alert("found lucky numbers"); // need to parse the text, format is "Lucky numbers: ###, ###, ###" var cookieText = doc.getElementById("effdiv").innerHTML.substring(pos, pos+30); // first one starts after the colon, next two after commas pos = cookieText.indexOf(":"); cookieText = cookieText.substring(pos+1); countdown1 = parseInt(cookieText); pos = cookieText.indexOf(","); cookieText = cookieText.substring(pos+1); countdown2 = parseInt(cookieText); pos = cookieText.indexOf(","); cookieText = cookieText.substring(pos+1); countdown3 = parseInt(cookieText); // elegantly sort them { var temp; if(countdown1 > countdown2) { temp = countdown1; countdown1 = countdown2; countdown2 = temp; } if(countdown2 > countdown3) { temp = countdown2; countdown2 = countdown3; countdown3 = temp; } if(countdown1 > countdown2) { temp = countdown1; countdown1 = countdown2; countdown2 = temp; } } // alert("processMain: cookie cookieText=" + cookieText + ", lucky numbers are " + countdown1 + ", " + countdown2 + ", " + countdown3); } } // speakeasy drink; format is "burp-speak the number 30" // doesn't work, is there really an effdiv for this? didn't see it in the text // try a blockquote instead... doesn't sound too reliable // else if((pos = doc.getElementById("effdiv").innerHTML.indexOf( "burp-speak the number" )) != -1 ) { if(bqElement[0] && (pos = bqElement[0].innerHTML.indexOf( "burp-speak the number" )) != -1 ) { // alert("processing speakeasy"); bqElement[0].innerHTML += ""; var cookieText = bqElement[0].innerHTML.substring(pos, pos+30); // just one number, bolded pos = cookieText.indexOf(""); cookieText = cookieText.substring(pos+3); countdown1 = parseInt(cookieText); countdown2 = -1; countdown3 = -1; // alert("processMain: speakeasy cookieText=" + cookieText + ", lucky numbers are " + countdown1 + ", " + countdown2 + ", " + countdown3); } if(countdown1>=0 || countdown2>=0 || countdown3>=0) { // filter out nonsense values: // - too high ( > 200 since last semi, 120 for oxy) // - too low ( < 160 since last semi, or < 100 if oxy) // the filters only apply if you found a semi recently; otherwise the counter starts // after a would-be semi, or ascension, with your first adventure.php location // adventure, but it's too much trouble to keep track GM_setValue(playerName+"_allowSanityCheck", false); // lock out the other pane from panicking // must call processCharsheet now, to clear this var allowBracket = GM_getValue(playerName+"_allowBracket",false); GM_setValue(playerName+"_allowBracket",false); // no reason to put up reminder after this var turncount = GM_getValue(playerName+"_turncount", 0); var lastSemiTurn = GM_getValue(playerName+"_lastSemiTurn",-1); var wasOxy = GM_getValue(playerName+"_isOxy", false); GM_setValue(playerName+"_isOxy", false); // ate a cookie, must not be oxy var minCount = 160; var maxCount = 200; if( wasOxy ) { minCount = 100; maxCount = 120; } if(turncount < 70) { // newly ascended minCount = 70; maxCount = 80; lastSemiTurn = 0; } var turnsSinceSemi = turncount - lastSemiTurn; // alert("filtering lucky numbers for " + playerName + " based on turncount=" + turncount + ", lastSemiTurn=" + lastSemiTurn + ", wasOxy=" + wasOxy + ", turnsSinceSemi = " + turnsSinceSemi ); // eliminate counters that are too high on an absolute basis, then try to // eliminate ones too high and low based on last semi if(countdown1 > maxCount) countdown1 = -1; if(countdown2 > maxCount) countdown2 = -1; if(countdown3 > maxCount) countdown3 = -1; if(lastSemiTurn != -1 && turnsSinceSemi <= maxCount && allowBracket) { if( countdown1 + turnsSinceSemi > maxCount ) countdown1 = -1; if( countdown2 + turnsSinceSemi > maxCount ) countdown2 = -1; if( countdown3 + turnsSinceSemi > maxCount ) countdown3 = -1; } if(lastSemiTurn != -1) { if( countdown1 + turnsSinceSemi < minCount ) countdown1 = -1; if( countdown2 + turnsSinceSemi < minCount ) countdown2 = -1; if( countdown3 + turnsSinceSemi < minCount ) countdown3 = -1; } // now to compare them vs existing numbers // if there is no match, stick them in as the current numbers // if there is a match, use that and zero the other two var oldLuckyTurn1 = GM_getValue(playerName+"_luckyTurn1",-1); var oldLuckyTurn2 = GM_getValue(playerName+"_luckyTurn2",-1); var oldLuckyTurn3 = GM_getValue(playerName+"_luckyTurn3",-1); var oldCountdown1 = oldLuckyTurn1 != -1 ? oldLuckyTurn1 - turncount : -1; var oldCountdown2 = oldLuckyTurn2 != -1 ? oldLuckyTurn2 - turncount : -1; var oldCountdown3 = oldLuckyTurn3 != -1 ? oldLuckyTurn3 - turncount : -1; var noPopups = GM_getValue("noPopups", false); // need to watch out for -1 matching -1 since we reset values above // check for self-match in new cookie values first if(countdown1 != -1 && (countdown1==countdown2 || countdown1 == countdown3)) { if(!noPopups) alert(countdown1 + " turns until a semi-rare!"); countdown2 = -1; countdown3 = -1; } else if(countdown2 != -1 && countdown2 == countdown3) { if(!noPopups) alert(countdown2 + " turns until a semi-rare!"); countdown1 = -1; countdown3 = -1; } else if (countdown1 != -1 && (countdown1 == oldCountdown1 || countdown1 == oldCountdown2 || countdown1 == oldCountdown3 || countdown2 == -1 && countdown3 == -1)) { if(!noPopups) alert(countdown1 + " turns until a semi-rare!"); countdown2 = -1; countdown3 = -1; } else if (countdown2 != -1 && (countdown2 == oldCountdown1 || countdown2 == oldCountdown2 || countdown2 == oldCountdown3 || countdown1 == -1 && countdown3 == -1)) { if(!noPopups) alert(countdown2 + " turns until a semi-rare!"); countdown1 = -1; countdown3 = -1; } else if (countdown3 != -1 && (countdown3 == oldCountdown1 || countdown3 == oldCountdown2 || countdown3 == oldCountdown3 || countdown1 == -1 && countdown2 == -1)) { if(!noPopups) alert(countdown3 + " turns until a semi-rare!"); countdown1 = -1; countdown2 = -1; } GM_setValue(playerName+"_luckyTurn1",countdown1 != -1 ? countdown1 + turncount : -1); GM_setValue(playerName+"_luckyTurn2",countdown2 != -1 ? countdown2 + turncount : -1); GM_setValue(playerName+"_luckyTurn3",countdown3 != -1 ? countdown3 + turncount : -1); // load and parse the main charsheet to set the behind the scene variables that // convert the cookie countdown into actual adventure number for each cookie // GM_get(baseURL + charSheet, processCharsheet); // trigger char pane refresh with the new info top.frames[0].location.reload(); } } //////////////////////////////////////////////////////////////////////////////// function manualCookieEntry() { var playerName = getPlayerNameFromCharpane().username; // alert("player name: " + playerName); GM_setValue("currentPlayer", playerName); // store for other functions that need to know who's playing var noPopups = GM_getValue("noPopups", false); if(noPopups) return; // need to figure out some way to enter values without using a popup var turncount = GM_getValue(playerName+"_turncount", -1); var luckyTurn1 = GM_getValue(playerName+"_luckyTurn1", -1); var luckyTurn2 = GM_getValue(playerName+"_luckyTurn2", -1); var luckyTurn3 = GM_getValue(playerName+"_luckyTurn3", -1); var oldCountdown1 = luckyTurn1 > 0 ? luckyTurn1 - turncount : -1; var oldCountdown2 = luckyTurn2 > 0 ? luckyTurn2 - turncount : -1; var oldCountdown3 = luckyTurn3 > 0 ? luckyTurn3 - turncount : -1; var countdown1 = -1; var countdown2 = -1; var countdown3 = -1; var displayText = ""; if(oldCountdown1 > -1) displayText += oldCountdown1; if(oldCountdown2 > -1) { if(displayText != "") displayText += ", "; displayText += oldCountdown2; } if(oldCountdown3 > -1) { if(displayText != "") displayText += ", "; displayText += oldCountdown3; } var lastSemiTurn = GM_getValue(playerName+"_lastSemiTurn",-1); var lastSemiLocation = GM_getValue(playerName+"_lastSemiLocation",""); var promptText = ""; if(lastSemiTurn > -1 && turncount >= lastSemiTurn ) { promptText = "Last semirare found " + (turncount - lastSemiTurn) + " turns ago, at " + lastSemiLocation + ".\n\n"; } else { promptText = "Last semirare unknown.\n\n" } cookieText = prompt(promptText + "Enter 1-3 new cookie vals (-1 to clear)\n", displayText); if(cookieText) { // will be null if user cancels countdown1 = parseInt(cookieText); var pos = cookieText.indexOf(","); if(pos > 0) { cookieText = cookieText.substring(pos+1); countdown2 = parseInt(cookieText); pos = cookieText.indexOf(","); if(pos > 0) { cookieText = cookieText.substring(pos+1); countdown3 = parseInt(cookieText); } } // alert("manual text entry, got values " + countdown1 + ", " + countdown2 + ", " + countdown3); GM_setValue(playerName+"_luckyTurn1",countdown1+turncount); GM_setValue(playerName+"_luckyTurn2",countdown2+turncount); GM_setValue(playerName+"_luckyTurn3",countdown3+turncount); GM_setValue(playerName+"_needSanityCheck", false); // just in case we were in the middle of something GM_setValue(playerName+"_allowBracket",false); // no reason to put up reminder after this // load and parse the main charsheet to set the behind the scene variables that // convert the cookie countdown into actual adventure number for each cookie // GM_get(baseURL + charSheet, processCharsheet); // trigger char pane refresh with the new info top.frames[0].location.reload(); } } //////////////////////////////////////////////////////////////////////////////// // this function must be called when we are in the char sidepane 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 } //////////////////////////////////////////////////////////////////////////////// // 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; 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) { // clear the counters, no point in doing anything else clearCounters(playerName); return; } // check the session ID to see if we are still in the same session // if a new session, check if an update is available var pwdHash = getPwdHash(document.documentElement.innerHTML); var oldPwdHash = GM_getValue(playerName + "_pwdHash", 0); if(pwdHash != oldPwdHash) { // new session GM_setValue(playerName + "_pwdHash", pwdHash); // run sanity check on new session, make sure we haven't missed anything // alert("calling sanity check"); GM_get(baseURL + charSheet, sanityCheckCharsheet); // check for a new version of script if none seen already (asynch call, will run in parallel) // (temporarily?) removed for use with greasy fork /* var webVer = GM_getValue("webVersion", "Error"); if(webVer == "Error" || webVer <= currentVersion) { // alert("calling version check"); GM_get(scriptURL, CheckScriptVersion); } */ } // new native counter! var turncount = getTurnsplayedVar(document.documentElement.innerHTML); var oldTurncount = GM_getValue(playerName+"_turncount", 0); GM_setValue(playerName+"_turncount", turncount); var lastSemiTurn = GM_getValue(playerName+"_lastSemiTurn",-1); var lastSemiLocation = GM_getValue(playerName+"_lastSemiLocation",""); if(lastSemiTurn > turncount + 1) { // turncount var lags behind when fighting, so can have an off-by-1 issue // could happen if ascended lastSemiTurn = -1; GM_setValue(playerName+"_lastSemiTurn",-1); GM_setValue(playerName+"_lastSemiLocation",""); } var luckyTurn1 = GM_getValue(playerName+"_luckyTurn1", -1); var luckyTurn2 = GM_getValue(playerName+"_luckyTurn2", -1); var luckyTurn3 = GM_getValue(playerName+"_luckyTurn3", -1); // if anything hit zero, warn the user var noPopups = GM_getValue("noPopups", false); var atSemi = false; if( turncount == luckyTurn1 || turncount == luckyTurn2 || turncount == luckyTurn3) { atSemi = true; if(!noPopups && turncount != oldTurncount) { var alertText = "Fortune cookie countdown hit zero!"; if(lastSemiTurn > -1 && turncount >= lastSemiTurn) { alertText += "\n\nLast semirare found " + (turncount - lastSemiTurn) + " turns ago, at " + lastSemiLocation + "."; } else { alertText += "\n\nLast semirare unknown."; } confirm(alertText); } } var displayText = ""; if(luckyTurn1 >= turncount) displayText += luckyTurn1 - turncount; if(luckyTurn2 >= turncount) { if(displayText != "") displayText += ", "; displayText += luckyTurn2 - turncount; } if(luckyTurn3 >= turncount) { if(displayText != "") displayText += ", "; displayText += luckyTurn3 - turncount; } if(atSemi && noPopups) displayText += " (last SR: " + (lastSemiLocation == "" ? "unknown" : lastSemiLocation) + ")"; if(atSemi && fullmode) { // shows the html literally in compact mode displayText = "" + displayText + ""; } // display turn var to figure out exactly how it works // displayText += " (tp=" + turnsplayedVar+")"; var hideIfZeros = GM_getValue("hideIfZeros", false); if(displayText != "" || hideIfZeros == false ) { // display an FC bracket if no cookie eaten, and have found a semi recently var isOxy = GM_getValue(playerName+"_isOxy", false); var turnsSinceSemi = turncount - lastSemiTurn; var minCount = 160; var maxCount = 200; if( isOxy ) { minCount = 100; maxCount = 120; } if(displayText == "" && lastSemiTurn != -1 && turnsSinceSemi <= maxCount ) { displayText = "[" + Math.max(0, minCount - turnsSinceSemi) + "..."; displayText += Math.max(0, maxCount - turnsSinceSemi) + "]"; } else if (displayText == "" && turncount < 70) { // default just-ascended bracket displayText = "[" + Math.max(0, 70 - turncount) + "..."; displayText += Math.max(0, 80 - turncount) + "]"; } if(fullmode) { var newElement = document.createElement("FONT"); newElement.innerHTML = "" + "Cookie Count: " + displayText + "

"; ; newElement.setAttribute("onmouseover", 'this.style.opacity="0.5"'); newElement.setAttribute("onmouseout", 'this.style.opacity="1"'); newElement.setAttribute("id", 'fortuneCookieCounter'); newElement.addEventListener("click", manualCookieEntry, true); newElement.align = "center"; var elements = document.getElementsByTagName( "FONT" ); for ( var i = 0; i < elements.length; ++i ){ if ( elements[i].innerHTML.indexOf( "Last Adventure" ) != -1 ){ // insert our before this one elements[i].parentNode.insertBefore(newElement,elements[i]); break; } } } else { // compact mode - different layout, make a table row and two data element var newTR = document.createElement('tr'); var newElement = document.createElement("td"); newElement.appendChild(document.createTextNode("Cookie:")); 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", 'fortuneCookieCounter'); newElement.addEventListener("click", manualCookieEntry, true); newElement.align = "left"; newElement.style.fontWeight = "bold"; newElement.appendChild(document.createTextNode(displayText)); 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; } } } } } } //////////////////////////////////////////////////////////////////////////////// // set preferences // code stolen from mr script // not set on a per-player basis, would need to add code to parse account pane for char name function toggleHideCountdown() { // alert("in toggleHideCountdown()"); var hideIfZeros = GM_getValue("hideIfZeros", false); hideIfZeros = !hideIfZeros; GM_setValue("hideIfZeros", hideIfZeros); // trigger char pane refresh with the new info top.frames[0].location.reload(); var msg = document.getElementById('cookiecountertoggle'); msg.innerHTML = (hideIfZeros ? "Hiding" : "Showing") + " Inactive Counter - Click to Change"; } function togglePopups() { // alert("in toggleHideCountdown()"); var noPopups = GM_getValue("noPopups", false); noPopups = !noPopups; GM_setValue("noPopups", noPopups); // trigger char pane refresh with the new info var msg = document.getElementById('cookiepopups'); msg.innerHTML = (noPopups ? "Popups Disabled " : "Using Popups") + " (disable for FF4) - Click to Change"; top.frames[0].location.reload(); } // -------------------------------------------- // ---------- account menu option ------------- // -------------------------------------------- // Charon's code function buildPrefs() { if (!document.querySelector('#privacy')) return; if (!document.querySelector('#scripts')) { //scripts tab is not built, do it here var scripts = document.querySelector('ul').appendChild(document.createElement('li')); scripts.id = 'scripts'; var a = scripts.appendChild(document.createElement('a')); a.href = '#'; var img = a.appendChild(document.createElement('img')); img.src = 'http://images.kingdomofloathing.com/itemimages/cmonkey1.gif'; img.align = 'absmiddle'; img.border = '0'; img.style.paddingRight = '10px'; a.appendChild(document.createTextNode('Scripts')); a.addEventListener('click', function (e) { //make our new tab active when clicked, clear out the #guts div and add our settings to it e.stopPropagation(); document.querySelector('.active').className = ''; document.querySelector('#scripts').className = 'active'; document.querySelector('#guts').innerHTML = '
'; document.querySelector('#guts').appendChild(getSettings()); }, false); } else { //script tab already exists document.querySelector('#scripts').firstChild.addEventListener('click', function (e) { //some other script is doing the activation work, just add our settings e.stopPropagation(); document.querySelector('#guts').appendChild(getSettings()); }, false); } } function getSettings() { //build our settings and return them for appending var guts = document.body.appendChild(document.createElement('div')); guts.id = 'cookieprefs'; var subhead = guts.appendChild(document.createElement('div')); subhead.className = 'subhead'; subhead.textContent = 'Fortune Cookie Counter'; var section = guts.appendChild(document.createElement('div')); section.className = 'indent'; //call function in main script to actually make the settings section.appendChild(buildSettings()); return guts; } function buildSettings() { var prefLink = document.createElement('a'); var hideIfZeros = GM_getValue("hideIfZeros", false); prefLink.setAttribute('href','javascript:void(0)'); prefLink.setAttribute('id','cookiecountertoggle'); prefLink.innerHTML = (hideIfZeros ? "Hiding" : "Showing") + " Inactive Counter - Click to Change"; prefLink.addEventListener("click", toggleHideCountdown, true); var prefAnchor = document.createElement('a'); prefAnchor.setAttribute('name','opt'); prefAnchor.innerHTML = " "; var pDiddy = document.createElement('p'); with(pDiddy) { appendChild(prefAnchor); appendChild(prefLink); } prefLink = document.createElement('a'); var noPopups = GM_getValue("noPopups", false); prefLink.setAttribute('href','javascript:void(0)'); prefLink.setAttribute('id','cookiepopups'); prefLink.innerHTML = (noPopups ? "Popups Disabled " : "Using Popups") + " (disable for FF4) - Click to Change"; prefLink.addEventListener("click", togglePopups, true); prefAnchor = document.createElement('p'); prefAnchor.setAttribute('name','opt'); prefAnchor.innerHTML = " "; with(pDiddy) { appendChild(prefAnchor); appendChild(prefLink); } // add a link to update, if an update is available // (temporarily?) removed for use with Greasy Fork /* var webVer = GM_getValue("webVersion"); 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 cookie 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); pDiddy.appendChild(newElement); } */ return pDiddy; } //////////////////////////////////////////////////////////////////////////////// // main prog, just call the proper routine if we are on a pane we care about var nodeBody = document.getElementsByTagName("body").item(0); var textBody = ""; var baseURL = ""; var charSheet = "charsheet.php"; var playerName = GM_getValue( "currentPlayer"); if (nodeBody) { if (nodeBody.textContent) { textBody = nodeBody.textContent; } else if (nodeBody.text) { textBody = nodeBody.text; } baseURL = nodeBody.baseURI.substring(0,nodeBody.baseURI.lastIndexOf('/')+1); } // do normal page processing independently of whether started sanity check if ( document.location.pathname.indexOf("charpane.php") != -1 ) { //////NEW STUFF (Firvagor) - since the charpane refreshes every time something is used, if the results section is found, run main function using manually located mainpane (can only use indexing for some reason)///// if (top.frames[2].document.getElementById("effdiv")!=null) // was frames[1] until aug 2014 topmenu update processMain(top.frames[2].document); updateCharacterPane(); } else if ( document.location.pathname.indexOf("inventory.php") != -1 || document.location.pathname.indexOf("clan_viplounge.php")!= -1){ //alert("calling processMain() for " + document.location.pathname); //////NEW STUFF (Firvagor) - passes in current document instead of nothing, just to define the frame used///// GM_setValue(playerName+"_fighting",0); processMain(document); } else if ( document.location.pathname.indexOf("charsheet.php") != -1){ // alert("calling sanityCheckCharsheet() for " + document.location.pathname); GM_setValue(playerName+"_fighting",0); sanityCheckCharsheet(document.body.innerHTML); } else if ( document.location.pathname.indexOf("account.php") != -1 ) { // alert("calling set prefs for " + document.location.pathname); GM_setValue(playerName+"_fighting",0); buildPrefs(); // setPrefs(); } else if ( document.location.pathname.indexOf("adventure.php") != -1 || document.location.pathname.indexOf("choice.php") != -1) { // adventure.php should trap all "normal" noncombats // choice noncombats are in choice.php - now needed for hobopolis // for the fortune cookie combat rares, need to process fight.php GM_setValue(playerName+"_fighting",0); checkForNoncombatSemi(document.body.innerHTML); } else if ( document.location.pathname.indexOf("fight.php") != -1 ) { // see if we are actively fighting (no asynch calls allowed while fighting!) var fighting = GM_getValue(playerName+"_fighting",0) // alert("prefight check - page = " + document.location.pathname + ", fighting = " + fighting); { if( fighting == 0) { // just started a fight, see if it's a combat semi (orc chasm, kitchens, treasury) checkForCombatSemi(document.body.innerHTML); } // alert("incrementing fighting from " + fighting); fighting = fighting + 1; // also count rounds, prevents doing things twice in same fight // fighting, but maybe we're done, look for adventure again tag // I forget why we look at paragraphs here! // var paragraphs = document.getElementsByTagName( "p" ); // for ( var i = 0; i < paragraphs.length; ++i ) { // if ( paragraphs[i].innerHTML.indexOf( "Adventure Again (" ) != -1 // || paragraphs[i].innerHTML.indexOf("Go back to Your Inventory") != -1) { if ( document.body.innerHTML.indexOf( "Adventure Again (" ) != -1 || document.body.innerHTML.indexOf("Go back to Your Inventory") != -1) { fighting = 0; // break; } // } } GM_setValue(playerName+"_fighting",fighting); // alert("post fight - fighting = " + fighting); } else { // alert("ignoring " + document.location.pathname); } // do sanity check (load charsheet asynchronously) if requested - not while fighting if(GM_getValue(playerName+"_needSanityCheck",false) == true && GM_getValue(playerName+"_allowSanityCheck",false) == true ) { // alert("need sanity check flag detected!"); // no sanity check allowed if fighting if( GM_getValue(playerName+"_fighting",0) == 0) { // alert("calling sanity check"); GM_setValue(playerName+"_needSanityCheck", false); // alert("calling sanity check"); GM_get(baseURL + charSheet, sanityCheckCharsheet); } }