Code snippets

From GreaseSpot Wiki
Revision as of 02:38, 13 May 2008 by Daylight (talk | contribs)
Jump to navigationJump to search

// // version 0.6.6 // 2008-04-05 // Copyright (c) 2008 // Original Script by: ImmortalNights // Special Enhancements: wphilipw, ecamanaut // Released under the GPL license // http://www.gnu.org/copyleft/gpl.html // // -------------------------------------------------------------------- // // This is a Greasemonkey user script. // // To install, you need Greasemonkey: http://greasemonkey.mozdev.org/ // Then restart Firefox and revisit this script. // Under Tools, there will be a new menu item to "Install User Script". // Accept the default configuration and install. // // To uninstall, go to Tools/Manage User Scripts, // select "IkariamScoreLinker", and click Uninstall. // // -------------------------------------------------------------------- // // Version History: // 0.3.0: Original Public Release // ================================================== // 0.4.0: Island View & Bugfixes // 0.4.1: Description change, Generals Version update // 0.4.5: Bugfixes, Script combination (versions) // 0.4.6: Changed the image sizes to 16x16 // 0.4.7: Implemented 'checkAlreadyShown' to prevent the icon displaying multiple times // ================================================== // 0.5.0: Inline Score Display Option (AJAX) // 0.5.2: Defaults of text inline now set. Icons and text have headers. Options link always shown // 0.5.3: Code clean up, Money Score option & Options dialog // 0.5.4: Previous score details are saved, so that they are not updated if the same town is viewed. // 0.5.5: BugFix for multiple scores + timeout for saved scores (10min) // 0.5.6: BugFix: "undefined" scores (timestamp too long, now stored in string) // 0.5.7: Options on Options page, no longer inline // ================================================== // 0.6.0: Saves scores in the page after loading them once. Code cleanup. Does not try to run on the forums. // 0.6.1: Shows max lootable gold, according to a formula by Lirave. Keyboard selection via (shift)tab, or j/k. // 0.6.2: Removed the 'Change Options' link and the 'loot:' prefix on lootable gold. // 0.6.3: Made the 'Change Options' link configurable. Bugfixed non-inline links. (And loot is back; sorry. ;-) // 0.6.4: Bugfix for confusion when Kronos Utils adds info to the town size field. Also made sure Gold Scores don't wrap even if their contents get long. // 0.6.5: Paints demilitarized (but neither allied nor your own) cities yellow. // 0.6.6: Added some more key bindings: d/t/p/b/s, for all(?) the city actions. // // -------------------------------------------------------------------- // // This script places an icon to the right of a players // name after selecting their town on the Island overview, // or when viewing their Town. This icon links the user to // the scoreboard, where you can see the players score. // // Feel free to have a go yourself; as long as you leave // a little credit, and of course publish for the players // of Ikariam! // // This script was originally created by ImmortalNights, // and further edited and enhanced by wphilipw and ecmanaut. // // -------------------------------------------------------------------- // // ==UserScript== // @name IkariamScoreLinker // @namespace ikariamScript // @description Adds a link to the Scoreboard besides a players name after selecting their town on the Island Overview or when viewing their Town. // @include http://*.ikariam.*/* // @exclude http://board.ikariam.*/* // ==/UserScript==

