GOTA Extender

Game of Thrones Ascent Extender

目前为 2014-12-23 提交的版本。查看 最新版本

// ==UserScript==
// @name        GOTA Extender
// @namespace   gota_extender
// @author      Panayot Ivanov
// @description Game of Thrones Ascent Extender
// @include     http://gota.disruptorbeam.com/*
// @include     http://gota-www.disruptorbeam.com/*
// @include     https://gota.disruptorbeam.com/*
// @include     https://gota-www.disruptorbeam.com/*
// @include     https://games.disruptorbeam.com/gamethrones/
// @exclude     http://gota.disruptorbeam.com/users/login*
// @exclude     http://gota-www.disruptorbeam.com/users/login*
// @license     WTFPL (more at http://www.wtfpl.net/)
// @require     http://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js
// @require     https://greasyfork.org/scripts/5427-gota-extender-constants/code/GOTA_Extender_Constants.js?version=28667
// @require     https://greasyfork.org/scripts/5279-greasemonkey-supervalues/code/GreaseMonkey_SuperValues.js?version=20819
// @resource 	custom https://greasyfork.org/scripts/5426-gota-extender-custom/code/GOTA_Extender_Custom.js?version=29156
// @resource    auxiliary https://greasyfork.org/scripts/5618-gota-extender-auxiliary/code/GOTA_Extender_Auxiliary.js?version=29117
// @resource    original https://greasyfork.org/scripts/6702-gota-extender-original/code/GOTA_Extender_Original.js?version=29118
// @version     5.8
// @grant       unsafeWindow
// @grant       GM_getValue
// @grant       GM_setValue
// @grant       GM_openInTab
// @grant       GM_xmlhttpRequest
// @grant       GM_getResourceText
// @grant       GM_getResourceURL
// @grant       GM_registerMenuCommand
// ==/UserScript==

GM_registerMenuCommand("HOME", openHome);
function openHome() {
    GM_openInTab("https://greasyfork.org/en/scripts/3788-gota-extender");    
}

GM_registerMenuCommand("DEBUG", enterDebugMode);
function enterDebugMode() {
    options.debugMode = true;    
    options.set("debugMode");

    alert("Debug mode has been enabled. Extender will now reload.");
    window.location.reload(true);
}

GM_registerMenuCommand("CHECK", checkScript);
function checkScript() {
    options.checkScript = true;
    options.set("checkScript");

    alert("Extender will check for game function updates.\nPress OK to reload.");
    window.location.reload(true);
}

$ = this.$ = this.jQuery = jQuery.noConflict(true);

$(window).bind("load", function () {
    setTimeout(function () {        
        initialize();
    }, (options.baseDelay / 2) * 1000);
});

// Observes DOM object mutations
MutationObserver = window.MutationObserver || window.WebKitMutationObserver;

// Observers construction
var mainToolbarObserver = new MutationObserver(main_toolbar_buttons_changed);

// define what element should be observed by the observer
// and what types of mutations trigger the callback
mainToolbarObserver.observe(document.getElementById("main_toolbar_buttons"), {
    //	childList: true,
    attributes: true,
    //	characterData: true,
    subtree: true,
    attributeOldValue: true
    // if attributes
    //	characterDataOldValue: true,    // if characterData
    //	attributeFilter: ["id", "dir"], // if attributes
});

function main_toolbar_buttons_changed() {
    //    log("Mutation on main toolbar buttons.");

    var menu = $("#extender-menu");
    var container = $("#navmenubox");
    if (container.length > 0 && menu.length == 0) {
        container.append(templates.menuBtn);
    }
}

// --> Page command handling
var signalObserver = new MutationObserver(signal_acknowledged);
function signal_acknowledged() {
    var observable = $("textarea#observable");

    if (!observable) {
        error("The observable DOM element was not found in the page.");
        return;
    }

    var commandObj = JSON.parse(observable.attr("command"));
    if (!commandObj || typeof commandObj != "object") {
        error("Cannot parse the command object given.");
        return;
    }

    if (!commandObj.name || !commandObj.args) {
        error("Command does not define a name or arguments.");
        return;
    }

    var args = commandObj.args;

    // Parse command
    switch (commandObj.name) {
        case "option":
            //log("argument 1 = " + args[0] + ", " +
            //    "has own prop? " + (options.hasOwnProperty(args[0])) + ", " +
            //    "argument 2 = " + args[1], "DEBUG");

            if (options.hasOwnProperty(args[0]) && (typeof args[1] == "undefined" || !args[1])) {

                if (typeof options[args[0]] == "object") {
                    log(args[0] + " is a composite object:");
                    console.log(typeof cloneInto == "function" ? cloneInto(options[args[0]], unsafeWindow) : options[args[0]]);
                } else {
                    log(args[0] + ": " + options[args[0]]);
                }

                return;
            }

            if (options.hasOwnProperty(args[0]) && typeof options[args[0]] === typeof args[1]) {
                options[args[0]] = args[1];
                options.set(args[0]);
                log("Option set.");
                return;
            }

            warn("Lack of or incorrect parameters passed to command.");
            break;
        default:
            error("Unknown command.");
            break;
    }    
}
// <-- Page command handling

// --> Initizalization
function initialize() {

    // Add global styles
    styles.addAllStyles();

    // Get all GM values
    options.get();

    try {

        if (options.checkScript) {

            checkSource();

            options.checkScript = false;
            options.set("checkScript");
            window.location.reload(true);
            return;
        }

        // Clean up GOTA shit...
        console.clear();

        // Try an injection sequence        
        inject.observable();
        inject.constants();
        inject.console();

        // Inject auxiliary code
        inject.code(GM_getResourceText("custom"));
        inject.code(GM_getResourceText("auxiliary"));

    } catch (e) {
        error("Fatal error, injection failed: " + e);
        inform("Fatal error, injection failed: " + e);
        return;
    }

    try {

        // Toggle
        toggleAll();

        // Claim
        quarterMasterDo();
        
        // If extender reloaded automatically, load queue
        if (options.productionQueue && options.productionQueue.length > 0) {
            loadProductionQueue();
        }

        // Store all sworn swords
        getSwornSwords();

        // Sort player inventory
        unsafeWindow.sort();

        log('Initialized. Happy hacking.');
        inform("Initialized.");


    } catch (e) {
        error("Fatal error, initialization failed: " + e);
        inform("Fatal error, initialization failed: " + e);
        return;
    }

}
// <-- Initizalization

