Eternity Tower Inventory Search

Search your inventory!

当前为 2018-10-02 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name          Eternity Tower Inventory Search
// @icon          https://www.eternitytower.net/favicon.png
// @namespace     http://mean.cloud/
// @version       2.05
// @description   Search your inventory!
// @match         *://*.eternitytower.net/*
// @author        [email protected]
// @copyright     2017-2018, MeanCloud
// @run-at        document-end
// @grant         GM_getValue
// @grant         GM_setValue
// @grant         GM_addStyle
// ==/UserScript==


////////////////////////////////////////////////////////////////
////////////////// ** CUSTOM SCRIPT STYLES ** //////////////////
GM_addStyle(".PETS_ItemVal { color: black; font-weight: bold; font-style: italic; padding: -2px; max-height: 12px; height: 11px; text-align: center; border-radius: 4px; border: 1px solid black; position: absolute; line-height: 9px; top: -5px !important; left: 4px; font-size: 10px; width: 30px; text-overflow: wrap; display: inline-block; white-space: nowrap; }");
////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////
////////////// ** SCRIPT GLOBAL INITIALIZATION ** //////////////
function startup() { ETMod_InvSearch.load(); }
////////////////////////////////////////////////////////////////


// Note: can't declare this with 'var' or 'window.' etc. or TamperMonkey barfs on eval();
ETMod_InvSearch = 
{
    NeedResort: true,
    did_manual_sort: false,
    sort_scheduled: false,
    returned_to_crafting: false,
    Counter: 0,

    load: function()
    {
        //ET.MCMF.WantDebug = true;

        ET.MCMF.Ready(function()
        {
            // trigger a re-sort whenever the game modifies the items collection store
            Meteor.connection._stream.on('message', function(sMeteorRawData)
            {
                try
                {
                    if (oMeteorData.collection == "items")
                        if ((oMeteorData.msg == "added") || (oMeteorData.msg == "removed"))
                            ETMod_InvSearch.NeedResort = true;
                }
                catch (err) { }

                if ((ETMod_InvSearch.sort_scheduled === false) && (ETMod_InvSearch.did_manual_sort) && (ETMod_InvSearch.returned_to_crafting))
                {
                    if ((jQ("div.item-icon-container").length > 7) && (ETMod_InvSearch.NeedResort))
                    {
                        ETMod_InvSearch.sort_scheduled = true;
                        setTimeout(ETMod_InvSearch.Resort, 500);
                    }
                }
            });

            // trigger a re-sort whenever the game updates the item HTML list visually
            jQ('body').on('DOMNodeInserted', 'div.body-content > div:first-of-type > div:first-of-type div.flex-wrap', function(e)
            {
                var oElAdded = jQ(e.target).find("div.item-icon-container:nth-last-of-type(2)");
                var iItemID = oElAdded.attr("data-id");
                
                if ((iItemID === undefined) || (iItemID === null) || (iItemID.length !== 17)) return;

                if (jQ("#PETSAutoSort").is(":checked"))
                {
                    ETMod_InvSearch.NeedResort = true;
                    ETMod_InvSearch.did_manual_sort = true;
                }
            });

            // gather all of the crafting recipes and when we receive data, start the main loop
            ET.MCMF.CallGameCmd("crafting.fetchRecipes", function(err, res)
            {
                //console.log(JSON.stringify(jQ.makeArray(res)));
                
                ETMod_InvSearch.AllRecipes = res;
                
                ETMod_InvSearch.main();
            });
        });

        ET.MCMF.Loaded(function()
        {
        }, "ETSearch");
    },
    
    CompareTwoWords: function(word1, word2)
    {
        try
        {
            if (word1 > word2) return 1;
            if (word1 < word2) return -1;
        }
        catch (err) { }

        return 0;
    },

    GetMetaWord: function(word, item)
    {
        try
        {
            word = word.toString().trim().toLowerCase();
            item = item.toString().trim().toLowerCase();

            if (word === "fragment") return "magic";
            if (word === "shard") return "magic";
            if (word === "crystal") return "magic";

            if (word === "jade") return "jewel";
            if (word === "lazuli") return "jewel";
            if (word === "ruby") return "jewel";
            if (word === "sapphire") return "jewel";
            if (word === "tanzanite") return "jewel";
            if (word === "emerald") return "jewel";

            if (item === "baby fox") return "gear_special_chest";
            if (item === "opal chest plate") return "gear_special_chest";

            if (word === "dagger") return "gear_weapon_phys";
            if (word === "rapiers") return "gear_weapon_phys";
            if (word === "knife") return "gear_weapon_phys";
            if (word === "battleaxe") return "gear_weapon_phys";
            if (word === "sword") return "gear_weapon_phys";
            if (word === "spear") return "gear_weapon_phys";
            if (word === "scimitar") return "gear_weapon_phys";
            if (word === "tentacle") return "gear_weapon_phys";
            if (item.indexOf("bone kings axe") !== -1) return "gear_weapon_phys";
            if (item.indexOf("bisons axe") !== -1) return "gear_weapon_phys";
            if (item.indexOf("battle axe") !== -1) return "gear_weapon_phys";
            if (word === "shadow") return "gear_weapon_phys";
            if (word === "smoke") return "gear_weapon_phys";

            if (word === "staff") return "gear_weapon_mag";
            if (word === "trident") return "gear_weapon_mag";
            if (word === "wand") return "gear_weapon_mag";

            if (word === "helmet") return "gear_phys_head";
            if (word === "heart") return "gear_phys_chest";
            if (word === "chestplate") return "gear_phys_chest";
            if (item.indexOf("snake skin") !== -1) return "gear_phys_chest";
            if (word === "platelegs") return "gear_phys_leg";
            if (word === "shield") return "gear_phys_offhand";
            if (word === "buckler") return "gear_phys_offhand";

            if (item.indexOf("phoenix hat") !== -1) return "gear_mag_head";
            if (item.indexOf("druidic hat") !== -1) return "gear_heal_head";
            if (item.indexOf("wizard hat") !== -1) return "gear_mag_head";
            if (item.indexOf("druidic shirt") !== -1) return "gear_heal_chest";
            if (item.indexOf("wizard shirt") !== -1) return "gear_mag_chest";
            if (item.indexOf("druidic pants") !== -1) return "gear_heal_leg";
            if (item.indexOf("wizard shorts") !== -1) return "gear_mag_leg";

            if (item.indexOf("mining") !== -1) return "mining";
            if (item.indexOf(" pickaxe") !== -1) return "mining";
            if (item.indexOf(" idol") !== -1) return "mining";

            if (word === "hammer") return "gear_weapon_mag";

            if (item.indexOf("pigment") !== -1) return "pigment";

            if (item.indexOf("seed") !== -1) return "farm_seed";

            if (item.indexOf("lettuce") !== -1) return "food";
            if (item.indexOf("lemon") !== -1) return "food"; // includes 'lemonade'
            if (item.indexOf("fruit") !== -1) return "food"; // grapefruit, dragonfruit, rockmelon
            if (item.indexOf("apple") !== -1) return "food"; // includes 'pineapple'
            if (item.indexOf("pear") !== -1) return "food";
            if (item.indexOf("melon") !== -1) return "food"; // watermelon
            if (item.indexOf("potato") !== -1) return "food"; // includes 'sweet potato'
            if (item.indexOf("carrot") !== -1) return "food";
            if (item.indexOf("acai") !== -1) return "food";
            if (item.indexOf("orange") !== -1) return "food"; // to avoid conflict with wizard gear, this logic comes late
            if (item.indexOf("banana") !== -1) return "food";
            if (item.indexOf("honey") !== -1) return "food";

            if (item.indexOf("rubia") !== -1) return "farm_herb";
            if (item.indexOf("basil") !== -1) return "farm_herb";
            if (item.indexOf("pink rose") !== -1) return "farm_herb";
            if (item.indexOf("endive") !== -1) return "farm_herb";
            if (item.indexOf("juniper") !== -1) return "farm_herb";
            if (item.indexOf("agrimony") !== -1) return "farm_herb";
            if (item.indexOf("chili") !== -1) return "farm_herb";
            if (item.indexOf("celery") !== -1) return "farm_herb";
            if (item.indexOf("feverfew") !== -1) return "farm_herb";
            if (item.indexOf("catnip") !== -1) return "farm_herb";
            if (item.indexOf("nasturtium") !== -1) return "farm_herb";
            if (item.indexOf("lavender") !== -1) return "farm_herb";
            if (item.indexOf("catnip") !== -1) return "farm_herb";
            if (item.indexOf("sorrell") !== -1) return "farm_herb";
            if (item.indexOf("chives") !== -1) return "farm_herb";
            if (item.indexOf("cardoon") !== -1) return "farm_herb";

            if (item.indexOf(" seed") === -1)
            {
                if (item.indexOf("marigold") !== -1) return "aaa_money";
                if (item.indexOf("blue rose") !== -1) return "aaa_money";
                if (item.indexOf("chrysanthemum") !== -1) return "aaa_money";
                if (item.indexOf("hydrangea") !== -1) return "aaa_money";
                if (item.indexOf("poppy") !== -1) return "aaa_money";
                if (item.indexOf("zinnia") !== -1) return "aaa_money";
            }
            if (item == "gold crown") return "aaa_money";
            if (item.indexOf("polished ") !== -1) return "aaa_money";
            if (item.indexOf(" sculpture") !== -1) return "aaa_money";

            if (item == "enhancer key") return "aaa_usable";
            if (item.indexOf("codex of ") !== -1) return "aaa_usable";

            if (item == "bamboo") return "farm_other";
            if (item == "cactus") return "farm_other";
            if (item == "reed") return "farm_other";
            if (item == "papyrus") return "farm_other";
            if (item == "palm") return "farm_other";
            if (item == "kenaf") return "farm_other";

            if (item.indexOf("_log") !== -1) return "wood";

            if (item.indexOf("tome") !== -1) return "tome";
            if (item.indexOf(" lv. 1") !== -1) return "tome";
            if (item.indexOf(" lv. 2") !== -1) return "tome";
            if (item.indexOf(" lv. 3") !== -1) return "tome";
            if (item.indexOf(" lv. 4") !== -1) return "tome";
            if (item.indexOf(" lv. 5") !== -1) return "tome";
            if (word === "affliction") return "tome";
            if (word === "lightning_speed") return "tome";
            if (word === "wisdom") return "tome";

            if (item == "stone") return "ore_basic";
            if (item == "coal") return "ore_basic";
            if (item == "copper") return "ore_raw01";
            if (item == "tin") return "ore_raw02";
            if (item == "bronze") return "ore_raw03";
            if (item == "iron") return "ore_raw04";
            if (item == "silver") return "ore_raw05";
            if (item == "gold") return "ore_raw06";
            if (item == "carbon") return "ore_raw07";
            if (item == "steel") return "ore_raw08";
            if (item == "platinum") return "ore_raw09";
            if (item == "titanium") return "ore_raw10";
            if (item == "tungsten") return "ore_raw11";
            if (item == "obsidian") return "ore_raw12";
            if (item == "cobalt") return "ore_raw13";
            if (item == "mithril") return "ore_raw14";
            if (item == "adamantium") return "ore_raw15";
            if (item == "orichalcum") return "ore_raw16";
            if (item == "meteorite") return "ore_raw17";
            if (item == "fairy steel") return "ore_raw18";
            if (item == "elven steel") return "ore_raw19";
            if (item == "cursed ore") return "ore_raw20";
            if (item == "radiant ore") return "ore_raw21";
            if (item == "astral ore") return "ore_raw22";

            if (item == "copper bar") return "ore_refined01";
            if (item == "tin bar") return "ore_refined02";
            if (item == "bronze bar") return "ore_refined03";
            if (item == "iron bar") return "ore_refined04";
            if (item == "silver bar") return "ore_refined05";
            if (item == "gold bar") return "ore_refined06";
            if (item == "carbon bar") return "ore_refined07";
            if (item == "steel bar") return "ore_refined08";
            if (item == "platinum bar") return "ore_refined09";
            if (item == "titanium bar") return "ore_refined10";
            if (item == "tungsten bar") return "ore_refined11";
            if (item == "obsidian bar") return "ore_refined12";
            if (item == "cobalt bar") return "ore_refined13";
            if (item == "mithril bar") return "ore_refined14";
            if (item == "adamantium bar") return "ore_refined15";
            if (item == "orichalcum bar") return "ore_refined16";
            if (item == "meteorite bar") return "ore_refined17";
            if (item == "fairy steel bar") return "ore_refined18";
            if (item == "elven steel bar") return "ore_refined19";
            if (item == "cursed bar") return "ore_refined20";
            if (item == "radiant bar") return "ore_refined21";
            if (item == "astral bar") return "ore_refined22";

            if (item == "silver essence") return "ore_essence05";
            if (item == "gold essence") return "ore_essence06";
            if (item == "carbon essence") return "ore_essence07";
            if (item == "steel essence") return "ore_essence08";
            if (item == "platinum essence") return "ore_essence09";
            if (item == "titanium essence") return "ore_essence10";
            if (item == "tungsten essence") return "ore_essence11";
            if (item == "obsidian essence") return "ore_essence12";
            if (item == "cobalt essence") return "ore_essence13";
            if (item == "mithril essence") return "ore_essence14";
            if (item == "adamantium essence") return "ore_essence15";
            if (item == "orichalcum essence") return "ore_essence16";
            if (item == "meteorite essence") return "ore_essence17";
            if (item == "fairy steel essence") return "ore_essence18";
            if (item == "elven steel essence") return "ore_essence19";
            if (item == "cursed essence") return "ore_essence20";
            if (item == "radiant essence") return "ore_essence21";
            if (item == "astral essence") return "ore_essence22";

            if (item == "lost silver scroll") return "ore_scroll05";
            if (item == "lost gold scroll") return "ore_scroll06";
            if (item == "lost carbon scroll") return "ore_scroll07";
            if (item == "lost steel scroll") return "ore_scroll08";
            if (item == "lost platinum scroll") return "ore_scroll09";
            if (item == "lost titanium scroll") return "ore_scroll10";
            if (item == "lost tungsten scroll") return "ore_scroll11";
            if (item == "lost obsidian scroll") return "ore_scroll12";
            if (item == "lost cobalt scroll") return "ore_scroll13";
            if (item == "lost mithril scroll") return "ore_scroll14";
            if (item == "lost adamantium scroll") return "ore_scroll15";
            if (item == "lost orichalcum scroll") return "ore_scroll16";
            if (item == "lost meteorite scroll") return "ore_scroll17";
            if (item == "lost fairy steel scroll") return "ore_scroll18";
            if (item == "lost elven steel scroll") return "ore_scroll19";
            if (item == "lost cursed scroll") return "ore_scroll20";
            if (item == "lost radiant scroll") return "ore_scroll21";
            if (item == "lost astral scroll") return "ore_scroll22";

            if (item == "pine log") return "wood_raw00";
            if (item == "beech log") return "wood_raw01";
            if (item == "ash log") return "wood_raw02";
            if (item == "oak log") return "wood_raw03";
            if (item == "maple log") return "wood_raw04";
            if (item == "walnut log") return "wood_raw05";
            if (item == "cherry log") return "wood_raw06";
            if (item == "mahogany log") return "wood_raw07";
            if (item == "elk log") return "wood_raw08";
            if (item == "elm log") return "wood_raw08";
            if (item == "black log") return "wood_raw09";
            if (item == "blue gum log") return "wood_raw10";
            if (item == "cedar log") return "wood_raw11";
            if (item == "denya log") return "wood_raw12";
            if (item == "gombe log") return "wood_raw13";
            if (item == "hickory log") return "wood_raw14";
            if (item == "larch log") return "wood_raw15";
            if (item == "poplar log") return "wood_raw16";
            if (item == "tali log") return "wood_raw17";
            if (item == "willow log") return "wood_raw18";
            if (item == "teak log") return "wood_raw19";

            if (item == "pine paper") return "wood_paper00";
            if (item == "beech paper") return "wood_paper01";
            if (item == "ash paper") return "wood_paper02";
            if (item == "oak paper") return "wood_paper03";
            if (item == "maple paper") return "wood_paper04";
            if (item == "walnut paper") return "wood_paper05";
            if (item == "cherry paper") return "wood_paper06";
            if (item == "mahogany paper") return "wood_paper07";
            if (item == "elk paper") return "wood_paper08";
            if (item == "elm paper") return "wood_paper08";
            if (item == "black paper") return "wood_paper09";
            if (item == "blue gum paper") return "wood_paper10";
            if (item == "cedar paper") return "wood_paper11";
            if (item == "denya paper") return "wood_paper12";
            if (item == "gombe paper") return "wood_paper13";
            if (item == "hickory paper") return "wood_paper14";
            if (item == "larch paper") return "wood_paper15";
            if (item == "poplar paper") return "wood_paper16";
            if (item == "tali paper") return "wood_paper17";
            if (item == "willow paper") return "wood_paper18";
            if (item == "teak paper") return "wood_paper19";

            if (item == "pine book") return "wood_book00";
            if (item == "beech book") return "wood_book01";
            if (item == "ash book") return "wood_book02";
            if (item == "oak book") return "wood_book03";
            if (item == "maple book") return "wood_book04";
            if (item == "walnut book") return "wood_book05";
            if (item == "cherry book") return "wood_book06";
            if (item == "mahogany book") return "wood_book07";
            if (item == "elk book") return "wood_book08";
            if (item == "elm book") return "wood_book08";
            if (item == "black book") return "wood_book09";
            if (item == "blue gum book") return "wood_book10";
            if (item == "cedar book") return "wood_book11";
            if (item == "denya book") return "wood_book12";
            if (item == "gombe book") return "wood_book13";
            if (item == "hickory book") return "wood_book14";
            if (item == "larch book") return "wood_book15";
            if (item == "poplar book") return "wood_book16";
            if (item == "tali book") return "wood_book17";
            if (item == "willow book") return "wood_book18";
            if (item == "teak book") return "wood_book19";
        }
        catch (err) { }

        return "";
    },

    MatchRecipe: function(recipe_name)
    {
        ETMod_InvSearch.RecipeResult = undefined;
        
        try
        {
            ETMod_InvSearch.RecipeSearch = recipe_name.trim().toLowerCase();
            
            if (ETMod_InvSearch.RecipeSearch == "lost silver scroll") ETMod_InvSearch.RecipeSearch = "silver essence";
            if (ETMod_InvSearch.RecipeSearch == "lost gold scroll") ETMod_InvSearch.RecipeSearch = "gold essence";
            if (ETMod_InvSearch.RecipeSearch == "lost carbon scroll") ETMod_InvSearch.RecipeSearch = "carbon essence";
            if (ETMod_InvSearch.RecipeSearch == "lost steel scroll") ETMod_InvSearch.RecipeSearch = "steel essence";
            if (ETMod_InvSearch.RecipeSearch == "lost platinum scroll") ETMod_InvSearch.RecipeSearch = "platinum essence";
            if (ETMod_InvSearch.RecipeSearch == "lost titanium scroll") ETMod_InvSearch.RecipeSearch = "titanium essence";
            if (ETMod_InvSearch.RecipeSearch == "lost tungsten scroll") ETMod_InvSearch.RecipeSearch = "tungsten essence";
            if (ETMod_InvSearch.RecipeSearch == "lost obsidian scroll") ETMod_InvSearch.RecipeSearch = "obsidian essence";
            if (ETMod_InvSearch.RecipeSearch == "lost cobalt scroll") ETMod_InvSearch.RecipeSearch = "cobalt essence";
            if (ETMod_InvSearch.RecipeSearch == "lost mithril scroll") ETMod_InvSearch.RecipeSearch = "mithril essence";
            if (ETMod_InvSearch.RecipeSearch == "lost adamantium scroll") ETMod_InvSearch.RecipeSearch = "adamantium essence";
            if (ETMod_InvSearch.RecipeSearch == "lost orichalcum scroll") ETMod_InvSearch.RecipeSearch = "orichalcum essence";
            if (ETMod_InvSearch.RecipeSearch == "lost meteorite scroll") ETMod_InvSearch.RecipeSearch = "meteorite essence";
            if (ETMod_InvSearch.RecipeSearch == "lost fairy steel scroll") ETMod_InvSearch.RecipeSearch = "fairy steel essence";
            if (ETMod_InvSearch.RecipeSearch == "lost elven steel scroll") ETMod_InvSearch.RecipeSearch = "elven steel essence";
            if (ETMod_InvSearch.RecipeSearch == "lost cursed scroll") ETMod_InvSearch.RecipeSearch = "cursed essence";
            if (ETMod_InvSearch.RecipeSearch == "lost radiant scroll") ETMod_InvSearch.RecipeSearch = "radiant essence";
            if (ETMod_InvSearch.RecipeSearch == "lost astral scroll") ETMod_InvSearch.RecipeSearch = "astral essence";
            
            if (ETMod_InvSearch.AllRecipes !== undefined)
            {
                jQ.makeArray(ETMod_InvSearch.AllRecipes).forEach(function(currentRecipe, index, array)
                {
                    if (ETMod_InvSearch.RecipeResult !== undefined) return;
                    if (currentRecipe.name.trim().toLowerCase() === ETMod_InvSearch.RecipeSearch)
                        ETMod_InvSearch.RecipeResult = currentRecipe;
                });
            }
        }
        catch (err) { }
        
        return ETMod_InvSearch.RecipeResult;
    },

    Resort: function()
    {
        ETMod_InvSearch.NeedResort = false;

        var itemContainer = jQ(jQ("div.item-icon-container").get(0)).parent();
        var items = itemContainer.find("div.item-icon-container");

        items.sort(function(a,b)
        {
            var sTextThisItem_a;
            var sTextThisItem_b;

            try
            {
                var oActualItemData_a = ET.MCMF.GetItem(jQ(a).attr("data-id"));
                var oActualItemData_b = ET.MCMF.GetItem(jQ(b).attr("data-id"));

                sTextThisItem_a = undefined; try { sTextThisItem_a = oActualItemData_a.name.trim().toLowerCase(); } catch (err) { }
                sTextThisItem_b = undefined; try { sTextThisItem_b = oActualItemData_b.name.trim().toLowerCase(); } catch (err) { }

                if ((sTextThisItem_a !== undefined) && (sTextThisItem_b !== undefined))
                {
                    //console.log(a._tippy);

                    var oRecipe_a = ETMod_InvSearch.MatchRecipe(oActualItemData_a.name);
                    var oRecipe_b = ETMod_InvSearch.MatchRecipe(oActualItemData_b.name);
                    var sLastWord_a = "";
                    var sLastWord_b = "";
                    var sRest_a = "";
                    var sRest_b = "";
                    
                    if (sTextThisItem_a.indexOf("(E)") !== -1) sTextThisItem_a = ChopperBlank(sTextThisItem_a, "", "(E)").trim();
                    if (sTextThisItem_b.indexOf("(E)") !== -1) sTextThisItem_b = ChopperBlank(sTextThisItem_b, "", "(E)").trim();

                    if (sTextThisItem_a.indexOf(" ") !== -1)
                    {
                        sLastWord_a = sTextThisItem_a.split(" ").splice(-1).toString().trim().toLowerCase();
                        sRest_a = ChopperBlank(sTextThisItem_a, "", " " + sLastWord_a);
                    }
                    else
                    {
                        sLastWord_a = sTextThisItem_a.toString().trim().toLowerCase();
                        sRest_a = "";
                    }

                    if (sTextThisItem_b.indexOf(" ") !== -1)
                    {
                        sLastWord_b = sTextThisItem_b.split(" ").splice(-1).toString().trim().toLowerCase();
                        sRest_b = ChopperBlank(sTextThisItem_b, "", " " + sLastWord_b);
                    }
                    else
                    {
                        sLastWord_b = sTextThisItem_b.toString().trim().toLowerCase();
                        sRest_b = "";
                    }

                    if (sLastWord_a === "") sLastWord_a = "zzzzz";
                    if (sLastWord_b === "") sLastWord_b = "zzzzz";

                    var sQual_a = 0; try { sQual_a = parseInt(ChopperBlank(jQ(a).find("div.item-quality").text(), "", "%")); } catch (err) { }
                    var sQual_b = 0; try { sQual_b = parseInt(ChopperBlank(jQ(b).find("div.item-quality").text(), "", "%")); } catch (err) { }

                    // meta words (more grouping!)
                    var sMetaWord_a = ETMod_InvSearch.GetMetaWord(sLastWord_a, sTextThisItem_a);
                    var sMetaWord_b = ETMod_InvSearch.GetMetaWord(sLastWord_b, sTextThisItem_b);

                    if (sMetaWord_a.startsWith("ore_scroll"))
                        if ((oRecipe_a !== undefined) && (oRecipe_a !== null))
                            sMetaWord_a = "aaa_money";
                    
                    if (sMetaWord_b.startsWith("ore_scroll"))
                        if ((oRecipe_b !== undefined) && (oRecipe_b !== null))
                            sMetaWord_b = "aaa_money";                
                    
                    //if ((sLastWord_a === "") || (sLastWord_b === ""))
                    //	console.log("Item '" + sRest_a + "' '" + sLastWord_a + "' ('" + sMetaWord_a + "') vs '" + sRest_b + "' '" + sLastWord_b + "' ('" + sMetaWord_b + "')");

                    if ((sMetaWord_a !== "") && (sMetaWord_b === "")) return -1;
                    if ((sMetaWord_a === "") && (sMetaWord_b !== "")) return 1;

                    if ((sMetaWord_a !== "") && (sMetaWord_b !== ""))
                    {
                        ret = ETMod_InvSearch.CompareTwoWords(sMetaWord_a, sMetaWord_b);
                        if (ret !== 0)
                            return ret;

                        if (sMetaWord_a === "pigment") return ETMod_InvSearch.CompareTwoWords(sTextThisItem_a, sTextThisItem_b);
                        if (sMetaWord_a === "tome") return ETMod_InvSearch.CompareTwoWords(sTextThisItem_a, sTextThisItem_b);
                    }

                    var ret = ETMod_InvSearch.CompareTwoWords(sLastWord_a, sLastWord_b);
                    if (ret !== 0)
                        return ret;

                    if ((sRest_a !== "") && (sRest_b !== ""))
                    {
                        ret = ETMod_InvSearch.CompareTwoWords(sRest_a, sRest_b);
                        if (ret !== 0)
                            return ret;
                    }

                    // descending order on quality
                    if ((sQual_a + sQual_b) > 0)
                    {
                        if (sQual_a > sQual_b) return -1;
                        if (sQual_a < sQual_b) return 1;
                    }

                    return ETMod_InvSearch.CompareTwoWords(sTextThisItem_a, sTextThisItem_b);
                }
            }
            catch (err) { console.log("Error: " + err); }

            if (sTextThisItem_a === undefined) return 1;
            if (sTextThisItem_b === undefined) return -1;
            return 0;
        });

        items.detach().appendTo(itemContainer);

        /* items.each(function()
        {
            console.log(ChopperBlank(this._tippy.popper.innerHTML, "popover-title text-capitalize\">", "</h3>").trim());
        }); */

        ETMod_InvSearch.sort_scheduled = false;
        //alert("Sorted!");
    },

    main: function()
    {
        if (window.location.href.indexOf("/crafting") !== -1)
        {
            var oJQ_Options;

            if (!ETMod_InvSearch.returned_to_crafting)
            {
                ETMod_InvSearch.did_manual_sort = false;
                ETMod_InvSearch.returned_to_crafting = true;

                if (jQ("#PETSAutoSort").is(":checked"))
                {
                    ETMod_InvSearch.NeedResort = true;
                    ETMod_InvSearch.did_manual_sort = true;
                }
            }

            try
            {
                oJQ_Options = jQ("div#PETSOptionsEl").get(0);

                if (oJQ_Options === undefined)
                {
                    jQ("div.flex-grow h2:first-of-type").after("<div id=\"PETSOptionsEl\"><small class=\"mx-2\">" +
                        "Search: <input type=\"text\" id=\"PETSSearchEl\" style=\"width: 200px;\" placeholder=\"search/filter by typing here\" value=\"" + (GM_getValue("PETSearch_Text") || "") + "\" /> &nbsp; " +
                        /* "<label for=\"PETSItemText\">item labels</label> <input type=\"checkbox\" id=\"PETSItemText\"" + ((GM_getValue("PETSearch_Labels")) ? " CHECKED" : "") + "> &nbsp; " + */
                        /* "<label for=\"PETSAutoExpand\">auto-expand</label> <input type=\"checkbox\" id=\"PETSAutoExpand\"" + ((GM_getValue("PETSearch_AutoExpand")) ? " CHECKED" : "") + "> &nbsp;" + */
                        "<label for=\"PETSAutoSort\">auto-sort</label> <input type=\"checkbox\" id=\"PETSAutoSort\"" + ((GM_getValue("PETSearch_AutoSort")) ? " CHECKED" : "") + "> &nbsp;" +
                        /* "<input type=\"button\" value=\"Sort\" onClick=\"javascript:PETSearch_did_manual_sort=true;PETSearch_Resort();\">" + */
                        "<label for=\"PETSItemColor\">colorize</label> <input type=\"checkbox\" id=\"PETSItemColor\"" + ((GM_getValue("PETSearch_Color")) ? " CHECKED" : "") + "> &nbsp; " +
                        "<select id=\"PETSFilterList\"><option default selected value=\"0\">All Items<option value=\"1\">Equipment<option value=\"2\">Axes &amp; Mining<option value=\"999\">Other</select>" + 
                        "</small><br /><br /></div>");
                    jQ("#PETSSearchEl").focus();
                    try
                    {
                        jQ("#PETSSearchEl").get(0).selectionStart = 10000;
                        jQ("#PETSSearchEl").get(0).selectionEnd = 10000;
                    }
                    catch (errSel) { }

                    jQ("#PETSAutoSort").change(function()
                    {
                        GM_setValue("PETSearch_AutoSort", jQ("#PETSAutoSort").is(":checked"));
                        ETMod_InvSearch.NeedResort = jQ("#PETSAutoSort").is(":checked");
                        ETMod_InvSearch.did_manual_sort = jQ("#PETSAutoSort").is(":checked");
                    });
                    
                    /* jQ("#PETSItemText").change(function() {
                        jQ("div.item-icon-container").each(function() {
                            try { jQ(this).attr("PETS_Modified", ""); } catch (err) { } }); }); */
                       
                    jQ("#PETSItemColor").change(function() {
                        jQ("div.item-icon-container").each(function() {
                            try { jQ(this).attr("PETS_Modified", ""); } catch (err) { } }); });                        
                            
                    jQ("#PETSFilterList").change(function() {
                        jQ("div.item-icon-container").each(function() {
                            try { jQ(this).attr("PETS_Modified", ""); } catch (err) { } }); });
                }
                else
                {
                    GM_setValue("PETSearch_Text",       jQ("#PETSSearchEl")  .val());
                    GM_setValue("PETSearch_Color",     jQ("#PETSItemColor")  .is(":checked")); 
                    //GM_setValue("PETSearch_Labels",     jQ("#PETSItemText")  .is(":checked")); 
                    //GM_setValue("PETSearch_AutoExpand", jQ("#PETSAutoExpand").is(":checked"));
                    GM_setValue("PETSearch_AutoSort",   jQ("#PETSAutoSort").  is(":checked"));
                }
            }
            catch (err) { console.log(err); }

            if (true /* &&  jQ("#PETSAutoExpand").is(":checked") */) jQ("div.show-all-items").click();
            //if (!jQ("#PETSItemText")  .is(":checked")) jQ(".PETS_SearchTag").remove();

            /*
            if (ETMod_InvSearch.sort_scheduled === false)
            {
                if (jQ("div.item-icon-container").length === 7)
                    ETMod_InvSearch.NeedResort = true;

                if ((jQ("div.item-icon-container").length > 7) && (ETMod_InvSearch.NeedResort))
                {
                    ETMod_InvSearch.sort_scheduled = true;
                    setTimeout(ETMod_InvSearch.Resort, 500);
                }
            }
            */

            var sTextToSearch = jQ("#PETSSearchEl").val().trim().toLowerCase();
            while (sTextToSearch.indexOf(" ") !== -1)
                sTextToSearch = sTextToSearch.replace(" ", "");
            
            var bDidAny = false;
            jQ("div.item-icon-container").each(function()
            {
                try
                {
                    var oThisItemEl = jQ(this);
                    
                    if (oThisItemEl.attr("data-id").length != 17) return;
                    
                    //var oActualItemData = ET.MCMF.ItemManager._collection._docs._map[oThisItemEl.attr("data-id")];
                    var oActualItemData = ET.MCMF.GetItem(oThisItemEl.attr("data-id"));
                    if (oActualItemData === undefined) return;
                    if (CInt(oActualItemData.amount) === 0) oActualItemData.amount = 1; // if the property is missing, then assume we only have 1 of this item
                    var sTextThisItem = oActualItemData.name.trim().toLowerCase();
                    var oRecipe = ETMod_InvSearch.MatchRecipe(sTextThisItem);
                    var sLastWord = "";

                    //sTextThisItem = undefined; try { sTextThisItem = oActualItemData.name.trim().toLowerCase(); } catch (err) { }

                    if (sTextThisItem.indexOf("(E)") !== -1) sTextThisItem = ChopperBlank(sTextThisItem, "", "(E)").trim();

                    if (sTextThisItem.indexOf(" ") !== -1)
                        sLastWord = sTextThisItem.split(" ").splice(-1).toString().trim().toLowerCase();
                    else
                        sLastWord = sTextThisItem.toString().trim().toLowerCase();

                    if (sLastWord === "") sLastWord = "zzzzz";

                    var sMetaWord = ETMod_InvSearch.GetMetaWord(sLastWord, sTextThisItem);
                    
                    var bWantToDecorate = (CInt(oThisItemEl.attr("PETS_Modified")) !== 1);
                    bWantToDecorate = bWantToDecorate || (CInt(oActualItemData.amount)  !== CInt(oThisItemEl.attr("PETS_LastQty")));
                    bWantToDecorate = bWantToDecorate || (CInt(oActualItemData.quality) !== CInt(oThisItemEl.attr("PETS_LastPct")));
                    
                    if (sMetaWord.startsWith("ore_scroll"))
                        if ((oRecipe !== undefined) && (oRecipe !== null))
                            sMetaWord = "aaa_money";
                    
                    if (bWantToDecorate)
                    {                
                        //ET.MCMF.Log(JSON.stringify(oActualItemData));
                    
                        oThisItemEl.attr("PETS_Modified", "1");
                        
                        if ((sMetaWord === "aaa_money") && (CInt(oActualItemData.amount) <= 1) && (oThisItemEl.find("div.item-amount").length === 0))
                            oThisItemEl.append("<div class=\"item-amount\" style=\"color: rgb(255, 255, 255); background-color: rgb(51, 51, 51); width: 55px;\">1</div>");                    
                        
                        // Colorize item background
                        if (jQ("#PETSItemColor").is(":checked"))
                        {
                            if (oActualItemData.isEquippable === true)  oThisItemEl.css("background-color", "#ffc");
                            else if (CInt(oActualItemData.quality) > 0) oThisItemEl.css("background-color", "#dde7ff");
                            else if (sMetaWord === "food")              oThisItemEl.css("background-color", "#fee");
                            else if (sMetaWord === "aaa_money")         oThisItemEl.css("background-color", "#fda");
                            else if (sMetaWord === "aaa_usable")        oThisItemEl.css("background-color", "#df9");
                        }
                        else
                            oThisItemEl.css("background-color", "");
                        
                        // Reposition quantity/quality text as bubbles in the upper-left
                        oThisItemEl.children("div.item-amount") .removeClass().addClass("PETS_ItemVal").addClass("item-amount");
                        oThisItemEl.children("div.item-quality").removeClass().addClass("PETS_ItemVal").addClass("item-quality");
                        
                        // Add some space between rows so the quantity/quality text bubbles don't overlap the prior row's item boxes
                        oThisItemEl.css("margin-bottom", "5px");
                        
                        // Calculate bubble text and color
                        var sBubbleText = "";
                        var sBubbleColor = "#e7e7e7";
                        
                        if (CInt(oActualItemData.quality) > 0)
                        {
                            sBubbleText = CInt(oActualItemData.quality).toString() + "%";
                            if (CInt(oActualItemData.quality) >= 85)
                                sBubbleColor = "#c9f";
                            else if (CInt(oActualItemData.quality) >= 65)
                                sBubbleColor = "#afa";
                            else if (CInt(oActualItemData.quality) >= 40)
                                sBubbleColor = "#ffa";
                            else
                                sBubbleColor = "#faa";
                        }
                        else if (CInt(oActualItemData.amount) > 1)
                            sBubbleText = ETMod_InvSearch.FriendlyNumber(CInt(oActualItemData.amount));
                        
                        if (sMetaWord === "aaa_money")
                            sBubbleText = ETMod_InvSearch.FriendlyNumber(CInt(oActualItemData.amount) * CInt(oActualItemData.sellPrice)) + "&nbsp;&nbsp;&nbsp;<img src=\"/icons/goldCoin.svg\" class=\"extra-small-icon\" style=\"position: absolute; top: -3px; right: 1px; margin: 0px; padding: 0px; width: 15px; height: 15px;\">";

                        // Tag with last quantity/percentage -- if it changes, we need to redecorate manually
                        oThisItemEl.attr("PETS_LastQty", CInt(oActualItemData.amount) .toString());
                        oThisItemEl.attr("PETS_LastPct", CInt(oActualItemData.quality).toString());

                        
                        if (sBubbleText.length > 0)
                        {
                            oThisItemEl.children("div.PETS_ItemVal").html(sBubbleText);
                            
                            if (sMetaWord !== "aaa_money")
                            {
                                oThisItemEl.children("div.PETS_ItemVal").css("color", "#000");
                                oThisItemEl.children("div.PETS_ItemVal").css("background-color", sBubbleColor);
                            }
                            else
                            {
                                oThisItemEl.children("div.PETS_ItemVal").css("color", "#fff");
                                oThisItemEl.children("div.PETS_ItemVal").css("background-color", "#333");
                                oThisItemEl.children("div.PETS_ItemVal").css("width", "55px");
                            }
                        }
                        
                        // Add item name/label
                        if (true /* && jQ("#PETSItemText").is(":checked") */)
                        {
                            //oThisItemEl.css("height", "76px");

                            //oThisItemEl.children("img.item-icon").css("height", "32px");
                            //oThisItemEl.children("img.item-icon").css("width", "32px");
                            //oThisItemEl.children("img.item-icon").css("margin-bottom", "10px");

                            /* if (oThisItemEl.children(".PETS_SearchTag").length === 0)
                                oThisItemEl.append("<div class=\"PETS_SearchTag\" style=\"position: absolute; bottom: 10px; right: 3px; font-size: 10px; max-width: 54px; text-overflow: hidden; display: inline-block; white-space: nowrap; overflow: hidden;\"><i>" + sTextThisItem + "</i></div>"); */

                            if (oThisItemEl.children(".PETS_SearchTag").length === 0)
                                oThisItemEl.append("<div class=\"PETS_SearchTag\" style=\"position: absolute; bottom: 0px; right: 3px; font-size: 10px; max-width: 54px; text-overflow: hidden; display: inline-block; white-space: nowrap; overflow: hidden;\"><i>" + sTextThisItem + "</i></div>");
                            
                            /* if (oThisItemEl.children(".PETS_SearchTag").length === 0)
                                oThisItemEl.append("<div class=\"PETS_SearchTag\" style=\"position: absolute; bottom: 10px; right: 3px; font-size: 10px; max-width: 54px; text-overflow: wrap; display: inline-block;\"><i>" + sTextThisItem + "</i></div>"); */
                        }
                        else
                        {
                            //oThisItemEl.css("height", "64px");

                            //oThisItemEl.children("img.item-icon").css("height", "36px");
                            //oThisItemEl.children("img.item-icon").css("width", "36px");
                            //oThisItemEl.children("img.item-icon").css("margin-bottom", "");
                        }
                        
                        oThisItemEl.children("img.item-icon").css("height", "48px");
                        oThisItemEl.children("img.item-icon").css("width", "48px");
                        oThisItemEl.children("img.item-icon").css("margin", "0px");
                        oThisItemEl.children("img.item-icon").css("margin-top", "-8px");
                        oThisItemEl.children("img.item-icon").css("margin-bottom", "");

                        /* oThisItemEl.hover
                        (
                            function()
                            {
                                oThisItemEl.children("img.item-icon").css("height", "80px");
                                oThisItemEl.children("img.item-icon").css("width", "80px");
                                oThisItemEl.children("img.item-icon").css("margin", "0px");
                                oThisItemEl.children("img.item-icon").css("margin-top", "-2px");
                                oThisItemEl.children("img.item-icon").css("margin-bottom", "");
                                oThisItemEl.css("border-width", "3px");
                                //oThisItemEl.children("div").hide();
                                oThisItemEl.children("div.PETS_SearchTag").hide();
                            },
                            
                            function()
                            {
                                oThisItemEl.css("border-width", "");
                                //oThisItemEl.children("div").show();
                                oThisItemEl.children("div.PETS_SearchTag").show();
                                oThisItemEl.children("img.item-icon").css("margin", "");

                                if (true && jQ("#PETSItemText").is(":checked"))
                                {
                                    //oThisItemEl.css("height", "76px");

                                    oThisItemEl.children("img.item-icon").css("height", "32px");
                                    oThisItemEl.children("img.item-icon").css("width", "32px");
                                    oThisItemEl.children("img.item-icon").css("margin-bottom", "10px");

                                    if (oThisItemEl.children(".PETS_SearchTag").length === 0)
                                        oThisItemEl.append("<div class=\"PETS_SearchTag\" style=\"position: absolute; bottom: 10px; right: 3px; font-size: 10px; max-width: 54px; text-overflow: hidden; display: inline-block; white-space: nowrap; overflow: hidden;\">" + sTextThisItem + "</div>");
                                }
                                else
                                {
                                    //oThisItemEl.css("height", "64px");

                                    oThisItemEl.children("img.item-icon").css("height", "36px");
                                    oThisItemEl.children("img.item-icon").css("width", "36px");
                                    oThisItemEl.children("img.item-icon").css("margin-bottom", "");
                                }
                            }
                        ); */
                        
                        
                        oThisItemEl.hover
                        (
                            function()
                            {
                                oThisItemEl.css("border-width", "3px");
                            },
                            
                            function()
                            {
                                oThisItemEl.css("border-width", "");
                            }
                        );
                        
                        bDidAny = true;
                    }
                    
                    var sTextThisItem_temp = sTextThisItem;
                    while (sTextThisItem_temp.indexOf(" ") !== -1) sTextThisItem_temp = sTextThisItem_temp.replace(" ", "");
                    sTextThisItem_temp = sTextThisItem_temp.toLowerCase();
                    var bWantHide = false;

                    if ((sTextToSearch.length > 0) && (sTextThisItem_temp.indexOf(sTextToSearch) === -1))
                        bWantHide = true;
                    
                    var iFilterLevel = CInt(jQ("select#PETSFilterList").val());
                    
                    if (iFilterLevel === 1)
                    {
                        if ((sMetaWord === "mining") || (oActualItemData.isEquippable !== true))
                            bWantHide = true;
                    }
                    else if (iFilterLevel === 2)
                    {
                        bWantHide = true;
                        
                        if ((CInt(oActualItemData.quality) > 0) && (oActualItemData.isEquippable !== true))
                            bWantHide = false;
                        if ((CInt(oActualItemData.quality) > 0) && (sMetaWord === "mining"))
                            bWantHide = false;
                    }
                    else if (iFilterLevel === 999)
                    {
                        if (sMetaWord === "mining")
                            bWantHide = true;
                        if (CInt(oActualItemData.quality) > 0)
                            bWantHide = true;
                        if (oActualItemData.isEquippable === true)
                            bWantHide = true;
                    }
                        
                    if (bWantHide)
                        oThisItemEl.hide();
                    else
                        oThisItemEl.show();
                }
                catch (err) { }
            });

            if ((bDidAny === true) && (jQ("#PETSAutoSort").is(":checked")))
            {
                ETMod_InvSearch.sort_scheduled = true;
                setTimeout(ETMod_InvSearch.Resort, 500);
            }
            
            if (bDidAny === true)
            {
                ETMod_InvSearch.Counter = 0;
            }
            else
            {
                ETMod_InvSearch.Counter++;
                if (ETMod_InvSearch.Counter === 10)
                {
                    ETMod_InvSearch.Counter = 0;
                    
                    jQ("div.item-icon-container").each(function() {
                        try { jQ(this).attr("PETS_Modified", ""); } catch (err) { } });
                }
            }
            
            jQ("div.recipe-container").each(function()
            {
                try
                {
                    var sTextThisRecipe = jQ(this).find("div div span.text-capitalize").text().trim().toLowerCase();

                    while (sTextThisRecipe.indexOf(" ") !== -1) sTextThisRecipe = sTextThisRecipe.replace(" ", "");

                    if ((sTextToSearch.length > 0) && (sTextThisRecipe.indexOf(sTextToSearch) === -1))
                        jQ(this).hide();
                    else
                        jQ(this).show();
                }
                catch (err) { }
            });
        }
        else
        {
            jQ("div#PETSOptionsEl").remove();
            ETMod_InvSearch.NeedResort = true;
            ETMod_InvSearch.did_manual_sort = false;
            ETMod_InvSearch.returned_to_crafting = false;
        }

        setTimeout(ETMod_InvSearch.main, 1000);
    },

    FriendlyNumber: function(num)
    {
        if (num < 1000) return num.toString();
        if (num < 10000) return ((Math.floor(num / 100) / 10.0).toString() + "k");
        if (num < 100000) return ((Math.floor(num / 1000)).toString() + "k");
        if (num < 1000000) return ((Math.floor(num / 1000)).toString() + "k");
        if (num < 10000000) return ((Math.floor(num / 100000) / 10.0).toString() + "m");
        if (num < 100000000) return ((Math.floor(num / 1000000)).toString() + "m");
        if (num < 1000000000) return ((Math.floor(num / 10000000)).toString() + "m");    
        if (num < 10000000000) return ((Math.floor(num / 100000000) / 10.0).toString() + "b");
        if (num < 100000000000) return ((Math.floor(num / 1000000000)).toString() + "b");
        if (num < 1000000000000) return ((Math.floor(num / 10000000000)).toString() + "b");    
        return num.toString();
    }
};


