NWPb

Automatically selects professions for empty slots

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name           NWPb
// @description    Automatically selects professions for empty slots
// @include        https://gateway.playneverwinter.com
// @include        https://gateway.playneverwinter.com/*
// @include        https://gatewaysitedown.playneverwinter.com
// @include        https://gatewaysitedown.playneverwinter.com/*
// @include        http://gateway.playneverwinter.com
// @include        http://gateway.playneverwinter.com/*
// @include        http://gatewaysitedown.playneverwinter.com
// @include        http://gatewaysitedown.playneverwinter.com/*
// @version        0.2.2
// @grant          GM_getValue
// @grant          GM_setValue
// @grant          GM_listValues
// @grant          GM_deleteValue
// @namespace https://greasyfork.org/users/2365
// ==/UserScript==

/* RELEASE NOTES
0.2.1 
-   Added BlackIce proffession
-   Added wont waste assets on all professions
0.1.0 
-   Initial release
 */

// Make sure it's running on the main page, no frames
if (window.self !== window.top) {
    throw "";
}

(function () {
    
    var image_pause = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAY" +
        "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" +
        "BNp57TyT3+MAAAAASUVORK5CYII=",
        
        image_play = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYA" +
        "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",
        
        image_prefs = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQC" +
        "AMAAAAoLQ9TAAAAllBMVEUAGQASEhIfHx8fJy8pKSk2NjZBQUFJR0ZQUE9RUVFSUlJNX3No" +
        "aGhsaWdramlycG1meY98fHx+fn5wgpV0iqKKh4R4jaR9jJx8kad9kad/mbONmaWEnrmEnrq" +
        "koZy3t7fIx8bKyMHT0c3S0dDU09DV1NPP1t3W1dXY2Njb2tfe29bf3tzj4uHr6+js6+r39/" +
        "f5+PgAAABrL3yvAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAACxMAAAsTA" +
        "QCanBgAAAAHdElNRQfWBRoFKh31UQ8DAAAAgUlEQVQY022OxxLCMAwFRSc4BEIPJZQQ08v+" +
        "/8+RsTExDDpIe3ijfSJ/hx9g62Dt4GaAI+8YT0t27+BxxvvE/no5pYT10lGFrE34Ja40W3g" +
        "1oMGmW7YZ6hnCYexKTPVkXivuvWe1Cz1aKqPNI3N0slI2TNYZiARJX30qERc7wBPKC4WRDz" +
        "WdWHfmAAAAAElFTkSuQmCC",
        
        image_close = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQC" +
        "AQAAAC1+jfqAAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfW" +
        "BRkTNhxuPxLkAAAAHXRFWHRDb21tZW50AENyZWF0ZWQgd2l0aCBUaGUgR0lNUO9kJW4AAAE" +
        "KSURBVCjPhdGxSgNBFAXQMzpgYWwsLEQUDBJBQgqFIChZEPR7/DA/QCGQTgQtJE1ENoWohY" +
        "UgbGKQyFjErNv52nObe19wqGWg7z0l5YVgVdOu+wUt507tqIVQ4Zodp861ooELe15M5KFI6" +
        "Zfr9u25MIj6Jl4cmSIPBWrq2o5cufO4aOJDYSozNTa2pK4t03PtwUdMKRRykAmW0dTRcyNX" +
        "pBQpI8GJDTR050zkNzK0bMMZLvUNZ8yCfy6Wvbc1NVyi4dloXjqWvds6uvp41pFmpVOKJWd" +
        "6bgwxkmTMIotWKpwrfBkZl7uMonUHf5wSlV2+fUZrjnXdzrmyy7djD8GWTW9e51z557o1Tz" +
        "85FH/WkOkaHQAAAABJRU5ErkJggg==";
    
    
    // Setup global closure variables
    
    var $ = unsafeWindow.jQuery;
    var dfdNextRun = $.Deferred();
    
    var timeoutHandle; // handle for setTimeouted dfdNextRun.reject();
    
    var delay = {
        SHORT: 1000,
        MEDIUM: 5000,
        LONG: 30000,
        DEFAULT: 6000, // default delay
        TIMEOUT: 60000, // delay for cycle processing timeout
    };
    
    
    /*
     * Tasklist can be modified to configure the training you want to perform.
     * The configurable options window sets how many profession slots you want to use for each profession.
     * The level array below for each professions specifies the tasks you want to learn at each crafting level.
     * Each craft slot will pick the first task that meets requirements.
     */
    var taskList = [{
        // Leadership
        taskName: "Leadership",
        level: {
            0:["Leadership_Tier0_Intro_1"],
            1:["Leadership_Tier0_Intro_5", "Leadership_Tier0_Intro_4","Leadership_Tier0_Intro_3", "Leadership_Tier0_Intro_2"],
            2:["Leadership_Tier1_2_Guardduty"],
            3:["Leadership_Tier1_2_Guardduty"],
            4:["Leadership_Tier1_4_Protect"],
            5:["Leadership_Tier1_4_Protect","Leadership_Tier1_5_Explore"],
            6:["Leadership_Tier1_4_Protect","Leadership_Tier1_5_Explore"],
            7:["Leadership_Tier1_4_Protect","Leadership_Tier1_5_Explore"],
            8:["Leadership_Tier1_4_Protect","Leadership_Tier1_5_Explore"],
            9:["Leadership_Tier1_4_Protect","Leadership_Tier2_9_Chart","Leadership_Tier1_5_Explore"],
            10:["Leadership_Tier1_4_Protect","Leadership_Tier2_9_Chart","Leadership_Tier1_5_Explore","Leadership_Tier2_10_Battle"],
            11:["Leadership_Tier1_4_Protect","Leadership_Tier2_9_Chart","Leadership_Tier1_5_Explore","Leadership_Tier2_10_Battle"],
            12:["Leadership_Tier1_4_Protect","Leadership_Tier2_9_Chart","Leadership_Tier1_5_Explore","Leadership_Tier2_10_Battle"],
            
            // Current model
            13:["Leadership_Tier3_13_Patrol","Leadership_Tier2_9_Chart","Leadership_Tier1_4_Protect","Leadership_Tier1_5_Explore","Leadership_Tier2_10_Battle"],
            14:["Leadership_Tier3_13_Patrol","Leadership_Tier2_9_Chart","Leadership_Tier1_4_Protect","Leadership_Tier1_5_Explore","Leadership_Tier2_10_Battle"],
            15:["Leadership_Tier3_13_Patrol","Leadership_Tier2_9_Chart","Leadership_Tier1_4_Protect","Leadership_Tier1_5_Explore","Leadership_Tier2_10_Battle"],
            16:["Leadership_Tier3_13_Patrol","Leadership_Tier2_9_Chart","Leadership_Tier3_16_Fight","Leadership_Tier1_5_Explore","Leadership_Tier2_10_Battle"],
            17:["Leadership_Tier3_13_Patrol","Leadership_Tier2_9_Chart","Leadership_Tier3_17_Deliver","Leadership_Tier3_16_Fight","Leadership_Tier1_5_Explore","Leadership_Tier2_12_Taxes","Leadership_Tier2_10_Battle"],
            18:["Leadership_Tier3_13_Patrol","Leadership_Tier2_9_Chart","Leadership_Tier3_17_Deliver","Leadership_Tier3_16_Fight","Leadership_Tier1_5_Explore","Leadership_Tier2_12_Taxes","Leadership_Tier2_10_Battle"],
            19:["Leadership_Tier3_13_Patrol","Leadership_Tier2_9_Chart","Leadership_Tier3_17_Deliver","Leadership_Tier3_16_Fight","Leadership_Tier1_5_Explore","Leadership_Tier2_12_Taxes","Leadership_Tier2_10_Battle"],
            20:["Leadership_Tier3_20r_Master2","Leadership_Tier3_20r_Master1","Leadership_Tier3_20r_Master3","Leadership_Tier3_20_Destroy","Leadership_Tier3_17_Deliver","Leadership_Tier3_13r_Protectdiamonds","Leadership_Tier2_12_Taxes","Leadership_Tier3_16_Fight","Leadership_Tier3_13_Patrol","Leadership_Tier2_9_Chart","Leadership_Tier1_5_Explore"],
            //19:["Patrol the Mines","Chart Region","Deliver Metals","Fight Off Spellplagued","Explore Local Area","Collect Taxes","Train a Guard","Battle Undead","Hire a Mercenary"],
            //20:["Assault Enemy Stronghold","Follow Map to an Unknown Location","Recover Large Mineral Claim","Destroy Enemy Camp","Deliver Metals","Protect Diamond Shipment","Collect Taxes","Fight Off Spellplagued","Patrol the Mines","Chart Region","Explore Local Area"],
            
            // Training Mode
            /*
            13:["Leadership_Tier3_13_Recruit","Leadership_Tier2_7_Recruit","Leadership_Tier1_2_Recruit"],
            14:["Leadership_Tier3_13_Recruit","Leadership_Tier2_7_Recruit","Leadership_Tier1_2_Recruit"],
            15:["Leadership_Tier3_13_Recruit","Leadership_Tier2_7_Recruit","Leadership_Tier1_2_Recruit"],
            16:["Leadership_Tier3_13_Recruit","Leadership_Tier2_7_Recruit","Leadership_Tier1_2_Recruit"],
            17:["Leadership_Tier3_13_Recruit","Leadership_Tier2_7_Recruit","Leadership_Tier1_2_Recruit"],
            */
        },
    }, {
        // Mailsmithing
        taskName:"Armorsmithing_Med",
        level: {
            0:["Med_Armorsmithing_Tier0_Intro"],
            1:["Med_Armorsmithing_Tier1_Chain_Boots_1","Med_Armorsmithing_Tier1_Chain_Shirt_1"],
            2:["Med_Armorsmithing_Tier1_Chain_Armor_1","Med_Armorsmithing_Tier1_Chain_Pants_1"],
            3:["Med_Armorsmithing_Tier1_Chain_Armor_1","Med_Armorsmithing_Tier1_Chain_Boots_Set_1"],
            4:["Med_Armorsmithing_Tier1_Chain_Armor_1","Med_Armorsmithing_Tier1_Chain_Boots_Set_1"],
            5:["Med_Armorsmithing_Tier1_Chain_Armor_Set_1","Med_Armorsmithing_Tier1_Chain_Boots_Set_1"],
            6:["Med_Armorsmithing_Tier1_Chain_Armor_Set_1","Med_Armorsmithing_Tier1_Chain_Boots_Set_1"],
            7:["Med_Armorsmithing_Tier1_Chain_Armor_Set_1","Med_Armorsmithing_Tier2_Chain_Boots_Set_1","Med_Armorsmithing_Tier2_Chain_Shirt"],
            8:["Med_Armorsmithing_Tier2_Chain_Armor_Set_1","Med_Armorsmithing_Tier2_Chain_Pants_1","Med_Armorsmithing_Tier2_Chain_Boots_Set_1","Med_Armorsmithing_Tier2_Chain_Shirt"],
            9:["Med_Armorsmithing_Tier2_Chain_Armor_Set_1","Med_Armorsmithing_Tier2_Chain_Pants_1","Med_Armorsmithing_Tier2_Chain_Boots_Set_1","Med_Armorsmithing_Tier2_Chain_Shirt"],
            10:["Med_Armorsmithing_Tier2_Chain_Armor_Set_1","Med_Armorsmithing_Tier2_Chain_Pants_1","Med_Armorsmithing_Tier2_Chain_Boots_Set_1","Med_Armorsmithing_Tier2_Chain_Shirt_2"],
            11:["Med_Armorsmithing_Tier2_Chain_Armor_Set_1","Med_Armorsmithing_Tier2_Chain_Pants_2","Med_Armorsmithing_Tier2_Chain_Boots_Set_1","Med_Armorsmithing_Tier2_Chain_Shirt_2","Med_Armorsmithing_Tier2_Chain_Pants_1"],
            12:["Med_Armorsmithing_Tier2_Chain_Armor_Set_1","Med_Armorsmithing_Tier2_Chain_Pants_2","Med_Armorsmithing_Tier2_Chain_Boots_Set_1","Med_Armorsmithing_Tier2_Chain_Shirt_2","Med_Armorsmithing_Tier2_Chain_Pants_1"],
            13:["Med_Armorsmithing_Tier2_Chain_Armor_Set_1","Med_Armorsmithing_Tier2_Chain_Pants_2","Med_Armorsmithing_Tier2_Chain_Boots_Set_1","Med_Armorsmithing_Tier2_Chain_Shirt_2","Med_Armorsmithing_Tier2_Chain_Pants_1"],
            14:["Med_Armorsmithing_Tier2_Chain_Armor_Set_1","Med_Armorsmithing_Tier2_Chain_Pants_2","Med_Armorsmithing_Tier3_Chain_Shirt","Med_Armorsmithing_Tier3_Chain_Boots_Set_1"],
            15:["Med_Armorsmithing_Tier3_Chain_Armor_Set_1","Med_Armorsmithing_Tier3_Chain_Pants","Med_Armorsmithing_Tier3_Chain_Shirt2","Med_Armorsmithing_Tier3_Chain_Boots_Set_1"],
            16:["Med_Armorsmithing_Tier3_Chain_Armor_Set_1","Med_Armorsmithing_Tier3_Chain_Pants2","Med_Armorsmithing_Tier3_Chain_Shirt2","Med_Armorsmithing_Tier3_Chain_Helm_Set_1","Med_Armorsmithing_Tier3_Chain_Pants"],
            17:["Med_Armorsmithing_Tier3_Chain_Armor_Set_1","Med_Armorsmithing_Tier3_Chain_Pants2","Med_Armorsmithing_Tier3_Chain_Shirt2","Med_Armorsmithing_Tier3_Chain_Helm_Set_1","Med_Armorsmithing_Tier3_Chain_Pants"],
            18:["Med_Armorsmithing_Tier3_Chain_Armor_Set_1","Med_Armorsmithing_Tier3_Chain_Pants2","Med_Armorsmithing_Tier3_Chain_Shirt2","Med_Armorsmithing_Tier3_Chain_Helm_Set_1","Med_Armorsmithing_Tier3_Chain_Pants"],
            19:["Med_Armorsmithing_Tier3_Chain_Armor_Set_1","Med_Armorsmithing_Tier3_Chain_Pants2","Med_Armorsmithing_Tier3_Chain_Shirt2","Med_Armorsmithing_Tier3_Chain_Helm_Set_1","Med_Armorsmithing_Tier3_Chain_Pants"],
            20:["Med_Armorsmithing_Tier2_Refine_Basic"],
            //19:["Chain Armor +4","Fancy Chain Pants","Fancy Chain Shirt","Chain Helm +4","Ornate Chain Pants","Upgrade Blacksmith","Upgrade Prospector","Hire an additional Prospector"],
            //20:["Forge Steel Rings and Scales"],
        },
    }, {
        // Platesmithing
        taskName:"Armorsmithing_Heavy",
        level: {
            0:["Hvy_Armorsmithing_Tier0_Intro"],
            1:["Hvy_Armorsmithing_Tier1_Plate_Boots_1","Hvy_Armorsmithing_Tier1_Plate_Shirt_1","Hvy_Armorsmithing_Tier1_Shield_1"],
            2:["Hvy_Armorsmithing_Tier1_Plate_Armor_1","Hvy_Armorsmithing_Tier1_Plate_Pants_1"],
            3:["Hvy_Armorsmithing_Tier1_Plate_Armor_1","Hvy_Armorsmithing_Tier1_Plate_Boots_Set_1"],
            4:["Hvy_Armorsmithing_Tier1_Plate_Armor_1","Hvy_Armorsmithing_Tier1_Plate_Boots_Set_1"],
            5:["Hvy_Armorsmithing_Tier1_Plate_Armor_Set_1","Hvy_Armorsmithing_Tier1_Plate_Boots_Set_1"],
            6:["Hvy_Armorsmithing_Tier1_Plate_Armor_Set_1","Hvy_Armorsmithing_Tier1_Plate_Boots_Set_1"],
            7:["Hvy_Armorsmithing_Tier1_Plate_Armor_Set_1","Hvy_Armorsmithing_Tier2_Plate_Boots_Set_1","Hvy_Armorsmithing_Tier2_Plate_Shirt","Hvy_Armorsmithing_Tier2_Shield_Set_1"],
            8:["Hvy_Armorsmithing_Tier2_Plate_Armor_Set_1","Hvy_Armorsmithing_Tier2_Plate_Pants_1","Hvy_Armorsmithing_Tier2_Plate_Boots_Set_1","Hvy_Armorsmithing_Tier2_Plate_Shirt"],
            9:["Hvy_Armorsmithing_Tier2_Plate_Armor_Set_1","Hvy_Armorsmithing_Tier2_Plate_Pants_1","Hvy_Armorsmithing_Tier2_Plate_Boots_Set_1","Hvy_Armorsmithing_Tier2_Plate_Shirt"],
            10:["Hvy_Armorsmithing_Tier2_Plate_Armor_Set_1","Hvy_Armorsmithing_Tier2_Plate_Pants_1","Hvy_Armorsmithing_Tier2_Plate_Boots_Set_1","Hvy_Armorsmithing_Tier2_Plate_Shirt_2"],
            11:["Hvy_Armorsmithing_Tier2_Plate_Armor_Set_1","Hvy_Armorsmithing_Tier2_Plate_Pants_2","Hvy_Armorsmithing_Tier2_Plate_Boots_Set_1","Hvy_Armorsmithing_Tier2_Plate_Shirt_2","Hvy_Armorsmithing_Tier2_Plate_Pants_1"],
            12:["Hvy_Armorsmithing_Tier2_Plate_Armor_Set_1","Hvy_Armorsmithing_Tier2_Plate_Pants_2","Hvy_Armorsmithing_Tier2_Plate_Boots_Set_1","Hvy_Armorsmithing_Tier2_Plate_Shirt_2","Hvy_Armorsmithing_Tier2_Plate_Pants_1"],
            13:["Hvy_Armorsmithing_Tier2_Plate_Armor_Set_1","Hvy_Armorsmithing_Tier2_Plate_Pants_2","Hvy_Armorsmithing_Tier2_Plate_Boots_Set_1","Hvy_Armorsmithing_Tier2_Plate_Shirt_2","Hvy_Armorsmithing_Tier2_Plate_Pants_1"],
            14:["Hvy_Armorsmithing_Tier2_Plate_Armor_Set_1","Hvy_Armorsmithing_Tier2_Plate_Pants_2","Hvy_Armorsmithing_Tier3_Plate_Shirt","Hvy_Armorsmithing_Tier3_Plate_Boots_Set_1"],
            15:["Hvy_Armorsmithing_Tier3_Plate_Armor_Set_1","Hvy_Armorsmithing_Tier3_Plate_Pants","Hvy_Armorsmithing_Tier3_Plate_Shirt2","Hvy_Armorsmithing_Tier3_Plate_Boots_Set_1"],
            16:["Hvy_Armorsmithing_Tier3_Plate_Armor_Set_1","Hvy_Armorsmithing_Tier3_Plate_Pants2","Hvy_Armorsmithing_Tier3_Plate_Shirt2","Hvy_Armorsmithing_Tier3_Plate_Helm_Set_1","Hvy_Armorsmithing_Tier3_Plate_Pants"],
            17:["Hvy_Armorsmithing_Tier3_Plate_Armor_Set_1","Hvy_Armorsmithing_Tier3_Plate_Pants2","Hvy_Armorsmithing_Tier3_Plate_Shirt2","Hvy_Armorsmithing_Tier3_Plate_Helm_Set_1","Hvy_Armorsmithing_Tier3_Plate_Pants"],
            18:["Hvy_Armorsmithing_Tier3_Plate_Armor_Set_1","Hvy_Armorsmithing_Tier3_Plate_Pants2","Hvy_Armorsmithing_Tier3_Plate_Shirt2","Hvy_Armorsmithing_Tier3_Plate_Helm_Set_1","Hvy_Armorsmithing_Tier3_Plate_Pants"],
            19:["Hvy_Armorsmithing_Tier3_Plate_Armor_Set_1","Hvy_Armorsmithing_Tier3_Plate_Pants2","Hvy_Armorsmithing_Tier3_Plate_Shirt2","Hvy_Armorsmithing_Tier3_Plate_Helm_Set_1","Hvy_Armorsmithing_Tier3_Plate_Pants"],
            20:["Hvy_Armorsmithing_Tier2_Refine_Basic"],
            //19:["Plate Armor +4","Fancy Plate Pants","Fancy Plate Shirt","Plate Helm +4","Ornate Plate Pants","Upgrade Armorer","Upgrade Miner","Hire an additional Miner"],
            //20:["Forge Steel Plates"],
        },
    }, {
        // Leatherworking
        taskName:"Leatherworking",
        level: {
            0:["Leatherworking_Tier0_Intro_1"],
            1:["Leatherworking_Tier1_Leather_Boots_1","Leatherworking_Tier1_Leather_Shirt_1"],
            2:["Leatherworking_Tier1_Leather_Armor_1","Leatherworking_Tier1_Leather_Pants_1"],
            3:["Leatherworking_Tier1_Leather_Armor_1","Leatherworking_Tier1_Leather_Boots_Set_1"],
            4:["Leatherworking_Tier1_Leather_Armor_1","Leatherworking_Tier1_Leather_Boots_Set_1"],
            5:["Leatherworking_Tier1_Leather_Armor_Set_1","Leatherworking_Tier1_Leather_Boots_Set_1"],
            6:["Leatherworking_Tier1_Leather_Armor_Set_1","Leatherworking_Tier1_Leather_Boots_Set_1"],
            7:["Leatherworking_Tier1_Leather_Armor_Set_1","Leatherworking_Tier2_Leather_Boots_Set_1","Leatherworking_Tier2_Leather_Shirt"],
            8:["Leatherworking_Tier2_Leather_Armor_Set_1","Leatherworking_Tier2_Leather_Pants_1","Leatherworking_Tier2_Leather_Boots_Set_1","Leatherworking_Tier2_Leather_Shirt"],
            9:["Leatherworking_Tier2_Leather_Armor_Set_1","Leatherworking_Tier2_Leather_Pants_1","Leatherworking_Tier2_Leather_Boots_Set_1","Leatherworking_Tier2_Leather_Shirt"],
            10:["Leatherworking_Tier2_Leather_Armor_Set_1","Leatherworking_Tier2_Leather_Pants_1","Leatherworking_Tier2_Leather_Boots_Set_1","Leatherworking_Tier2_Leather_Shirt_2"],
            11:["Leatherworking_Tier2_Leather_Armor_Set_1","Leatherworking_Tier2_Leather_Pants_2","Leatherworking_Tier2_Leather_Boots_Set_1","Leatherworking_Tier2_Leather_Shirt_2","Leatherworking_Tier2_Leather_Pants_1"],
            12:["Leatherworking_Tier2_Leather_Armor_Set_1","Leatherworking_Tier2_Leather_Pants_2","Leatherworking_Tier2_Leather_Boots_Set_1","Leatherworking_Tier2_Leather_Shirt_2","Leatherworking_Tier2_Leather_Pants_1"],
            13:["Leatherworking_Tier2_Leather_Armor_Set_1","Leatherworking_Tier2_Leather_Pants_2","Leatherworking_Tier2_Leather_Boots_Set_1","Leatherworking_Tier2_Leather_Shirt_2","Leatherworking_Tier2_Leather_Pants_1"],
            /*
            14:["Leatherworking_Tier2_Leather_Armor_Set_1","Leatherworking_Tier2_Leather_Pants_2","Ornate Leatherworking_Tier1_Leather_Shirt_1","Leatherworking_Tier3_Leather_Boots_Set_1"],
            15:["Leatherworking_Tier3_Leather_Armor_Set_1","Leatherworking_Tier3_Leather_Pants","Leatherworking_Tier3_Leather_Shirt2","Leatherworking_Tier3_Leather_Boots_Set_1"],
            16:["Leatherworking_Tier3_Leather_Armor_Set_1","Leatherworking_Tier3_Leather_Pants2","Leatherworking_Tier3_Leather_Shirt2","Leatherworking_Tier3_Leather_Helm_Set_1","Leatherworking_Tier3_Leather_Pants"],
            17:["Leatherworking_Tier3_Leather_Armor_Set_1","Leatherworking_Tier3_Leather_Pants2","Leatherworking_Tier3_Leather_Shirt2","Leatherworking_Tier3_Leather_Helm_Set_1","Leatherworking_Tier3_Leather_Pants"],
            18:["Leatherworking_Tier3_Leather_Armor_Set_1","Leatherworking_Tier3_Leather_Pants2","Leatherworking_Tier3_Leather_Shirt2","Leatherworking_Tier3_Leather_Helm_Set_1","Leatherworking_Tier3_Leather_Pants"],
            19:["Leatherworking_Tier3_Leather_Armor_Set_1","Leatherworking_Tier3_Leather_Pants2","Leatherworking_Tier3_Leather_Shirt2","Leatherworking_Tier3_Leather_Helm_Set_1","Leatherworking_Tier3_Leather_Pants"],
            20:["Leatherworking_Tier2_Refine_Basic"],
            */
            //19:["Leather Armor +4","Fancy Leather Pants","Fancy Leather Shirt","Leather Helm +4","Ornate Leather Pants","Upgrade Tanner","Upgrade Skinner","Hire an additional Skinner"],
            //20:["Cure Tough Pelts"],
            
            // Experience
            14:["Leatherworking_Tier3_Gather_Basic_Mass","Leatherworking_Tier3_Refine_Basic_Mass"],
            15:["Leatherworking_Tier3_Gather_Basic_Mass","Leatherworking_Tier3_Refine_Basic_Mass"],
            16:["Leatherworking_Tier3_Gather_Basic_Mass","Leatherworking_Tier3_Refine_Basic_Mass"],
            17:["Leatherworking_Tier3_Gather_Basic_Mass","Leatherworking_Tier3_Refine_Basic_Mass"],
            18:["Leatherworking_Tier3_Gather_Basic_Mass","Leatherworking_Tier3_Refine_Basic_Mass"],
        },
    }, {
        // Tailoring
        taskName:"Tailoring",
        level: {
            0:["Tailoring_Tier0_Intro"],
            1:["Tailoring_Tier1_Cloth_Boots_1","Tailoring_Tier1_Cloth_Shirt_1"],
            2:["Tailoring_Tier1_Cloth_Armor_1","Tailoring_Tier1_Cloth_Pants_1"],
            3:["Tailoring_Tier1_Cloth_Armor_1","Tailoring_Tier1_Cloth_Boots_Set_1"],
            4:["Tailoring_Tier1_Cloth_Armor_1","Tailoring_Tier1_Cloth_Boots_Set_1"],
            5:["Tailoring_Tier1_Cloth_Armor_Set_1","Tailoring_Tier1_Cloth_Boots_Set_1"],
            6:["Tailoring_Tier1_Cloth_Armor_Set_1","Tailoring_Tier1_Cloth_Boots_Set_1"],
            7:["Tailoring_Tier1_Cloth_Armor_Set_1","Tailoring_Tier2_Cloth_Boots_Set_1","Tailoring_Tier2_Cloth_Shirt"],
            8:["Tailoring_Tier2_Cloth_Armor_Set_1","Tailoring_Tier2_Cloth_Pants_1","Tailoring_Tier2_Cloth_Boots_Set_1","Tailoring_Tier2_Cloth_Shirt"],
            9:["Tailoring_Tier2_Cloth_Armor_Set_1","Tailoring_Tier2_Cloth_Pants_1","Tailoring_Tier2_Cloth_Boots_Set_1","Tailoring_Tier2_Cloth_Shirt"],
            10:["Tailoring_Tier2_Cloth_Armor_Set_1","Tailoring_Tier2_Cloth_Pants_1","Tailoring_Tier2_Cloth_Boots_Set_1","Tailoring_Tier2_Cloth_Shirt_2"],
            11:["Tailoring_Tier2_Cloth_Armor_Set_1","Tailoring_Tier2_Cloth_Pants_2","Tailoring_Tier2_Cloth_Boots_Set_1","Tailoring_Tier2_Cloth_Shirt_2","Tailoring_Tier2_Cloth_Pants_1"],
            12:["Tailoring_Tier2_Cloth_Armor_Set_1","Tailoring_Tier2_Cloth_Pants_2","Tailoring_Tier2_Cloth_Boots_Set_1","Tailoring_Tier2_Cloth_Shirt_2","Tailoring_Tier2_Cloth_Pants_1"],
            13:["Tailoring_Tier2_Cloth_Armor_Set_1","Tailoring_Tier2_Cloth_Pants_2","Tailoring_Tier2_Cloth_Boots_Set_1","Tailoring_Tier2_Cloth_Shirt_2","Tailoring_Tier2_Cloth_Pants_1"],
            14:["Tailoring_Tier2_Cloth_Armor_Set_1","Tailoring_Tier2_Cloth_Pants_2", "Tailoring_Tier3_Cloth_Shirt","Tailoring_Tier3_Cloth_Boots_Set_1"],
            15:["Tailoring_Tier3_Cloth_Armor_Set_1","Tailoring_Tier3_Cloth_Pants","Tailoring_Tier3_Cloth_Shirt2","Tailoring_Tier3_Cloth_Boots_Set_1"],
            16:["Tailoring_Tier3_Cloth_Armor_Set_1","Tailoring_Tier3_Cloth_Pants2","Tailoring_Tier3_Cloth_Shirt2","Tailoring_Tier3_Cloth_Helm_Set_1","Tailoring_Tier3_Cloth_Pants"],
            17:["Tailoring_Tier3_Cloth_Armor_Set_1","Tailoring_Tier3_Cloth_Pants2","Tailoring_Tier3_Cloth_Shirt2","Tailoring_Tier3_Cloth_Helm_Set_1","Tailoring_Tier3_Cloth_Pants"],
            18:["Tailoring_Tier3_Cloth_Armor_Set_1","Tailoring_Tier3_Cloth_Pants2","Tailoring_Tier3_Cloth_Shirt2","Tailoring_Tier3_Cloth_Helm_Set_1","Tailoring_Tier3_Cloth_Pants"],
            19:["Tailoring_Tier3_Cloth_Armor_Set_1","Tailoring_Tier3_Cloth_Pants2","Tailoring_Tier3_Cloth_Shirt2","Tailoring_Tier3_Cloth_Helm_Set_1","Tailoring_Tier3_Cloth_Pants"],
            20:["Tailoring_Tier2_Refine_Basic"],
            //19:["Cloth Robes +4","Fancy Cloth Pants","Fancy Cloth Shirt","Cloth Cap +4","Ornate Cloth Pants","Upgrade Outfitter","Upgrade Weaver","Hire an additional Weaver"],
            //20:["Weave Cotton Cloth"],
        },
    }, {
        // Artificing
        taskName: "Artificing",
        level: {
             0:["Artificing_Tier0_Intro_1"],
             1:["Artificing_Tier1_Symbol_Virtuous_1"],
             2:["Artificing_Tier1_Icon_Virtuous_1"],
             3:["Artificing_Tier1_Icon_Virtuous_1"],
             4:["Artificing_Tier1_Icon_Virtuous_2"],
             5:["Artificing_Tier1_Icon_Virtuous_2"],
             6:["Artificing_Tier1_Icon_Virtuous_2"],
             7:["Artificing_Tier1_Icon_Virtuous_2"],
             8:["Artificing_Tier2_Icon_Virtuous_3"],
             9:["Artificing_Tier2_Icon_Virtuous_3"],
             10:["Artificing_Tier2_Icon_Virtuous_3"],
             11:["Artificing_Tier2_Icon_Virtuous_3"],
             12:["Artificing_Tier2_Icon_Virtuous_3"],
             13:["Artificing_Tier2_Icon_Virtuous_3"],
             14:["Artificing_Tier3_Icon_Virtuous_4"],
             15:["Artificing_Tier3_Icon_Virtuous_4"],
             16:["Artificing_Tier3_Icon_Virtuous_4"],
             17:["Artificing_Tier3_Icon_Virtuous_5"],
             18:["Artificing_Tier3_Icon_Virtuous_5"],
             19:["Artificing_Tier3_Icon_Virtuous_5"],
             20:["Artificing_Tier2_Refine_Basic"],
             //19:["Virtuous Icon +5","Upgrade Engraver","Upgrade Carver","Hire an additional Carver"],
             //20:["7:Craft Ornamental metal and Carved Wood"],
        },
    }, {
        // Weaponsmithing
        taskName: "Weaponsmithing",
        level: {
            0: ["Hire your first Smelter"],
            1: ["Weaponsmithing_Tier1_Gather_Basic","Weaponsmithing_Tier1_Refine_Basic"],
            2: ["Weaponsmithing_Tier1_Gather_Basic","Weaponsmithing_Tier1_Refine_Basic"],
            3: ["Weaponsmithing_Tier1_Gather_Basic","Weaponsmithing_Tier1_Refine_Basic"],
            4: ["Weaponsmithing_Tier1_Gather_Basic","Weaponsmithing_Tier1_Refine_Basic"],
            5: ["Weaponsmithing_Tier1_Gather_Basic","Weaponsmithing_Tier1_Refine_Basic"],
            6: ["Weaponsmithing_Tier1_Gather_Basic","Weaponsmithing_Tier1_Refine_Basic"],
            7: ["Weaponsmithing_Tier1_Refine_Basic","Weaponsmithing_Tier1_Refine_Basic"],
            8: ["Weaponsmithing_Tier2_Gather_Basic","Weaponsmithing_Tier2_Refine_Basic"],
            9: ["Weaponsmithing_Tier2_Gather_Basic","Weaponsmithing_Tier2_Refine_Basic"],
            10: ["Weaponsmithing_Tier2_Gather_Basic","Weaponsmithing_Tier2_Refine_Basic"],
            11: ["Weaponsmithing_Tier2_Gather_Basic","Weaponsmithing_Tier2_Refine_Basic"],
            12: ["Weaponsmithing_Tier2_Gather_Basic","Weaponsmithing_Tier2_Refine_Basic"],
            13: ["Weaponsmithing_Tier2_Gather_Basic","Weaponsmithing_Tier2_Refine_Basic"],
            14: ["Weaponsmithing_Tier3_Gather_Basic_Mass","Weaponsmithing_Tier3_Refine_Basic_Mass"],
            15: ["Weaponsmithing_Tier3_Gather_Basic_Mass","Weaponsmithing_Tier3_Refine_Basic_Mass"],
            16: ["Weaponsmithing_Tier3_Gather_Basic_Mass","Weaponsmithing_Tier3_Refine_Basic_Mass"],
            17: ["Weaponsmithing_Tier3_Gather_Basic_Mass","Weaponsmithing_Tier3_Refine_Basic_Mass"],
            18: ["Weaponsmithing_Tier3_Gather_Basic_Mass","Weaponsmithing_Tier3_Refine_Basic_Mass"],
            //19: ["Weaponsmithing_Tier3_Gather_Basic_Mass","Weaponsmithing_Tier3_Refine_Basic_Mass"],
            //20: ["Weaponsmithing_Tier3_Gather_Basic_Mass","Weaponsmithing_Tier3_Refine_Basic_Mass"],
        },
    }, {
        // Alchemy
        taskName: "Alchemy",
        level: {
            0:["Alchemy_Tier0_Intro_1"],
            1:["Alchemy_Tier1_Experiment_Rank2","Alchemy_Tier1_Experimentation_Rank1",],
            2:["Alchemy_Tier1_Experiment_Rank3","Alchemy_Tier1_Experimentation_Rank2"],
            3:["Alchemy_Tier1_Experiment_Rank4","Alchemy_Tier1_Experimentation_Rank3"],
            4:["Alchemy_Tier1_Experiment_Rank5","Alchemy_Tier1_Experimentation_Rank4"],
            5:["Alchemy_Tier1_Experiment_Rank6","Alchemy_Tier1_Experimentation_Rank5"],
            6:["Alchemy_Tier1_Experiment_Rank7","Alchemy_Tier1_Experimentation_Rank6"],
            7:["Alchemy_Tier2_Experiment_Rank08","Alchemy_Tier2_Experimentation_Rank07"],
            8:["Alchemy_Tier2_Experiment_Rank09","Alchemy_Tier2_Experimentation_Rank08"],
            9:["Alchemy_Tier2_Experiment_Rank10","Alchemy_Tier2_Experimentation_Rank09"],
            10:["Alchemy_Tier2_Experiment_Rank11","Alchemy_Tier2_Experimentation_Rank10"],
            11:["Alchemy_Tier2_Experiment_Rank12","Alchemy_Tier2_Experimentation_Rank11"],
            12:["Alchemy_Tier2_Experiment_Rank13","Alchemy_Tier2_Experimentation_Rank12"],
            13:["Alchemy_Tier2_Experiment_Rank14","Alchemy_Tier2_Experimentation_Rank13"],
            14:["Alchemy_Tier3_Experiment_Rank15","Alchemy_Tier3_Experimentation_Rank14"],
            15:["Alchemy_Tier3_Experiment_Rank16","Alchemy_Tier3_Experimentation_Rank15"],
            16:["Alchemy_Tier3_Experiment_Rank17","Alchemy_Tier3_Experimentation_Rank16"],
            17:["Alchemy_Tier3_Experiment_Rank18","Alchemy_Tier3_Experimentation_Rank17"],
            18:["Alchemy_Tier3_Experiment_Rank19","Alchemy_Tier3_Experimentation_Rank18"],
            19:["Alchemy_Tier3_Experiment_Rank20","Alchemy_Tier3_Experimentation_Rank19"],
            20:["Alchemy_Tier3_Experimentation_Rank20"],
        },
    }, {
        // Black Ice
        taskName:"BlackIce",
        level: {
            0:["Blackice_Tier1_Mass_Process_Blackice","Blackice_Tier1_Process_Blackice"],
            1:["Blackice_Tier2_Purified_Enchantment_Healthsteal_Lesser","Blackice_Tier2_Corrupt_Enchantment_Lesser","Blackice_Tier1_Process_Blackice"],
            2:["Blackice_Tier3_Corrupt_Enchantment_Greater","Blackice_Tier3_Purified_Enchantment_Healthsteal_Greater","Blackice_Tier2_Purified_Enchantment_Healthsteal_Lesser","Blackice_Tier2_Corrupt_Enchantment_Lesser","Blackice_Tier1_Process_Blackice"],           
            //Blackice_Tier1_Process_Blackice
            //Blackice_Tier1_Mass_Process_Blackice
            //Blackice_Tier2_Corrupt_Enchantment_Lesser
            //Blackice_Tier2_Purified_Enchantment_Healthsteal_Lesser
            //Blackice_Tier2_Purified_Enchantment_Deflect_Lesser
            //Blackice_Tier2_Purified_Enchantment_Lesser
            //Blackice_Tier3_Corrupt_Enchantment_Greater
            //Blackice_Tier3_Purified_Enchantment_Healthsteal_Greater
            //Blackice_Tier3_Purified_Enchantment_Deflect_Greater
            //Blackice_Tier3_Purified_Enchantment_Greater
            //Blackice_Tier2_Corrupt_Enchantment_Lesser,Blackice_Tier2_Purified_Enchantment_Healthsteal_Lesser,
            //Blackice_Tier3_Corrupt_Enchantment_Greater,Blackice_Tier3_Purified_Enchantment_Healthsteal_Greater,
        },
    }, ];
              
    // settings definitions
    var globalSettingNames = [
        {name: 'paused',            title: 'Pause Script',                          def: false, type: 'checkbox',   tooltip: 'Disable All Automation'}, 
        {name: 'trainassets',       title: 'Train additional assets if needed',     def: false, type: 'checkbox',   tooltip: 'Enable to train/recruit assets if required by a task'},
        {name: 'preservelog',       title: 'Preserve log',                          def: false, type: 'checkbox',   tooltip: 'Enable to preserve log between page reloads'}, 
        {name: 'optionals',         title: 'Fill Optional Assets',                  def: false, type: 'checkbox',   tooltip: 'Enable to include selecting the optional assets of tasks'}, 
        {name: 'autopurchase',      title: 'Auto Purchase Resources',               def: true,  type: 'checkbox',   tooltip: 'Automatically purchase required resources from gateway shop (100 at a time)'}, 
        {name: 'refinead',          title: 'Refine AD',                             def: true,  type: 'checkbox',   tooltip: 'Enable refining of AD on character switch'}, 
        {name: 'autoreload',        title: 'Auto Reload',                           def: false, type: 'checkbox',   tooltip: 'Enabling this will reload the gateway periodically'}, 
        {name: 'autologin',         title: 'Attempt to login automatically',        def: false, type: 'checkbox',   tooltip: 'Automatically attempt to login to the neverwinter gateway site'}, 
        {name: 'nw_username',       title: '  Neverwinter Username',                def: '',    type: 'text',       tooltip: ''}, 
        {name: 'nw_password',       title: '  Neverwinter Password',                def: '',    type: 'password',   tooltip: ''}, 

    ];
    
    var charSettingNames = [
        {name: 'entity',                title: 'Entity',            def: 'character@handle',    type: 'text',   tooltip: 'Entity'}, 
        {name: 'Leadership',            title: 'Leadership',        def: '9',                   type: 'text',   tooltip: 'Number of slots to assign to Leadership'}, 
        {name: 'Armorsmithing_Med',     title: 'Mailsmithing',      def: '0',                   type: 'text',   tooltip: 'Number of slots to assign to Mailsmithing'},
        {name: 'Armorsmithing_Heavy',   title: 'Platesmithing',     def: '0',                   type: 'text',   tooltip: 'Number of slots to assign to Platesmithing'}, 
        {name: 'Leatherworking',        title: 'Leatherworking',    def: '0',                   type: 'text',   tooltip: 'Number of slots to assign to Leatherworking'}, 
        {name: 'Tailoring',             title: 'Tailoring',         def: '0',                   type: 'text',   tooltip: 'Number of slots to assign to Tailoring'}, 
        {name: 'Artificing',            title: 'Artificing',        def: '0',                   type: 'text',   tooltip: 'Number of slots to assign to Artificing'}, 
        {name: 'Weaponsmithing',        title: 'Weaponsmithing',    def: '0',                   type: 'text',   tooltip: 'Number of slots to assign to Weaponsmithing'}, 
        {name: 'Alchemy',               title: 'Alchemy',           def: '0',                   type: 'text',   tooltip: 'Number of slots to assign to Alchemy'}, 
        {name: 'BlackIce',              title: 'Black Ice Shaping', def: '0',                   type: 'text',   tooltip: 'Number of slots to assign to BlackIce'},

    ];
                            
    // Load global settings from persistent store
    var globalSettings = JSON.parse(GM_getValue('mlng_globalSettings', "{}"));
                            
    // Set defaults if not found for each setting
    globalSettingNames.forEach(function (element, index, array) {
    // Ignore label types
    if (element.type === 'label') {
    return;
    }
    if (globalSettings[element.name] === undefined) {
        globalSettings[element.name] = element.def;
    }
    // call the onsave for the setting if it exists
    if (typeof (element.onsave) === "function") {
        _log("Calling 'onsave' for", element.name);
        element.onsave(globalSettings[element.name], globalSettings[element.name]);
    }
});

// write back in case new defaults were merged
GM_setValue('mlng_globalSettings', JSON.stringify(globalSettings));

// Load character settings from persistent store
var charSettings = JSON.parse(GM_getValue('mlng_charSettings', "{}"));

// Page definitions
var pages = {
    LOGIN: {
        name: "Login",
        path: "div#login"
    },
    GUARD: {
        name: "Account Guard",
        path: "div#page-accountguard"
    },
    CHARSELECT: {
        name: "Character Select",
        path: "div.page-characterselect"
    },
    FRONTPAGE: {
        name: "Front Page",
        path: "div.page-front"
    },
    PROFESSIONS: {
        name: "Professions",
        path: "div.page-professions"
    },
};

function _log(msg) {
    var _el = $("#logWindow");
    // append msg to log window
    _el.append('<p>[' + new Date().toLocaleString() + '] ' + msg + '</p>');
    // if user doesn't hover the div, scroll it; warning: buggy in jquery 1.9.x
    if (!_el.is(':hover')) {
        _el.prop('scrollTop', _el.prop('scrollHeight'));
    }
}

function settingsCharacterInjectHTML() {
    // replace HTML containing character settings UI.
    
    var _text = '';
    Object.keys(charSettings).forEach(function (i) {
        _text += '<li><input type="radio" name="radio_position" id="value_' + i.replace(' ', '_') + '" value="' + i.replace(' ', '_') + '" /><label for="value_' + i.replace(' ', '_') + '">' + charSettings[i].entity + '</label>';
        _text += '<div id="charContainer_' + i.replace(' ', '_') + '" style="display:none"><ul style="list-style: none outside none;overflow:none;">';
        charSettingNames.forEach(function (element) {
            var id = 'charsettings_' + i.replace(' ', '_') + '_' + element.name;
            _text += '<li title="' + element.tooltip + '"><input style="margin:4px" name="' + id + '" id="' + id + '" type="text" value="' + charSettings[i][element.name] + '"/><label for="' + id + '">' + element.title + '</label></li>';
        });
        _text += '</ul></li></div>';
    });
    $("#charSettings ul").html(_text);
}

function settingsCharacterAdd() {
    var _entity, i;
    
    _entity = prompt("Enter your character's entity / ingame mail address", "character@handle");
    
    if (_entity === null || _entity.match(/\w@\w/) === null) {
        alert('Incorrect data, use charactername@accountname format');
        return;
    }
    charSettings[_entity] = {};
    for (i = 0; i < charSettingNames.length; i++) {
        charSettings[_entity][charSettingNames[i].name] = charSettingNames[i].name === 'entity' ? _entity : charSettingNames[i].def;
    }
    settingsCharacterInjectHTML();
}

function settingsCharacterWipe() {
    charSettings = {};
    GM_deleteValue('mlng_charSettings');
    
    settingsCharacterAdd();
    _log('Character settings wiped');
}

function settingsSave() {
    var name, el, value, i, j;
    
    // Global Settings
    for (i = 0; i < globalSettingNames.length; i++) {
        name = globalSettingNames[i].name;
        el = $('#globalsettings_' + name);
        value = false;
        switch (globalSettingNames[i].type) {
            case "checkbox":
                value = el.prop("checked");
                break;
            case "text":
            case "password":
            case "select":
                value = el.val();
                break;
            case "label":
                // Labels don't have values
                continue;
        }
        if (typeof (globalSettingNames[i].onsave) === "function") {
            _log("Calling 'onsave' for: " + name);
            globalSettingNames[i].onsave(value, globalSettings[name]);
        }
        if (globalSettings[name] !== value) {
            globalSettings[name] = value;
        }
    }
    GM_setValue('mlng_globalSettings', JSON.stringify(globalSettings));
    
    // Get character settings from UI
    for (i in charSettings) {
        for (j in charSettingNames) {
            name = charSettingNames[j].name;
            value = $('#charsettings_' + i.replace('@', '\\@').replace(' ', '_') + '_' + name).val();
            if (charSettings[i][name] !== value) {
                charSettings[i][name] = value;
            }
        }
    }
    // Check if entity changed
    for (i in charSettings) {
        if (charSettings[i].entity !== i) {
            charSettings[charSettings[i].entity] = charSettings[i];
            delete charSettings[i];
        }
    }
    GM_setValue('mlng_charSettings', JSON.stringify(charSettings));
    
    settingsCharacterInjectHTML();
    _log('Settings saved.');
}

function AddCss(cssString) {
    var newCss = document.createElement('style');
    
    newCss.type = "text/css";
    newCss.innerHTML = cssString;
    
    document.getElementsByTagName('head')[0].appendChild(newCss);
}

function injectHTML() {
    if ($("#settingsButton").length) {
        return;
    }
    // Add the required CSS
    AddCss("\
#logWindow{ position: fixed; right:0px; bottom:0px; width:50%; height:20%; overflow:auto;z-index: 1000; background: none rgb(238, 238, 238); text-align:left;font: 12px sans-serif; border:1px solid rgb(102, 102, 102);}\
#logWindow * { margin:0.2em; }\
#settingsButtons{ position: fixed; right: 0px; top: 0px; z-index: 1000; background: none rgb(238, 238, 238);}\
#settingsButtons div {float:left; border-right: 1px solid rgb(102, 102, 102);}\
#settingsButtons a, #settingsButtons a:visited {display:block; width: 22px; height: 22px; text-decoration: none; font-weight:bold; text-align:center; line-height:22px; color:black;}\
#settingsButtons img { background: repeat scroll 0% 0%; padding: 3px;}\
#settingsPanel{border-bottom: 1px solid rgb(102, 102, 102); border-right: 1px solid rgb(102, 102, 102); background: none repeat scroll 0% 0% rgb(238, 238, 238); color: rgb(0, 0, 0); position: fixed; overflow: auto; right: 0px; top: 0px; width: 350px;max-height:750px;font: 12px sans-serif; text-align: left; display: block; z-index: 1000;}\
#settings_title{font-weight: bolder; background: none repeat scroll 0% 0% rgb(204, 204, 204); border-bottom: 1px solid rgb(102, 102, 102); padding: 3px;}\
#settingsPanelButtonContainer {background: none repeat scroll 0% 0% rgb(204, 204, 204); border-top: 1px solid rgb(102, 102, 102);padding: 3px;text-align:center} \
#charPanel {width:340px;max-height:400px;overflow:auto;display:block;padding:3px;}\
");
    
    // Add settings panel to page body
    $("body").append(
        '<div id="settingsPanel">\
<div id="settings_title">\
<img src=' + image_prefs + ' style="float: left; vertical-align: text-bottom;">\
<img id="settings_cancel" src=' + image_close + ' title="Click to hide preferences" style="float: right; vertical-align: text-bottom; cursor: pointer; display: block;">\
<span style="margin:3px">Settings</span>\
</div>\
<div>\
<form style="margin: 0px; padding: 0px">\
<div id="globalSettings">\
<span>Global Settings</span>\
<ul style="list-style: none outside none; max-height: 500px; overflow: auto; margin: 3px; padding: 0px;">\
</ul>\
</div>\
<div id="charSettings">\
<span>Character Settings</span>\
<ul style="list-style: none outside none; max-height: 500px; overflow: auto; margin: 3px; padding: 0px;">\
</ul>\
</div>\
<div id="settingsPanelButtonContainer">\
<input id="settings_addchar" type="button" value="Add Character">\
<input id="settings_save" type="button" value="Save and Apply">\
<input id="settings_close" type="button" value="Close">\
<input id="settings_wipechar" type="button" value="Wipe" title="Wipe character settings">\
</div>\
</form>\
</div>\
</div>\
');
    
    // Add pause & open settings buttons to page
    $("body").append('\
<div id="settingsButtons">\
<div id="pauseButton">\
<img src="' + (globalSettings.paused ? image_play : image_pause) + '" title="Click to ' + (globalSettings.paused ? "resume" : "pause") + ' task script" style="cursor: pointer; display: block;">\
</div>\
<div id="logButton">\
<a title="Click to toggle log window" href="javascript:void(0)">L</a>\
</div>\
<div id="settingsButton">\
<img src="' + image_prefs + '" title="Click to show preferences" style="cursor: pointer; display: block;">\
</div>\
</div>\
');
    
    // Add log window
    $("body").append('\
<div id="logWindow" />\
');
    // restore log contents
    var _el = $("#logWindow");
    _el.append(GM_getValue('mlng_log', ""));
    _el.prop('scrollTop', _el.prop('scrollHeight'));
    
    
    // Add global settings
    var settingsList = $("div#globalSettings ul");
    for (var i = 0; i < globalSettingNames.length; i++) {
        var id = 'globalsettings_' + globalSettingNames[i].name;
        
        switch (globalSettingNames[i].type) {
            case "checkbox":
                settingsList.append('<li title="' + globalSettingNames[i].tooltip + '"><input style="margin:4px" name="' + id + '" id="' + id + '" type="checkbox" /><label for="' + id + '">' + globalSettingNames[i].title + '</label></li>');
                $('#' + id).prop('checked', globalSettings[globalSettingNames[i].name]);
                break;
            case "text":
                settingsList.append('<li title="' + globalSettingNames[i].tooltip + '"><label for="' + id + '">' + globalSettingNames[i].title + '</label><input style="margin:4px" name="' + id + '" id="' + id + '" type="text" /></li>');
                $('#' + id).val(globalSettings[globalSettingNames[i].name]);
                break;
            case "password":
                settingsList.append('<li title="' + globalSettingNames[i].tooltip + '"><label for="' + id + '">' + globalSettingNames[i].title + '</label><input style="margin:4px" name="' + id + '" id="' + id + '" type="password" /></li>');
                $('#' + id).val(globalSettings[globalSettingNames[i].name]);
                break;
            case "select":
                settingsList.append('<li title="' + globalSettingNames[i].tooltip + '"><label style="padding-left:4px" for="' + id + '">' + globalSettingNames[i].title + '</label><select style="margin:4px" name="' + id + '" id="' + id + '" /></li>');
                var options = globalSettingNames[i].opts;
                var select = $('#' + id);
                for (var j = 0; j < options.length; j++) {
                    if (globalSettings[globalSettingNames[i].name] == options[j].path) select.append('<option value="' + options[j].path + '" selected="selected">' + options[j].name + '</option>');
                    else select.append('<option value="' + options[j].path + '">' + options[j].name + '</option>');
                }
                break;
            case "label":
                settingsList.append('<li title="' + globalSettingNames[i].tooltip + '"><label>' + globalSettingNames[i].title + '</label></li>');
                break;
        }
    }
    
    // Add character settings for each char
    settingsCharacterInjectHTML();
    
    $("#settingsPanel, #logWindow").hide();
    
    // Add click handlers
    $("#charSettings").on('click', 'input[name=radio_position]', function () {
        var _container = "#charContainer_" + $(this).attr('value').replace('@', '\\@');
        console.log(_container);
        $("#charSettings div[id^=charContainer_]").each(function () {
            $(this).hide();
        });
        $(_container).show();
        
    });
    $("#settingsButton, #settings_close, #settings_cancel").click(function () {
        $("#settingsButtons, #settingsPanel").toggle();
    });
    $("#logButton").click(function () {
        $("#logWindow").toggle();
    });
    $("#pauseButton").click(function () {
        globalSettings.paused = !globalSettings.paused;
        setTimeout(function () {
            GM_setValue('mlng_globalSettings', JSON.stringify(globalSettings));
        }, 0);
        $("#globalsettings_paused").prop("checked", globalSettings.paused);
        $("#pauseButton img").attr("src", (globalSettings.paused ? image_play : image_pause));
        $("#pauseButton img").attr("title", "Click to " + (globalSettings.paused ? "resume" : "pause") + " task script");
    });
    $("#settings_save").click(function () {
        setTimeout(function () {
            settingsSave();
        }, 0);
    });
    $("#settings_wipechar").click(function () {
        setTimeout(function () {
            settingsCharacterWipe();
        }, 0);
    });
    $("#settings_addchar").click(function () {
        setTimeout(function () {
            settingsCharacterAdd();
        }, 0);
    });
    $(unsafeWindow).one('beforeunload', function () {
        setTimeout(function () {
            if (globalSettings.preservelog === true) {
                GM_setValue('mlng_log', $('#logWindow').html());
            } else {
                GM_deleteValue('mlng_log');
            }
        }, 0);
    });
}
/**
     * Uses the page settings to determine which page is currently displayed
     */
function getCurrentPage() {
    var _page, i;
    
    for (i in pages) {
        if ($(pages[i].path).filter(":visible").length) {
            _page = pages[i];
            break;
        }
    }
    _log('Current page is: ' + (_page ? _page.name : 'Unknown'));
    return _page;
}

function characterGetCurrent() {
    try {
        var _ent = unsafeWindow.client.dataModel.model.ent.main;
        return _ent.name + '@' + _ent.publicaccountname;
    } catch (e) {}
    return undefined;
}

function characterGetNext() {
    // we need to get the next char in charSettings that actually is available for the logged in account.
    
    var _available, // array of characters available to the current account
        _filtered, // array of characters from charSettings containing only those in _available
        _currentChar = characterGetCurrent(); // current character
    
    // assignment finish time info
    characterGetNext.assignmentFinish = characterGetNext.assignmentFinish || {};
    
    // save current characters earliest assignment finish time
    if (_currentChar !== undefined) {
        characterGetNext.assignmentFinish[_currentChar] = Math.min.apply(Math,unsafeWindow.client.dataModel.model.ent.main.itemassignments.assignments.map(function(x) {
            return new Date(x.ufinishdate).getTime() === 946684800000 ? 8640000000000000 : new Date(x.ufinishdate).getTime();
        }));
    }
    
    try {
        
        _available = unsafeWindow.client.dataModel.model.loginInfo.choices.map(function (element) {
            return element.name + '@' + element.pubaccountname;
        });
        
        _filtered = Object.keys(charSettings).filter(function (element) {
            return _available.indexOf(element) !== -1;
        });
        if (_filtered.length === Object.keys(characterGetNext.assignmentFinish).length) {
            // assignment finish times are available for all chars, return character with earliest finish
            return Object.keys(characterGetNext.assignmentFinish).sort(function (a, b) { return characterGetNext.assignmentFinish[a] - characterGetNext.assignmentFinish[b]; })[0];
        }
        // assignment finish times not available for all chars, return next char in list
        return _filtered[_filtered.indexOf(_currentChar) + 1] || _filtered[0]; // working for _currentChar === undefined just fine, because indexOf() will return -1
        
    } catch (e) {}
    return undefined;
}

function characterSwitch() {
    // TODO: make switching priority based on time left to completion; no point in switching back and forth every delay.DEFAULT when we're waiting for tasks anyway.
    var _currentChar, _nextChar;
    
    _log('Switching characters:');
    
    _currentChar = characterGetCurrent();
    
    _nextChar = characterGetNext();
    
    if (_nextChar && unsafeWindow.location.hash === '#/characterselect') {
        _log('-->Selected character: ' + _nextChar);
        unsafeWindow.location.hash = '#char(' + _nextChar + ')/';
    } else if (_nextChar === _currentChar) {
        _log('-->Current character: ' + _currentChar + ' equals next character: ' + _nextChar + '. Doing nothing');
    } else if (_nextChar && _currentChar) {
        _log('-->Switching character from: ' + _currentChar + ' to: ' + _nextChar);
        // fix for memory leak?
        unsafeWindow.client.dataModel.rmgr.rmgr.dicts.PersonalProject[unsafeWindow.client.dataModel.model.ent.main.id]._requesters = [];
        unsafeWindow.location.hash = unsafeWindow.location.hash.replace(_currentChar, _nextChar);
    } else {
        _log('-->.logininfo not available. Switching chars failed.');
    }
    dfdNextRun.resolve();
}

function utilRefineAD() {
    // refine AD; WARNING: this function doesn't resolve()!
    if (globalSettings.refinead === false) {
        return;
    }
    
    try {
        var lv_cur = unsafeWindow.client.dataModel.model.ent.main.currencies;
        if (lv_cur && lv_cur.roughdiamonds && lv_cur.diamondsconvertleft) {
            unsafeWindow.client.sendCommand('Gateway_ConvertNumeric', 'Astral_Diamonds');
            _log('Refined AD');
        }
    } catch (e) {}
    //TODO: wait for message back from server
}

function professionCollectRewards() {
    // collect profession rewards if they exist; WARNING: this function doesn't resolve()!
    var _dfd, _collected, _ia;
    
    _dfd = $.Deferred();
    _collected = false;
    
    try {
        _ia = unsafeWindow.client.dataModel.model.ent.main.itemassignments;
        if (_ia.complete && _ia.complete > 0) {
            _ia.assignments.forEach(function (element) {
                if (element.rewards) {
                    unsafeWindow.client.professionTaskCollectRewards(JSON.stringify(element.uassignmentid));
                    _collected = true;
                }
            });
        }
    } catch (e) {}
    
    if (_collected === true) {
        _log('Collected profession rewards');
        setTimeout(function () {
            professionCollectRewards()
            .then( function() {
                unsafeWindow.client.dataModel.model.craftinglist = {}; // need to invalidate the old craftinglists, they refresh after a few secs
                _dfd.resolve();
            });
        }, delay.SHORT);
    } else {
        _dfd.resolve();
    }
    return _dfd.promise();
    
}

/**
     * Will continually test for the given query state and resolve the given deferred object when the state is reached
     * and the loading symbol is not visible
     *
     * @param {string} query The query for the state to wait for
     */
function WaitForElement(selector) {
    var _dfd, _check;
    
    _dfd = $.Deferred();
    _check = setInterval(function () {
        if ((selector === '' || $(selector).length) && $("div.loading-image:visible").length === 0) {
            clearInterval(_check);
            _dfd.resolve();
        }
    }, 300);
    
    return _dfd.promise();
}

function WaitForProperty(property) {
    // need to use eval() because of GM userscript being wrapped in an anonymous function
    // didn't want to have it work for unsafeWindow[property] only
    // could rewrite not to use exceptions, but meh, performance loss from stack trace should be negligible in this case
    var _dfd, _check;
    
    _dfd = $.Deferred();
    _check = setInterval(function () {
        try {
            if (eval(property) === undefined) {
                throw ''; //needed when foo exists && foo.bar doesn't exist
            }
            clearInterval(_check);
            _dfd.resolve();
        } catch (e) {}
    }, 300);
    
    return _dfd.promise();
}

    /**
     * Selects the highest level asset for the i'th button in the list. Uses an iterative approach
     * in order to apply a sufficient delay after the asset is assigned
     *
     * @param {Array} The list of buttons to use to click and assign assets for
     * @param {int} i The current iteration number. Will select assets for the i'th button
     * @param {Deferred} jQuery Deferred object to resolve when all of the assets have been assigned
     */
function SelectItemFor(buttonListIn, i, def, prof) {
    buttonListIn[i].click();
    WaitForElement("")
    .then(function () {
        var specialItems = $("div.modal-item-list a.Special");
        var goldItems = $("div.modal-item-list a.Gold");
        var silverItems = $("div.modal-item-list a.Silver");
        var bronzeItems = $("div.modal-item-list a.Bronze");
        var clicked = false;
        
        // Try to avoid using up higher rank assets needlessly  
        // Comment out for random use of all high assets
         
        if (prof.taskName === "Leadership") {
            var mercenarys = $("div.modal-item-list a.Bronze:contains('Mercenary')");
            var guards = $("div.modal-item-list a.Bronze:contains('Guard')");
            var footmen = $("div.modal-item-list a.Bronze:contains('Footman')");
            var manatarms = $("div.modal-item-list a.Silver:contains('Man-at-Arms')");
            var adventurers = $("div.modal-item-list a.Gold:contains('Adventurer')");
            var heros = $("div.modal-item-list a.Special:contains('Hero')");

            if (mercenarys.length) {
                clicked = true;
                mercenarys[0].click();
            } else if (guards.length) {
                clicked = true;
                guards[0].click();
            } else if (footmen.length) {
                clicked = true;
                footmen[0].click();
            } else if (manatarms.length) {
                clicked = true;
                manatarms[0].click();
            } else if (adventurers.length) {
                clicked = true;
                adventurers[0].click();
            } else if (heros.length) {
                clicked = true;
                heros[0].click();
            }
        }
        
        if (prof.taskName === "Armorsmithing_Med") {
            var prospectors = $("div.modal-item-list a.Bronze:contains('Prospector')");
            var blacksmiths = $("div.modal-item-list a.Bronze:contains('Blacksmith')");
            var amailsmiths = $("div.modal-item-list a.Bronze:contains('Assistant Mailsmith')");
            var mailsmiths = $("div.modal-item-list a.Silver:contains('Mailsmith')");
            var mmailsmiths = $("div.modal-item-list a.Gold:contains('Master Mailsmith')");
            var gmailsmiths = $("div.modal-item-list a.Special:contains('Grandmaster Mailsmith')");
            
            if (prospectors.length) {
                clicked = true;
                prospectors[0].click();
            } else if (blacksmiths.length) {
                clicked = true;
                blacksmiths[0].click();
            } else if (amailsmiths.length) {
                clicked = true;
                amailsmiths[0].click();
            } else if (mailsmiths.length) {
                clicked = true;
                mailsmiths[0].click();
            } else if (mmailsmiths.length) {
                clicked = true;
                mmailsmiths[0].click();
            } else if (gmailsmiths.length) {
                clicked = true;
                gmailsmiths[0].click();
            }
        }

        if (prof.taskName === "Armorsmithing_Heavy") {
            var miners = $("div.modal-item-list a.Bronze:contains('Miner')");
            var armorers = $("div.modal-item-list a.Bronze:contains('Armorer')");
            var aplatesmiths = $("div.modal-item-list a.Bronze:contains('Assistant Platesmith')");
            var platsmiths = $("div.modal-item-list a.Silver:contains('Platsmith')");
            var mplatsmiths = $("div.modal-item-list a.Gold:contains('Master Platsmith')");
            var gplatsmiths = $("div.modal-item-list a.Special:contains('Grandmaster Platsmith')");
            
            if (miners.length) {
                clicked = true;
                miners[0].click();
            } else if (armorers.length) {
                clicked = true;
                armorers[0].click();
            } else if (aplatesmiths.length) {
                clicked = true;
                aplatesmiths[0].click();
            } else if (platsmiths.length) {
                clicked = true;
                platsmiths[0].click();
            } else if (mplatsmiths.length) {
                clicked = true;
                mplatsmiths[0].click();
            } else if (gplatsmiths.length) {
                clicked = true;
                gplatsmiths[0].click();
            }
        }
        
        if (prof.taskName === "Leatherworking") {
            var skinners = $("div.modal-item-list a.Bronze:contains('Skinner')");
            var tanners = $("div.modal-item-list a.Bronze:contains('Tanner')");
            var aleatherworkers = $("div.modal-item-list a.Bronze:contains('Assistant Leatherworker')");
            var leatherworkers = $("div.modal-item-list a.Silver:contains('Leatherworker')");
            var mleatherworkers = $("div.modal-item-list a.Gold:contains('Master Leatherworker')");
            var gleatherworkers = $("div.modal-item-list a.Special:contains('Grandmaster Leatherworker')");
            
            if (skinners.length) {
                clicked = true;
                skinners[0].click();
            } else if (tanners.length) {
                clicked = true;
                tanners[0].click();
            } else if (aleatherworkers.length) {
                clicked = true;
                aleatherworkers[0].click();
            } else if (leatherworkers.length) {
                clicked = true;
                leatherworkers[0].click();
            } else if (mleatherworkers.length) {
                clicked = true;
                mleatherworkers[0].click();
            } else if (gleatherworkers.length) {
                clicked = true;
                gleatherworkers[0].click();
            }
        }
        
        if (prof.taskName === "Tailoring") {
            var weavers = $("div.modal-item-list a.Bronze:contains('Weaver')");
            var outfitters = $("div.modal-item-list a.Bronze:contains('Outfitter')");
            var atailors = $("div.modal-item-list a.Bronze:contains('Assistant Tailor')");
            var tailors = $("div.modal-item-list a.Silver:contains('Leatherworker')");
            var mtailors = $("div.modal-item-list a.Gold:contains('Master Tailor')");
            var gtailors = $("div.modal-item-list a.Special:contains('Grandmaster Tailor')");
            
            if (weavers.length) {
                clicked = true;
                weavers[0].click();
            } else if (outfitters.length) {
                clicked = true;
                outfitters[0].click();
            } else if (atailors.length) {
                clicked = true;
                atailors[0].click();
            } else if (tailors.length) {
                clicked = true;
                tailors[0].click();
            } else if (mtailors.length) {
                clicked = true;
                mtailors[0].click();
            } else if (gtailors.length) {
                clicked = true;
                gtailors[0].click();
            }
        }
        if (prof.taskName === "Artificing") {
            var carvers = $("div.modal-item-list a.Bronze:contains('Carver')");
            var engravers = $("div.modal-item-list a.Bronze:contains('Engraver')");
            var aartificers = $("div.modal-item-list a.Bronze:contains('Assistant Artificer')");
            var artificers = $("div.modal-item-list a.Silver:contains('Artificer')");
            var martificers = $("div.modal-item-list a.Gold:contains('Master Artificer')");
            var gartificers = $("div.modal-item-list a.Special:contains('Grandmaster Artificer')");
            
            if (carvers.length) {
                clicked = true;
                carvers[0].click();
            } else if (engravers.length) {
                clicked = true;
                engravers[0].click();
            } else if (aartificers.length) {
                clicked = true;
                aartificers[0].click();
            } else if (aartificers.length) {
                clicked = true;
                aartificers[0].click();
            } else if (martificers.length) {
                clicked = true;
                martificers[0].click();
            } else if (gartificers.length) {
                clicked = true;
                gartificers[0].click();
            }
        }
        if (prof.taskName === "Weaponsmithing") {
            var smelters = $("div.modal-item-list a.Bronze:contains('Smelter')");
            var grinders = $("div.modal-item-list a.Bronze:contains('Grinder')");
            var aweaponsmiths = $("div.modal-item-list a.Bronze:contains('Assistant Weaponsmith')");
            var weaponsmiths = $("div.modal-item-list a.Silver:contains('Weaponsmith')");
            var mweaponsmiths = $("div.modal-item-list a.Gold:contains('Master Weaponsmith')");
            var gweaponsmiths = $("div.modal-item-list a.Special:contains('Grandmaster Weaponsmith')");
            
            if (smelters.length) {
                clicked = true;
                smelters[0].click();
            } else if (grinders.length) {
                clicked = true;
                grinders[0].click();
            } else if (aweaponsmiths.length) {
                clicked = true;
                aweaponsmiths[0].click();
            } else if (weaponsmiths.length) {
                clicked = true;
                weaponsmiths[0].click();
            } else if (mweaponsmiths.length) {
                clicked = true;
                mweaponsmiths[0].click();
            } else if (gweaponsmiths.length) {
                clicked = true;
                gweaponsmiths[0].click();
            }
        }
        if (prof.taskName === "Alchemy") {
            var apothecarys = $("div.modal-item-list a.Bronze:contains('Apothecary')");
            var mixologists = $("div.modal-item-list a.Bronze:contains('Mixologist')");
            var aalchemists = $("div.modal-item-list a.Bronze:contains('Assistant Alchemist')");
            var alchemists = $("div.modal-item-list a.Silver:contains('Alchemist')");
            var malchemists = $("div.modal-item-list a.Gold:contains('Master Alchemist')");
            var galchemists = $("div.modal-item-list a.Special:contains('Grandmaster Alchemist')");
            
            if (apothecarys.length) {
                clicked = true;
                apothecarys[0].click();
            } else if (mixologists.length) {
                clicked = true;
                mixologists[0].click();
            } else if (aalchemists.length) {
                clicked = true;
                aalchemists[0].click();
            } else if (alchemists.length) {
                clicked = true;
                alchemists[0].click();
            } else if (malchemists.length) {
                clicked = true;
                malchemists[0].click();
            } else if (galchemists.length) {
                clicked = true;
                galchemists[0].click();
            }
        }
        if (prof.taskName === "BlackIce") {
            var chillwrights = $("div.modal-item-list a.Bronze:contains('Apothecary')");
            var black_ice_smiths = $("div.modal-item-list a.Bronze:contains('Black Ice Smith')");
            var acryomancers = $("div.modal-item-list a.Bronze:contains('Assistant Cryomancer')");
            var cryomancers = $("div.modal-item-list a.Silver:csontains('Cryomancer')");
            var mcryomancers = $("div.modal-item-list a.Gold:contains('Master Cryomancer')");
            var gcryomancers = $("div.modal-item-list a.Special:contains('Grandmaster Cryomancer')");
            
            if (chillwrights.length) {
                clicked = true;
                chillwrights[0].click();
            } else if (black_ice_smiths.length) {
                clicked = true;
                black_ice_smiths[0].click();
            } else if (acryomancers.length) {
                clicked = true;
                acryomancers[0].click();
            } else if (cryomancers.length) {
                clicked = true;
                cryomancers[0].click();
            } else if (mcryomancers.length) {
                clicked = true;
                mcryomancers[0].click();
            } else if (gcryomancers.length) {
                clicked = true;
                gcryomancers[0].click();
            }
        }

        if (!clicked) {
            // Click the highest slot
            if (specialItems.length) {
                specialItems[0].click();
            } else if (goldItems.length) {
                goldItems[0].click();
            } else if (silverItems.length) {
                silverItems[0].click();
            } else if (bronzeItems.length) {
                bronzeItems[0].click();
            } else {
                $("button.close-button").click();
            }
        }
        
        _log("Clicked item");
        
        return true;
    })
    .then( function () {
        return WaitForElement("");
    })
    .then(function () {
        // Get the new set of select buttons created since the other ones are removed when the asset loads
        var buttonList = $("h3:contains('Optional Assets:')").closest("div").find("button");
        if (i < buttonList.length - 1) {
            SelectItemFor(buttonList, i + 1, def, prof);
        } else {
            _log('all optional slots populated');
            // Let main loop continue
            def.resolve();
        }
    });
    
}

function professionBuyResource(item, count) {
    _log("Purchasing resources:" + item);
    
    count = count || 100;
    
    var resourceID = {
        Crafting_Resource_Charcoal: 0,
        Crafting_Resource_Rocksalt: 1,
        Crafting_Resource_Spool_Thread: 2,
        Crafting_Resource_Porridge: 3,
        Crafting_Resource_Solvent: 4,
        Crafting_Resource_Brimstone: 5,
        Crafting_Resource_Coal: 6,
        Crafting_Resource_Moonseasalt: 7,
        Crafting_Resource_Quicksilver: 8,
        Crafting_Resource_Spool_Threadsilk: 9,
    };
    
    // fetch store; we don't really need the vars to be populated, just that the buy command won't execute unless we send this 1st;
    unsafeWindow.client.dataModel.fetchVendor('Nw_Gateway_Professions_Merchant');
    //send an 'analytic tick', that's what the outer client function does ...
    unsafeWindow.client.analyticTick("User:BuyItem", count);
    // Make purchase
    unsafeWindow.client.sendCommand(
        "GatewayVendor_PurchaseVendorItem", {
            vendor: 'Nw_Gateway_Professions_Merchant',
            store: 'Store_Crafting_Resources',
            idx: resourceID[item],
            count: count
        });
    // Wait for the notification and close it
    WaitForElement("button.closeNotification").done(function () {
        $('button.closeNotification').click();
    });
}

function searchForTask(taskname, profName) {
    // assuming we are on the correct profession page - NEEDS FIX;
    // looks through ALL available tasks, even if they are not visible on the page (i.e. paginated or filtered)
    var _dfd = $.Deferred();
    
    var searchItem = null;
    var searchAsset = false;
    
    try {
        
        // Return first object that matches exact craft name
        var thisTask = unsafeWindow.client.dataModel.model.craftinglist['craft_' + profName].entries.filter(function (entry) {
            return entry.def && entry.def.name == taskname;
        })[0];
        
        // If no task is returned then we have three of this task already or wrong name in settings
        if (!thisTask) {
            _log('searchForTask: Task ' + taskname + ' not available.');
            _dfd.resolve(false);
            return _dfd.promise();
        }
        // start task if requirements are met
        if (!thisTask.failedrequirementsreasons.length) {
            _dfd.resolve(thisTask.def.name);
            return _dfd.promise();
        }
        // Too high level
        if (thisTask.failslevelrequirements) {
            _log("searchForTask: Wrong task settings: task level is too high: " + taskname);
            _dfd.resolve(false);
            return _dfd.promise();
        }
        // Missing assets or ingredients
        if (thisTask.failsresourcesrequirements) {
            var failedAssets = thisTask.required.filter(function (entry) {
                return !entry.fillsrequirements;
            });
            
            // Missing required assets
            if (failedAssets.length) {
                var failedCrafter = failedAssets.filter(function (entry) {
                    return entry.categories.indexOf("Person") >= 0;
                });
                if (failedCrafter.length && globalSettings.trainassets) {
                    _log("searchForTask: Crafting: " + failedCrafter[0].icon);
                    searchItem = failedCrafter[0].icon;
                    searchAsset = true;
                } else {
                    _log("searchForTask: Auto crafting assets disabled, missing: " + failedCrafter[0].icon);
                    _dfd.resolve(false);
                    return _dfd.promise();
                }
            } else {
                // Check for craftable or buyable ingredients
                var failedResources = thisTask.consumables.filter(function (entry) {
                    return entry.required && !entry.fillsrequirements;
                });
                
                // Check first required ingredient only
                // If it fails to buy or craft task cannot be completed anyway
                // If it succeeds script will search for tasks anew
                var itemName = failedResources[0].hdef.match(/\[(\w+)\]/)[1];
                
                // purchase buyable resources
                if (itemName.match(/^Crafting_Resource_(Charcoal|Rocksalt|Spool_Thread|Porridge|Solvent|Brimstone|Coal|Moonseasalt|Quicksilver|Spool_Threadsilk)$/)) {
                    if (globalSettings.autopurchase) {
                        professionBuyResource(itemName);
                        _dfd.resolve(null);
                        return _dfd.promise();
                    }
                    _log("searchForTask: Auto resource purchasing disabled, missing: " + itemName);
                    _dfd.resolve(false);
                    return _dfd.promise();
                }
                // craft ingredient items
                _log("searchForTask: Crafting: " + itemName);
                searchItem = itemName;
            }
        }
        
        // either no craftable items/assets found or other task requirements are not met
        // Skip crafting ingredient tasks for Leadership
        if (searchItem === null || !searchItem.length || (profName == 'Leadership' && !searchAsset && !searchItem.match(/Crafting_Asset_Craftsman/))) {
            _log("searchForTask: Failed to resolve item requirements for task: " + taskname);
            _dfd.resolve(false);
            return _dfd.promise();
        }
        
        // Generate list of available tasks to search ingredients/assets from
        _log("searchForTask: Searching tasks for ingredient: " + searchItem);
        var taskList = unsafeWindow.client.dataModel.model.craftinglist['craft_' + profName].entries.filter(function (entry) {
            // remove header lines first to avoid null def
            if (entry.isheader) {
                return false;
            }
            // Too high level
            if (entry.failslevelrequirements) {
                return false;
            }
            // Rewards do not contain item we want to make
            if (searchAsset === true) {
                if (entry.def.icon != searchItem || entry.def.name.indexOf('Recruit') === -1 || entry.def.requiredrank > 14) {
                    return false;
                }
            } else {
                if (!(entry.rewards.some(function (itm) {
                    return (itm.hdef.indexOf(searchItem) !== -1);
                }))) {
                    return false;
                }
            }
            // Skip mass production tasks
            if (entry.def.displayname.match(/^(Batch|Mass|Deep|Intensive) /)) {
                return false;
            }
            // Skip trading tasks
            if (entry.def.displayname.indexOf('Trading') !== -1) {
                return false;
            }
            // Skip looping Transmute tasks
            if (entry.def.displayname.match(/^(Transmute|Create) /)) {
                return false;
            }
            return true;
        });
        
        if (!taskList.length) {
            _log("searchForTask: Unable to find tasks for ingredient: " + searchItem);
            _dfd.resolve(false);
            return _dfd.promise();
            
        }
        
        // Use more efficient Empowered task for Aqua Vitae if available.
        if (searchItem == "Crafting_Resource_Aquavitae" && taskList.length > 1) {
            taskList.shift();
        }
        
        // Should really only be one result now
        _log("searchForTask: Attempting to find task for ingredient: " + taskList[0].def.name);
        searchForTask(taskList[0].def.name, profName)
        .then( function (task) {
            _dfd.resolve(task);
        });
        return _dfd.promise();
        
    } catch (e) {}
    
    _log('searchForTask: No valid task found for: ' + taskname);
    _dfd.resolve(false);
    return _dfd.promise();
}

function professionGetCurrentLevel(professionName) {
    var _level;
    
    _level = 0;
    if (professionName !== undefined) {
        try {
            unsafeWindow.client.dataModel.model.ent.main.itemassignmentcategories.categories.some(function(entry) {
                if(entry.name == professionName) {
                    _level = entry.currentrank;
                    return true;
                }
                return false; 
            });
        } catch (e) {}
    }
    return _level;
}

// TO BE REPLACED
function professionStartTask() {
    var _dfd = $.Deferred();
    
    //Get the start task button if it is enabled
    var enabledButton = $("div.footer-body > div.input-field.button:not('.disabled') > button:contains('Start Task')");
    if (enabledButton.length) {
        _log("professionStartTask(): clicking Start Task button");
        enabledButton.click();
    } else { // Button not enabled, something required was probably missing
        _log('professionStartTask(): something went wrong, start task button not enabled.');
    }
    
    // an ugly hack, not sure how to go about waiting for the task to actually start/fail
    // 2 options: either read notifications or hook client functions; both ugly
    setTimeout(function () {
        _dfd.resolve();
    }, delay.SHORT);
    
    return _dfd.promise();
}

// TO BE REPLACED
function professionSlotItems(prof) {
    var _dfd, _buttonList;
    
    _dfd = $.Deferred();
    
    if (!globalSettings.optionals) {
        _dfd.resolve();
    } else {
        _buttonList = $("h3:contains('Optional Assets:')").closest("div").find("button");
        if (_buttonList.length) {
            _log('createNextTask: populating optional slots.');
            SelectItemFor(_buttonList, 0, _dfd, prof);
        } else {
            _dfd.resolve();
        }
    }
    
    return _dfd.promise();
}

function professionGetCraftingList(profName) {
    // resolving with the actual craftinglist, not used atm
    var _dfd, _craftingList;
    
    _dfd = $.Deferred();
    
    _craftingList = unsafeWindow.client.dataModel.model.craftinglist;
    if (_craftingList && _craftingList['craft_' + profName]) {
        _log('professionGetTaskList(): ' + profName + ' craftlist available.');
        _dfd.resolve(_craftingList['craft_' + profName]);
    } else {
        _log('professionGetTaskList(): ' + profName + '  craftlist not available, fetching.');
        unsafeWindow.client.professionFetchTaskList('craft_' + profName);
        WaitForProperty('unsafeWindow.client.dataModel.model.craftinglist["craft_' + profName + '"]')
        .then(function() {
            _log('professionGetTaskList(): done fetching ' + profName + ' craftlist');
            _dfd.resolve(unsafeWindow.client.dataModel.model.craftinglist['craft_' + profName]);
        });
    }
    return _dfd.promise();
}

// TO BE REPLACED
/**
     * Iterative approach to finding the next task to assign to an open slot.
     *
     * @param {Array} list The list of task names to try in order of precedence
     * @param {int} i The current attempt number. Will try to find the i'th task.
     */
function createNextTask(prof, i) {
    var _dfd = $.Deferred();
    
    // get tasks for current profession and rank
    var list = prof.level[ professionGetCurrentLevel(prof.taskName) ];
    
    if (list.length <= i) {
        _log("Nothing Found");
        _dfd.resolve();
    } else {
        var taskName = list[i];
        _log("createNextTask: searching for: " + list.length + ' ' + i + ' ' + taskName);
        professionGetCraftingList(prof.taskName)
        .then( function () {
            return searchForTask(taskName, prof.taskName);
        })
        .then( function (task) {
            if (task == null) {
                _log("Skipping task selection to purchase resources");
                _dfd.resolve();
            } else if (task) {
                _log('createNextTask: found: ' + task);
                
                // navigate to task details page
                unsafeWindow.location.hash = unsafeWindow.location.hash.replace(/\)\/.+/, ')' + '/professions-tasks/' + prof.taskName + '/' + task);
                WaitForElement("div.page-professions-taskdetails")
                .then(function () {
                    // fill optional slots if needed
                    return professionSlotItems(prof);
                })
                .then(function () {
                    // start the task
                    return professionStartTask();
                })
                .then(function() {
                    _dfd.resolve();
                });
                
            } else {
                _log('createNextTask: finding next task.');
                createNextTask(prof, i + 1)
                .then( function() { _dfd.resolve(); });
            }
        });
    }
    
    return _dfd.promise();
}

function pageCHARSELECT() {
    //select next character
    characterSwitch();
    dfdNextRun.resolve();
}

function pageFRONTPAGE() {
    // go to professions page
    _log('pageFRONTPAGE: switching to professions page.');
    $("a.professions").click();
    dfdNextRun.resolve();
}

function professionAssignTasks() {
    // returns true if work still needs to be done, false if finished
    var _assignments, _currentTasks, _craftingList, i;
    
    try {
        
        _assignments = unsafeWindow.client.dataModel.model.ent.main.itemassignments.assignments;
        
        // check if slots available
        if (!_assignments.filter(function (entry) {
            return (!entry.islockedslot && !entry.uassignmentid);
        }).length) {
            _log("professionAssignTasks(): No available task slots.");
            return false;
        }
        
        // Go through the professions to assign tasks until specified slots filled
        for (i = 0; i < taskList.length; i++) {
            _currentTasks = _assignments.filter(function (entry) {
                return entry.category == taskList[i].taskName;
            });
            if (_currentTasks.length < charSettings[characterGetCurrent()][taskList[i].taskName]) {
                _log(taskList[i].taskName + ': ' + _currentTasks.length + '/' + charSettings[characterGetCurrent()][taskList[i].taskName] + ' filled.');
                
                createNextTask(taskList[i], 0)
                .then( function() {
                    _log('createNextTask(): done.');
                    dfdNextRun.resolve();
                });
                return true;
            }
        }
        _log("professionAssignTasks(): All task counts assigned.");
        return false;
    } catch (e) { }
    // if we got here stuff is still loading, let's wait another cycle
    _log('professionAssignTasks(): assignments still loading, wating.');
    dfdNextRun.resolve();
    return true;
}


function pagePROFESSIONS() {
    
    WaitForElement("")
    .then(function () {
        return professionCollectRewards();
    })
    .then( function () {
        // assign tasks if slots available
        if (professionAssignTasks()) {
            //                dfdNextRun.resolve();
            return;
        }
        // switch characters
        characterSwitch();
    });
}

function pageLOGIN() {
    if (globalSettings.autologin === true) {
        _log('pageLOGIN: logging in.');
        $("input#user").val(globalSettings.nw_username);
        $("input#pass").val(globalSettings.nw_password);
        $("div#login > input").click();
    } else {
        _log('pageLOGIN: auto logging in disabled.');
    }
    dfdNextRun.resolve(delay.LONG);
}

/**
     * The main process loop:
     * - Determine which page we are on and call the page specific logic
     * - When processing is complete, process again later
     *   - Use a short timer when something changed last time through
     *   - Use a longer timer when waiting for tasks to complete
     */
function process() {
    _log('starting cycle');
    // Make sure our custom HTML is injected and working
    injectHTML();
    
    
    // Check if timer is paused
    if (globalSettings.paused) {
        // Just continue later - the deferred object is still set and nothing will resolve it until we get past this point
        _log('Paused...');
        // clear the timeout
        clearTimeout(timeoutHandle);
        window.setTimeout(function () {
            process();
        }, delay.DEFAULT);
        return;
    }
    
    
    // refine AD; page doesn't matter, no need to do resolve();
    utilRefineAD();
    
    switch (getCurrentPage()) {
            
        case pages.CHARSELECT:
            pageCHARSELECT();
            break;
        case pages.FRONTPAGE:
            pageFRONTPAGE();
            break;
        case pages.PROFESSIONS:
            pagePROFESSIONS();
            break;
        case pages.LOGIN:
            pageLOGIN();
            break;
        default:
            //                _log('No logic defined for current page, sleeping for: ' + delay.LONG + 'ms.');
            //                dfdNextRun.resolve(delay.LONG);
    }
    
    // Continue again later
    dfdNextRun.done(function (delayTimer) {
        var _cycleDelay = delayTimer || delay.DEFAULT;
        var _cycleTimeout = _cycleDelay + delay.TIMEOUT;
        
        _log('Scheduling next cycle in: ' + _cycleDelay + 'ms. Timeout in: ' + _cycleTimeout + 'ms.');
        
        dfdNextRun = $.Deferred();
        // cycle succeeded, clear the timeout
        clearTimeout(timeoutHandle);
        // setting timeout for next cycle
        timeoutHandle = setTimeout(function () {
            dfdNextRun.reject(_cycleTimeout);
        }, _cycleTimeout);
        
        // scheduling next cycle
        setTimeout(function () {
            process();
        }, _cycleDelay);
        
    }).fail(function (timeout) {
        _log('Last cycle took longer than: ' + timeout + 'ms. Reloading gateway...');
        unsafeWindow.location.href = "http://gateway.playneverwinter.com";
    });
}


// inject our HTML and schedule main loop
injectHTML();
_log('Script started.');
dfdNextRun.resolve();

setTimeout(function () {
    process();
}, delay.SHORT);
})();