// --> Options object
var options = {
    swornSwords: [],
    default_swornSwords: [],

    productionQueue: [],
    default_productionQueue: [],

    debugMode: true,
    default_debugMode: true,
    checkScript: false,
    default_checkScript: false,
    baseDelay: 4,
    default_baseDelay: 4,
    queueDelay: 4,
    default_queueDelay: 4,
    autoCollectInterval: 60,
    default_autoCollectInterval: 60,
    superiorMaterials: true,
    default_superiorMaterials: true,
    queueTimerInterval: 30,
    default_queueTimerInterval: 30,
    allowBruting: false,
    default_allowBruting: false,
    bruteWounds: 1,
    default_bruteWounds: 1,
    bruteSwitchOff: true,
    default_bruteSwitchOff: true,
    doTooltips: false,
    default_doTooltips: false,
    neverSpendGold: true,
    default_neverSpendGold: true,
    autoReloadInterval: 6,
    default_autoReloadInterval: 6,
    boonsSortBy: "available_quantity",
    default_boonsSortBy: "available_quantity",
    boonsSortBy2: "rarity",
    default_boonsSortBy2: "rarity",
    shopSortBy: "price",
    default_shopSortBy: "price",
    shopSortBy2: "rarity",
    default_shopSortBy2: "rarity",
    sendAllAction: "friendly",
    default_sendAllAction: "friendly",
    autoBossChallenge: false,
    default_autoBossChallenge: false,

    get: function () {
        var prefix = "";

        // Separate variable retrieval for both hosts
        if (unsafeWindow.location.host === "gota-www.disruptorbeam.com") {
            prefix = "gota-";
        }

        for (var property in this) {
            if (this.hasOwnProperty(property) && property.indexOf("default_") == -1 && typeof this[property] != "function") {
                // console.debug("Retirevieng " + prefix + property + " with default value of " + this["default_" + property]);

                this[property] = GM_SuperValue.get(prefix + property, this["default_" + property]);

                // console.debug("Property " + property + " has a value of " + this[property]);
            }
        }
    },

    set: function (opt) {

        var prefix = "";
        // Separate variable set for both hosts
        if (unsafeWindow.location.host === "gota-www.disruptorbeam.com") {
            prefix = "gota-";
        }

        if (opt && this.hasOwnProperty(opt)) {
            GM_SuperValue.set(prefix + opt, this[opt]);
            return;
        }

        var newValues = [];

        // Store property values in array
        for (var newProperty in this) {
            if (newProperty.indexOf("default_") > -1)
                continue;

            if (this.hasOwnProperty(newProperty) && typeof this[newProperty] != "function") {
                newValues.push(this[newProperty]);
            }
        }

        // console.debug(newValues);

        // Revert
        this.get();

        var i = 0;

        // Detect and change if necessary
        for (var oldProperty in this) {
            if (oldProperty.indexOf("default_") > -1)
                continue;

            if (this.hasOwnProperty(oldProperty) && typeof this[oldProperty] != "function" && this[oldProperty] != newValues[i]) {
                // console.debug("Setting property " + oldProperty + " with old value of " + this[oldProperty] + " to the new value of " + newValues[i]);

                GM_SuperValue.set(prefix + oldProperty, newValues[i]);
                this[oldProperty] = newValues[i];
            }

            i++;
        }
    },

    reset: function () {
        for (var property in this) {
            if (this.hasOwnProperty(property) && property.indexOf("default_") == -1 && typeof this[property] != "function") {
                this[property] = this["default_" + property];
            }
        }

        this.set();
    }
};
// <-- Options object