////////////////////////////////////////////////////////////////
/////////////// ** common.js -- DO NOT MODIFY ** ///////////////
time_val = function()
{
    return CDbl(Math.floor(Date.now() / 1000));
};

IsValid = function(oObject)
{
    if (oObject === undefined) return false;
    if (oObject === null) return false;
    return true;
};

Random = function(iMin, iMax)
{
    return parseInt(iMin + Math.floor(Math.random() * iMax));
};

ShiftClick = function(oEl)
{
    jQ(oEl).trigger(ShiftClickEvent());
};

ShiftClickEvent = function(target)
{
	let shiftclickOrig = jQ.Event("click");
    shiftclickOrig.which = 1; // 1 = left, 2 = middle, 3 = right
    //shiftclickOrig.type = "click"; // "mousedown" ?
    shiftclickOrig.currentTarget = target;
	shiftclickOrig.shiftKey = true;

	let shiftclick = jQ.Event("click");
    //shiftclick.type = "click"; // "mousedown" ?
    shiftclick.which = 1; // 1 = left, 2 = middle, 3 = right
	shiftclick.shiftKey = true;
    shiftclick.currentTarget = target;
	shiftclick.originalEvent = shiftclickOrig;

    //document.ET_Util_Log(shiftclick);

	return shiftclick;
};