/* The startup functions and global variables. Original Author: ImmortalNights & wphilipw For version: 0.3.0 Last changed: 0.6.0

  • /

var show = { gold: 4, military: 2, total: 1 }; var post = {

   total: "score",
military: "army_score_main",
    gold: "trader_score_secondary"

};

var saving; var gameServer = location.host; var valueCache = eval(GM_getValue(gameServer, "({})")); var changeLink = GM_getValue("link", true); var whatToShow = GM_getValue("show", "7"); var inlineScore = GM_getValue("inline", true);

if ($("options_changePass"))

 displayOnOptions_fn();

else

 init();

function init() {

 function maybeLookup(e) {
   var n = $X('.//span[@class="textLabel"]', e.target);
   var ul = $X('ancestor-or-self::li[1]/ul[@class="cityinfo"]', e.target);
   if ($X('li[contains(@class," name")]', ul)) return; // already fetched!
   var who = $X('li[@class="owner"]/text()[preceding::*[1]/self::span]', ul);
   var name = trim(who.textContent);
   fetchScoresFor(name, ul, n, number(n.parentNode.id));
 }
 function lookupOnClick(a) {
   onClick(a, function(e) { setTimeout(maybeLookup, 10, e); });
 }
 function peek(e) {
   var on = e.target;
   cities.map(click);
   if (/^a$/i.test(on.nodeName))
     click(on);
 }
 if ("island" == document.body.id) {
   GM_addStyle(<><![CDATA[#island #information .messageSend img {
     position: absolute;
     margin: -3px 0 0 4px;
   }]]></>.toXMLString());
   var id = location.href.match(/[&?]id=(\d+)/);
   if (id) id = id[1];
 }
 var cities = getCityLinks();
 if (cities.length) {
   cities.forEach(lookupOnClick);
   var body = document.body;
   addEventListener("keypress", keyboard, true);
   return inlineScore && onClick(body, peek, 0, "dbl");
 }
 var player = itemValue("owner");
 if (player)
   fetchScoresFor(player, null, null, id);

}

function saveCache() {

 //console.log("Saving cache: %x", uneval(valueCache));
 GM_setValue(gameServer, uneval(valueCache).replace(/ /g, ""));

}

function cacheValue(id, type, value) {

 //console.log("caching", id, type, value);
 var city = valueCache[id] || {};
 type = type.charAt();
 city[type] = number(value);
 city.T = time();
 valueCache[id] = city;
 saving && clearTimeout(saving);
 saving = setTimeout(saveCache, 1e3);

}

function focus(direction) {

 var all = getCityLinks();
 var now = unsafeWindow.selectedCity;
 var cur = $X('id("cityLocation'+ now +'")/a') || all[all.length - 1];
 if (all.length) {
   now = all.map(function(a) { return a.id; }).indexOf(cur.id);
   click(all[(now + direction + all.length * 3) % all.length]);
 }

}

function keyboard(e) {

 function invoke(a) {
   a = $X('id("actions")/ul[@class="cityactions"]/li[@class="'+ a +'"]/a');
   return function() { if (a && a.href) location.href = a.href; };
 }
 function counterClockwise() { focus(-1); }
 function clockwise() { focus(1); }
 function tab() {
   if (!e.altKey && !e.ctrlKey && !e.metaKey)
     focus(e.shiftKey ? -1 : 1);
 }
 var keys = {
   "\t": tab, j: counterClockwise, k: clockwise,
   d: invoke("diplomacy"), t: invoke("transport"),
   p: invoke("plunder"), b: invoke("blockade"), s: invoke("espionage")
 };
 var action = keys[String.fromCharCode(e.keyCode || e.charCode)];
 if (action) {
   e.stopPropagation();
   e.preventDefault();
   action();
 }

}

function fetchScoresFor(name, ul, n, id) {

 function searchbutton(type) {
   var url = "url(/skin/" + ({
      total: "layout/medallie32x32_gold.gif) no-repeat -7px -9px",
   military: "layout/sword-icon2.gif) no-repeat 0 2px;",
       gold: "resources/icon_gold.gif) no-repeat 0 0; width:18px"
     })[type];
   return <input type="submit" name="highscoreType"
     value=" " title={"View player's "+ type +" score"}
     style={"border: 0; height: 23px; width: 16px; cursor: pointer; " +
            "color: transparent; background:"+ url}
     onclick={"this.value = '"+ post[type] +"'; this.form.submit();"}/>;
 }
 var scores = changeLink &&
   <a href="/index.php?view=options"
     title="Change score options">Change Options</a>;
 if (!inlineScore) {
   var form = <form action="/index.php" method="post">
     <input type="hidden" name="view" value="highscore"/>
     <input type="hidden" name="" id="searchfor"/>
     <input type="hidden" name="searchUser" value={name}/>
   </form>;
   for (var type in post)
     if (whatToShow & show[type])
       form.* += searchbutton(type);
   if (changeLink) {
     scores.@style = "position: relative; top: -6Px;";
     form.* += scores;
   }
   form.@style = "position: relative; "+ (changeLink ? "left:-26px; " : "") +
     "white-space: nowrap;";
   scores = form;
 }
 if (!inlineScore || changeLink)
   addItem("options", scores, ul);
 if (!inlineScore) return;
 for (type in show) {
   if (!(whatToShow & show[type]))
     continue;
   if ("gold" == type && isMyCity(ul) && viewingRightCity(ul)) {
     var gold = $("value_gold").innerHTML;
     updateItem(type, gold, cityinfoPanel(), null, lootable(gold));
     continue;
   }
   addItem(type, "fetching...");
   requestScore(name, type, id, makeShowScoreCallback(name, type, ul, n, id));
 }

}

function isMyCity(ul, name) {

 if ("city" == document.body.id)
   return $X('id("position0")/a').href != "#";
 var name = getItem("owner", ul);
 var a = $X('a', name);
 if (a) {
   var id = a.search.match(/destinationCityId=(\d+)/)[1];
   return $X('id("citySelect")/option[@value="'+ id +'"]');
 }
 var city = itemValue("name", ul);
 return $X('id("citySelect")/option[.="'+ city +'"]');

}

function lootable(score, ul) {

 var amount = parseInt((score||"").replace(/\D+/g, "") || "0", 10);
 var panel = getItem("citylevel");
 var level = getItem("citylevel", ul);
 var size = itemValue(level);
 var max = Math.round(size * (size - 1) / 10000 * amount);
 if (isNaN(max)) return;
 max = node("span", "", null, "\xA0("+ fmtNumber(max) +"\)");
 max.title = "Amount of gold lootable from this town";
 return max;

}

function viewingRightCity(ul) {

 return itemValue("name") == itemValue("name", ul) &&
       itemValue("owner") == itemValue("owner", ul);

}

function makeShowScoreCallback(name, type, ul, n, id) {

 return function showScore(xhr, cached) {
   var score = xhr;
   if ("yes" == cached) {
     score = fmtNumber(score);
   } else { // need to parse out the score
     score = $X('.//div[@class="content"]//tr[td[@class="name"]="' +
                name + '"]/td[@class="score" or @class="§"]',
                node("div", "", null, xhr.responseText));
     score = score.innerHTML;
   }
   if (score) {
     if ("yes" != cached) cacheValue(id, type, score);
     ul = ul || cityinfoPanel();
     if (n && "0" == score && "military" == type) {
       n.style.fontWeight = "bold"; // n.style.fontStyle = "italic";
       n = $X('../preceding-sibling::div[@class="cityimg"]', n);
       if (n)
         n.style.backgroundImage = getComputedStyle(n,"").
           backgroundImage.replace("red.gif", "yellow.gif");
     }
     // You rob gold (size * (size - 1)) % of the treasury of the city:
     if ("gold" == type)
       var max = lootable(score, ul);
     updateItem(type, score, ul, !!n, max);
   }
 };

}

function getCityLinks() {

 return $x('id("cities")/li[contains(@class,"city level")]/a');

}

function itemValue(item, ul) {

 var li = "string" == typeof item ? getItem(item, ul) : item;
 var xpath = 'text()[preceding-sibling::*[1]/self::span[@class="textLabel"]]';
 var text = $X(xpath, li);
 return text && trim(text.textContent || "");

}

function getItem(type, ul) {

 return $X('li[contains(concat(" ",normalize-space(@class)," ")," '+
           type +' ")]', ul || cityinfoPanel());

}

function mkItem(type, value) {

 var li = node("li", type + " name", null, value);
 var title = (type in show) ?
   type.charAt().toUpperCase() + type.slice(1) + " Score:" : "Scores:";
 li.insertBefore(node("span", "textLabel", null, title), li.firstChild);
 return li;

}

function addItem(type, value, save) {

 var li = getItem(type);
 if (li) {
   li.lastChild.nodeValue = value;
 } else {
   var ul = cityinfoPanel();
   var next = $X('li[@class="ally"]/following-sibling::*', ul);
   ul.insertBefore(li = mkItem(type, value), next);
 }
 if (save && !getItem(type, save)) {
   next = $X('li[@class="ally"]/following-sibling::*', save);
   save.insertBefore(li.cloneNode(true), next);
 }
 return li;

}

function updateItem(type, value, ul, islandView, append) {

 var li = getItem(type, ul);
 if (li) {
   li.lastChild.nodeValue = value;
 } else {
   var next = $X('li[@class="ally"]/following-sibling::*', ul);
   ul.insertBefore(li = mkItem(type, value), next);
   if (viewingRightCity(ul) && islandView) // only touch panel on right focus
     updateItem(type, value, null, null, append && append.cloneNode(true));
 }
 if (append && !$X('span[@title]', li)) {
   li.style.whiteSpace = "nowrap";
   li.appendChild(append);
 }
 return li;

}

function cityinfoPanel() {

 return $X('id("information")//ul[@class="cityinfo"]');

}

function node(type, className, styles, content) {

 var n = document.createElement(type||"div");
 if (className) n.className = className;
 if (styles)
   for (var prop in styles)
     n.style[prop] = styles[prop];
 if (content)
   n.innerHTML = "string" == typeof content ? content : content.toXMLString();
 return n;

}

function click(node) {

 var event = node.ownerDocument.createEvent("MouseEvents");
 event.initMouseEvent("click", true, true, node.ownerDocument.defaultView,
                      1, 0, 0, 0, 0, false, false, false, false, 0, node);
 node.dispatchEvent(event);

}

function fmtNumber(n) {

 n += "";
 for (var i = n.length - 3; i > 0; i -= 3)
   n = n.slice(0, i) +","+ n.slice(i);
 return n;

}

function number(n) {

 n = { string: 1, number: 1 }[typeof n] ? n+"" : n.textContent;
 return parseInt(n.replace(/\D+/g, "") || "0", 10);

}

function trim(str) {

 return str.replace(/^\s+|\s+$/g, "");

}

function onClick(node, fn, capture, e) {

 node.addEventListener((e||"") + "click", fn, !!capture);

}

function $(id) {

 return document.getElementById(id);

}

function $x( xpath, root ) {

 var doc = root ? root.evaluate ? root : root.ownerDocument : document, next;
 var got = doc.evaluate( xpath, root||doc, null, 0, null ), result = [];
 switch (got.resultType) {
   case got.STRING_TYPE:
     return got.stringValue;
   case got.NUMBER_TYPE:
     return got.numberValue;
   case got.BOOLEAN_TYPE:
     return got.booleanValue;
   default:
     while (next = got.iterateNext())
       result.push( next );
     return result;
 }

}

function time(t) {

 t = t || Date.now();
 return Math.floor(t / 6e4) - 2e7; // ~minute precision is enough

}

function $X( xpath, root ) {

 var got = $x( xpath, root );
 return got instanceof Array ? got[0] : got;

}

/* The AJAX request system so we can display the scores inline Original Author: wphilipw For version: 0.5.0 Last changed: 0.5.0

  • /

function requestScore(name, type, id, onload) {

 var cached = id && valueCache[id], key = type.charAt();
 if (cached && cached[key] && ((time() - cached.T) < 10))
   return onload(cached[key], "yes");
 //else delete valueCache[id]; // stale -- but save for now; could be useful
 GM_xmlhttpRequest({
   method: "POST",
   url: "http://" + gameServer + "/index.php",
   data: "view=highscore&highscoreType="+ post[type] +"&searchUser="+ name,
   headers: {
     "User-agent": "Mozilla/4.0 (compatible) Greasemonkey",
     "Content-type": "application/x-www-form-urlencoded",
     "Accept": "application/atom+xml,application/xml,text/xml",
     "Referer": "http://" + gameServer + "/index.php"
   },
   onload: onload
 });

}

/* runs on first run to set up default values Original Author: ImmortalNights For version: 0.5.4 Last changed: 0.6.0

  • /

function displayOnOptions_fn() {

 var mybox = node("div", "", { textAlign: "left" });
 var opts = <>

Score Display Options

Show Total Score: <input type="checkbox" id="totalScore"/>
Show Army Score: <input type="checkbox" id="militaryScore"/>
Show Gold Score: <input type="checkbox" id="goldScore"/>
Show Score Inline: <input type="checkbox" id="inlineScore"/>
Show Score Options link: <input type="checkbox" id="changeScoreOptions"/>

</>;

 mybox.innerHTML = opts.toXMLString();
 var pwd = $('options_changePass');
 pwd.appendChild(mybox);
 var checkboxes = $x('//input[@type="checkbox" and contains(@id,"Score")]');
 for (var i = 0; i < checkboxes.length; i++) {
   var input = checkboxes[i];
   var id = input.id.replace("Score", "");
   if (id == "inline")
     input.checked = !!inlineScore;
   else if ("changeOptions" == id)
     input.checked = !!changeLink;
   else
     input.checked = !!(show[id] & whatToShow);
 }
 var inputs = $x('//input[@type="submit"]');
 for (var e = 0; e < inputs.length; e++)
   onClick(inputs[e], changeShow_fn, true);

}

/* This function saves the options chosen above Original Author: wphilipw For version: 0.4.5 Last changed: 0.6.0

  • /

function changeShow_fn(e) {

 GM_setValue("show", (
               (show.total * $('totalScore').checked) |
               (show.military * $('militaryScore').checked) |
               (show.gold * $('goldScore').checked)
             ) + "");
 GM_setValue("inline", $('inlineScore').checked);
 GM_setValue("link", $('changeScoreOptions').checked);
 e.target.form.submit();

}