// --> Inject object
var inject = {

    // Constants required by the page
    constants: function () {

        // Safe string
        unsafeWindow.phraseText.shop_filter_extender = "Extender";

        // EXTENDER :: Modification - add custom filter
        if(unsafeWindow.shopFilters.indexOf("extender") == -1){
            log("Injecting extender filter...");
            unsafeWindow.shopFilters.push("extender");
        }

        // Inject structured clone (for Mozilla)
        if (typeof (cloneInto) == "function") {
            unsafeWindow.extender_queueDelay = cloneInto(options.queueDelay, unsafeWindow);
            unsafeWindow.extender_confirmSuperiorMaterials = cloneInto(options.superiorMaterials, unsafeWindow);
            unsafeWindow.extender_bruteWounds = cloneInto(options.bruteWounds, unsafeWindow);
            unsafeWindow.extender_bruteSwitchOff = cloneInto(options.bruteSwitchOff, unsafeWindow);
            unsafeWindow.extender_debugMode = cloneInto(options.debugMode, unsafeWindow);
            unsafeWindow.extender_baseDelay = cloneInto(options.baseDelay, unsafeWindow);            
            unsafeWindow.extender_neverSpendGold = cloneInto(options.neverSpendGold, unsafeWindow);

            unsafeWindow.extender_boonsSortBy = cloneInto(options.boonsSortBy, unsafeWindow);
            unsafeWindow.extender_boonsSortBy2 = cloneInto(options.boonsSortBy2, unsafeWindow);
            unsafeWindow.extender_shopSortBy = cloneInto(options.shopSortBy, unsafeWindow);
            unsafeWindow.extender_shopSortBy2 = cloneInto(options.shopSortBy2, unsafeWindow);            

            unsafeWindow.extender_sendAllAction = cloneInto(options.sendAllAction, unsafeWindow);
            unsafeWindow.extender_autoBossChallenge = cloneInto(options.autoBossChallenge, unsafeWindow);

            unsafeWindow.userContext.tooltipsEnabled = cloneInto(options.doTooltips, unsafeWindow);

        } else {
            unsafeWindow.extender_queueDelay = options.queueDelay;
            unsafeWindow.extender_confirmSuperiorMaterials = options.superiorMaterials;
            unsafeWindow.extender_bruteWounds = options.bruteWounds;
            unsafeWindow.extender_bruteSwitchOff = options.bruteSwitchOff;
            unsafeWindow.extender_debugMode = options.debugMode;
            unsafeWindow.extender_baseDelay = options.baseDelay;            
            unsafeWindow.extender_neverSpendGold = options.neverSpendGold;

            unsafeWindow.extender_boonsSortBy = options.boonsSortBy;
            unsafeWindow.extender_boonsSortBy2 = options.boonsSortBy2;
            unsafeWindow.extender_shopSortBy = options.shopSortBy;
            unsafeWindow.extender_shopSortBy2 = options.shopSortBy2;            

            unsafeWindow.extender_sendAllAction = options.sendAllAction;
            unsafeWindow.extender_autoBossChallenge = options.autoBossChallenge;

            unsafeWindow.userContext.tooltipsEnabled = options.doTooltips;
        }

        log("Constants injected successfully.");
    },

    // Inject code
    code: function (code) {

        var script = document.createElement('script');
        script.type = "text/javascript";
        script.innerHTML = code;
        document.head.appendChild(script);

        log("Code injected successfully.");
    },

    outSource: function (src, delay) {

        var script = document.createElement('script');
        script.type = "text/javascript";
        script.src = src;

        delay ? setTimeout(function () {
            document.head.appendChild(script);
        }, (options.baseDelay / 4) * 1000) : document.head.appendChild(script);
    },

    // Injects a DOM object and starts observing it
    observable: function () {

        $("div#outerwrap div.footer").prepend(templates.observable);
        signalObserver.observe(document.getElementById("observable"), {
            //	childList: true,
            attributes: true,
            //	characterData: true,
            //  subtree: true,
            attributeOldValue: true
             // if attributes
            //	characterDataOldValue: true,    // if characterData
            //	attributeFilter: ["id", "dir"], // if attributes
        });

        log("Observable injected successfully.");
    },

    // Inject console and alert handling separately once
    console: function () {
        if (typeof exportFunction == "function") {
            exportFunction(log, unsafeWindow, { defineAs: "log" });
            exportFunction(warn, unsafeWindow, { defineAs: "warn" });
            exportFunction(error, unsafeWindow, { defineAs: "error" });

            exportFunction(inform, unsafeWindow, { defineAs: "inform" });

        } else {
            unsafeWindow.log = log;
            unsafeWindow.warn = warn;
            unsafeWindow.error = error;

            unsafeWindow.inform = inform;
        }

        log("Messaging system injected successfully.");
    }
};
// <-- Inject object

// --> Message handling
function log(message, type) {
    if (options && options.debugMode && console && console.log
        && typeof (console.log) == "function") {
        if (!type)
            type = "extender";

        var prefix = type.toString().toUpperCase() + " <" + new Date().toLocaleTimeString() + "> ";
        console.log(prefix + message);
    }
}

function error(message, type) {
    if (console && console.error && typeof (console.error) == "function") {
        if (!type)
            type = "extender";

        var prefix = type.toString().toUpperCase() + " - ERROR <" + new Date().toLocaleTimeString() + "> ";
        console.error(prefix + message);
    }
}

function warn(message, type) {
    if (console && console.warn && typeof (console.warn) == "function") {
        if (!type)
            type = "extender";

        var prefix = type.toString().toUpperCase() + " - WARNING <" + new Date().toLocaleTimeString() + "> ";
        console.warn(prefix + message);
    }
}

function inform(msg) {

    if (unsafeWindow && typeof unsafeWindow.doAlert == "function") {
        unsafeWindow.doAlert("EXTENDER", templates.formatMessage(msg));

        //$("div#modals_container_high div#modalwrap div#exalert")
        //    .parents("div.alertboxinner").css("min-height", "0")
        //    .parent("div.alertbox").css("min-height", "0")
        //    .parent("div.alertcontents").css("min-height", "0");

    } else if (alert && typeof alert == "function")
        alert(msg);
}
// <-- Message handling

$("#building_items").on('click', ".unlocked", upgradetab_opened);
function upgradetab_opened() {
    //    log("Building info & upgrades.");

    var btn = $("#upgradeQueue");
    var container = $("#selected_upgrade");
    if (container.length > 0 && btn.length == 0) {
        container.append(templates.queueUpgradeBtn);
    }
}

$("#modal_dialogs_top").on('click', ".buildingupgradetree .upgradeicon", upgradetab_changed);
function upgradetab_changed() {
    //    log('Upgrade description changed.');

    var btn = $("#upgradeQueue");
    var container = $("#selected_upgrade");
    if (container.length > 0 && btn.length == 0) {
        container.append(templates.queueUpgradeBtn);
    }
}

$("#modal_dialogs_top").on('click', ".production .productionrow", productiontab_onchange);
function productiontab_onchange() {
    //    log('Production view changed.');

    var btns = $(".production .craftbox .statviewbtm:visible .btnwrap.btnmed.equipbtn.queue:visible");
    var container = $(".production .craftbox .statviewbtm:visible");
    if (container.length > 0 && btns.length == 0) {
        container.prepend(templates.queue5Btn);
        container.prepend(templates.queueBtn);
    }
}

// --> Loops handling
function toggleAll() {
    toggleAutoCollect();
    toggleQueueTimer();
    toggleReloadWindow();
}

var autoCollectLoop;
function toggleAutoCollect() {
    if (options.autoCollectInterval > 0) {
        autoCollectLoop = setInterval(collectTax, options.autoCollectInterval * 60 * 1000);
        log("Auto collect loop set to: " + options.autoCollectInterval + "min.");
    } else {
        autoCollectLoop = clearInterval(autoCollectLoop);
        log("Auto collect loop disabled.");
    }
}

