KoLE2-alpha

Misc enhancements for kingdom of loathing

当前为 2014-09-16 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name        KoLE2-alpha
// @namespace   fnoot/kol/kole2-alpha
// @description Misc enhancements for kingdom of loathing
// @include     http://www.kingdomofloathing.com/*
// @version     0.2
// @grant    	GM_getValue
// @grant    	GM_setValue
// @grant    	GM_deleteValue
// ==/UserScript==
/*

changes 0.1 > 0.2
- Topbar icon theming
- Auto fight min HP% setting
- /qs * <item> to sell all

*/
// kole 1 (defunct): http://userscripts.org/scripts/show/149194
(function() {
    var top = unsafeWindow.top; // :(

    //  debug
    //	GM_deleteValue("kole2Settings");
    //	GM_deleteValue("iconThemes");

    var defaultIconThemes = {
        example: {
            "displayName": "Example Theme",
            "icons": {
                "http://images.kingdomofloathing.com/itemimages/map.gif": "http://upload.wikimedia.org/wikipedia/en/8/8d/Icon_globe.png",
                "http://images.kingdomofloathing.com/itemimages/backpack.gif": "http://www.jcmotors.com/images/Category/icon/1205.jpg",
                "http://images.kingdomofloathing.com/itemimages/donate.gif": "http://ecx.images-amazon.com/images/I/51jHfChZ14L._SS30_.jpg"
            }
        }
    };

    var saveIconThemes = function() {
        GM_setValue("iconThemes", JSON.stringify(top.kole.iconThemes));
    }

    var setOption = function(name, value) {
        console.log("setOption", name, value);
        GM_setValue(name, JSON.stringify(value));
    };

    var getOption = function(name) {
        var v = GM_getValue(name, JSON.stringify(configOptions[name].default));
        return JSON.parse(v);
    };

    var closePanel = null;

    var controlTypes = {
        input: function(name, spec, parent) {
            var input = crel("input", {}, parent);
            input.id = "kole_config_" + name;
            var setValue = function() {
                if (spec.onchange) spec.onchange(input.value);
                setOption(name, input.value);
            };
            input.value = getOption(name);
            input.onkeyup = setValue;
            input.onpaste = setValue;
        },
        yesno: function(name, spec, parent) {
            var select = crel("select", {}, parent);
            select.id = "kole_config_" + name;
            with(crel("option", {}, select)) {
                innerHTML = "Yes";
                value = 1;
            }
            with(crel("option", {}, select)) {
                innerHTML = "No";
                value = 0;
            }
            select.selectedIndex = getOption(name) ? 0 : 1;
            select.onchange = function() {
                setOption(name, this.selectedIndex == 0);
            };
        },
        check: function(name, spec, parent) {
            var cbox = crel("input", {}, parent);
            cbox.checked = getOption(name);
            cbox.id = "kole_config_" + name;
            cbox.type = "checkbox";
            cbox.onclick = function() {
                setOption(name, this.checked);
                if (spec.onchange) spec.onchange();
            }
        },
        spin: function(name, spec, parent) {
            var min = spec.range[0],
                max = spec.range[1];
            var value = getOption(name);
            if (isNaN(value)) value = configOptions[name].default;
            var displayCallback = spec.displayCallback || function(v) {
                return v;
            };
            var downButton = crel("button", {}, parent);
            downButton.innerHTML = "&lt;";
            var valueSpan = crel("span", {
                display: "inline-block",
                "margin": "0 6px",
                "width": "50px",
                "text-align": "center"
            }, parent);
            var fixPrecision = function() {
                value = Math.round(value * 100) / 100;
            }
            valueSpan.innerHTML = displayCallback(value);
            var upButton = crel("button", {}, parent);
            upButton.innerHTML = "&gt;";
            downButton.onclick = function() {
                value -= spec.step;
                fixPrecision();
                if (value < min) value = min;
                valueSpan.innerHTML = displayCallback(value);
                setOption(name, value);
                if (spec.onchange) spec.onchange(value);
            };
            upButton.onclick = function() {
                value += spec.step;
                fixPrecision();
                if (value > max) value = max;
                valueSpan.innerHTML = displayCallback(value);
                setOption(name, value);
                if (spec.onchange) spec.onchange(value);
            };
        },
        button: function(name, spec, parent) {
            with(crel("a", {}, parent)) {
                onclick = function() {
                    spec.onclick();
                    return false;
                };
                href = "#" + name;
                innerHTML = spec.buttonCaption;
            }
        },
        select: function(name, spec, parent) {
            var ctrl = crel("select", {}, parent);
            var options = typeof spec.options == "function" ? spec.options() : spec.options;
            var idx = 0;
            var selIndex = 0;
            for (var value in options) {
                var opt = crel("option", {}, ctrl);
                opt.innerHTML = options[value];
                opt.value = value;
                if (value == getOption("iconTheme")) selIndex = idx;
                idx++;
            }
            ctrl.selectedIndex = selIndex;
            ctrl.onchange = function() {
                setOption(name, ctrl.options[ctrl.selectedIndex].value);
                if (spec.onchange) spec.onchange(value);
            };
        },
        raw: function(name, spec, parent) {
            parent.innerHTML = spec.html;
        }
    };

    var getSetting = function(name) {
        console.trace();
        throw new Error("Obsolete getSetting()");
    };

    var itemLookup = function(fuzzyName) {
        if (!top.kole) return null;
        var matches = [];
        var item = null;
        for (var name in top.kole.itemIds) {
            if (name.trim().toUpperCase().indexOf(fuzzyName.toUpperCase()) > -1) {
                if (name.toLowerCase() == fuzzyName.toLowerCase()) {
                    return {
                        name: name,
                        id: top.kole.itemIds[name]
                    };
                }
                matches.push({
                    name: name,
                    id: top.kole.itemIds[name]
                });
            }
        }
        if (matches.length > 1) {
            return {
                error: "Multiple matches for \"" + fuzzyName + "\". Please be more specific."
            };
        } else if (matches.length == 1) {
            return matches[0];
        }
        return null;
    };

    var configOptions = {
        hoverHints: {
            default: true,
            control: "check",
            caption: "Hover hints",
            description: "Shows information about an item/effect/icon when the mouse pointer hovers over it"
        },
        hintDelay: {
            default: 225,
            caption: "Hint delay (milliseconds)",
            description: "Specifies how long you need to hover over an item/effect/icon before its description is shown",
            control: "spin",
            range: [0, 2000],
            step: 25
        },
        darkness: {
            default: 0.2,
            caption: "Darkness",
            description: "Darkens the whole game; great for headaches and photosensitives!",
            control: "spin",
            range: [0, 0.8],
            step: 0.1,
            onchange: function(value) {
                top.kole.setDarkness(value);
            },
            displayCallback: function(value) {
                return ((value / 0.8) * 100).toFixed(1).replace(/(\d+)\.0+$/, '$1') + "%";
            }
        },
        stayLoggedIn: {
            default: true,
            caption: "Stay logged in",
            description: "Defeats the timeout that logs you out after an idle period by sending a dummy request every two minutes",
            control: "check",
            onchange: function(value) {
                if (value) xhr("main.php", function() {}, false);
            }
        },
        nullifySword: {
            default: true,
            caption: "Fix prepositions",
            description: "Undoes the effect of the Sword",
            control: "check"
        },
        iconTheme: {
            default: "",
            caption: "Topbar icon theme",
            control: "select",
            description: "Replaces the icons on the topbar with a custom theme",
            options: function() {
                var options = {
                    "": "Vanilla"
                };
                console.log("retr top", unsafeWindow.top);
                for (themeName in unsafeWindow.top.kole.iconThemes) {
                    options[themeName] = unsafeWindow.top.kole.iconThemes[themeName].displayName
                }
                return options;
            }
        },
        manageIconThemes: {
            caption: "Manage icon themes",
            description: "Import, delete and edit topbar icon themes",
            control: "button",
            buttonCaption: "Manage",
            onclick: function() {
                var reshow = function() {
                    var pop = crel("div");
                    var vanillaManage = crel("div");
                    var newThemeButton = crel("a", {}, vanillaManage);
                    crel("span", {}, vanillaManage).innerHTML = " ";
                    newThemeButton.innerHTML = "New theme";
                    newThemeButton.href = "#";
                    newThemeButton.onclick = function() {
                        managePoop.close();
                        editIconTheme("", reshow);
                    };
                    var importButton = crel("a", {}, vanillaManage);
                    crel("span", {}, vanillaManage).innerHTML = " ";
                    importButton.innerHTML = "Import";
                    importButton.href = "#";
                    importButton.onclick = function() {
                        managePoop.close();
                        importIconTheme(reshow);
                    };
                    crel("h1", {
                        margin: "0 0 12px 0",
                        "font-weight": "100"
                    }, pop).innerHTML = "Topbar Icon Themes";
                    var themeList = [
                        ["Vanilla", vanillaManage]
                    ];
                    for (var name in unsafeWindow.top.kole.iconThemes) {
                        var manage = crel("div");
                        var editButton = crel("a", {}, manage);
                        crel("span", {}, manage).innerHTML = " ";
                        editButton.innerHTML = "Edit";
                        editButton.href = "#";
                        var deleteButton = crel("a", {}, manage);
                        crel("span", {}, manage).innerHTML = " ";
                        deleteButton.innerHTML = "Delete";
                        deleteButton.href = "#";
                        var exportButton = crel("a", {}, manage);
                        crel("span", {}, manage).innerHTML = " ";
                        exportButton.innerHTML = "Export";
                        exportButton.href = "#";
                        with({
                            name: name,
                            theme: unsafeWindow.top.kole.iconThemes[name]
                        }) {
                            deleteButton.onclick = function() {
                                if (!confirm("Delete this theme?")) return;
                                delete top.kole.iconThemes[name];
                                if (name == getOption("iconTheme")) {
                                    setOption("iconTheme", "");
                                    closePanel();
                                    openPanel();
                                    saveSettings();
                                }
                                saveIconThemes();
                                managePoop.close();
                                reshow();
                                return false;
                            };
                            editButton.onclick = function() {
                                editIconTheme(name, reshow);
                                managePoop.close();
                                return false;
                            };
                            exportButton.onclick = function() {
                                poop("<h1 style='font-weight:100; margin:0 0 12px 0'>Export code for " + theme.displayName + "</h1>" + "<textarea onclick='this.select()' style='width:100%; background:rgba(0,0,0,0.2); height:200px' readonly='readonly'>" + exportIconTheme(name).replace(/\</g, "&lt;").replace(/\>/g, "&gt;") + "</textarea><p>Copy and share the above code to distribute your theme.</p>");
                                return false;
                            };
                        }

                        themeList.push([top.kole.iconThemes[name].displayName, manage]);
                    }
                    pop.appendChild(tabulate(themeList, "rgba(0,0,0,0.06"));
                    var managePoop = poop(pop);
                }; // /reshow
                reshow();
            }
        },
        autoFight: {
            default: false,
            caption: "Automatic fighting",
            description: "Always clicks the last item in the combat bar or \"Adventure again\" when available; requires combat bar enabled in KoL options",
            control: "check"
        },
        autoFightDelay: {
            default: 4000,
            caption: "Automatic fighting delay",
            description: "Defines how long to wait before taking an automatic action in a fight",
            control: "spin",
            range: [1000, 10000],
            step: 500,
            displayCallback: function(v) {
                return (v / 1000) + "sec";
            }
        },
        autoFightMinHp: {
            default: 40,
            caption: "Auto fighting minimum HP",
            description: "Cancels automatic fighting when HP drops below this",
            control: "spin",
            range: [0, 100],
            step: 5,
            displayCallback: function(v) {
                return v + "%";
            }
        },
        chatHelp: {
            caption: "Chat commands",
            buttonCaption: "Show",
            "description": "Shows a list of chat commands added by KoLE",
            "control": "button",
            onclick: function() {
                var pop = crel("div");
                crel("h1", {
                    margin: "0 0 12px 0",
                    "font-weight": "100"
                }, pop).innerHTML = "KoLE Chat Commands";
                crel("p", {
                    "font-size": "small"
                }, pop).innerHTML = "These commands require <i>Modern</i> chat version selected in <a href='account.php?tab=chat'>KoL options</a>";
                pop.appendChild(tabulate([
                    ["<code>/wiki &lt;searchterm&gt;</code>", "Search Coldfront KoL wiki"],
                    ["<code>/qs [amount] &lt;itemname&gt;</code>", "Quicksell item; default amount is 1"],
                    ["<code>/qs * &lt;itemname&gt;</code>", "Quicksell all of an item"]
                ], "rgba(0,0,0,0.06"));
                poop(pop);
            }
        },
        donate: {
            caption: "Appreciation",
            control: "raw",
            description: "I am poorly and well below the poverty line. Has this been useful?",
            html: '<form target="_blank" action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_top" style="margin:none;padding:none;display:inline">' +
                '<input type="hidden" name="cmd" value="_s-xclick">' +
                '<input type="hidden" name="hosted_button_id" value="G33Q3HVDX4G3Y">' +
                '<input type="submit" value="Show via PayPal" src="https://www.paypalobjects.com/en_GB/i/btn/btn_donate_SM.gif" border="0" name="submit">' +
                '<img alt="" border="0" src="https://www.paypalobjects.com/en_GB/i/scr/pixel.gif" width="1" height="1">' +
                '</form>'
        }

    };




    var prepositions = ["about", "above", "across", "after", "against", "along", "among", "around", "at", "before",
        "behind", "below", "beneath", "beside", "between", "beyond", "by", "down", "during", "except",
        "for", "from", "in", "inside", "into", "like", "near", "of", "off", "on", "onto", "out", "outside",
        "over", "past", "through", "throughout", "to", "under", "up", "upon", "with", "within", "without"
    ];



    var nullifySword = function(s) {
        for (var i = 0; i < prepositions.length; i++) {
            var prep = prepositions[i];
            var search = new RegExp(" " + prep, "g");
            s = s.replace(search, "\x09" + prep);
            var search = new RegExp(prep + " ", "g");
            s = s.replace(search, prep + "\x09");
        }
        return s;
    };

    var tabulate = function(data, altColour, cellCallback) {
        var table = crel("table", {
            width: "100%",
            "font-size": "small"
        });
        table.setAttribute("cellspacing", 0);
        table.setAttribute("cellpadding", 4);
        if (!cellCallback) cellCallback = function(v) {
            return v;
        };
        var tb = crel("tbody", {}, table);
        if (data.length == 0) return table;
        var alt = false;
        for (var i = 0; i < data.length; i++) {
            var row = data[i];
            var tr = crel("tr", {}, tb);
            if (alt) tr.style.background = altColour;
            alt = !alt;
            for (var x = 0; x < row.length; x++) {
                var td = crel("td", {}, tr);
                var cbResult = cellCallback(row[x]);
                if (typeof cbResult == "string") {
                    td.innerHTML = cbResult;
                } else {
                    td.appendChild(cbResult);
                }
            }
        }
        return table;
    };

    var applyStyles = function(el, styles) {
        for (var k in styles) {
            el.style.setProperty(k, styles[k]);
            //lazily prepend browser-specific prefixes
            el.style.setProperty("-moz-" + k, styles[k]);
            el.style.setProperty("-webkit-" + k, styles[k]);
        }
    };

    // document.createElement > applyStyles > parent.append shortcut
    var crel = function(tag, styles, parent) {
        var el = document.createElement(tag);
        if (typeof styles != "undefined") applyStyles(el, styles);
        if (parent === null) {
            console.warn("parent is null for", tag);
            console.trace();
            return;
        }
        if (typeof parent != "undefined") parent.appendChild(el);
        return el;
    };

    var elX = function(el) {
        return el.offsetParent ? elX(el.offsetParent) + el.offsetLeft : el.offsetLeft;
    };
    var elY = function(el) {
        return el.offsetParent ? elY(el.offsetParent) + el.offsetTop : el.offsetTop;
    };


    if (window !== top) {
        var darkCover = crel("div", {
            "position": "fixed",
            "top": "0",
            "left": "0",
            "right": "0",
            "bottom": "0",
            "background": "#000",
            "opacity": 0,
            "pointer-events": "none",
            "z-index": 999999,
        }, document.body);
        var hintBox = crel("div", {
            "position": "absolute",
            "width": "260px",
            "padding": "12px 0px",
            "border": "1px #bbb solid",
            "border-radius": "5px",
            "box-shadow": "2px 2px 3px rgba(0,0,0,0.4)",
            "pointer-events": "none",
            "background": "#fff",
            "opacity": 0,
            "transform": "scale(0.1,0.1)",
            "transition": "100ms opacity ease-out, 100ms transform ease-out, 100ms -moz-transform ease-out, 100ms -webkit-transform ease-out",
            "z-index": 999998,
            "font-size": "small"
        }, document.body);
        setTimeout(function() {
            darkCover.style.transition = "600ms opacity";
        });

        var initialHintWidth = hintBox.clientWidth;
        var showHint = function(forEl, html) {
            forX = elX(forEl);
            forY = elY(forEl);
            hintBox.innerHTML = html;
            hintBox.style.width = initialHintWidth + "px";
            hintBox.style.left = forX + forEl.clientWidth + 12 + "px";
            if ((elX(hintBox) + hintBox.clientWidth) > document.body.clientWidth) {
                hintBox.style.width = document.body.clientWidth - elX(hintBox) + "px";
            }
            hintBox.style.top = (forY + (hintBox.clientHeight * 1.5)) > document.body.scrollHeight ? document.body.scrollHeight - (hintBox.clientHeight * 1.5) + "px" : forY + "px";
            crel("div", {
                background: "rgba(100, 150, 255,0.04)",
                "position": "absolute",
                "box-shadow": "0 0 22px rgba(100, 150, 255,0.09)",
                "top": "42px",
                "left": "0",
                "right": "0",
                "bottom": "0"
            }, hintBox);
            applyStyles(hintBox, {
                opacity: 1,
                transform: "scale(1,1)"
            });
            hintElement = forEl;
        };

        var hintTimer = null;
        var hintElement = null;

        var xhr = function(url, callback, cached) {
            if (cached && typeof top.kole.xhrCache[url] != "undefined") {
                setTimeout(function() {
                    callback(top.kole.xhrCache[url]);
                }, 0);
                return {
                    cancel: function() {}
                };
            };
            var canceled = false;
            var req = new XMLHttpRequest();
            var stateChange = function() {
                if (this.readyState == 4) {
                    top.kole.xhrCache[url] = this.responseText;
                    if (!canceled)
                        callback(this.responseText);
                    req.removeEventListener("readystatechange", stateChange);
                }
            };
            req.addEventListener("readystatechange", stateChange, false);
            req.open("GET", url, true);
            req.send();
            return {
                cancel: function() {
                    canceled = true;
                }
            }
        };

        var extractDescription = function(url, callback, cached) {
            return xhr(url, function(response) {
                var tempEl = crel("div");
                tempEl.innerHTML = response;
                var scripts = tempEl.querySelectorAll("script");
                for (var i = scripts.length - 1; i >= 0; i--) scripts[i].parentNode.removeChild(scripts[i]);
                callback(tempEl.querySelectorAll("#description")[0].innerHTML);
            }, cached);
        };

        var cancelLastHintCallback = function() {};

        var cancelHint = function() {
            cancelLastHintCallback();
            if (hintTimer !== null) clearTimeout(hintTimer);
            applyStyles(hintBox, {
                opacity: 0,
                transform: "scale(0.9,0.9)"
            });
        };

        // htmlCallback(done(html)) should return {cancel:function(){...}}
        var setHintTimer = function(el, htmlCallback, ___args) {
            cancelHint();
            // show when both timer AND callback async complete
            var asyncRemaining = 2;
            var hintHtml = "";
            var asyncDone = function() {
                asyncRemaining--;
                if (asyncRemaining == 0) {
                    showHint(el, hintHtml);
                }
            };
            hintTimer = setTimeout(asyncDone, getOption("hintDelay"));
            var cancelLastHintCallback = htmlCallback(function(html) {
                hintHtml = html;
                asyncDone();
            }).cancel;
        };
    } // !top

    if (unsafeWindow == unsafeWindow.top) {
        var whenReady = function(cb) {
            for (var i = 0; i < unsafeWindow.frames.length; i++)
                if (typeof unsafeWindow.frames[i].kole == "undefined") {
                    setTimeout(function() {
                        whenReady(cb);
                    }, 200);
                    return;
                }
            cb();
        }
        if (typeof this.kole != "undefined") {
            alert("A browser plugin or KoL update is conflicting with KoLE");
            return;
        }
        unsafeWindow.kole = null;
        whenReady(function() {
            var GM_itemIds = GM_getValue("itemIds");
            var GM_iconThemes = GM_getValue("iconThemes");
            unsafeWindow.kole = {
                setDarkness: function(darkness) {
                    for (var i = 0; i < frames.length; i++) {
                        unsafeWindow.frames[i].window.kole.setDarkness(darkness);
                    }
                },
                itemIds: typeof GM_itemIds == "undefined" ? {} : JSON.parse(GM_itemIds),
                iconThemes: typeof GM_iconThemes == "undefined" ? defaultIconThemes : JSON.parse(GM_iconThemes),
                xhrCache: {}
            };
            console.log("Set top.kole", unsafeWindow.kole);
        });
    } else {
        unsafeWindow.kole = {
            setDarkness: function(darkness) {
                darkCover.style.opacity = darkness + 0.00001; // fixes gre render bug
            },
            top: top.kole
        };
        unsafeWindow.kole.setDarkness(getOption("darkness"));
    }

    var timedClick = function(el, delay, cancelCaption, oncancel) {
        var cancelButton = crel("button", {
            background: "#fff",
            border: "2px #000 solid",
            transition: delay + "ms box-shadow linear",
            "box-shadow": "inset 0 0 0 rgba(255,0,0,0.9)",
            position: "fixed",
            bottom: 0,
            right: 0,
            padding: "4px",
            width: "200px"
        }, document.body);
        applyStyles(el, {
            transition: delay + "ms box-shadow linear, " + delay + "ms border-color linear",
            "box-shadow": "inset 0 0 0 rgba(0,255,0,0.5)",
            "border-color": "rgba(0,255,0,0)"
        });
        cancelButton.innerHTML = cancelCaption;
        var timer = setTimeout(function() {
            el.click();
        }, delay + 100);
        setTimeout(function() {
            cancelButton.style.boxShadow = "inset 208px 0 0 rgba(255,0,0,1)";
            el.style.boxShadow = "inset " + el.scrollWidth + "px 0 0 rgba(0,255,0,0.3)";
            el.style.borderColor = "rgba(0,255,0,1)";
        }, 100);
        cancelButton.onclick = function() {
            if (oncancel) oncancel();
            clearTimeout(timer);
            document.body.removeChild(cancelButton);
        };
    };


    if (window.name == "mainpane") {
        if (!document) throw new Error("no document");
        if (!document.body) throw new Error("document has no body");
        var openPanel = function() {
            if (!top.kole) return;
            if (closePanel !== null) {
                console.warn("Panel already open?");
                return;
            }
            koleButton.disabled = true;
            var panel = crel("div", {
                "position": "fixed",
                "top": "0",
                "left": "0",
                "right": "0",
                "max-height": "90%",
                "box-shadow": "0 0 8px rgba(0,0,0,0.6)",
                "border-bottom": "1px #888 solid",
                "transform": "scale(0.1,0.1)",
                "transform-origin": "100% 0",
                "padding": "12px",
                "overflow": "auto",
                "opacity": 0,
                "z-index": 9001,
                background: "#eef",
                "transition": "320ms all ease-in"
            }, document.body);
            crel("h1", {
                "font-weight": "100"
            }, panel).innerHTML = "KoLE Settings";
            var settingsTable = crel("table", {
                "font-size": "small",
                "width": "100%",
            }, panel);
            settingsTable.setAttribute("cellpadding", 4);
            settingsTable.setAttribute("cellspacing", 0);
            var tbody = crel("tbody", {}, settingsTable);
            var alt = false;
            for (var name in configOptions) {
                alt = !alt;
                var spec = configOptions[name];
                var tr = crel("tr", {
                    background: alt ? "rgba(255,255,255,0.5)" : "transparent"
                }, tbody);
                var labelCell = crel("td", {
                    height: "2em",
                    "width": "200px",
                    "vertical-align": "middle"
                }, tr);
                var label = crel("label", {}, labelCell);
                label.innerHTML = spec.caption;
                label.setAttribute("for", "kole_config_" + name);
                var editCell = crel("td", {
                    "vertical-align": "middle"
                }, tr);
                controlTypes[spec.control](name, spec, editCell);
                if (spec.description) {
                    var descTr = crel("tr", {
                        "font-size": "0.8em",
                        "color": "#666",
                        background: alt ? "rgba(255,255,255,0.5)" : "transparent"
                    }, tbody);
                    var descTd = crel("td", {}, descTr);
                    descTd.innerHTML = spec.description;
                    descTd.setAttribute("colspan", 2);
                }
            }
            with(crel("p", {
                "font-size": "small"
            }, panel)) {
                innerHTML = "Kingdom of Loathing Enhancement <b>alpha</b> by <a href='showplayer.php?who=2362564'>fnoot</a><br>This is an <b>alpha testing</b> version; please report any problems by kmail.";
            }
            var closeButton = crel("button", {
                position: "absolute",
                top: 0,
                right: 0
            }, panel);
            closeButton.innerHTML = "X";
            closePanel = function() {
                closePanel = null;
                koleButton.disabled = false;
                applyStyles(panel, {
                    opacity: 0,
                    "pointer-events": "none",
                    transform: "scale(0.02,0.02)"
                });
                setTimeout(function() {
                    document.body.removeChild(panel);
                    panel = null;
                }, 1000);
            };
            closeButton.onclick = closePanel;
            setTimeout(function() {
                applyStyles(panel, {
                    opacity: 1,
                    transform: "scale(1,1)"
                });
            }, 100)
        };
        var koleButton = crel("button", {
            "position": "fixed",
            "top": "0",
            "right": "0",
            "z-index": 9000
        }, document.body);
        with(koleButton) {
            innerHTML = "KoLE";
            onclick = openPanel;
        }

        if (getOption("autoFight")) {
            (function() { // auto fighting
                if (!top.kole) return;
                var hpPercent = (top.kole.hp / top.kole.maxHp) * 100;
                if (hpPercent < getOption("autoFightMinHp")) {
                    setOption("autoFight", false);
                    return;
                }
                var links = document.querySelectorAll("a");
                var adventureAgainRegex = /Adventure Again \(/;
                for (var i = 0; i < links.length; i++) {
                    if (adventureAgainRegex.test(links[i].innerHTML)) {
                        timedClick(links[i], getOption("autoFightDelay"), "Cancel automatic fighting", function() {
                            setOption("autoFight", false);
                        });
                        return;
                    }
                }
                var button12 = document.getElementById("button12");
                if (button12 != null) {
                    timedClick(button12, getOption("autoFightDelay"), "Cancel automatic fighting", function() {
                        setOption("autoFight", false);
                    });
                }
            })();
        }

        setInterval(function() {
            if (getOption("stayLoggedIn")) {
                xhr("main.php", function() {}, false);
            }
        }, 1000 * 60 * 2);
        var scanItems = function() {
            if (top.kole == null) return;
            var itemIds = top.kole.itemIds;
            var learnedItems = false;
            var itemCells = document.querySelectorAll(".stuffbox table.item .ircm");
            for (var i = 0; i < itemCells.length; i++) {
                var cell = itemCells[i];
                var itemName = cell.innerHTML;
                if (typeof itemIds[itemName] == "undefined") {
                    learnedItems = true;
                    itemIds[itemName] = cell.parentNode.id.replace(/i/, '');
                    // console.log("Learned item '"+itemName +"' id: "+itemIds[itemName]);
                }
            }
            if (learnedItems) GM_setValue("itemIds", JSON.stringify(itemIds));
        };
        setInterval(scanItems, 3500);

    } // /mainpane

    var chatXhr = function(msg, callback) {
        var url = '/submitnewchat.php?playerid=' + unsafeWindow.playerid + '&pwd=' + unsafeWindow.pwdhash + '&graf=' + encodeURIComponent(msg) + '&j=1';
        xhr(url, function(response) {
            callback(response);
        });
    };

    var getItemCount = function(item, callback) {
        chatXhr("/count " + item, function(res) {
            var countMatches = res.match(/You have (\d+)/);
            if (countMatches && countMatches.length > 0) {
                callback(countMatches[1]);
            } else {
                callback(0);
            }
        });
    };

    var hoverLinks = function() {
        window.addEventListener("load", function() {
            console.log("styling hover for " + window.name);
            crel("style", {}, document.querySelectorAll("head")[0]).textContent = "a{opacity:0.8;} a:hover{opacity:1;}";
        }, false);
    }

    if (window.name == "menupane") {
        hoverLinks();
        var lastAppliedIconTheme = "";
        var replaceImages = function() {
            var theme;
            if (!unsafeWindow.top.kole) {
                setTimeout(replaceImages, 100);
                return;
            }
            var themeName = getOption("iconTheme");
            if (themeName == lastAppliedIconTheme) return;
            lastAppliedIconTheme = themeName;
            if (themeName == "") {
                theme = {
                    icons: {}
                };
            } else {
                if (typeof unsafeWindow.top.kole.iconThemes[themeName] == "undefined") {
                    setOption("iconTheme", "");
                    console.warn("Can't find icon theme " + themeName);
                    return;
                }
                theme = unsafeWindow.top.kole.iconThemes[themeName];
            }
            var imgs = document.querySelectorAll("img");
            for (var i = 0; i < imgs.length; i++) {
                if (typeof imgs[i].originalSrc == "undefined") imgs[i].originalSrc = imgs[i].src;
                var src = imgs[i].originalSrc;
                if (typeof theme.icons[src] != "undefined") {
                    imgs[i].src = theme.icons[src];
                } else {
                    imgs[i].src = imgs[i].originalSrc;
                }
            }
        };
        replaceImages();
        setInterval(replaceImages, 500);
    }

    if (window.name == "charpane") {
        hoverLinks();
        var getHp = function() {
            if (!top.kole) {
                setTimeout(getHp, 200);
                return;
            }
            var blacks = document.querySelectorAll(".black");
            for (var i = 0; i < blacks.length; i++) {
                var sib = blacks[i];
                while (sib && sib.tagName.toLowerCase() != "img") {
                    sib = sib.previousSibling;
                }
                if (sib && sib.title == "Hit Points") {
                    var matches = blacks[i].innerHTML.match(/(\d+)&nbsp;\/&nbsp;(\d+)/);
                    top.kole.hp = matches[1];
                    top.kole.maxHp = matches[2];
                    var percent = (top.kole.hp / top.kole.maxHp) * 100;
                    if (percent < getOption("autoFightMinHp")) {
                        blacks[i].style.background = "rgba(255,0,0,0.2)";
                    }
                    return;
                }
            }
            top.kole.hp = 1;
            top.kole.maxHp = 1;
            console.warn("[kole] Couldn't find HP");
        };
        getHp();
    }

    if (window.name == "chatpane") {
        hoverLinks();
        var chatLog = function(s) {
            var activeWindow = unsafeWindow.$$('.chatdisplay:visible')[0];
            var toscroll = (activeWindow.scrollHeight - (activeWindow.scrollTop + activeWindow.offsetHeight) < 4);
            var msg = crel("div", {
                color: "#090"
            });
            msg.innerHTML = "<span style='color:#0c0'><span style='opacity:0.4'>[</span>kole<span style='opacity:0.4'>]</span></span> " + s;
            activeWindow.appendChild(msg);
            if (toscroll) {
                activeWindow.scrollTop = activeWindow.scrollHeight;
            }
        };
        var chatCommands = {
            wiki: function(args) {
                window.open("http://kol.coldfront.net/thekolwiki/index.php/Special:Search?search=" + encodeURIComponent(args.trim()) + "&go=Go");
            },
            qs: function(args) {
                args = args.trim();
                var amountMatches = args.match(/(\d+|\*)\s+([\w\s]+)/);
                if (amountMatches && amountMatches.length > 1) {
                    var amount = amountMatches[1];
                    args = amountMatches[2];
                } else {
                    var amount = 1;
                }
                var item = itemLookup(args);
                if (item === null) {
                    chatLog("KoLE doesn't know that item's ID! Teach it by opening your inventory.");
                } else if (item.error) {
                    chatLog("" + item.error);
                } else {
                    if (amount == "*") {
                        chatLog("Retrieving item count");
                        getItemCount(item.name, function(count) {
                            if (count == 0) {
                                chatLog("You don't have any of those!");
                            }
                            chatLog("Quickselling " + count + " x " + item.name);
                            unsafeWindow.dojax("sellstuff.php?action=sell&ajax=1&type=quant&whichitem%5B%5D=" + item.id + "&howmany=" + count + "&pwd=" + unsafeWindow.pwdhash);
                        });
                    } else {
                        chatLog("Quickselling " + amount + " x " + item.name);
                        unsafeWindow.dojax("sellstuff.php?action=sell&ajax=1&type=quant&whichitem%5B%5D=" + item.id + "&howmany=" + amount + "&pwd=" + unsafeWindow.pwdhash);
                    }
                }
            },
            jump: function(args) {
                chatLog("Weeeeeeeee");
            }
        }

        var previousOnload = window.onload;
        window.onload = function() {
            if (previousOnload) previousOnload.apply(this, arguments);
            var oldForm = document.getElementById('InputForm');
            if (oldForm == null) return;
            oldForm = oldForm.parentNode;
            newForm = oldForm.cloneNode(true);
            newForm.style.background = "red";
            oldForm.parentNode.replaceChild(newForm, oldForm);
            var old$inp = unsafeWindow.$inp;
            var $inp = unsafeWindow.$$("#graf");
            unsafeWindow.$inp = $inp;
            newForm.onsubmit = function(ev) {
                ev.preventDefault();
                var inp = $inp.val();
                var matches = inp.match(/^\/(\w+)(\s+(.*))?/);
                if (matches && matches.length > 0) {
                    var cmd = matches[1];
                    if (typeof chatCommands[cmd] != "undefined") {
                        $inp.val("");
                        chatCommands[cmd].call(this, matches.length > 1 ? matches[2] : undefined);
                        return;
                    }
                }
                old$inp.val(getOption("nullifySword") ? nullifySword($inp.val()) : $inp.val());
                unsafeWindow.submitchat(ev);
            };
        };
    };

    var poop = function(htmlOrElement, onclose) {
        var cover = crel("div", {
            position: "fixed",
            top: 0,
            left: 0,
            right: 0,
            bottom: 0,
            background: "rgba(0,0,0,0.2)",
            opacity: 0,
            transition: "200ms all",
            "z-index": 9002
        });
        var win = crel("div", {
            width: "480px",
            margin: "9% auto 0 auto",
            background: "#e8e8ff",
            position: "relative",
            transform: "scale(3,3)",
            "max-height": "78%",
            "overflow": "auto",
            opacity: 0,
            transition: "300ms all",
            "box-shadow": "1px 1px 6px rgba(0,0,0,0.4)"
        }, cover);
        var inner = crel("div", {
            padding: "12px"
        }, win);
        var winner = crel("div", {
            padding: "12px"
        }, inner);
        if (typeof htmlOrElement == "string") {
            winner.innerHTML = htmlOrElement;
        } else {
            winner.appendChild(htmlOrElement)
        }
        var close = function() {
            cover.style.opacity = 0;
            applyStyles(win, {
                "margin-top": "20%",
                opacity: 0
            });
            setTimeout(function() {
                document.body.removeChild(cover);
            }, 600);
            if (onclose) onclose();
        }
        with(crel("button", {
            position: "absolute",
            top: 0,
            right: 0
        }, win)) {
            onclick = close;
            innerHTML = "X";
        }
        document.body.appendChild(cover);
        setTimeout(function() {
            cover.style.opacity = 1;
            applyStyles(win, {
                "transform": "scale(1,1)",
                opacity: 1
            });
        }, 100);
        return {
            close: close
        };
    };

    var applyHoverHints = function() {
        var els = document.querySelectorAll("a,img");
        var onclickRegex = /\b(descitem|eff|javascript:poop)\("?([\w\.\?\=]+)"?\b(\s*,\s*(\w+)\b\))?/;
        for (var i = 0; i < els.length; i++) {
            var funcUrls = {
                descitem: "desc_item.php?whichitem=",
                eff: "desc_effect.php?whicheffect=",
                "javascript:poop": ""
            };
            var onclick = els[i].getAttribute("onclick") + "";
            var matches = onclick.match(onclickRegex);
            if (onclick && matches && (matches.length > 0)) {
                if (typeof els[i]['@kole2_hoverhint_init'] == "undefined") {
                    els[i]['@kole2_hoverhint_init'] = true;
                    els[i].style.cursor = "help";
                    els[i].title = "";
                    with({
                            func: matches[1],
                            itemId: matches[2],
                            otherPlayer: matches[4]
                        }) {
                            els[i].addEventListener("mouseenter", function() {
                                cancelHint();
                                if (!getOption("hoverHints")) return;
                                hintElement = this;
                                var query = typeof otherPlayer == "undefined" ? itemId : itemId + "&otherplayer=" + otherPlayer;
                                setHintTimer(this, function(callback) {
                                    return extractDescription(funcUrls[func] + query, callback, true);
                                });
                            }, false);
                            els[i].addEventListener("mouseout", function() {
                                if (this == hintElement) cancelHint();
                            }, false);
                        } // with
                } // if not init
            } // if onclick match
        } // for i in els
    }; // applyHoverHints()

    if (window !== top) {
        applyHoverHints();
        setInterval(applyHoverHints, 2500);
    }

    var editIconTheme = function(name, ondone) {
        var startingTheme = getOption("iconTheme");
        var blankSrc = "http://images.kingdomofloathing.com/pixel.gif";
        // temporarily disable theme so Copy Link Location gets the correct URL
        setOption("iconTheme", "");
        //		top.kole.userSettings.iconTheme = "";
        var theme = name == "" ? {
            displayName: "New Theme",
            icons: {}
        } : top.kole.iconThemes[name];
        if (typeof theme == "undefined") theme = {
            displayName: "",
            icons: {}
        };
        var editor = crel("div", {});
        var tableRows = [
            ["<b>Original Image Location</b>", "<b>Icon</b>"]
        ];

        var createImg = function(src) {
            var img = crel("img", {
                cursor: "pointer",
                "vertical-align": "middle"
            });
            img.width = 30;
            img.height = 30;
            img.src = src;
            img.onclick = function() {
                var fileSelect = crel("input");
                var img = this;
                fileSelect.setAttribute("type", "file");
                fileSelect.onchange = function(ev) {
                    var reader = new FileReader();
                    reader.onloadend = function() {
                        img.src = reader.result;
                    };
                    var file = this.files[0];
                    reader.readAsDataURL(file)
                    console.log(file);
                };
                fileSelect.click();
            };
            return img;
        };
        var createInput = function(value) {
            var input = crel("input", {
                width: '340px',
                "font-size": "0.8em"
            });
            input.value = value;
            return input;
        };
        var createDeleteButton = function() {
            var deleteButton = crel("button", {
                border: "none"
            });
            deleteButton.innerHTML = "X";
            deleteButton.onclick = function() {
                if (!confirm("Delete this icon?")) return;
                console.log("del btn", this);
                var row = this.parentNode.parentNode.parentNode;
                console.log("del row", row);
                row.parentNode.removeChild(row);
                reapplyAlt();
            };
            return deleteButton;
        };

        for (var orig in theme.icons) {
            var right = crel("div");
            right.appendChild(createImg(theme.icons[orig]));
            right.appendChild(createDeleteButton());
            tableRows.push([createInput(orig), right]);
        }

        var table = tabulate(tableRows);
        var reapplyAlt = function() {
            var trs = table.querySelectorAll("tr");
            for (var i = 0; i < trs.length; i++) trs[i].style.background = i % 2 == 1 ? "rgba(0,0,0,0.05)" : "transparent";
        };
        reapplyAlt();
        crel("h1", {
            "font-weight": 100,
            margin: "0 0 12px 0"
        }, editor).innerHTML = "Editing " + theme.displayName;
        var uniqueNameInput = crel("input", {
            width: "200px"
        });
        uniqueNameInput.value = name;
        var displayNameInput = crel("input", {
            width: "200px"
        });
        displayNameInput.value = theme.displayName;
        editor.appendChild(tabulate([
            ["Unique name", uniqueNameInput],
            ["Display name", displayNameInput]
        ], "rgba(0,0,0,0.05)"));


        editor.appendChild(table);
        var addButton = crel("button", {
            "float": "right"
        }, editor);
        addButton.innerHTML = "+";
        addButton.onclick = function() {
            var tr = crel("tr", {}, table);
            var tdl = crel("td", {}, tr);
            var tdr = crel("td", {}, tr);
            var right = crel("div", {}, tdr);
            tdl.appendChild(createInput(""));
            right.appendChild(createImg(blankSrc));
            right.appendChild(createDeleteButton());
            table.appendChild(tr);
        };

        var p = crel("p", {}, editor);
        p.innerHTML = "Click an image to select a replacement. ";
        var helpLink = crel("a", {}, p);
        helpLink.innerHTML = "Help";
        helpLink.href = "#";
        helpLink.onclick = function() {
            poop("<h1 style='font-weight:100; margin:0 0 12px 0'>Theme Editor Help</h1><ol style='font-size:small'>" + "<li>Click the + button to add replacements</li>" + "<li>Right-click on a topbar icon and choose 'Copy Image Location'</li>" + "<li>Paste into the input on the left</li>" + "<li>Left-click on the icon on the right to choose a replacement</li>" + "<li>Give your theme a unique name and a fancy display name</li>" + "<li>????</li>" + "<li>Profit!</li>" + "</ol>" + "<p>Unique names are like filenames. If you change it to something new you'll create a new theme. If you change it to something that exists, you'll overwrite it.</p>");
            return false;
        };

        var saveButton = crel("Button", {}, editor);
        var saved = false;
        saveButton.innerHTML = "Save";
        saveButton.onclick = function() {
            if (uniqueNameInput.value.trim() == "") {
                poop("Please supply a unique name");
                return;
            }
            if (displayNameInput.value.trim() == "") {
                poop("Please supply a display name");
                return;
            }
            var rows = table.querySelectorAll("tr");
            var theme = {
                displayName: displayNameInput.value,
                icons: {}
            };
            for (var i = 1; i < rows.length; i++) {
                var orig = rows[i].querySelectorAll("input")[0].value;
                var repl = rows[i].querySelectorAll("img")[0].src;
                if (orig && repl != blankSrc) {
                    theme.icons[orig] = repl;
                }
            }
            top.kole.iconThemes[uniqueNameInput.value] = theme;
            console.log("Saved theme", theme);
            saveIconThemes();
            saved = true;
            editorPoop.close();
        };

        var editorPoop = poop(editor, function() {
            if (saved) {
                if (uniqueNameInput.value != startingTheme) {
                    setOption("iconTheme", startingTheme);
                }
                if (closePanel) closePanel();
                openPanel();
            } else {
                setOption("iconTheme", startingTheme);
            }
            if (ondone) ondone();
        });
    };

    var exportIconTheme = function(name) {
        var theme = top.kole.iconThemes[name];
        if (!theme) throw new Error("No such theme: " + name);
        var exported = {
            displayName: theme.displayName,
            uniqueName: name,
            icons: theme.icons
        };
        return JSON.stringify(exported);
    };

    var importIconTheme = function(onclose) {
        var pop = crel("div");
        crel("h1", {
            margin: "0 0 12px 0",
            "font-weight": "100"
        }, pop).innerHTML = "Import Icon Theme";
        var textarea = crel("textarea", {
            "width": "100%",
            height: "200px",
            background: "#fff"
        }, pop);
        textarea.placeholder = "Paste the theme code here";
        var button = crel("button", {}, pop);
        button.innerHTML = "Import";
        button.onclick = function() {
            try {
                if (textarea.value.trim().substr(0, 1) !== "{") throw new Error("Import data must start with {");
                var theme = JSON.parse(textarea.value);
                var name = theme.uniqueName;
                if (!theme.icons) throw new error("Missing theme.icons");
                top.kole.iconThemes[name] = theme;
                saveIconThemes();
                importPoop.close();
                if (name == getOption("iconTheme")) {
                    // current theme was changed - reset current to vanilla to clear menupane's cache
                    // alternatively, cache by theme obj rather than name?
                    top.kol.userSettings.iconTheme = "";
                }
                if (closePanel) {
                    closePanel();
                    openPanel();
                }
            } catch (ex) {
                console.warn(ex.message);
                poop("Oops! That theme code doesn't seem quite right!");
                return;
            }
        };
        var importPoop = poop(pop, onclose);
    };

})();