if (!String.prototype.replaceAll)
    String.prototype.replaceAll = function(search, replace) { return ((replace === undefined) ? this.toString() : this.replace(new RegExp('[' + search + ']', 'g'), replace)); };

if (!String.prototype.startsWith)
    String.prototype.startsWith = function(search, pos) { return this.substr(((!pos) || (pos < 0)) ? 0 : +pos, search.length) === search; };

CInt = function(v)
{
	try
	{
		if (!isNaN(v)) return Math.floor(v);
		if (typeof v === 'undefined') return parseInt(0);
		if (v === null) return parseInt(0);
		let t = parseInt(v);
		if (isNaN(t)) return parseInt(0);
		return Math.floor(t);
	}
	catch (err) { }

	return parseInt(0);
};

CDbl = function(v)
{
	try
	{
		if (!isNaN(v)) return parseFloat(v);
		if (typeof v === 'undefined') return parseFloat(0.0);
		if (v === null) return parseFloat(0.0);
		let t = parseFloat(v);
		if (isNaN(t)) return parseFloat(0.0);
		return t;
	}
	catch (err) { }

	return parseFloat(0.0);
};

// dup of String.prototype.startsWith, but uses indexOf() instead of substr()
startsWith = function (haystack, needle) { return (needle === "") || (haystack.indexOf(needle) === 0); };
endsWith   = function (haystack, needle) { return (needle === "") || (haystack.substring(haystack.length - needle.length) === needle); };