var queueTimer;
function toggleQueueTimer() {
    if (options.queueTimerInterval > 0) {
        queueTimer = setInterval(unsafeWindow.attemptProduction, options.queueTimerInterval * 60 * 1000);
        log("Queue timer interval set to: " + options.queueTimerInterval + "min.");
    } else {
        queueTimer = clearInterval(queueTimer);
        log("Queue timer disabled.");
    }
}

var reloadWindowTimeout;
function toggleReloadWindow() {
    if (options.autoReloadInterval > 0) {
        setTimeout(function () {
            saveProductionQueue();
            window.location.reload(true);
        }, options.autoReloadInterval * 60 * 60 * 1000);
        log("Auto reload interval set to: " + options.autoReloadInterval + "h.");
    } else {
        reloadWindowTimeout = clearTimeout(reloadWindowTimeout);
        log("Auto reloading cancelled.");
    }

}

function quarterMasterDo(status){

    if(!status) {
        ajax({
            url: "/play/quartermaster_status",
            success: function(response){
                quarterMasterDo(response);
            }
        });

        return;
    }

    if (status.total_keys) {
        openBox();
        return;
    }

    if(status.available_daily_key){
        claimDailyQuarterMaster();
        return;
    }

    if(status.available_bonus_key){
        claimBonusQuarterMaster();
        return;
    }

    log("All quartermaster rewards have been taken.");

    unsafeWindow.claimDaily();
    log("Daily reward claimed.")
}

function claimDailyQuarterMaster(){

    ajax({
        url: "/play/quartermaster_claim_daily",
        success: function(r){
            quarterMasterDo(r.status);
        }
    });
}

function claimBonusQuarterMaster() {

    ajax({
        url: "/play/quartermaster_claim_bonus",
        success: function (r) {
            quarterMasterDo(r.status);
        }
    });
}

function openBox(){
    ajax({
        url: "/play/quartermaster_open_chest/?bribes=0&nonce=" + unsafeWindow.userContext.purchase_nonce,
        success: function (r) {
            unsafeWindow.userContext.purchase_nonce = r.purchase_nonce;
            log("Quartermaster opened a reward box. Rewards:")
            console.debug(r.rewards);

            quarterMasterDo();
        }
    });
}

function collectTax() {
    try {

        var itemId = unsafeWindow.buildingBySymbol('counting_house').item_id;
        unsafeWindow.doCollect(itemId);
        log("Silver collected.");

    } catch (err) {
        error(err);
        return;
    }
}
// <-- Loops handling

// --> Settings handling
function showSettings() {
    try {

        $("#credits_page").empty();
        $("#credits_page").append(templates.optionsHeader);
        $("#extenderTabMenu .charactertabs").append(templates.optionsTab("mainTab", "MAIN"));
        $("#extenderTabMenu .charactertabs").append(templates.optionsTab("queueTab", "QUEUE"));

        if (options.allowBruting) {
            $("#extenderTabMenu .charactertabs").append(templates.optionsTab("bruteTab", "BRUTING"));
        }

        $("#credits_page").append(templates.tabContent);
        $("#mainTab").trigger('click');
        optionsBottom();

        $("#credits_roll").show();

    } catch (err) {
        error(err);
        return;
    }
}

function optionsBottom() {
    var saveBtn = $("#saveOptions");
    var container = $("#creditsclose");
    if (saveBtn.length == 0 && container.length > 0) {
        container.before(templates.saveOptionsBtn);
    }

    $("#creditsclose").attr('onclick', '(function(){ $("#credits_page").empty(); $("#credits_roll").hide(); })()');

    var resetBtn = $("#resetOptions");
    if (resetBtn.length == 0 && container.length > 0) {
        container.after(templates.resetOptionsBtn);
    }

    $("resetOptions").append('<div class="barbtmedge"></div>');
}

$("#credits_roll").on("click", ".inventorytabwrap", tab_onchange);
function tab_onchange(e) {
    e.preventDefault();

    $(".inventorytab.active").removeClass("active");
    $(this).find(".inventorytab").addClass("active");

    switch (this.id) {
        case "mainTab":
            $("#extenderTabContent").html(templates.mainTab(options));
            break;
        case "queueTab":
            $("#extenderTabContent").html(templates.queueTab(options));
            renderProductionItems();
            break;
        case "bruteTab":
            getSwornSwords();
            $("#extenderTabContent").html(templates.bruteTab(options));
            break;
        default:
            break;
    }
}

function renderProductionItems() {

    var qTable = $("#queueTable");
    if (qTable.length == 0) {
        error("Can't find queue table! Rendering production items failed.");
        return;
    }

    // Clear table from any rows first
    $("#queueTable .tableRow").each(function () {
        $(this).remove();
    });

    var queue = unsafeWindow.productionQueue;
    if (!queue || queue.length == 0) {
        log("No queue was found to render.");
        return;
    }

    // Render items
    for (var i = 0; i < queue.length; i++) {
        $("#headerRow").after(templates.tableRow(i, queue[i]));
    }

    log("Production queue rendered " + queue.length + " items.");
}
// <-- Settings handling

$("#modal_dialogs_top").on('click', '#incomingtab', wireEvents);
function wireEvents(e) {
    e.preventDefault();

    window.setTimeout(function () {
        GM_xmlhttpRequest({
            method: "GET",
            url: "/play/incoming_attacks",
            onload: function (response) {
                try {
                    var a = JSON.parse(response.responseText);
                    // console.debug(response, a);
                    $('div.perkscroll div.achiev-content').each(function () {
                        var id = /[0-9]+/.exec($(this).find('div.increspond').attr('onclick'));

                        var attack = a.attacks.filter(function (e) {
                            return e.camp_attack_id === null ? e.pvp_id == id : e.camp_attack_id == id;
                        })[0];

						if(!attack)
							return;

						$(this).find("span.charname").attr("onclick", "return characterMainModal(" + attack.attacker.user_id + ")");
                        $(this).find("span.charportrait").attr("onclick", "return characterMainModal(" + attack.attacker.user_id + ")");
                        $(this).find("span.targetalliancename").attr("onclick", "return allianceInfo(" + attack.alliance_id + ")");
                    });
                } catch (e) {
                    error(e);
                }

            }
        });
    }, (options.baseDelay / 2) * 1000);
}

