GOTA Extender

Game of Thrones Ascent Extender

当前为 2014-12-23 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==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);
}