Chopper = function(sText, sSearch, sEnd)
{
	let sIntermediate = "";

	if (sSearch === "")
		sIntermediate = sText.substring(0, sText.length);
	else
	{
		let iIndexStart = sText.indexOf(sSearch);
		if (iIndexStart === -1)
			return sText;

		sIntermediate = sText.substring(iIndexStart + sSearch.length);
	}

	if (sEnd === "")
		return sIntermediate;

	let iIndexEnd = sIntermediate.indexOf(sEnd);

	return (iIndexEnd === -1) ? sIntermediate : sIntermediate.substring(0, iIndexEnd);
};

ChopperBlank = function(sText, sSearch, sEnd)
{
	let sIntermediate = "";

	if (sSearch === "")
		sIntermediate = sText.substring(0, sText.length);
	else
	{
		let iIndexStart = sText.indexOf(sSearch);
		if (iIndexStart === -1)
			return "";

		sIntermediate = sText.substring(iIndexStart + sSearch.length);
	}

	if (sEnd === "")
		return sIntermediate;

	let iIndexEnd = sIntermediate.indexOf(sEnd);

	return (iIndexEnd === -1) ? "" : sIntermediate.substring(0, iIndexEnd);
};

CondenseSpacing = function(text)
{
	while (text.indexOf("  ") !== -1)
		text = text.replace("  ", " ");
	return text;
};