$("#main_toolbar_buttons").on('click', "#extender-menu", extenderButton_clicked);
function extenderButton_clicked(e) {
    e.preventDefault();
    showSettings();

    //    log("Menu was accessed.");
}

$("#credits_roll").on('click', "#saveOptions", saveOptions_click);
function saveOptions_click(e) {
    e.preventDefault();

    try {

        var tab = $(".inventorytab.active:visible").parents(".inventorytabwrap").attr("id");

        switch (tab) {
            case "mainTab":
                saveMainTab();
                break;
            case "queueTab":
                saveQueueTab();
                break;
            case "bruteTab":
                saveBruteTab();
                break;

            default:
                return;
        }

        //options.set();
        //inject.constants();
        //unsafeWindow.sort();
        //toggleAll();

        $("#credits_page").empty();
        $("#credits_roll").hide();
        inform("Settings saved.");

    } catch (e) {
        inform(e);
    }
}

function saveMainTab() {
    if ($("#credits_roll").is(":hidden")) {
        return;
    }

    var bd = parseInt($("#baseDelay").text());
    if (!isNaN(bd) && options.baseDelay != bd) {
        options.baseDelay = bd;
    }

    options.debugMode = $("#toggleDebugModes").hasClass("checked");    
    options.doTooltips = $("#toggleTooltips").hasClass("checked");
    options.neverSpendGold = $("#neverSpendGold").hasClass("checked");
    options.autoBossChallenge = $("#autoBossChallenge").hasClass("checked");


    var ari = parseInt($("#autoReloadInterval").val());
    if (!isNaN(ari) && options.autoReloadInterval !== ari) {
        options.autoReloadInterval = ari;
        toggleReloadWindow();
    }

    var aci = parseInt($("#autoCollectInterval").val());
    if (!isNaN(aci) && options.autoCollectInterval !== aci) {
        options.autoCollectInterval = aci;
        toggleAutoCollect();
    }

    options.boonsSortBy = $("#boonsSortBy").val();
    options.boonsSortBy2 = $("#boonsSortBy2").val();

    options.shopSortBy = $("#shopSortBy").val();
    options.shopSortBy2 = $("#shopSortBy2").val();

    options.sendAllAction = $("#sendAllAction").val();    

    options.set();
    inject.constants();
    unsafeWindow.sort();
}

function saveQueueTab() {
    if ($("#credits_roll").is(":hidden")) {
        return;
    }

    options.superiorMaterials = $("#toggleSuperiorMaterials").hasClass("checked");

    var qd = parseInt($("#queueDelay").text());
    if (!isNaN(qd) && options.queueDelay !== qd) {
        options.queueDelay = qd;
    }

    var qti = parseInt($("#queueTimerInterval").val());
    if (!isNaN(qti) && options.queueTimerInterval !== qti) {
        options.queueTimerInterval = qti;
        toggleQueueTimer();
    }

    saveProductionQueue();

    options.set();
    inject.constants();
    //unsafeWindow.sort();
}

function saveBruteTab() {
    if ($("#credits_roll").is(":hidden")) {
        return;
    }

    var bWounds = parseInt($("#bruteWounds").text());
    if (!isNaN(bWounds)) {
        options.bruteWounds = bWounds;
    }

    var bSwitch = $("#bruteSwitchOff").find("a.btngold");
    options.bruteSwitchOff = bSwitch.text() == "switch off";

    options.set();
    inject.constants();
    //unsafeWindow.sort();
}

$("#credits_roll").on('click', "#resetOptions", resetOptions_click);
function resetOptions_click(e) {
    e.preventDefault();

    options.reset();
    inject.constants();
    unsafeWindow.sort();
    toggleAll();

    $("#credits_page").empty();
    $("#credits_roll").hide();
    inform("Options reset.");
}

$("#modal_dialogs_top").on('click', '#upgradeQueue', queue_clicked);
$("#modal_dialogs_top").on('click', 'span.btnwrap.btnmed.equipbtn.queue', queue_clicked);

function queue_clicked(e) {
    e.preventDefault();

    try {
        var queueingUpgrade = $(this).hasClass('upgradeQueue');
        log("Queing " + (queueingUpgrade ? "upgrade." : "item(s)."));

        if (queueingUpgrade) {

            var container = $(this).parents('div#selected_upgrade');
            var name = $(container).find('h5:first').text();

            var infoBtm = $(this).parents('div.buildinginfobtm');
            var func = $(infoBtm).find('.upgradeicon.active').attr('onclick');
            var upgradeImg = $(infoBtm).find('.upgradeicon.active .upgradeiconart img').attr('src');

            if (func.indexOf("clickSelectUpgrade") == -1) {
                error("Cannot resolve upgrade id.");
                return;
            }

            // TODO: Improve...
            // "return clickSelectUpgrade('7', 'balcony');"
            var symbol = func.split("'")[3];
            log("Selected " + symbol + " upgrade. Retrieve successfull.");

            var upgradeId;

            var buildingUpgrades = unsafeWindow.buildingUpgrades[unsafeWindow.userContext.activeBuildingPanel];
            for (var j = 0; j < buildingUpgrades.length; j++) {
                if (buildingUpgrades[j].symbol == symbol) {
                    upgradeId = buildingUpgrades[j].id;
                    break;
                }
            }

            if (!upgradeId) {
                error("Fatal error, cannot resolve upgrade id.");
                return;
            }

            log("Upgrade id resolved: " + upgradeId);

            var upgrade = {
                "name": name,
                "upgradeId": upgradeId,
                "type": "upgrade",
                "symbol": symbol,
                "img": upgradeImg,
                "activeBuildingPanel": unsafeWindow.userContext.activeBuildingPanel
            };

            // Insert the element into the queueArray (cloneInto for Mozilla)
            if (typeof cloneInto == "function") {
                var upgradeClone = cloneInto(upgrade, unsafeWindow);
                unsafeWindow.productionQueue.push(upgradeClone);
            } else {
                unsafeWindow.productionQueue.push(upgrade);
            }

            log("Pushed upgrade to queue.");

        } else {

            // Extract and construct object
            var statview = $(this).parents(".statview");
            var imgSrc = $(statview).find("div.statviewimg img").attr('src');

            if (typeof (imgSrc) == "undefined") {
                imgSrc = $(statview).find("span.iconview img").attr('src');
            }

            var statViewName = $(statview).find(".statviewname h3").text();
            var quantity = $(this).attr("data-quantity");

            // Extract variables needed
            var recipeName;

            var source = unsafeWindow.userContext.productionItemsClick[unsafeWindow.userContext.currentProductionItem];

            if (!source) {
                error('Failed to extract source production item.');
                return;
            }

            for (var i = 0; i < unsafeWindow.userContext.recipeData.length; i++) {
                var r = unsafeWindow.userContext.recipeData[i];
                if (r.output == source.outputSymbol) {
                    recipeName = r.symbol;
                    break;
                }

                if (r.success_loot_table && r.success_loot_table == source.outputSymbol) {
                    recipeName = r.symbol;
                    break;
                }

                if (r.success_loot_item && r.success_loot_item == source.outputSymbol) {
                    recipeName = r.symbol;
                    break;
                }

                // Last attempt, these here are expensive operations
                var recipeInputs = JSON.stringify(r.input.split(","));
                if (JSON.stringify(source.recipeInputs) === recipeInputs) {
                    recipeName = r.symbol;
                    break;
                }

            }

            if (!recipeName) {
                error('Failed to extract recipeName.');
                return;
            }

            log('All needed variables were extracted.');

            do {

                // Construct production element
                var element = {
                    "recipeName": recipeName,
                    "name": statViewName,
                    "img": imgSrc,
                    "type": "item",
                    "outputSymbol": source.outputSymbol,
                    "recipeCategory": source.recipeCategory,
                    "recipeData": unsafeWindow.userContext.recipeData,
                    "activeBuildingPanel": unsafeWindow.userContext.activeBuildingPanel
                };

                // Insert the element into the queueArray (cloneInto for Mozilla)
                if (typeof (cloneInto) == "function") {
                    var elementClone = cloneInto(element, unsafeWindow);
                    unsafeWindow.productionQueue.push(elementClone);
                } else {
                    unsafeWindow.productionQueue.push(element);
                }

                quantity--;

                log('Pushed element to queue.');

            } while (quantity > 0);
        }

        log('Attempting immediate production...');
        unsafeWindow.attemptProduction(unsafeWindow.userContext.activeBuildingPanel);
        inform('Enqueued.');

    } catch (err) {
        error(err);
    }
}

$("#credits_roll").on('click', '.tableRow', deleteTableRow);
function deleteTableRow(e) {
    e.preventDefault();

    try {
        var index = $(this).find("td:first span.ranklist").text();

        log("Attempting to delete element with index " + index + " from the queue array.");

        if (unsafeWindow.productionQueue.length == 1) {
            unsafeWindow.productionQueue.pop();
        } else {
            unsafeWindow.productionQueue.splice(index, 1);
        }

        renderProductionItems();

    } catch (err) {
        error(err);
    }

}

//--> Brute force adventure
$("#modal_dialogs_top").on("click", "#speedupbtn", viewAdventure_onclick);
function viewAdventure_onclick() {
        log("View adventure details.");

    var vBtn = $(this).find("a.btngold");
    //    log("Append condition: " +
    //        "is there a button? (" + (typeof vBtn != "undefined") + "), " +
    //        "correct text? (" + vBtn.text() + " - " + (vBtn.text() == 'View Results!') + "), " +
    //        "are we bruting? (" + (options.allowBruting) + "), " +
    //        "then: " + ((!vBtn || vBtn.text() != "View Results!" || options.allowBruting) ? "return" : "execute"), "DEBUG");

    if (!vBtn || vBtn.text() != "View Results!" || !options.allowBruting) {
        return;
    }

    setTimeout(function () {

        var btn = $("#bruteBtn");
        var container = $(".challengerewards .challengerewarditems:first");
        if (container.length > 0 && btn.length == 0) {
            container.after(templates.bruteBtn);
        }
    }, (options.baseDelay / 2) * 1000);
}

$("#quests_container").on("click", "span#bruteBtn.btnwrap.btnmed", brute_onclick);
$("#credits_roll").on("click", "span#bruteBtn.btnwrap.btnmed", brute_onclick);
$("#credits_roll").on("click", "span#bruteAllBtn.btnwrap.btnmed", brute_onclick);

function brute_onclick() {
    //    log("Brute!");

    // Save settings first
    saveBruteTab();

    // Find button text
    var b = $(this).find("a.btngold");

    if (!b || b.length == 0) {
        warn("Cannot find brute button!");
    }

    if (!options.allowBruting) {
        error("Bruting is not allowed.");
        return;
    }

    unsafeWindow.brutingImmediateTermination = false;
    b.text("Bruting...");    

    if (this.id == "bruteAllBtn") {
        // Brute all sworn swords adventure...
        unsafeWindow.bruteSendAll();
    } else {
        // Else, brute adventure...
        unsafeWindow.bruteForce(true);
    }

}
// <-- Brute force adventure

// --> Bulk sell
$("#modal_dialogs").on("click", "#shop_miniview .offersitem.paginated_sellitem .miniview", sellitem_onchange);
function sellitem_onchange() {
    //    log("Item for sale changed");
    var itemId = this.id.replace("item_mini_", "");

    setTimeout(function () {

        var btn = $("#do_sell_bulk");
        var container = $("#modal_dialogs #statview_container_right .statviewbtm:first");
        if (container.length > 0 && btn.length == 0) {
            container.append(templates.sellBulkBtn(itemId));
        }
    }, 1000);
}

var amount;
var amountOwned;