// pad available both ways as pad(string, width, [char]) or string.pad(width, [char])
pad = function(sText, iWidth, sChar)
{
    sChar = ((sChar !== undefined) ? sChar : ('0'));
    sText = sText.toString();
    return ((sText.length >= iWidth) ? (sText) : (new Array(iWidth - sText.length + 1).join(sChar) + sText));
};

if (!String.prototype.pad)
    String.prototype.pad = function(iWidth, sChar)
    {
        sChar = ((sChar !== undefined) ? sChar : ('0'));
        sText = sText.toString();
        return ((sText.length >= iWidth) ? (sText) : (new Array(iWidth - sText.length + 1).join(sChar) + sText));
    };

String.prototype.toHHMMSS = function () {
    var sec_num = parseInt(this, 10);
    var hours   = Math.floor(sec_num / 3600);
    var minutes = Math.floor((sec_num - (hours * 3600)) / 60);
    var seconds = sec_num - (hours * 3600) - (minutes * 60);

    if (hours   < 10) {hours   = "0"+hours;}
    if (minutes < 10) {minutes = "0"+minutes;}
    if (seconds < 10) {seconds = "0"+seconds;}
    return hours+':'+minutes+':'+seconds;
};
    
is_visible = (function () {
    var x = window.pageXOffset ? window.pageXOffset + window.innerWidth - 1 : 0,
        y = window.pageYOffset ? window.pageYOffset + window.innerHeight - 1 : 0,
        relative = !!((!x && !y) || !document.elementFromPoint(x, y));
    function inside(child, parent) {
        while(child){
            if (child === parent) return true;
            child = child.parentNode;
        }
        return false;
    }
    return function (elem) {
        if (
            hidden ||
            elem.offsetWidth==0 ||
            elem.offsetHeight==0 ||
            elem.style.visibility=='hidden' ||
            elem.style.display=='none' ||
            elem.style.opacity===0
        ) return false;
        var rect = elem.getBoundingClientRect();
        if (relative) {
            if (!inside(document.elementFromPoint(rect.left + elem.offsetWidth/2, rect.top + elem.offsetHeight/2),elem)) return false;
        } else if (
            !inside(document.elementFromPoint(rect.left + elem.offsetWidth/2 + window.pageXOffset, rect.top + elem.offsetHeight/2 + window.pageYOffset), elem) ||
            (
                rect.top + elem.offsetHeight/2 < 0 ||
                rect.left + elem.offsetWidth/2 < 0 ||
                rect.bottom - elem.offsetHeight/2 > (window.innerHeight || document.documentElement.clientHeight) ||
                rect.right - elem.offsetWidth/2 > (window.innerWidth || document.documentElement.clientWidth)
            )
        ) return false;
        if (window.getComputedStyle || elem.currentStyle) {
            var el = elem,
                comp = null;
            while (el) {
                if (el === document) {break;} else if(!el.parentNode) return false;
                comp = window.getComputedStyle ? window.getComputedStyle(el, null) : el.currentStyle;
                if (comp && (comp.visibility=='hidden' || comp.display == 'none' || (typeof comp.opacity !=='undefined' && comp.opacity != 1))) return false;
                el = el.parentNode;
            }
        }
        return true;
    };
})();
////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////
////////////// ** common_ET.js -- DO NOT MODIFY ** /////////////
if (window.ET === undefined) window.ET = { };
if ((window.ET.MCMF === undefined) || (CDbl(window.ET.MCMF.version) < 1.08)) // MeanCloud mod framework
{
    window.ET.MCMF =
    {
        version: 1.07,
        
        TryingToLoad: false,
        WantDebug: false,
        WantFasterAbilityCDs: false,

        InBattle: false,
        FinishedLoading: false,
        Initialized: false,
        AbilitiesReady: false,
        InitialAbilityCheck: true,
        TimeLeftOnCD: 9999,
        TimeLastFight: 0,

        CombatID: undefined,
        BattleID: undefined,

        ToastMessageSuccess: function(msg)
        {
            toastr.success(msg);
        },

        ToastMessageWarning: function(msg)
        {
            toastr.warning(msg);
        },

        EventSubscribe: function(sEventName, fnCallback, sNote)
        {
            if (window.ET.MCMF.EventSubscribe_events === undefined)
                window.ET.MCMF.EventSubscribe_events = [];

            let newEvtData = {};
                newEvtData.name = ((!sEventName.startsWith("ET:")) ? ("ET:" + sEventName) : (sEventName));
                newEvtData.callback = fnCallback;
                newEvtData.note = sNote;

            window.ET.MCMF.EventSubscribe_events.push(newEvtData);

            /*
            jQ("div#ET_meancloud_bootstrap").off("ET:" + sEventName.trim()).on("ET:" + sEventName.trim(), function()
            {
                window.ET.MCMF.EventSubscribe_events.forEach(function(oThisEvent)
                {
                    if (sEventName === oThisEvent.name)
                    {
                        if (window.ET.MCMF.WantDebug) window.ET.MCMF.Log("FIRING '" + oThisEvent.name + "'!" + ((oThisEvent.note === undefined) ? "" : " (" + oThisEvent.note + ")"));
                        oThisEvent.callback();
                    }
                });
            });
            */

            if (window.ET.MCMF.WantDebug) window.ET.MCMF.Log("Added event subscription '" + sEventName + "'!" + ((sNote === undefined) ? "" : " (" + sNote + ")"));
        },

        EventTrigger: function(sEventName)
        {
            //jQ("div#ET_meancloud_bootstrap").trigger(sEventName);

            if (window.ET.MCMF.EventSubscribe_events === undefined) return;

            window.ET.MCMF.EventSubscribe_events.forEach(function(oThisEvent)
            {
                if (sEventName === oThisEvent.name)
                {
                    if (window.ET.MCMF.WantDebug) window.ET.MCMF.Log("FIRING '" + oThisEvent.name + "'!" + ((oThisEvent.note === undefined) ? "" : " (" + oThisEvent.note + ")"));
                    try { oThisEvent.callback(); } catch (err) { if (window.ET.MCMF.WantDebug) window.ET.MCMF.Log("Exception: " + err); }
                }
            });
        },
        
        Log: function(msg)
        {
            try
            {
                let now_time = new Date();
                let timestamp = (now_time.getMonth() + 1).toString() + "/" + now_time.getDate().toString() + "/" + (now_time.getYear() + 1900).toString() + " " + ((now_time.getHours() === 0) ? (12) : ((now_time.getHours() > 12) ? (now_time.getHours() - 12) : (now_time.getHours()))).toString() + ":" + now_time.getMinutes().toString().padStart(2, "0") + ":" + now_time.getSeconds().toString().padStart(2, "0") + ((now_time.getHours() < 12) ? ("am") : ("pm")) + " :: ";
                console.log(timestamp.toString() + msg);
            }
            catch (err) { }
        },

        Time: function() // returns time in milliseconds (not seconds!)
        {
            return CInt((new Date()).getTime());
        },

        SubscribeToGameChannel: function(channel_name)
        {
            let oChannel;

            try
            {
                channel_name = channel_name.toString().trim();

                let bAlreadySubscribed = false;

                jQuery.makeArray(Object.keys(Package.meteor.global.Accounts.connection._subscriptions).map(key => Package.meteor.global.Accounts.connection._subscriptions[key])).forEach(function(oThisConnection)
                {
                    try
                    {
                        if (oThisConnection.name === channel_name)
                            bAlreadySubscribed = true;
                    }
                    catch (err) { }
                });

                if (!bAlreadySubscribed)
                {
                    Meteor.subscribe(channel_name);
                    if (window.ET.MCMF.WantDebug) window.ET.MCMF.Log("Meteor::Subscribed to channel '" + channel_name + "'");
                }
                //else if (ET.MCMF.WantDebug)
                //    window.ET.MCMF.Log("Meteor::Already subscribed to channel '" + channel_name + "'");
            }
            catch (err)
            {
                if (window.ET.MCMF.WantDebug) window.ET.MCMF.Log("Meteor::Exception in SubscribeToGameChannel(\"" + channel_name + "\")");
                if (window.ET.MCMF.WantDebug) window.ET.MCMF.Log(err);
            }

            return oChannel;
        },
        
        CraftingBuff: function()
        {
            let oDate, iTimeLeft;
            
            try
            {
                oDate = new Date(Meteor.connection._stores.state._getCollection().find({ name: "buffCrafting" }).fetch()[0].value.activeTo);            
                iTimeLeft = ((oDate) > (new Date())) ? CInt(Math.floor(Math.abs(oDate - (new Date())) / 1000.0)) : 0;
                
                return { active: (iTimeLeft > 0), remaining: iTimeLeft, expires: oDate };
            }
            catch (err) { }
            
            return { active: false, remaining: 0, expires: oDate };
        },
        
        CombatBuff: function()
        {
            let oDate, iTimeLeft;
            
            try
            {
                oDate = new Date(Meteor.connection._stores.state._getCollection().find({ name: "buffCombat" }).fetch()[0].value.activeTo);            
                iTimeLeft = ((oDate) > (new Date())) ? CInt(Math.floor(Math.abs(oDate - (new Date())) / 1000.0)) : 0;
                
                return { active: (iTimeLeft > 0), remaining: iTimeLeft, expires: oDate };
            }
            catch (err) { }
            
            return { active: false, remaining: 0, expires: oDate };
        },
        
        GatheringBuff: function()
        {
            let oDate, iTimeLeft;
            
            try
            {
                oDate = new Date(Meteor.connection._stores.state._getCollection().find({ name: "buffGathering" }).fetch()[0].value.activeTo);            
                iTimeLeft = ((oDate) > (new Date())) ? CInt(Math.floor(Math.abs(oDate - (new Date())) / 1000.0)) : 0;
                
                return { active: (iTimeLeft > 0), remaining: iTimeLeft, expires: oDate };
            }
            catch (err) { }
            
            return { active: false, remaining: 0, expires: oDate };
        },
        
        IsNewCombatTab: function()
        {
            try {
                if (window.location.href.indexOf("/newCombat") !== -1) {
                    return true; }
            }
            catch (err) {
            }
            
            return false;
        },
        
        GetActiveTab: function()
        {
            let active_tab = "";
            
            /*
            try
            {
                active_tab = jQuery(jQuery("a.active").get(0)).text().trim().toLowerCase();
                
                if (active_tab.length === 0)
                    throw "Invalid active tab";
                
                if (active_tab === "mine") active_tab = "mining";
                if (active_tab === "craft") active_tab = "crafting";
                if (active_tab === "battle") active_tab = "combat";
                if (active_tab === "woodcut") active_tab = "woodcutting";
                if (active_tab === "farm") active_tab = "farming";
                if (active_tab === "inscribe") active_tab = "inscription";
                //if (active_tab === "inscription") active_tab = "inscription";
                //if (active_tab === "magic") active_tab = "magic";
                //if (active_tab === "shop") active_tab = "shop";
            }
            catch (err)
            {
            */
                if (window.location.href.indexOf("/gameHome") !== -1) active_tab = "home";
                if (window.location.href.indexOf("/mining") !== -1) active_tab = "mining";
                if (window.location.href.indexOf("/crafting") !== -1) active_tab = "crafting";
                if (window.location.href.indexOf("/combat") !== -1) active_tab = "combat";
                if (window.location.href.indexOf("/newCombat") !== -1) active_tab = "combat";
                if (window.location.href.indexOf("/woodcutting") !== -1) active_tab = "woodcutting";
                if (window.location.href.indexOf("/farming") !== -1) active_tab = "farming";
                if (window.location.href.indexOf("/inscription") !== -1) active_tab = "inscription";
                if (window.location.href.indexOf("/magic") !== -1) active_tab = "magic";
                if (window.location.href.indexOf("/faq") !== -1) active_tab = "faq";
                if (window.location.href.indexOf("/chat") !== -1) active_tab = "chat";
                if (window.location.href.indexOf("/skills") !== -1) active_tab = "skills";
                if (window.location.href.indexOf("/achievements") !== -1) active_tab = "achievements";
                if (window.location.href.indexOf("/updates") !== -1) active_tab = "updates";
            /*
            }
            */
            
            return active_tab;
        },
        
        GetActiveTabSection: function()
        {
            let active_tab_section = "";
            
            try
            {
                let active_tab = window.ET.MCMF.GetActiveTab();

                if (active_tab === "mining") active_tab_section = Meteor.connection._stores.users._getCollection().find().fetch()[0].uiState.miningTab;
                if (active_tab === "crafting") active_tab_section = Meteor.connection._stores.users._getCollection().find().fetch()[0].uiState.craftingFilter;
                if (active_tab === "combat")
                {
                    if (window.ET.MCMF.IsNewCombatTab())
                        active_tab_section = Meteor.connection._stores.users._getCollection().find().fetch()[0].uiState.newCombatType;
                    else
                        active_tab_section = Meteor.connection._stores.users._getCollection().find().fetch()[0].uiState.combatTab;
                }
                if (active_tab === "farming") active_tab_section = Meteor.connection._stores.users._getCollection().find().fetch()[0].uiState.farmingTab;
                if (active_tab === "inscription") active_tab_section = Meteor.connection._stores.users._getCollection().find().fetch()[0].uiState.inscriptionFilter;
                if (active_tab === "achievements") active_tab_section = Meteor.connection._stores.users._getCollection().find().fetch()[0].uiState.achievementTab;
                if (active_tab === "magic") active_tab_section = Meteor.connection._stores.users._getCollection().find().fetch()[0].uiState.magicTab;

                active_tab_section = active_tab_section.trim().toLowerCase();

                if (active_tab_section === "minepit") active_tab_section = "mine pit";
                if (active_tab_section === "personalquest") active_tab_section = "personal quest";
                if (active_tab_section === "tower") active_tab_section = "the tower";
                if (active_tab_section === "battlelog") active_tab_section = "battle log";
                if (active_tab_section === "pigment") active_tab_section = "pigments";
                if (active_tab_section === "book") active_tab_section = "books";
                if (active_tab_section === "magic_book") active_tab_section = "magic books";
                if (active_tab_section === "spellbook") active_tab_section = "spell book";
                
                if (active_tab_section.length === 0)
                    throw "Invalid active tab section";
            }
            catch (err)
            {
                try
                {
                    active_tab_section = jQuery(jQuery("a.active").get(1)).text().trim().toLowerCase();
                    
                    if (active_tab_section.length === 0)
                        throw "Invalid active tab section";
                }
                catch (err) { }
            }
            
            return active_tab_section;
        },
        
        BattleSocket_UseAbility: function(abil, targ)
        {
            try
            {
                let sMsg = '';
                
                if (targ === undefined)
                {
                    sMsg = '["action",{"abilityId":"' + abil + '","targets":[],"caster":"' + window.ET.MCMF.UserID + '"}]';
                    if (window.ET.MCMF.WantDebug) window.ET.MCMF.Log("Battle socket emitting: '" + sMsg + "'");
                    
                    battleSocket.emit
                    (
                        "action",
                        {
                            abilityId: abil,
                            targets: [],
                            caster: window.ET.MCMF.UserID
                        }
                    );
                }
                else
                {
                    sMsg = '["action",{"abilityId":"' + abil + '","targets":[' + targ + '],"caster":"' + window.ET.MCMF.UserID + '"}]';
                    if (window.ET.MCMF.WantDebug) window.ET.MCMF.Log("Battle socket emitting: '" + sMsg + "'");
                    
                    battleSocket.emit
                    (
                        "action",
                        {
                            abilityId: abil,
                            targets: [targ],
                            caster: window.ET.MCMF.UserID
                        }
                    );
                }
            }
            catch (err) { }
        },

        CallGameCmd: function()
        {
            try
            {
                if (arguments.length > 0)
                {
                    let cmd = arguments[0];
                    let fnc = function() { };

                    if (arguments.length === 1)
                    {
                        if (window.ET.MCMF.WantDebug) window.ET.MCMF.Log("Calling: '" + cmd + "' with no data");
                        Package.meteor.Meteor.call(cmd, fnc);
                    }
                    else
                    {
                        let data1, data2, data3, data4;

                        if (typeof arguments[arguments.length - 1] === "function")
                        {
                            fnc = arguments[arguments.length - 1];
                            if (arguments.length >= 3) data1 = arguments[1];
                            if (arguments.length >= 4) data2 = arguments[2];
                            if (arguments.length >= 5) data3 = arguments[3];
                            if (arguments.length >= 6) data4 = arguments[4];
                        }
                        else
                        {
                            if (arguments.length >= 2) data1 = arguments[1];
                            if (arguments.length >= 3) data2 = arguments[2];
                            if (arguments.length >= 4) data3 = arguments[3];
                            if (arguments.length >= 5) data4 = arguments[4];
                        }

                        if (data1 === undefined)
                        {
                            if (window.ET.MCMF.WantDebug) window.ET.MCMF.Log("Calling: '" + cmd + "' with no data");
                            Package.meteor.Meteor.call(cmd, fnc);
                        }
                        else if (data2 === undefined)
                        {
                            if (window.ET.MCMF.WantDebug) window.ET.MCMF.Log("Calling: '" + cmd + "' with { " + JSON.stringify(data1) + " }");
                            Package.meteor.Meteor.call(cmd, data1, fnc);
                        }
                        else if (data3 === undefined)
                        {
                            if (window.ET.MCMF.WantDebug) window.ET.MCMF.Log("Calling: '" + cmd + "' with { " + JSON.stringify(data1) + ", " + JSON.stringify(data2) + " }");
                            Package.meteor.Meteor.call(cmd, data1, data2, fnc);
                        }
                        else if (data4 === undefined)
                        {
                            if (window.ET.MCMF.WantDebug) window.ET.MCMF.Log("Calling: '" + cmd + "' with { " + JSON.stringify(data1) + ", " + JSON.stringify(data2) + ", " + JSON.stringify(data3) + " }");
                            Package.meteor.Meteor.call(cmd, data1, data2, data3, fnc);
                        }
                        else
                        {
                            if (window.ET.MCMF.WantDebug) window.ET.MCMF.Log("Calling: '" + cmd + "' with { " + JSON.stringify(data1) + ", " + JSON.stringify(data2) + ", " + JSON.stringify(data3) + ", " + JSON.stringify(data4) + " }");
                            Package.meteor.Meteor.call(cmd, data1, data2, data3, data4, fnc);
                        }
                    }
                }
                else if (window.ET.MCMF.WantDebug)
                    window.ET.MCMF.Log("Meteor::Warning, CallGameCmd() with no arguments!");
            }
            catch (err)
            {
                if (window.ET.MCMF.WantDebug) window.ET.MCMF.Log("Meteor::Exception in CallGameCmd()");
                if (window.ET.MCMF.WantDebug) window.ET.MCMF.Log(err);
            }
        },

        SendGameCmd: function(cmd)
        {
            try
            {
                Meteor.connection._send(cmd);
                if (window.ET.MCMF.WantDebug) window.ET.MCMF.Log("Meteor::Sending: " + JSON.stringify(cmd));
            }
            catch (err)
            {
                if (window.ET.MCMF.WantDebug) window.ET.MCMF.Log("Meteor::Exception in SendGameCmd(" + JSON.stringify(cmd) + ")");
                if (window.ET.MCMF.WantDebug) window.ET.MCMF.Log(err);
            }
        },

        FasterAbilityUpdates: function()
        {
            try
            {
                window.ET.MCMF.SubscribeToGameChannel("abilities");
                
                if ((window.ET.MCMF.WantFasterAbilityCDs) && (window.ET.MCMF.FinishedLoading) && (!window.ET.MCMF.InBattle) && (!window.ET.MCMF.AbilitiesReady))
                    window.ET.MCMF.CallGameCmd("abilities.gameUpdate");
            }
            catch (err) { }

            setTimeout(window.ET.MCMF.FasterAbilityUpdates, 2000);
        },

        PlayerInCombat: function()
        {
            return ((window.ET.MCMF.InBattle) || ((time_val() - window.ET.MCMF.TimeLastFight) < 3));
        },
        
        AbilityCDTrigger: function()
        {
            try
            {
                if ((window.ET.MCMF.FinishedLoading) && (!window.ET.MCMF.PlayerInCombat()))
                {
                    iTotalCD = 0;
                    iTotalCDTest = 0;
                    iHighestCD = 0;

                    window.ET.MCMF.GetAbilities().forEach(function(oThisAbility)
                    {
                        if (oThisAbility.equipped)
                        {
                            if (parseInt(oThisAbility.currentCooldown) > 0)
                            {
                                iTotalCD += parseInt(oThisAbility.currentCooldown);
                                if (iHighestCD < parseInt(oThisAbility.currentCooldown))
                                    iHighestCD = parseInt(oThisAbility.currentCooldown);
                            }
                        }

                        iTotalCDTest += parseInt(oThisAbility.cooldown);
                    });

                    if ((iTotalCDTest > 0) && (iTotalCD === 0))
                    {
                        if (!window.ET.MCMF.AbilitiesReady)
                        {
                            if (!window.ET.MCMF.InitialAbilityCheck)
                            {
                                if (window.ET.MCMF.WantDebug) window.ET.MCMF.Log("<-- triggering ET:abilitiesReady -->");
                                window.ET.MCMF.EventTrigger("ET:abilitiesReady");
                            }
                        }

                        window.ET.MCMF.AbilitiesReady = true;
                        window.ET.MCMF.TimeLeftOnCD = 0;
                    }
                    else
                    {
                        window.ET.MCMF.AbilitiesReady = false;
                        window.ET.MCMF.TimeLeftOnCD = iHighestCD;
                    }

                    window.ET.MCMF.InitialAbilityCheck = false;
                }
                else
                {
                    window.ET.MCMF.AbilitiesReady = false;
                    window.ET.MCMF.TimeLeftOnCD = 9999;
                }
            }
            catch (err) { }

            setTimeout(window.ET.MCMF.AbilityCDTrigger, 500);
        },
        
        BattleFloorRoom: "0.0",
        BattleFirstFrame: undefined,
        BattleUnitList: [],
        BattleUITemplate: undefined,

        LiveBattleData: function()
        {
            try
            {
                if (window.ET.MCMF.BattleUITemplate !== undefined)
                    return window.ET.MCMF.BattleUITemplate.state.get("currentBattle");
            }
            catch (err) { }
            
            return undefined;
        },
        
        InitGameTriggers: function()
        {
            if ((Package.meteor.Meteor === undefined) || (Package.meteor.Meteor.connection === undefined) || (Package.meteor.Meteor.connection._stream === undefined) || (Template.currentBattleUi === undefined))
            {
                setTimeout(window.ET.MCMF.InitGameTriggers, 100);
                return;
            }
            
            Blaze._getTemplate("battleUnit").onRendered(function()
            {
                if ((this.data !== undefined) && (this.data.unit !== undefined))
                {
                    window.ET.MCMF.BattleUnitList.push(this);
                    if (window.ET.MCMF.WantDebug) window.ET.MCMF.Log("<-- Template.battleUnit.onRendered triggered -->");
                }
            });
            
            Template.currentBattleUi.onCreated(function()
            {
                window.ET.MCMF.BattleUITemplate = this;
                if (window.ET.MCMF.WantDebug) window.ET.MCMF.Log("<-- Template.currentBattleUi.onCreated triggered -->");
            });
            
            Template.currentBattleUi.onDestroyed(function()
            {
                window.ET.MCMF.BattleUITemplate = undefined;
                window.ET.MCMF.BattleUnitList = [];
                if (window.ET.MCMF.WantDebug) window.ET.MCMF.Log("<-- Template.currentBattleUi.onDestroyed triggered -->");
            });

            Package.meteor.Meteor.connection._stream.on('message', function(sMeteorRawData)
            {
                if (window.ET.MCMF.CombatID === undefined)
                    window.ET.MCMF.GetPlayerCombatData();

                try
                {
                    oMeteorData = JSON.parse(sMeteorRawData);

                    /////////////////////////////////////////////////////////////////////////////////////////////////////////
                    //
                    //  BACKUP TO RETRIEVE USER AND COMBAT IDS
                    //
                    if (oMeteorData.collection === "users")
                        if ((window.ET.MCMF.UserID === undefined) || (window.ET.MCMF.UserID.length !== 17))
                            window.ET.MCMF.UserID = oMeteorData.id;

                    if (oMeteorData.collection === "combat")
                        if ((window.ET.MCMF.CombatID === undefined) || (window.ET.MCMF.CombatID.length !== 17))
                            if (oMeteorData.fields.owner === window.ET.MCMF.UserID)
                                window.ET.MCMF.CombatID = oMeteorData.id;
                    //
                    /////////////////////////////////////////////////////////////////////////////////////////////////////////

                    if (oMeteorData.collection === "battlesList")
                    {
                        window.ET.MCMF.AbilitiesReady = false;

                        if ((oMeteorData.msg === "added") || (oMeteorData.msg === "removed"))
                        {
                            window.ET.MCMF.BattleUnitList = [];
                            window.ET.MCMF.InBattle = (oMeteorData.msg === "added");
                            if (window.ET.MCMF.WantDebug) window.ET.MCMF.Log("<-- triggering ET:combat" + (((oMeteorData.msg === "added")) ? ("Start") : ("End")) + " -->");
                            window.ET.MCMF.EventTrigger("ET:combat" + (((oMeteorData.msg === "added")) ? ("Start") : ("End")));
                            
                            if (window.ET.MCMF.InBattle)
                            {
                                battleSocket.on('tick', function(oAllData)
                                {
                                    try
                                    {
                                        let battleData = window.ET.MCMF.LiveBattleData();                                    
                                        
                                        if (battleData !== undefined)
                                        {
                                            let currentFloorRoom = CInt(battleData.floor).toFixed(0) + "." + CInt(battleData.room).toFixed(0);
                                            
                                            if (window.ET.MCMF.BattleFloorRoom !== currentFloorRoom)
                                            {
                                                window.ET.MCMF.BattleFloorRoom = currentFloorRoom;
                                                window.ET.MCMF.BattleFirstFrame = undefined;
                                            }
                                            
                                            if (window.ET.MCMF.BattleFirstFrame === undefined)
                                                window.ET.MCMF.BattleFirstFrame = battleData;
                                        }
                                    }
                                    catch (err) { }
                                    
                                    if (window.ET.MCMF.WantDebug) window.ET.MCMF.Log("<-- triggering ET:combatTick -->");
                                    window.ET.MCMF.EventTrigger("ET:combatTick");
                                });
                            }
                            else
                            {
                                window.ET.MCMF.BattleFloorRoom = "0.0";
                                window.ET.MCMF.BattleFirstFrame = undefined;
                            }
                        }
                    }

                    if ((oMeteorData.collection === "battles") && (oMeteorData.msg === "added"))
                    {
                        if (oMeteorData.fields.finished)
                        {
                            window.ET.MCMF.WonLast = oMeteorData.fields.win;
                            window.ET.MCMF.TimeLastFight = time_val();

                            if (window.ET.MCMF.WantDebug) window.ET.MCMF.Log("<-- triggering ET:combat" + ((oMeteorData.fields.win) ? ("Won") : ("Lost")) + " -->");
                            window.ET.MCMF.EventTrigger("ET:combat" + ((oMeteorData.fields.win) ? ("Won") : ("Lost")));
                        }
                    }
                }
                catch (err) { }
            });
        },
        
        PlayerHP: function()
        {
            //if (!window.ET.MCMF.PlayerInCombat())
                return window.ET.MCMF.GetPlayerCombatData().stats.health;
            
            //return window.ET.MCMF.PlayerUnitData.stats.health;
        },
        
        PlayerHPMax: function()
        {
            //if (!window.ET.MCMF.PlayerInCombat())
                return window.ET.MCMF.GetPlayerCombatData().stats.healthMax;
            
            //return window.ET.MCMF.PlayerUnitData.stats.healthMax;
        },
        
        PlayerEnergy: function()
        {
            //if (!window.ET.MCMF.PlayerInCombat())
                return window.ET.MCMF.GetPlayerCombatData().stats.energy;
            
            //return window.ET.MCMF.PlayerUnitData.stats.energy;
        },

        AbilityCDCalc: function()
        {
            iTotalCD = 0;
            iTotalCDTest = 0;
            iHighestCD = 0;

            window.ET.MCMF.GetAbilities().forEach(function(oThisAbility)
            {
                if (oThisAbility.equipped)
                {
                    if (parseInt(oThisAbility.currentCooldown) > 0)
                    {
                        iTotalCD += parseInt(oThisAbility.currentCooldown);
                        if (iHighestCD < parseInt(oThisAbility.currentCooldown))
                            iHighestCD = parseInt(oThisAbility.currentCooldown);
                    }
                }

                iTotalCDTest += parseInt(oThisAbility.cooldown);
            });

            if ((iTotalCDTest > 0) && (iTotalCD === 0))
            {
                if (!window.ET.MCMF.AbilitiesReady)
                {
                    if (!window.ET.MCMF.InitialAbilityCheck)
                    {
                        if (window.ET.MCMF.WantDebug) window.ET.MCMF.Log("<-- triggering ET:abilitiesReady -->");
                        window.ET.MCMF.EventTrigger("ET:abilitiesReady");
                        //jQ("div#ET_meancloud_bootstrap").trigger("ET:abilitiesReady");
                    }
                }

                window.ET.MCMF.AbilitiesReady = true;
                window.ET.MCMF.TimeLeftOnCD = 0;
            }
            else
            {
                window.ET.MCMF.AbilitiesReady = false;
                window.ET.MCMF.TimeLeftOnCD = iHighestCD;
            }

            window.ET.MCMF.InitialAbilityCheck = false;
        },

        GetUnitCombatData: function(sUnitID)
        {
            let oCombatPlayerData;
            
            try
            {        
                // get recent combat data from stored 'state' data in 'BattleUITemplate' template (comes from 'battleSocket')
                if (window.ET.MCMF.LiveBattleData() !== undefined)
                {
                    
                    jQ.makeArray(window.ET.MCMF.LiveBattleData().units).forEach(function(oCurrentUnit)
                    {
                        if (oCurrentUnit.id === sUnitID)
                            oCombatPlayerData = oCurrentUnit;
                    });
                }
            }
            catch (err) { }
            
            return oCombatPlayerData;
        },
 
        GetEnemyCombatData: function(sUnitID)
        {
            let oCombatEnemyData;
            
            try
            {        
                // get recent combat data from stored 'state' data in 'BattleUITemplate' template (comes from 'battleSocket')
                if (window.ET.MCMF.LiveBattleData() !== undefined)
                {
                    jQ.makeArray(window.ET.MCMF.LiveBattleData().enemies).forEach(function(oCurrentUnit)
                    {
                        if (oCurrentUnit.id === sUnitID)
                            oCombatEnemyData = oCurrentUnit;
                    });
                }
            }
            catch (err) { }
            
            return oCombatEnemyData;
        },
        
        GetPlayerCombatData: function()
        {
            let oCombatPlayerData;
            
            try
            {        
                window.ET.MCMF.CombatID = undefined;
            
                Meteor.connection._stores.combat._getCollection().find().fetch().forEach(function(oThisCombatUnit)
                {
                    if (oThisCombatUnit.owner === window.ET.MCMF.UserID)
                    {
                        oCombatPlayerData = oThisCombatUnit;
                        
                        window.ET.MCMF.CombatID = oCombatPlayerData._id;
                        
                        if (!window.ET.MCMF.PlayerInCombat())
                            window.ET.MCMF.PlayerUnitData = oCombatPlayerData;
                    }
                });
                
                // new: get updated combat data from stored 'state' data in 'BattleUITemplate' template (comes from 'battleSocket')
                if (window.ET.MCMF.LiveBattleData() !== undefined)
                {
                    jQ.makeArray(window.ET.MCMF.LiveBattleData().units).forEach(function(oCurrentUnit)
                    {
                        if (oCurrentUnit.id === window.ET.MCMF.UserID)
                            window.ET.MCMF.PlayerUnitData = oCurrentUnit;
                    });
                    
                    oCombatPlayerData = window.ET.MCMF.PlayerUnitData;
                }
            }
            catch (err) { }
            
            return oCombatPlayerData;
        },
        
        GetAbilities: function()
        {
            return Meteor.connection._stores.abilities._getCollection().find().fetch()[0].learntAbilities;
        },
      
        GetAdventures: function()
        {
            let oAdventureDetails = { AllAdventures: [], ShortAdventures: [], LongAdventures: [], EpicAdventures: [], PhysicalAdventures: [], MagicalAdventures: [], ActiveAdventures: [], CurrentAdventure: undefined };
            
            // oThisAdventure
            //    .duration     {duration in seconds} (integer)
            //    .endDate      {end date/time} (Date()) (property only exists if the adventure is ongoing)
            //    .floor        {corresponding Tower Floor} (integer)
            //    .icon         "{imageofbattle.ext}" (string)
            //    .id           "{guid}" (13-digit alphanumeric string)
            //    .length       "short" / "long" / "epic" (string)
            //    .level        {general level} (integer)
            //    .name         "{Name of Battle}" (string)
            //    .room         {corresponding Tower Room in Tower Floor} (integer)
            //    .type         "physical" / "magic" (string)
            //    .startDate    {start date/time} (Date()) (property only exists if the adventure is ongoing)    
            window.ET.MCMF.GetAdventures_raw().forEach(function(oThisAdventure)
            {
                try
                {
                    oAdventureDetails.AllAdventures.push(oThisAdventure);
                    if (oThisAdventure.length  === "short")    oAdventureDetails.ShortAdventures   .push(oThisAdventure);
                    if (oThisAdventure.length  === "long")     oAdventureDetails.LongAdventures    .push(oThisAdventure);
                    if (oThisAdventure.length  === "epic")     oAdventureDetails.EpicAdventures    .push(oThisAdventure);
                    if (oThisAdventure.type    === "physical") oAdventureDetails.PhysicalAdventures.push(oThisAdventure);
                    if (oThisAdventure.type    === "magic")    oAdventureDetails.MagicalAdventures .push(oThisAdventure);
                    if (oThisAdventure.endDate !== undefined)  oAdventureDetails.ActiveAdventures  .push(oThisAdventure);
                }
                catch (err) { }
            });
            
            oAdventureDetails.AllAdventures.sort(function(advA, advB)
            {
                if ((advA.startDate === undefined) && (advB.startDate !== undefined)) return 1;
                if ((advA.startDate !== undefined) && (advB.startDate === undefined)) return -1;
                if ((advA.startDate !== undefined) && (advB.startDate !== undefined))
                {
                    if (advA.startDate > advB.startDate) return 1;
                    if (advA.startDate < advB.startDate) return -1;
                }
                if (advA.duration > advB.duration) return 1;
                if (advA.duration < advB.duration) return -1;
                return 0;
            });
            
            oAdventureDetails.ActiveAdventures.sort(function(advA, advB)
            {
                if (advA.startDate > advB.startDate) return 1;
                if (advA.startDate < advB.startDate) return -1;
                return 0;
            });
            
            oAdventureDetails.PhysicalAdventures.sort(function(advA, advB)
            {
                if (advA.duration > advB.duration) return 1;
                if (advA.duration < advB.duration) return -1;
                return 0;
            });
             
            oAdventureDetails.MagicalAdventures.sort(function(advA, advB)
            {
                if (advA.duration > advB.duration) return 1;
                if (advA.duration < advB.duration) return -1;
                return 0;
            });
            
            if (oAdventureDetails.ActiveAdventures.length > 0)
                oAdventureDetails.CurrentAdventure = oAdventureDetails.ActiveAdventures[0];
            
            return oAdventureDetails;
        },

        GetAdventures_raw: function()
        {
            return Meteor.connection._stores.adventures._getCollection().find().fetch()[0].adventures;
        },        
  
        GetChats: function()
        {
            return Meteor.connection._stores.simpleChats._getCollection().find().fetch();
        },

        GetItem: function(id)
        {
            let oTmp = Meteor.connection._stores.items._getCollection().find({_id: id}).fetch();
            
            if (oTmp.length === 0)
                oTmp = Meteor.connection._stores.items._getCollection().find({itemId: id}).fetch();
            if (oTmp.length === 0)
                oTmp = Meteor.connection._stores.items._getCollection().find({name: id}).fetch();
            
            if (oTmp.length > 0)
                return oTmp[0];
        
            return undefined;
        },

        GetItems: function()
        {
            return Meteor.connection._stores.items._getCollection().find().fetch();
        },
        
        GetSkills: function()
        {
            return Meteor.connection._stores.skills._getCollection().find().fetch();
        },

        // need a better way to check if the game has loaded basic data, but this is fine for now
        Setup: function()
        {
            if ((!window.ET.MCMF.TryingToLoad) && (!window.ET.MCMF.FinishedLoading))
            {
                // use whatever version of jQuery available to us
                $("body").append("<div id=\"ET_meancloud_bootstrap\" style=\"visibility: hidden; display: none;\"></div>");
                window.ET.MCMF.TryingToLoad = true;
                window.ET.MCMF.Setup_Initializer();
            }
        },

        Setup_Initializer: function()
        {
            // wait for Meteor availability
            if ((Package === undefined) || (Package.meteor === undefined) || (Package.meteor.Meteor === undefined) || (Package.meteor.Meteor.connection === undefined) || (Package.meteor.Meteor.connection._stream === undefined))
            {
                setTimeout(window.ET.MCMF.Setup_Initializer, 10);
                return;
            }

            if (!window.ET.MCMF.Initialized)
            {
                window.ET.MCMF.Initialized = true;
                window.ET.MCMF.Setup_SendDelayedInitializer();
                window.ET.MCMF.InitGameTriggers();
                window.ET.MCMF.Setup_remaining();
            }
        },

        Setup_SendDelayedInitializer: function()
        {
            try
            {
                jQ("div#ET_meancloud_bootstrap").trigger("ET:initialized");
                window.ET.MCMF.EventTrigger("ET:initialized");
                //if (window.ET.MCMF.WantDebug) window.ET.MCMF.Log("<-- triggering ET:initialized -->");
            }
            catch (err)
            {
                setTimeout(window.ET.MCMF.Setup_SendDelayedInitializer, 100);
            }
        },

        Setup_remaining: function()
        {
            try
            {
                if (Meteor === undefined) throw "[MCMF Setup] Not loaded yet: Meteor not initialized";
                if (Meteor.connection === undefined) throw "[MCMF Setup] Not loaded yet: Meteor not initialized";
                if (Meteor.connection._userId === undefined) throw "[MCMF Setup] Not loaded yet: Meteor not initialized";
                
                window.ET.MCMF.UserID = Meteor.connection._userId;
                window.ET.MCMF.UserName = Meteor.connection._stores.users._getCollection()._collection._docs._map[window.ET.MCMF.UserID].username;
                window.ET.MCMF.GetPlayerCombatData();

                if (window.ET.MCMF.GetAbilities().length < 0) throw "[MCMF Setup] Not loaded yet: no abilities";
                if (window.ET.MCMF.GetItems().length < 0) throw "[MCMF Setup]Not loaded yet: no items";
                if (window.ET.MCMF.GetChats().length < 0) throw "[MCMF Setup]Not loaded yet: no chats";
                if (window.ET.MCMF.GetSkills().length < 0) throw "[MCMF Setup]Not loaded yet: no skills";

                // if the above is all good, then this should be no problem:

                window.ET.MCMF.AbilityCDTrigger();     // set up ability CD trigger
                window.ET.MCMF.AbilityCDCalc();
                window.ET.MCMF.FasterAbilityUpdates(); // set up faster ability updates (do not disable, this is controlled via configurable setting)

                // trigger finished-loading event
                if (!window.ET.MCMF.FinishedLoading)
                {
                    if (window.ET.MCMF.WantDebug) window.ET.MCMF.Log("<-- triggering ET:loaded -->");
                    window.ET.MCMF.EventTrigger("ET:loaded");
                    window.ET.MCMF.FinishedLoading = true;
                }
            }
            catch (err) // any errors and we retry setup
            {                
                if (err.toString().indexOf("[MCMF Setup]") !== -1)
                {
                    window.ET.MCMF.Log("ET MCMF setup exception");
                    window.ET.MCMF.Log(err);
                }
                
                setTimeout(window.ET.MCMF.Setup_remaining, 500);
            }
        },

        // Ready means the mod framework has been initialized, but Meteor is not yet available
        Ready: function(fnCallback, sNote)
        {
            if (!window.ET.MCMF.Initialized)
                window.ET.MCMF.EventSubscribe("initialized", fnCallback, sNote);
            else
                fnCallback();
        },

        // Loaded means the mod framework and Meteor are fully loaded and available
        Loaded: function(fnCallback, sNote)
        {
            if (!window.ET.MCMF.FinishedLoading)
                window.ET.MCMF.EventSubscribe("loaded", fnCallback, sNote);
            else
                fnCallback();
        },
    };

    window.ET.MCMF.Setup();
}
////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////
////////// ** CORE SCRIPT STARTUP -- DO NOT MODIFY ** //////////
function LoadJQ(callback) {
    if (window.jQ === undefined) { var script=document.createElement("script");script.setAttribute("src","//ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js");script.addEventListener('load',function() {
        var subscript=document.createElement("script");subscript.textContent="window.jQ=jQuery.noConflict(true);("+callback.toString()+")();";document.body.appendChild(subscript); },
    !1);document.body.appendChild(script); } else callback(); } LoadJQ(startup);
////////////////////////////////////////////////////////////////