$("#modal_dialogs").on("click", "#do_sell_bulk", bulkSell_onclick);
function bulkSell_onclick() {
    var amountInput = $("#sell_bulk_amount");
    if (!amountInput) {
        error("Cannot find the input for the sale amoun!");
        return;
    }

    amount = null;
    amountOwned = null;

    amount = parseInt(amountInput.val());
    if (!amount || isNaN(amount)) {
        error("Failed to parse amount of items to be sold.");

        amountInput.val("");
        amountInput.attr("placeholder", "Invalid...");
        return;
    }

    var parent = $(this).parents("div.statviewbtm");
    var ownedHtml = parent.children("p.itemowned").html();

    amountOwned = parseInt(ownedHtml.replace("Owned: ", ""));
    if (!amountOwned || isNaN(amountOwned)) {
        error("Failed to parse amount of owned items.");

        amountInput.val("");
        amountInput.attr("placeholder", "Error!");
        return;
    }

    var itemId = $(this).attr("item-id");
    if (!itemId) {
        error("Cannot resolve item id!");

        amountInput.val("");
        amountInput.attr("placeholder", "Error!");
        return;
    }

    if (0 >= amount) {
        amountInput.val("");
        amountInput.attr("placeholder", "Greater pls...");
        return;
    }

    if (amount > amountOwned) {
        warn("You don't have enough of that item.");

        amountInput.val("");
        amountInput.attr("placeholder", "Insufficient...");
        return;
    }

    bulkSell(itemId, amount);

    var observableId = "owned_quantity_" + itemId;
    itemSoldObserver.observe(document.getElementById(observableId), {
        childList: true
    });

    amountInput.val("");
    amountInput.attr("placeholder", "Selling...");
}

var itemSoldObserver = new MutationObserver(item_sold);
function item_sold(mutations) {

    var txt = mutations[mutations.length - 1].addedNodes[0].textContent;
    var val = parseInt(txt);

    if (amountOwned - val == amount) {
        itemSoldObserver.disconnect();
        inform("Done!");

        var amountInput = $("#sell_bulk_amount");
        amountInput.attr("placeholder", "Sold.");

    }
}

function bulkSell(id, count) {
    if (!id || !count) {
        error("Please specify both id (" + id + ") and count (" + count + ") and run again.");
        return;
    }

    for (var i = 0; i < count; i++) {
        try {
            unsafeWindow.doSell(id);
        } catch (e) {
            error("Error occured (processing " + i + ", id " + id + "): " + e);
            return;
        }
    }
}
// <-- Bulk sell

function getSwornSwords() {

    var ss = [];
    var pi = unsafeWindow.playerInventory;
    for (var i = 0; i < pi.length; i++) {
        var s = pi[i];
        if (s.slot == "Sworn Sword") {
            ss.push(s);
        }
    }

    if (ss.length > 0) {
        options.swornSwords = ss;
        options.set();
    }
}

// Do adventures anytime
$("#modal_dialogs_top").on("click", ".adventurebox .adventuremenu .adventurescroll .adventureitem.locked", lockedAdventure_onclick);
function lockedAdventure_onclick(e) {
    log("Trying to unlock adventure.");

    try {
        e.preventDefault();
        e.stopPropagation();

        var id = this.id;
        var aid = id.replace("adventure_", "");

        // console.debug(id, aid);

        unsafeWindow.chooseAdventure(aid);
    } catch (e) {
        error(e);
    }

}

function saveProductionQueue() {

    var p = unsafeWindow.productionQueue;
    if (p && p.length > 0) {
        options.productionQueue = p;
        options.set("productionQueue");
    }
}

function loadProductionQueue() {

    // console.debug("Conditions: ", !options.productionQueue, options.productionQueue.length == 0);
    if (!options.productionQueue || options.productionQueue.length == 0) {
        warn("No stored queue was found in options.");
        return;
    }

    // console.debug("Conditions: ", !unsafeWindow.productionQueue);
    if (!unsafeWindow.productionQueue) {
        warn("No queue was found on page to fill.");
        return;
    }

    if (typeof cloneInto == "function") {
        unsafeWindow.productionQueue = cloneInto(options.productionQueue, unsafeWindow);
    } else {
        unsafeWindow.productionQueue = options.productionQueue;
    }

    // Clear this from options
    options.productionQueue = null;
    options.set("productionQueue");


    // When done attempt production
    if (typeof unsafeWindow.attemptProduction == "function") {
        unsafeWindow.attemptProduction();
    }

}

$("#credits_roll").on('click', "#infoBtn", info_onclick);
function info_onclick() {
    $("#extenderTabContent").html(templates.bruteInfo);
}

//$("#modals_container").on("click", "#hudchatbtn", warmap_onclick);
$("#modals_container").on("click", ".messagetab", warmap_onclick);
//$("#modals_container").on("click", "div.avaregions a.avaregion", warmap_onclick);

function warmap_onclick(e) {
    e.preventDefault();
    e.stopPropagation();

    if (this.id !== "messagetab-wars") {
        $("#ex_search_row").remove();
        return;
    }

    setTimeout(function () {

        var row = $("#ex_search_row");
        var container = $("div#alliance_main_content");
        if (container.length > 0 && row.length == 0) {
            container.after(templates.searchAllianceBtn);
        }
    }, (options.baseDelay / 2) * 1000);
}

$("#modals_container").on("click", "#ex_alliance_search", searchAlliance_onclick);
function searchAlliance_onclick(e) {
    e.preventDefault();
    unsafeWindow.showSpinner();

    window.setTimeout(function() {
        
        var keys = $("#ex_alliance_search_input").val();
        if (!keys || keys.length == 0) {
            return;
        }
        
        var keysArray = keys.split(" ");
        var c = keysArray[0];
        for (var i = 1; i < keysArray.length; i++) {
            c += "+" +
                keysArray[i];
        }

        //console.debug("Sending data: ", c);

        GM_xmlhttpRequest({
            method: "GET",
            url: "/play/alliance_search/?tags=0&name=" + c,
            onload: function(a) {
                unsafeWindow.hideSpinner();

                if (a.error) {
                    var e = "Something went awry with your search. Please try again.";
                    "query-error" == e.error && (e = "Alliance Search by name requires more than 3 characters.");
                    unsafeWindow.doAlert("Alliance Search Error!", e);
                } else {
                    //console.debug("Raw response: ", a);
                    var response = JSON.parse(a.responseText);
                    //console.debug("Response text parsed: ", response);
                    displayResults(response.alliances);
                }
            }
        });

    }, (options.baseDelay / 2) * 1000);
}

function displayResults(a) {
    console.debug("Alliances to be displayed: ", a);

    $("#ex_alliance_search_input").val("");

    if (!(a instanceof Array) || a.length == 0) {
        $("#ex_alliance_search_input").attr("placeholder", "No alliances found");
        return;
    }   

    // Clean table
    $(".avaranking:visible:first tr:not(:first)").empty();

    // Fill table
    var l = a.length > 4 ? 4 : a.length;
    for (var i = 0; i < l; i++) {
        $(".avaranking:visible:first tr:first").after(templates.allianceRow(a[i], unsafeWindow.userContext.activeRegion));
    }

}

$("#modal_dialogs_top").on('click', "#adventurebox_holder .adventuremenu .adventurescroll .adventureitem", adventureItem_onclick);
function adventureItem_onclick() {
    //log("Adventures display.");

    setTimeout(function () {

        var btn = $("#adventureSendAll");
        var container = $("div.infobar span#backbtn.btnwrap.btnsm:visible");
        if (container.length > 0 && btn.length == 0) {
            container.after(templates.sendAllBtn);
        }
    }, (options.baseDelay / 2) * 1000);
}

$("#modals_container").on('click', "[onclick*='pvpStartWithTarget']", pvpStart_onclick);
function pvpStart_onclick() {
    //log('Displaying pvp dialog with a target specified.');

    setTimeout(function () {

        var btn = $("#pvpSendAll");
        var container = $("div.infobar span#backbtn.btnwrap.btnsm:visible");
        if (container.length > 0 && btn.length == 0) {
            container.after(templates.pvpSendAllBtn);
        }
    }, (options.baseDelay / 2) * 1000);
}

$(document).on('click', "[onclick*='campChoseTarget']", avaStart_onclick);
function avaStart_onclick() {
    log('Displaying ava dialog with a target specified.');

    setTimeout(function () {

        var btn = $("#avaSendAll");
        var container = $("div.infobar span#backbtn.btnwrap.btnsm:visible");
        if (container.length > 0 && btn.length == 0) {
            container.after(templates.avaSendAllBtn);
        }
    }, (options.baseDelay / 2) * 1000);
}

function checkSource() {
    console.log("-------------------------------------\\");
    console.log("Script scheduled for update check.");
    console.log("-------------------------------------/");

    var lastUpdateCheck = GM_SuperValue.get("lastUpdateCheck", "never");
    console.log("Function definitions updated: " + lastUpdateCheck);
    console.log("Source control check for integrity initiated...");
    var updateRequired = false;

    eval(GM_getResourceText("original"));
    if (typeof original == "undefined") {
        error("Cannot find original function data.");
        return;
    }

    try {

        for (var fn in original) {
            console.log("Current function: " + fn);

            if (!original.hasOwnProperty(fn)) {
                console.error("Function does not have a stored value!");
                continue;
            }
            
            console.log("Retrieving page function...");

            if (!unsafeWindow[fn]) {
                console.error("No such function on page!");
                continue;
            }

            var pageFn = unsafeWindow[fn].toString();
            console.log("Function retrieved. Comparing...");

            if (pageFn !== original[fn]) {
                console.warn("Changes detected! Please revise: " + fn);

                updateRequired = true;
                continue;
            }

            console.log("No changes were detected. Continuing...");
        }
    } catch (e) {
        alert("Source control encountered an error: " + e);
        return;
    }

    console.log("-------------------------------------|");
    console.log("-------------------------------------| > End of script update check");

    if(!updateRequired){
        GM_SuperValue.set("lastUpdateCheck", new Date());
    }

    alert("Source control resolved that " +
        (updateRequired ? "an update is required." : "no changes are necessary.") +
        "\nSee the console log for details.\nPress OK to reload again.");
}

// jQuery ajax
function ajax(params){
    if(typeof params != "object") {
        error("The request requires parameters.");
        return;
    }

    // Required
    if(!params.url) {
        error("Request url was not passed.");
        return;
    }

    if(!params.onload && !params.success){
        error("Callback handler missing. Cannot execute.");
        return;
    }

    if(!params.type){
        params.type = "GET";
    }

    if(!params.timeout){
        params.timeout = 3E4;
    }

    if(!params.onerror){
        params.onerror = function(gme){
            error("Error occurred while running the request. Details:");
            console.debug("Original ajax request parameters: ", params);
            console.debug("Grease monkey error response: ", gme);
        }
    }

    if(!params.onload){
        params.onload = function(gmr){

            if(!gmr.response){
                params.error ? params.error(gmr) : params.onerror(gmr);
            } else {
                if (gmr.responseHeaders.contains("json")) {
                    var response = JSON.parse(gmr.responseText);
                    params.success(response);
                } else {
                    params.success(gmr.responseText);
                }
            }

            if(params.complete)
                params.complete(gmr);
        }
    }

    if(!params.ontimeout){
        params.ontimeout = function(gmt){
            warn("The request timed out. Details:");
            console.debug("Original ajax request parameters: ", params);
            console.debug("Grease monkey error response: ", gmt);
        }
    }

    window.setTimeout(function () {
        GM_xmlhttpRequest({
            //binary: false,
            //context: {},
            //data: "",
            //headers: {},
            method: params.type,
            //onabort: params.onabort,
            onerror: params.onerror,
            onload: params.onload,
            //onprogress: params.onprogress,
            //onreadystatechange: params.onreadystatechange,
            ontimeout: params.ontimeout,
            //overrideMimeType: "",
            //password: "",
            //synchronous: false,
            timeout: params.timeout,
            //upload: {},
            url: params.url
            //user: ""
        });
    }, 1E3);
}