Eternity Tower Stats UI

Adds stats to the UI for the Eternity Tower game

目前為 2018-08-15 提交的版本,檢視 最新版本

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

You will need to install an extension such as Tampermonkey to install this script.

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name          Eternity Tower Stats UI
// @icon          https://www.eternitytower.net/favicon.png
// @namespace     http://mean.cloud/
// @version       1.20
// @description   Adds stats to the UI for the Eternity Tower game
// @match         http*://*.eternitytower.net/*
// @author        [email protected]
// @copyright     2017-2018, MeanCloud
// @run-at        document-end
// ==/UserScript==


////////////////////////////////////////////////////////////////
////////////// ** SCRIPT GLOBAL INITIALIZATION ** //////////////
function startup() { ET_StatsUIMod(); }
ET_Stat_UserID = "";
ET_Stat_CombatID = "";
ET_Stat_CritChance = 0;
ET_Stat_CritDamage = 0;
ET_Stat_HealingPower = 0;
ET_Stat_DamageTaken = 0;
ET_Stat_MP = 0;
ET_JustExpanded = false;
ET_LastModal = null;
ET_LastForm = null;
////////////////////////////////////////////////////////////////

ET_StatsUIMod = function()
{
    ET.MCMF.Ready(function()
    {
        Meteor.connection._stream.on('message', function(sMeteorRawData)
        {
            try
            {
                var oMeteorData = JSON.parse(sMeteorRawData);

                if (oMeteorData.collection == "users")
                {
                    //console.log(oMeteorData);

                    ET_Stat_UserID = oMeteorData.id;
                }

                if (oMeteorData.collection == "combat")
                    if ((oMeteorData.fields.owner === ET_Stat_UserID) &&( ET_Stat_CombatID === ""))
                        ET_Stat_CombatID = oMeteorData.id;

                if (oMeteorData.collection == "combat")
                {
                    if ((oMeteorData.fields.owner === ET_Stat_UserID) || (oMeteorData.id === ET_Stat_CombatID))
                    {
                        //console.log(oMeteorData);

                        ET_Stat_CritChance = oMeteorData.fields.stats.criticalChance;
                        ET_Stat_CritDamage = oMeteorData.fields.stats.criticalDamage;
                        ET_Stat_HealingPower = oMeteorData.fields.stats.healingPower;
                        ET_Stat_DamageTaken = oMeteorData.fields.stats.damageTaken;
                        ET_Stat_MP = oMeteorData.fields.stats.magicPower;

                        var oHPEl = jQ("div.bg-danger").parent().parent().find("div.d-flex > i.extra-small-icon").parent();

                        /*
                        if (jQ("#PET_DemonMaxHP").length === 0)
                            oHPEl.append("<span id=\"PET_DemonMaxHP\" style=\"white-space: nowrap; font-size: 9pt;\"></span>");

                        jQ("#PET_DemonMaxHP").html("&nbsp;&nbsp;(demon &lt; " + ((oMeteorData.fields.stats.healthMax * 0.2) + 1).toFixed(0) + ")");
                        */
                    }
                }
            }
            catch (err) { }
        });
    });

    ET.MCMF.Loaded(function()
    {
    	// Set background tasks
        ET_StatsUIMod_DPSShow();
    });
};

ET_StatsUIMod_GetAdventure = function(sMatchID)
{
    // oAdventure
    //    .duration
    //    .endDate
    //    .floor
    //    .icon
    //    .id
    //    .length
    //    .level
    //    .name
    //    .room
    //    .type
    //    .startDate
    var oAdventureData = undefined;
    
    jQuery.makeArray(Object.keys(Package.meteor.global.Accounts.connection._stores.adventures._getCollection()._collection._docs._map).map(key => Package.meteor.global.Accounts.connection._stores.adventures._getCollection()._collection._docs._map[key])[0].adventures).forEach(function(oAdventure)
    {
        try
        {
            if (oAdventure.id === sMatchID)
                oAdventureData = oAdventure;
        }
        catch (err) { }
    });
    
    return oAdventureData;
};

var sETStatsDB = "";
var ETStatsDB_PQ = [];
var ETStatsDB_Tower = [];

ET_StatsUIMod_ConvertItemIDToImage = function(sItemID)
{
    var s = sItemID;

    var sExt = ".svg";
    
    if (endsWith(s, "_essence")) sExt = ".png";
    if (endsWith(s, "_shield")) sExt = ".png";
    if (endsWith(s, "_scimitar")) sExt = ".png";
    if (endsWith(s, "_broad_sword")) sExt = ".png";
    if (endsWith(s, "_plate")) sExt = ".png";
    if (s.indexOf("_wizard") !== -1) sExt = ".png";
    if (endsWith(s, "_battle_axe")) sExt = ".png";
    if (endsWith(s, "_long_sword")) sExt = ".png";
    if (endsWith(s, "_helm")) sExt = ".png";
    if (endsWith(s, "_knife")) sExt = ".png";
    if (endsWith(s, "_wand")) sExt = ".png";
    if (endsWith(s, "_spear")) sExt = ".png";
    if (endsWith(s, "_rapiers")) sExt = ".png";
    if (endsWith(s, "_dagger")) sExt = ".png";
    if (endsWith(s, "_log")) sExt = ".png";
    if (startsWith(s, "ore_")) { s = s.substr(4); sExt = ".png"; }
    if (startsWith(s, "poison_shard")) sExt = ".png";
    if (startsWith(s, "fire_shard")) sExt = ".png";
    if (startsWith(s, "earth_shard")) sExt = ".png";
    if (startsWith(s, "air_shard")) sExt = ".png";
    if (startsWith(s, "water_shard")) sExt = ".png";
    
    if (s.indexOf("_tome") !== -1) s = "tome";
    
    while (s.indexOf("_a") !== -1) s = s.replace("_a", "A");
    while (s.indexOf("_b") !== -1) s = s.replace("_b", "B");
    while (s.indexOf("_c") !== -1) s = s.replace("_c", "C");
    while (s.indexOf("_d") !== -1) s = s.replace("_d", "D");
    while (s.indexOf("_e") !== -1) s = s.replace("_e", "E");
    while (s.indexOf("_f") !== -1) s = s.replace("_f", "F");
    while (s.indexOf("_g") !== -1) s = s.replace("_g", "G");
    while (s.indexOf("_h") !== -1) s = s.replace("_h", "H");
    while (s.indexOf("_i") !== -1) s = s.replace("_i", "I");
    while (s.indexOf("_j") !== -1) s = s.replace("_j", "J");
    while (s.indexOf("_k") !== -1) s = s.replace("_k", "K");
    while (s.indexOf("_l") !== -1) s = s.replace("_l", "L");
    while (s.indexOf("_m") !== -1) s = s.replace("_m", "M");
    while (s.indexOf("_n") !== -1) s = s.replace("_n", "N");
    while (s.indexOf("_o") !== -1) s = s.replace("_o", "O");
    while (s.indexOf("_p") !== -1) s = s.replace("_p", "P");
    while (s.indexOf("_q") !== -1) s = s.replace("_q", "Q");
    while (s.indexOf("_r") !== -1) s = s.replace("_r", "R");
    while (s.indexOf("_s") !== -1) s = s.replace("_s", "S");
    while (s.indexOf("_t") !== -1) s = s.replace("_t", "T");
    while (s.indexOf("_u") !== -1) s = s.replace("_u", "U");
    while (s.indexOf("_v") !== -1) s = s.replace("_v", "V");
    while (s.indexOf("_w") !== -1) s = s.replace("_w", "W");
    while (s.indexOf("_x") !== -1) s = s.replace("_x", "X");
    while (s.indexOf("_y") !== -1) s = s.replace("_y", "Y");
    while (s.indexOf("_z") !== -1) s = s.replace("_z", "Z");
    s = s.split('_').join('');
    
    s = s + sExt;
    
    return s;
};

ET_StatsUIMod_GetDropsForTower = function(iFloor, iRoom)
{
    var sResults = "";
    
    try
    {
        if (ETStatsDB_Tower[iFloor][iRoom] !== undefined)
        {
            jQuery.makeArray(ETStatsDB_Tower[iFloor][iRoom]).forEach(function(dropInfo)
            {
                try
                {
                    //if (sResults !== "") sResults += ", ";
                    //sResults += dropInfo.name + " (" + dropInfo.percent.toString() + "%)";
                    
                    var s = ET_StatsUIMod_ConvertItemIDToImage(dropInfo.name);
                    
                    var sHover = dropInfo.name + " (" + dropInfo.percent.toString() + "%)";
                    //sResults += "<img src=\"/icons/" + s + "\" class=\"ml-1 extra-small-icon\" />";
                    sResults += "<img src=\"/icons/" + s + "\" style=\"width: 24px; height: 24px; font-size: 24px; line-height: 24px;\" alt=\"" + sHover + "\" title=\"" + sHover + "\">";
                }
                catch (err) { }
            });
        }
    }
    catch (err) { }
    
    return sResults;
};

ET_StatsUIMod_DPSShow = function()
{
	var i = 0;
	var oStatLines = null;
	var sBareDamageRange = "";
	var sBareAttackSpeed = "";
	var sBareCriticalChance = "";
	var dDamageMin = 0.0;
	var dDamageMax = 0.0;
	var sDamagePartMin = "";
	var sDamagePartMax = "";
	var dAverageDamageAvgQuality = 0.0;
	var dAverageDamageMaxQuality = 0.0;
	var sThisLine = "";
	var sThisLineText = "";
	var dAttackSpeed = 0.0;
	var dActualDPSAverageQuality = 0.0;
	var dActualDPSMaxQuality = 0.0;
	var dCriticalChance = 0.0;

    ///////////////////////////////////////////////////////////////////////////////////////
    //
    //  Adventure Details
    //
    if (sETStatsDB === "")
    {
        jQ.get("https://news.mediacentermaster.com/ETStats/", function(data)
        {
            // don't hold bad data
            if (data.indexOf("Level 50-54") === -1)
                return;
            
            sETStatsDB = data;

            //console.log("ET Stats loaded!");
            //console.log(sETStatsDB);
            
            var rawPQData = ChopperBlank(sETStatsDB, "<pre>", "Tower").trim().split("Level ");
            
            //ETStatsDB_Tower
            jQuery.makeArray(rawPQData).forEach(function(rawPQChunk)
            {
                //console.log("Raw PQ chunk: " + rawPQChunk.trim());

                var iRangeStart = 0;
                var iRangeEnd = 0;
                
                jQuery.makeArray(rawPQChunk.trim().split("\n")).forEach(function(rawPQLine, idx, arr)
                {
                    try
                    {
                        rawPQLine = rawPQLine.trim();
                        
                        //console.log(idx.toString() + " :: " + rawPQLine);
                        
                        if (idx === 0)
                        {
                            iRangeStart = CInt(ChopperBlank(rawPQLine, "",  "-"));
                            iRangeEnd   = CInt(ChopperBlank(rawPQLine, "-", " "));
                        }
                        else if ((iRangeStart > 0) && (iRangeEnd > 0))
                        {
                            var temp_Percent = CDbl(ChopperBlank(rawPQLine, "", "%"));
                            var temp_Name    = ChopperBlank(rawPQLine, " - ", " (").trim();
                            
                            var oDataObj = { name: temp_Name, percent: temp_Percent };
                            
                            for (var i = iRangeStart; i <= iRangeEnd; i++)
                            {
                                if (ETStatsDB_PQ[i] === undefined)
                                    ETStatsDB_PQ[i] = [];
                                
                                ETStatsDB_PQ[i].push(oDataObj);
                            }
                            
                            //console.log("PQ data recorded: L" + iRangeStart.toString() + "-L" + iRangeEnd.toString() + " " + JSON.stringify(oDataObj));
                        }
                    }
                    catch (err)
                    {
                        console.log("PQ line parse failure: " + JSON.stringify(err));
                    }
                });
            });            
                        
            var rawTowerData = ChopperBlank(sETStatsDB, "Tower F0R0", "</pre>").trim().split("Tower ");
            
            //ETStatsDB_Tower
            jQuery.makeArray(rawTowerData).forEach(function(rawTowerChunk)
            {
                var iFloor = CInt(ChopperBlank(rawTowerChunk, "F", "R"));
                var iRoom  = CInt(ChopperBlank(rawTowerChunk, "R", " - "));
                
                // don't hold data for full tower floor runs or any invalid data
                if ((iFloor === 0) || (iRoom === 0))
                    return;
                
                jQuery.makeArray(rawTowerChunk.trim().split("\n")).forEach(function(rawTowerLine, idx, arr)
                {
                    try
                    {
                        rawTowerLine = rawTowerLine.trim();
                        
                        //console.log(idx.toString() + " :: " + rawTowerLine);
                        
                        if (idx === 0)
                            return;
                        
                        var temp_Percent = CDbl(ChopperBlank(rawTowerLine, "", "%"));
                        var temp_Name    = ChopperBlank(rawTowerLine, " - ", " (").trim();
                        
                        var oDataObj = { name: temp_Name, percent: temp_Percent };
                        
                        if (ETStatsDB_Tower[iFloor] === undefined)
                            ETStatsDB_Tower[iFloor] = [];
                        if (ETStatsDB_Tower[iFloor][iRoom] === undefined)
                            ETStatsDB_Tower[iFloor][iRoom] = [];
                        
                        ETStatsDB_Tower[iFloor][iRoom].push(oDataObj);
                            
                        //console.log("Tower data recorded: F" + iFloor.toString() + "R" + iRoom.toString() + " " + JSON.stringify(oDataObj));
                    }
                    catch (err)
                    {
                        console.log("Tower line parse failure: " + JSON.stringify(err));
                    }
                });
            });
            
            for (var i = 0; i <= 1000; i++)
            {
                if (ETStatsDB_PQ[i] !== undefined)
                {
                    //console.log("PQ L" + i.toString() + " sorted!");
                    
                    ETStatsDB_PQ[i].sort(function(dropInfo_a, dropInfo_b)
                    {
                        if (dropInfo_a.percent > dropInfo_b.percent) return -1;
                        if (dropInfo_a.percent < dropInfo_b.percent) return 1;
                        return 0;
                    });
                }
            }
            
            for (var i = 0; i <= 30; i++)
            {
                if (ETStatsDB_Tower[i] !== undefined)
                {
                    for (var j = 0; j <= 30; j++)
                    {
                        if (ETStatsDB_Tower[i][j] !== undefined)
                        {
                            //console.log("Tower F" + i.toString() + "R" + j.toString() + " sorted!");
                            
                            ETStatsDB_Tower[i][j].sort(function(dropInfo_a, dropInfo_b)
                            {
                                if (dropInfo_a.percent > dropInfo_b.percent) return -1;
                                if (dropInfo_a.percent < dropInfo_b.percent) return 1;
                                return 0;
                            });
                        }
                    }
                }
            }
        });
    }
    
    jQ("div.adventure-item-container").each(function()
    {
        try
        {
            var sAdvID = jQ(this).find("button").attr("data-id");
            
            var oAdventureData = ET_StatsUIMod_GetAdventure(sAdvID);
            
            jQ(this).find("div.PETSDetails_error").remove();
            
            if (jQ(this).find("div.PETSDetails_success").length > 0)
                return;

            jQ(this).find("div.PETSDetails").remove();
            

            var sLoot = ET_StatsUIMod_GetDropsForTower(oAdventureData.floor, oAdventureData.room);
            
            /*
            if (sLoot !== "")
                jQ(this).append("<div class=\"d-flex PETSDetails\">" + sLoot + "</div>\r\n");
            else
                jQ(this).append("<div class=\"d-flex PETSDetails PETSDetails_err\"><i>no loot data available</i></div>\r\n");
            */
            
            if (sLoot !== "")
            {
                jQ(this).append("<div class=\"d-flex PETSDetails PETSDetails_success\"><div class=\"d-flex\" style=\"margin-left: 60px;\">" + sLoot + "</div></div>\r\n");
            }
            else
            {
                jQ(jQ(this).find("div.ml-3").get(0)).append("<div class=\"PETSDetails PETSDetails_error\">Loot From F" + oAdventureData.floor.toString() + "R" + oAdventureData.room.toString() + "</div>\r\n");
                jQ(jQ(this).find("div.mx-3").get(0)).append("<div class=\"PETSDetails PETSDetails_error\"><a target=\"_blank\" href=\"http://etstats.com/debug.html\">ETStats Loot List</a></div>\r\n");
            }
        }
        catch (err) { }
    });
    //
    ///////////////////////////////////////////////////////////////////////////////////////
    

    ///////////////////////////////////////////////////////////////////////////////////////
    //
    //  Stat Descriptions
    //
    jQ(".PeteUI_TooltipStat").remove();
    // Removed: this is baked into the actual game now
    /*
    jQ("div.item-tooltip-content div i.lilIcon-attackSpeed").parent().append("<span class=\"PeteUI_TooltipStat\">&nbsp;attacks per second</span>");
    jQ("div.item-tooltip-content div i.lilIcon-accuracy").parent().append("<span class=\"PeteUI_TooltipStat\">&nbsp;accuracy</span>");
    jQ("div.item-tooltip-content div i.lilIcon-criticalChance").parent().append("<span class=\"PeteUI_TooltipStat\">&nbsp;crit. chance</span>");
    jQ("div.item-tooltip-content div i.lilIcon-healthMax").parent().append("<span class=\"PeteUI_TooltipStat\">&nbsp;max. health</span>");
    jQ("div.item-tooltip-content div i.lilIcon-defense").parent().append("<span class=\"PeteUI_TooltipStat\">&nbsp;dodge (defense)</span>");
    jQ("div.item-tooltip-content div i.lilIcon-armor").parent().append("<span class=\"PeteUI_TooltipStat\">&nbsp;physical armor</span>");
    jQ("div.item-tooltip-content div i.lilIcon-magicPower").parent().append("<span class=\"PeteUI_TooltipStat\">&nbsp;magic power</span>");
    jQ("div.item-tooltip-content div i.lilIcon-magicArmor").parent().append("<span class=\"PeteUI_TooltipStat\">&nbsp;magic armor</span>");
    jQ("div.item-tooltip-content div i.lilIcon-healingPower").parent().append("<span class=\"PeteUI_TooltipStat\">&nbsp;increased healing %</span>");
    jQ("form.craft-amount-form div.modal-body > div i.lilIcon-attackSpeed").parent().append("<span class=\"PeteUI_TooltipStat\">&nbsp;attacks per second</span>");
    jQ("form.craft-amount-form div.modal-body > div i.lilIcon-accuracy").parent().append("<span class=\"PeteUI_TooltipStat\">&nbsp;accuracy</span>");
    jQ("form.craft-amount-form div.modal-body > div i.lilIcon-criticalChance").parent().append("<span class=\"PeteUI_TooltipStat\">&nbsp;crit. chance</span>");
    jQ("form.craft-amount-form div.modal-body > div i.lilIcon-healthMax").parent().append("<span class=\"PeteUI_TooltipStat\">&nbsp;max. health</span>");
    jQ("form.craft-amount-form div.modal-body > div i.lilIcon-defense").parent().append("<span class=\"PeteUI_TooltipStat\">&nbsp;dodge (defense)</span>");
    jQ("form.craft-amount-form div.modal-body > div i.lilIcon-armor").parent().append("<span class=\"PeteUI_TooltipStat\">&nbsp;physical armor</span>");
    jQ("form.craft-amount-form div.modal-body > div i.lilIcon-magicPower").parent().append("<span class=\"PeteUI_TooltipStat\">&nbsp;magic power</span>");
    jQ("form.craft-amount-form div.modal-body > div i.lilIcon-magicArmor").parent().append("<span class=\"PeteUI_TooltipStat\">&nbsp;magic armor</span>");
    jQ("form.craft-amount-form div.modal-body > div i.lilIcon-healingPower").parent().append("<span class=\"PeteUI_TooltipStat\">&nbsp;increased healing %</span>");
    */
    //
    ///////////////////////////////////////////////////////////////////////////////////////


    ///////////////////////////////////////////////////////////////////////////////////////
    //
    //  Woodcutters
    //    
    /*
    jQ("div.item-tooltip-content").each(function()
    {
        try
        {
            if (jQ(this).find("div.PeteUI_TooltipStatWC").length === 0)
            {
                if (jQ(this).html().toLowerCase().indexOf("lumber jack") !== -1)
                {
                    var dAttackSpeed = CDbl(ChopperBlank(jQ(this).html(), "<i class=\"lilIcon-attackSpeed extra-small-icon mr-1\"></i>", "<").trim());
                    var dAccuracy    = CDbl(ChopperBlank(jQ(this).html(), "<i class=\"lilIcon-accuracy extra-small-icon mr-1\"></i>", "<").trim());
                    var dRating      = dAttackSpeed * dAccuracy;
                    
                    jQ(this).find("div.popover-content").append("<div class=\"PeteUI_TooltipStatWC d-flex align-items-center\"><br /><br /><b>" + dRating.toString() + "</b>&nbsp;overall rating</div>\r\n");
                }
            }
        }
        catch (err) { }
    });
    */
    //    
    ///////////////////////////////////////////////////////////////////////////////////////
    
    ///////////////////////////////////////////////////////////////////////////////////////
    //
    //  Crafting Recipes
    //
	try
	{
		if (jQ("a.show-stats").length > 0)
		{
			ET_LastForm = jQ("a.show-stats").parent().parent();
			ET_LastModal = ET_LastForm.parent().parent().parent();

			jQ("a.show-stats")[0].click();
			ET_JustExpanded = true;
		}

		if ((ET_LastModal !== null) && (ET_LastModal.hasClass("show")) && (ET_JustExpanded === true))
		{
			var oForm = ET_LastForm;
			//oForm = jQ("form.craft-amount-form");

			if (oForm.html().indexOf("Hide Stat Range") !== -1)
			{
				oStatLines = oForm.find("div.modal-body > div");

				for (i = 1; i < oStatLines.length; i++)
				{
					sThisLine = jQ(oStatLines.get(i)).html().trim();
					sThisLineText = jQ(oStatLines.get(i)).text().trim();

					if ((sThisLine.indexOf("lilIcon-attack ") !== -1) && (sThisLine.indexOf(" - ") === -1))
						continue;

					if     ((sThisLine.indexOf("lilIcon-attack ")         !== -1) && (sBareDamageRange === "")) sBareDamageRange = sThisLineText + " [[#" + i.toString() + "]]";
					else if (sThisLine.indexOf("lilIcon-attackSpeed ")    !== -1) sBareAttackSpeed = sThisLineText + " [[#" + i.toString() + "]]";
					else if (sThisLine.indexOf("lilIcon-criticalChance ") !== -1) sBareCriticalChance = sThisLineText + " [[#" + i.toString() + "]]";
				}
                
				//console.log("******************BEGIN******************");
				//console.log("Bare DPS: " + sBareDamageRange);
				//console.log("Bare attack speed: " + sBareAttackSpeed);
				//console.log("Bare crit chance: " + sBareCriticalChance);
				//console.log("*******************END*******************");

                sBareDamageRange    = ChopperBlank(sBareDamageRange.replace(' attack', '').replace(' damage', '') + "\n", "", "\n").trim();
                sBareAttackSpeed    = ChopperBlank(sBareAttackSpeed.replace(' attack speed', '') + "\n", "", "\n").trim();
                sBareCriticalChance = ChopperBlank(sBareCriticalChance.replace(' critical chance', '') + "\n", "", "\n").trim();
                
				//console.log("******************BEGIN******************");
				//console.log("Bare DPS: " + sBareDamageRange);
				//console.log("Bare attack speed: " + sBareAttackSpeed);
				//console.log("Bare crit chance: " + sBareCriticalChance);
				//console.log("*******************END*******************");

				if (sBareDamageRange.indexOf("(") !== -1)
				{
					sDamagePartMin = ChopperBlank(sBareDamageRange, "", ") - (") + ")";
					sDamagePartMax = "(" + ChopperBlank(sBareDamageRange, ") - (", "");

					var dDamageRangeLow1 = CDbl(ChopperBlank(sDamagePartMin, "(", " - ").trim());
					var dDamageRangeLow2 = CDbl(ChopperBlank(sDamagePartMin, " - ", ")").trim());
					dDamageMin = (dDamageRangeLow1 + dDamageRangeLow2) / 2.0;
					var dDamageRangeHigh1 = CDbl(ChopperBlank(sDamagePartMax, "(", " - ").trim());
					var dDamageRangeHigh2 = CDbl(ChopperBlank(sDamagePartMax, " - ", ")").trim());
					dDamageMax = (dDamageRangeHigh1 + dDamageRangeHigh2) / 2.0;

					dAverageDamageMaxQuality = ((dDamageRangeHigh2 - dDamageRangeLow2) / 2.0) + dDamageRangeLow2;
				}
				else
				{
					sDamagePartMin = ChopperBlank(sBareDamageRange, "", " - ");
					sDamagePartMax = ChopperBlank(sBareDamageRange, " - ", "");

					dDamageMin = CDbl(sDamagePartMin);
					dDamageMax = CDbl(sDamagePartMax);

					dAverageDamageMaxQuality = ((dDamageMax - dDamageMin) / 2.0) + dDamageMin;
				}

				dAverageDamageAvgQuality = ((dDamageMax - dDamageMin) / 2.0) + dDamageMin;

				dAttackSpeed = CDbl(sBareAttackSpeed);
				dCriticalChance = CDbl(sBareCriticalChance);

				if (dCriticalChance > 0.0)
				{
					dAverageDamageAvgQuality += dAverageDamageAvgQuality * (dCriticalChance / 100.0);
					dAverageDamageMaxQuality += dAverageDamageMaxQuality * (dCriticalChance / 100.0);
				}

				dActualDPSAverageQuality = dAverageDamageAvgQuality * dAttackSpeed;
				dActualDPSMaxQuality = dAverageDamageMaxQuality * dAttackSpeed;

                if (!isNaN(dActualDPSAverageQuality))
                {
                    oForm.find("div.modal-body").append
                    (
                        "<div class=\"d-flex flex-wrap\"></div><b>Rated Damage at 50% Quality</b>\r\n" +
                        "<div class=\"d-flex flex-wrap\"><b class=\"lilIcon-attack extra-small-icon mx-1\"></b> " + dAverageDamageAvgQuality.toFixed(1) + " (per hit / base damage for abilities)</div>\r\n" +
                        "<div class=\"d-flex flex-wrap\"><b class=\"lilIcon-attackSpeed extra-small-icon mx-1\"></b> " + dActualDPSAverageQuality.toFixed(1) + " (per second / DPS)</div>\r\n" +
                        "<b>Rated Damage at 100% Quality</b>\r\n" +
                        "<div class=\"d-flex flex-wrap\"><b class=\"lilIcon-attack extra-small-icon mx-1\"></b> " + dAverageDamageMaxQuality.toFixed(1) + " (per hit / base damage for abilities)</div>\r\n" +
                        "<div class=\"d-flex flex-wrap\"><b class=\"lilIcon-attackSpeed extra-small-icon mx-1\"></b> " + dActualDPSMaxQuality.toFixed(1) + " (per second / DPS)</div>\r\n"
                    );
                }

				ET_JustExpanded = false;
			}
		}
	}
	catch (err) { console.log("ERROR: " + err); }
    //
    ///////////////////////////////////////////////////////////////////////////////////////


    ///////////////////////////////////////////////////////////////////////////////////////
    //
    //  Item Hover Tooltips (Combat > Equipment & Stats, viewing profiles, etc.)
    //
	try
	{
		jQ("div.item-tooltip-content").each(function()
        {
            if (jQ(this).find("div.PeteUI_TooltipExp").length !== 0)
				return;

			sBareDamageRange = "";
			sBareAttackSpeed = "";
			sBareAccuracy = "";
			sBareCriticalChance = "";
            sBareEnergyPerHit = "";
            sBareEnergyRegen = "";
            var bIsWoodcutter = false;

			oStatLines = jQ(this).find("div > div");

			for (i = 0; i < oStatLines.length; i++)
			{
				sThisLine = jQ(oStatLines.get(i)).html().trim();
				sThisLineText = jQ(oStatLines.get(i)).text().trim();

                //console.log("DEBUG::" + sThisLine);
                //console.log("DEBUG::" + sThisLineText);

				if      ((sThisLine.indexOf("lilIcon-attack ")         !== -1) && (sThisLine.indexOf(" - ") !== -1) && (sBareDamageRange === "")) sBareDamageRange = sThisLineText + " [[#" + i.toString() + "]]";
				else if ((sThisLine.indexOf("lilIcon-attack ")         !== -1) && (sThisLine.indexOf(" - ") === -1) && (sBareDamageRange === "")) { bIsWoodcutter = true; sBareDamageRange = sThisLineText + " - " + sThisLineText + " [[#" + i.toString() + "]]"; }
				else if  (sThisLine.indexOf("lilIcon-attackSpeed ")    !== -1) sBareAttackSpeed = sThisLineText + " [[#" + i.toString() + "]]";
				else if  (sThisLine.indexOf("lilIcon-accuracy ")       !== -1) sBareAccuracy = sThisLineText + " [[#" + i.toString() + "]]";
				else if  (sThisLine.indexOf("lilIcon-criticalChance ") !== -1) sBareCriticalChance = sThisLineText + " [[#" + i.toString() + "]]";
				else if  (sThisLine.indexOf("lilIcon-energyPerHit ")   !== -1) sBareEnergyPerHit = sThisLineText + " [[#" + i.toString() + "]]";
				else if  (sThisLine.indexOf("lilIcon-energyRegen ")    !== -1) sBareEnergyRegen = sThisLineText + " [[#" + i.toString() + "]]";
			}

            //console.log("******************BEGIN******************");
            //console.log("Bare DPS: " + sBareDamageRange);
            //console.log("Bare attack speed: " + sBareAttackSpeed);
            //console.log("Bare accuracy: " + sBareAccuracy);
            //console.log("Bare crit chance: " + sBareCriticalChance);
            //console.log("Bare energy per hit: " + sBareEnergyPerHit);
            //console.log("Bare energy regen: " + sBareEnergyRegen);
            //console.log("*******************END*******************");

            sBareDamageRange    = ChopperBlank(sBareDamageRange.replace(' damage', '').replace(' damage', '') + "\n", "", "\n").trim();
            sBareAttackSpeed    = ChopperBlank(sBareAttackSpeed.replace(' attack speed', '') + "\n", "", "\n").trim();
            sBareAccuracy       = ChopperBlank(sBareAccuracy.replace(' accuracy', '').trim() + " ", "", " ").trim();
            sBareCriticalChance = ChopperBlank(sBareCriticalChance.replace(' critical chance', '') + "\n", "", "\n").trim();
            sBareEnergyPerHit   = ChopperBlank(sBareEnergyPerHit.replace(' energy per hit', '') + "\n", "", "\n").trim();
            sBareEnergyRegen    = ChopperBlank(sBareEnergyRegen.replace(' energy regen', '') + "\n", "", "\n").trim();
            
            //console.log("******************BEGIN******************");
            //console.log("Bare DPS: " + sBareDamageRange);
            //console.log("Bare attack speed: " + sBareAttackSpeed);
            //console.log("Bare accuracy: " + sBareAccuracy);
            //console.log("Bare crit chance: " + sBareCriticalChance);
            //console.log("Bare energy per hit: " + sBareEnergyPerHit);
            //console.log("Bare energy regen: " + sBareEnergyRegen);
            //console.log("*******************END*******************");

			sDamagePartMin = ChopperBlank(sBareDamageRange, "", " - ");
			sDamagePartMax = ChopperBlank(sBareDamageRange, " - ", "");

			dDamageMin = CDbl(sDamagePartMin);
			dDamageMax = CDbl(sDamagePartMax);

			dAverageDamageAvgQuality = ((dDamageMax - dDamageMin) / 2.0) + dDamageMin;

			dAttackSpeed = CDbl(sBareAttackSpeed);
			dCriticalChance = CDbl(sBareCriticalChance);

			if (dCriticalChance > 0.0)
				dAverageDamageAvgQuality += dAverageDamageAvgQuality * (dCriticalChance / 100.0);

			dActualDPSAverageQuality = dAverageDamageAvgQuality * dAttackSpeed;

            if (!isNaN(dActualDPSAverageQuality) && !bIsWoodcutter)
            {
                if (jQ(this).find("div.PeteUI_TooltipExp").length === 0)
                {
                    jQ(jQ(this).find("div").get(0)).append
                    (
                        "<div class=\"PeteUI_TooltipExp\"><div class=\"d-flex flex-wrap\"></div><b>Rated Damage</b>\r\n" +
                        "<div class=\"d-flex flex\" style=\"white-space: nowrap;\"><b class=\"lilIcon-attack extra-small-icon mx-1\"></b> " + dAverageDamageAvgQuality.toFixed(1) + " (per hit / base damage)</div>\r\n" +
                        "<div class=\"d-flex flex\" style=\"white-space: nowrap;\"><b class=\"lilIcon-attackSpeed extra-small-icon mx-1\"></b> " + dActualDPSAverageQuality.toFixed(1) + " (per second / DPS)</div></div>\r\n"
                    );
                }
            }
            else if (CDbl(sBareEnergyPerHit) > 0.0)
            {
                if (jQ(this).find("div.PeteUI_TooltipExp").length === 0)
                {
                    dActualDPSAverageQuality = dAverageDamageAvgQuality / CDbl(sBareEnergyPerHit) * CDbl(sBareEnergyRegen);
                    
                    jQ(jQ(this).find("div").get(0)).append
                    (
                        "<div class=\"PeteUI_TooltipExp\"><div class=\"d-flex flex-wrap\"></div><b>Rated Efficiency</b>\r\n" +
                        "<div class=\"d-flex flex\" style=\"white-space: nowrap;\"><b class=\"lilIcon-mining extra-small-icon mx-1\"></b> " + dActualDPSAverageQuality.toFixed(1) + " rating</div>\r\n"
                    );
                }
            }
            else if ((dAttackSpeed > 0.0) && (CDbl(sBareAccuracy) > 0.0) && (bIsWoodcutter))
            {
                if (jQ(this).find("div.PeteUI_TooltipExp").length === 0)
                {
                    dActualDPSAverageQuality = CDbl(sBareAccuracy) * dAttackSpeed;
                    
                    var sTier = "maple logs (only)";
                    if (CInt(dAverageDamageAvgQuality) === 5) sTier = "beech logs (and lower)";
                    else if (CInt(dAverageDamageAvgQuality) === 10) sTier = "ash logs (and lower)";
                    else if (CInt(dAverageDamageAvgQuality) === 15) sTier = "oak logs (and lower)";
                    else if (CInt(dAverageDamageAvgQuality) === 20) sTier = "maple logs (and lower)";
                    else if (CInt(dAverageDamageAvgQuality) === 25) sTier = "walnut logs (and lower)";
                    else if (CInt(dAverageDamageAvgQuality) === 30) sTier = "cherry logs (and lower)";
                    else if (CInt(dAverageDamageAvgQuality) === 35) sTier = "mahogany logs (and lower)";
                    else if (CInt(dAverageDamageAvgQuality) === 40) sTier = "elm logs (and lower)";
                    else if (CInt(dAverageDamageAvgQuality) === 45) sTier = "black logs (and lower)";
                    else if (CInt(dAverageDamageAvgQuality) === 50) sTier = "blue gum logs (and lower)";
                    else if (CInt(dAverageDamageAvgQuality) === 55) sTier = "cedar logs (and lower)";
                    else if (CInt(dAverageDamageAvgQuality) === 60) sTier = "denya logs (and lower)";
                    else if (CInt(dAverageDamageAvgQuality) === 65) sTier = "gombe logs (and lower)";
                    else if (CInt(dAverageDamageAvgQuality) === 70) sTier = "hickory logs (and lower)";
                    else if (CInt(dAverageDamageAvgQuality) === 75) sTier = "larch logs (and lower)";
                    else if (CInt(dAverageDamageAvgQuality) === 80) sTier = "poplar logs (and lower)";
                    else if (CInt(dAverageDamageAvgQuality) === 85) sTier = "tali logs (and lower)";
                    else if (CInt(dAverageDamageAvgQuality) === 90) sTier = "willow logs (and lower)";
                    else if (CInt(dAverageDamageAvgQuality) === 95) sTier = "teak logs (and lower)";
                    
                    jQ(jQ(this).find("div").get(0)).append
                    (
                        "<div class=\"PeteUI_TooltipExp\"><div class=\"d-flex flex-wrap\"></div><br /><b>Rated Efficiency</b>\r\n" +
                        "<div class=\"d-flex flex\" style=\"white-space: nowrap;\"><b class=\"lilIcon-woodcutting extra-small-icon mx-1\"></b> " + dActualDPSAverageQuality.toFixed(1) + " rating</div>\r\n" + 
                        "<div class=\"d-flex flex\" style=\"white-space: nowrap;\"><b class=\"lilIcon-woodcutter extra-small-icon mx-1\"></b> chops " + sTier + "</div>\r\n"                        
                    );
                }
            }
		});
	}
	catch (err) { console.log("ERROR: " + err); }
    //
    ///////////////////////////////////////////////////////////////////////////////////////


    ///////////////////////////////////////////////////////////////////////////////////////
    //
    //  Combat Stats page (personal)
    //
	try
	{
		if (jQ("div.stats-row").length > 0)
		{
			sBareDamageRange = CondenseSpacing(jQ(jQ("div.stats-row").find("div > div.col > div").get(0)).text().trim().replaceAll('\r', ' ').replaceAll('\n', ' '));
            //console.log("Bare damage range: " + sBareDamageRange);

			sDamagePartMin = ChopperBlank(sBareDamageRange, "", " - ");
			sDamagePartMax = ChopperBlank(sBareDamageRange, " - ", "");

			dDamageMin = CDbl(sDamagePartMin);
			dDamageMax = CDbl(sDamagePartMax);

			dAverageDamageAvgQuality = ((dDamageMax - dDamageMin) / 2.0) + dDamageMin;

            sBareAttackSpeed = jQ(jQ("div.stats-row").find("div > div.col > div").get(1)).text().trim();
            //console.log("Bare attack speed: " + sBareAttackSpeed);

            dAttackSpeed = CDbl(sBareAttackSpeed);
			dCriticalChance = CDbl(ET_Stat_CritChance);

			if (dCriticalChance > 0.0)
				dAverageDamageAvgQuality += dAverageDamageAvgQuality * (dCriticalChance / 100.0); // note: not using ET_Stat_CritDamage!

			dActualDPSAverageQuality = dAverageDamageAvgQuality * dAttackSpeed;

            //console.log(dActualDPSAverageQuality.toFixed(1));

            if (!isNaN(dActualDPSAverageQuality))
            {
                jQ(".PeteUI_CombatStat").remove();

                jQ(jQ("div.stats-row").find("div > div.col > div").get(0)).append("<span class=\"PeteUI_CombatStat\">&nbsp;(" + dAverageDamageAvgQuality.toFixed(1) + " average)</span>");
                jQ(jQ("div.stats-row").find("div > div.col > div").get(1)).append("<span class=\"PeteUI_CombatStat\">&nbsp;attacks per second (" + dActualDPSAverageQuality.toFixed(1) + " DPS)</span>");
                jQ(jQ("div.stats-row").find("div > div.col > div").get(2)).append("<span class=\"PeteUI_CombatStat\">&nbsp;magic power</span>");
                jQ(jQ("div.stats-row").find("div > div.col > div").get(3)).append("<span class=\"PeteUI_CombatStat\">&nbsp;accuracy</span>");
                jQ(jQ("div.stats-row").find("div > div.col > div").get(4)).append("<span class=\"PeteUI_CombatStat\">&nbsp;health</span>");
                jQ(jQ("div.stats-row").find("div > div.col > div").get(5)).append("<span class=\"PeteUI_CombatStat\">&nbsp;dodge (defense)</span>");
                jQ(jQ("div.stats-row").find("div > div.col > div").get(6)).append("<span class=\"PeteUI_CombatStat\">&nbsp;physical armor</span>");
                jQ(jQ("div.stats-row").find("div > div.col > div").get(7)).append("<span class=\"PeteUI_CombatStat\">&nbsp;magic armor</span>");

                if (CDbl(ET_Stat_CritChance) > 0.0)
                    jQ(jQ("div.stats-row").find("div > div.col").get(0)).append("<div class=\"d-flex flex-row mb-1 PeteUI_CombatStat\">\r\n<div class=\"d-flex align-items-center attack-tooltip-container drop-target drop-abutted drop-abutted-left drop-abutted-top drop-element-attached-bottom drop-element-attached-left drop-target-attached-top drop-target-attached-left\">\r\n<img src=\"/icons/criticalChance.svg\" class=\"extra-small-icon\">\r\n</div>\r\n<div class=\"d-flex align-items-center ml-1\">\r\n" + ET_Stat_CritChance.toFixed(1) + " critical chance for " + ET_Stat_CritDamage + "x damage\r\n</div>\r\n");
                else
                    jQ(jQ("div.stats-row").find("div > div.col").get(0)).append("<div class=\"d-flex flex-row mb-1 PeteUI_CombatStat\">\r\n<div class=\"d-flex align-items-center attack-tooltip-container drop-target drop-abutted drop-abutted-left drop-abutted-top drop-element-attached-bottom drop-element-attached-left drop-target-attached-top drop-target-attached-left\">\r\n<img src=\"/icons/criticalChance.svg\" class=\"extra-small-icon\">\r\n</div>\r\n<div class=\"d-flex align-items-center ml-1\">\r\nno critical chance\r\n</div>\r\n");

                if (CDbl(ET_Stat_HealingPower) !== 0.0)
                    jQ(jQ("div.stats-row").find("div > div.col").get(1)).append("<div class=\"d-flex flex-row mb-1 PeteUI_CombatStat\">\r\n<div class=\"d-flex align-items-center attack-tooltip-container drop-target drop-abutted drop-abutted-left drop-abutted-top drop-element-attached-bottom drop-element-attached-left drop-target-attached-top drop-target-attached-left\">\r\n<img src=\"/icons/healingPower.svg\" class=\"extra-small-icon\">\r\n</div>\r\n<div class=\"d-flex align-items-center ml-1\">\r\n" + ET_Stat_HealingPower.toFixed(1) + "% " + ((CDbl(ET_Stat_HealingPower) > 0.0) ? "increased" : "lowered") + " healing\r\n</div>\r\n");
                else
                    jQ(jQ("div.stats-row").find("div > div.col").get(1)).append("<div class=\"d-flex flex-row mb-1 PeteUI_CombatStat\">\r\n<div class=\"d-flex align-items-center attack-tooltip-container drop-target drop-abutted drop-abutted-left drop-abutted-top drop-element-attached-bottom drop-element-attached-left drop-target-attached-top drop-target-attached-left\">\r\n<img src=\"/icons/healingPower.svg\" class=\"extra-small-icon\">\r\n</div>\r\n<div class=\"d-flex align-items-center ml-1\">\r\nnormal healing\r\n</div>\r\n");

                //jQ(jQ("div.stats-row").find("div > div.col").get(0)).append("<div class=\"d-flex flex-row mb-1 PeteUI_CombatStat\">\r\n<div class=\"d-flex align-items-center attack-tooltip-container drop-target drop-abutted drop-abutted-left drop-abutted-top drop-element-attached-bottom drop-element-attached-left drop-target-attached-top drop-target-attached-left\">\r\n<img src=\"/icons/doubleEdgedSword.svg\" class=\"extra-small-icon\">\r\n</div>\r\n<div class=\"d-flex align-items-center ml-1\">\r\n" + (dDamageMax * 1.5 * 1.7 * 2.5 * 5.0).toFixed(0) + " DE damage vs. 0 armor\r\n</div>\r\n");
                //jQ(jQ("div.stats-row").find("div > div.col").get(1)).append("<div class=\"d-flex flex-row mb-1 PeteUI_CombatStat\">\r\n<div class=\"d-flex align-items-center ml-1\">\r\n(using demon curse, war cry, and berserk)\r\n</div>\r\n");
                jQ(jQ("div.stats-row").find("div > div.col").get(0)).append("<div class=\"d-flex flex-row mb-1 PeteUI_CombatStat\">\r\n<div class=\"d-flex align-items-center attack-tooltip-container drop-target drop-abutted drop-abutted-left drop-abutted-top drop-element-attached-bottom drop-element-attached-left drop-target-attached-top drop-target-attached-left\">\r\n<img src=\"/icons/doubleEdgedSword.svg\" class=\"extra-small-icon\">\r\n</div>\r\n<div class=\"d-flex align-items-center ml-1\">\r\n" + (dDamageMax * 1.5 * 1.7 * 5.0).toFixed(0) + " DE damage vs. 0 armor\r\n</div>\r\n");
                jQ(jQ("div.stats-row").find("div > div.col").get(1)).append("<div class=\"d-flex flex-row mb-1 PeteUI_CombatStat\">\r\n<div class=\"d-flex align-items-center ml-1\">\r\n(using L.5 war cry, berserk, and DE)\r\n</div>\r\n");
                jQ(jQ("div.stats-row").find("div > div.col").get(0)).append("<div class=\"d-flex flex-row mb-1 PeteUI_CombatStat\">\r\n<div class=\"d-flex align-items-center attack-tooltip-container drop-target drop-abutted drop-abutted-left drop-abutted-top drop-element-attached-bottom drop-element-attached-left drop-target-attached-top drop-target-attached-left\">\r\n<img src=\"/icons/airDart.svg\" class=\"extra-small-icon\">\r\n</div>\r\n<div class=\"d-flex align-items-center ml-1\">\r\n" + ((1.10 * ET_Stat_MP) + 1).toFixed(0) + " air dart armor reduction\r\n</div>\r\n");
                jQ(jQ("div.stats-row").find("div > div.col").get(1)).append("<div class=\"d-flex flex-row mb-1 PeteUI_CombatStat\">\r\n<div class=\"d-flex align-items-center attack-tooltip-container drop-target drop-abutted drop-abutted-left drop-abutted-top drop-element-attached-bottom drop-element-attached-left drop-target-attached-top drop-target-attached-left\">\r\n<img src=\"/icons/airBall.svg\" class=\"extra-small-icon\">\r\n</div>\r\n<div class=\"d-flex align-items-center ml-1\">\r\n" + ((1.60 * ET_Stat_MP) + 10).toFixed(0) + " air ball armor reduction\r\n</div>\r\n");
                jQ(jQ("div.stats-row").find("div > div.col").get(0)).append("<div class=\"d-flex flex-row mb-1 PeteUI_CombatStat\">\r\n<div class=\"d-flex align-items-center attack-tooltip-container drop-target drop-abutted drop-abutted-left drop-abutted-top drop-element-attached-bottom drop-element-attached-left drop-target-attached-top drop-target-attached-left\">\r\n<img src=\"/icons/lightningDart.svg\" class=\"extra-small-icon\">\r\n</div>\r\n<div class=\"d-flex align-items-center ml-1\">\r\n" + ((0.90 * ET_Stat_MP) + 2).toFixed(0) + " lightning dart armor reduction\r\n</div>\r\n");
            }
		}
	}
	catch (err) { console.log("ERROR: " + err); }
    //
    ///////////////////////////////////////////////////////////////////////////////////////

	setTimeout(ET_StatsUIMod_DPSShow, 1000);
};


////////////////////////////////////////////////////////////////
/////////////// ** 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)
{
    if (oEl === undefined)
    {
        var shiftclick = jQ.Event("click");
        shiftclick.shiftKey = true;

        var shiftclickOrig = jQ.Event("click");
        shiftclickOrig.shiftKey = true;

        shiftclick.originalEvent = shiftclick;
        return shiftclick;
    }

    jQ(oEl).trigger(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);
		var 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);
		var 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); };

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

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

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

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

	var iIndexEnd = sIntermediate.indexOf(sEnd);

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

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

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

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) || !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(elementFromPoint(rect.left + elem.offsetWidth/2, rect.top + elem.offsetHeight/2),elem)) return false;
        } else if (
            !inside(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 || documentElement.clientHeight) ||
                rect.right - elem.offsetWidth/2 > (window.innerWidth || 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) // MeanCloud mod framework
{
    window.ET.MCMF =
    {
        TryingToLoad: false,
        WantDebug: false,
        WantFasterAbilityCDs: false,

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

        CombatID: undefined, // technically not required

        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 = [];

            var 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, index, array)
                {
                    if (sEventName === oThisEvent.name)
                    {
                        if (window.ET.MCMF.WantDebug) console.log("FIRING '" + oThisEvent.name + "'!" + ((oThisEvent.note === undefined) ? "" : " (" + oThisEvent.note + ")"));
                        oThisEvent.callback();
                    }
                });
            });
            */

            if (window.ET.MCMF.WantDebug) console.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, index, array)
            {
                if (sEventName === oThisEvent.name)
                {
                    if (window.ET.MCMF.WantDebug) console.log("FIRING '" + oThisEvent.name + "'!" + ((oThisEvent.note === undefined) ? "" : " (" + oThisEvent.note + ")"));
                    try { oThisEvent.callback(); } catch (err) { if (window.ET.MCMF.WantDebug) console.log("Exception: " + err); }
                }
            });
        },

        MeteorCall: function(sMethod, oParam1, oParam2, sMsgSuccess, sMsgFailure)
        {
            Package.meteor.Meteor.call("crafting.craftItem", sRecipeID, iBatchAmt, function(errResp)
            {
                if (errResp)
                    window.ET.MCMF.ToastMessageWarning(sMsgFailure);
                else
                    window.ET.MCMF.ToastMessageSuccess(sMsgSuccess);
            });
        },

        FasterAbilityUpdates: function()
        {
            try
            {
                if ((window.ET.MCMF.WantFasterAbilityCDs) && (window.ET.MCMF.FinishedLoading) && (!window.ET.MCMF.InBattle) && (!window.ET.MCMF.AbilitiesReady))
                    Meteor.call("abilities.gameUpdate", function(e, t) { });
            }
            catch (err) { }

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

        AbilityCDTrigger: function()
        {
            try
            {
                bStillInCombat = window.ET.MCMF.InBattle || ((time_val() - window.ET.MCMF.TimeLastFight) < 3);

                if ((window.ET.MCMF.FinishedLoading) && (!bStillInCombat))
                {
                    iTotalCD = 0;
                    iTotalCDTest = 0;
                    iHighestCD = 0;

                    window.ET.MCMF.GetAbilities().forEach(function(oThisAbility, index, array)
                    {
                        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) console.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;
                }
                else
                {
                    window.ET.MCMF.AbilitiesReady = false;
                    window.ET.MCMF.TimeLeftOnCD = 9999;
                }
            }
            catch (err) { }

            setTimeout(window.ET.MCMF.AbilityCDTrigger, 500);
        },

        InitMeteorTriggers: function()
        {
            if ((Package.meteor.Meteor === undefined) || (Package.meteor.Meteor.connection === undefined) || (Package.meteor.Meteor.connection._stream === undefined))
            {
                setTimeout(window.ET.MCMF.InitMeteorTriggers, 100);
                return;
            }

            Package.meteor.Meteor.connection._stream.on('message', function(sMeteorRawData)
            {
                if (window.ET.MCMF.CombatID === undefined)
                {
                    try
                    {
                        oDataTemp = Package.meteor.global.Accounts.connection._stores.combat._getCollection()._collection._docs._map;
                        window.ET.MCMF.CombatID = oDataTemp[Object.keys(oDataTemp)[0]]._id;
                    }
                    catch (err) { }
                }

                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.T_CombatID === undefined) || (window.ET.MCMF.CombatID.length !== 17))
                            if (oMeteorData.fields.owner === window.ET.MCMF.UserID)
                                window.ET.MCMF.CombatID = oMeteorData.id;
                    //
                    /////////////////////////////////////////////////////////////////////////////////////////////////////////

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

                            if ((oMeteorData.msg === "added") || (oMeteorData.msg === "removed"))
                            {
                                window.ET.MCMF.InBattle = (oMeteorData.msg === "added");
                                if (window.ET.MCMF.WantDebug) console.log("<-- triggering ET:combat" + (((oMeteorData.msg === "added")) ? ("Start") : ("End")) + " -->");
                                window.ET.MCMF.EventTrigger("ET:combat" + (((oMeteorData.msg === "added")) ? ("Start") : ("End")));
                                //jQ("div#ET_meancloud_bootstrap").trigger("ET:combat" + (((oMeteorData.msg === "added")) ? ("Start") : ("End")));
                            }
                        }

                        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 (!oMeteorData.fields.win)
                                    window.ET.MCMF.HP = 0;

                                if (window.ET.MCMF.WantDebug) console.log("<-- triggering ET:combat" + ((oMeteorData.fields.win) ? ("Won") : ("Lost")) + " -->");
                                window.ET.MCMF.EventTrigger("ET:combat" + ((oMeteorData.fields.win) ? ("Won") : ("Lost")));
                                //jQ("div#ET_meancloud_bootstrap").trigger("ET:combat" + ((oMeteorData.fields.win) ? ("Won") : ("Lost")));
                            }
                        }
                    }

                    try
                    {
                        if (window.ET.MCMF.FinishedLoading)
                        {
                            if (oMeteorData.id)
                            {
                                if (oMeteorData.id.startsWith("battles-"))
                                {
                                    if (oMeteorData.msg !== "removed")
                                    {
                                        battleID = oMeteorData.id;
                                        battleData = JSON.parse(oMeteorData.fields.value);

                                        jQ.makeArray(battleData.units).forEach(function(currentPlayer, index, array)
                                        {
                                            try
                                            {
                                                if (currentPlayer.name === window.ET.MCMF.UserName)
                                                {
                                                    jQ.makeArray(currentPlayer.buffs).forEach(function(currentBuff, index2, array2)
                                                     {
                                                        try
                                                        {
                                                            if (currentBuff.id === "demons_heart")
                                                            {
                                                                if (currentBuff.data.active)
                                                                {
                                                                    if (!window.ET.MCMF.IsDemon)
                                                                    {
                                                                        window.ET.MCMF.IsDemon = true;

                                                                        if (window.ET.MCMF.WantDebug) console.log("<-- triggering ET:combat:buffDemon -->");
                                                                        window.ET.MCMF.EventTrigger("ET:combat:buffDemon");
                                                                        //jQ("div#ET_meancloud_bootstrap").trigger("ET:combat:buffDemon");
                                                                    }
                                                                }
                                                            }
                                                        }
                                                        catch (err) { }
                                                    });

                                                    return true; // break out of forEach()
                                                }
                                            }
                                            catch (err) { }
                                        });
                                    }
                                }
                            }
                        }
                    }
                    catch (err) { }
                }
                catch (err) { }

                try
                {
                    //todo: use life data from Meteor vs captured meteor response data
                    oMeteorData = JSON.parse(sMeteorRawData);

                    if (oMeteorData.collection === "combat")
                    {
                        if ((oMeteorData.fields.owner === window.ET.MCMF.UserID) || (oMeteorData.id === window.ET.MCMF.CombatID))
                        {
                            window.ET.MCMF.HP  = oMeteorData.fields.stats.health;
                            window.ET.MCMF.NRG = oMeteorData.fields.stats.energy;
                        }
                    }
                }
                catch (err) { }
            });
        },

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

            window.ET.MCMF.GetAbilities().forEach(function(oThisAbility, index, array)
            {
                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) console.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;
        },

        GetAbilities: function()
        {
            return Object.keys(window.ET.MCMF.AbilityManager._collection._docs._map).map(key => window.ET.MCMF.AbilityManager._collection._docs._map[key])[0].learntAbilities;
        },

        GetAdventures: function()
        {
            return Object.keys(window.ET.MCMF.AdventureManager._collection._docs._map).map(key => window.ET.MCMF.AdventureManager._collection._docs._map[key])[0].adventures;
        },

        GetChats: function()
        {
            return Object.keys(window.ET.MCMF.ChatManager._collection._docs._map).map(key => window.ET.MCMF.ChatManager._collection._docs._map[key]);
        },

        GetItems: function()
        {
            return Object.keys(window.ET.MCMF.ItemManager._collection._docs._map).map(key => window.ET.MCMF.ItemManager._collection._docs._map[key]);
        },

        // 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.InitMeteorTriggers();
                window.ET.MCMF.Setup_remaining();
            }
        },

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

        Setup_remaining: function()
        {
            try
            {
                window.ET.MCMF.UserID = Package.meteor.global.Accounts.connection._userId;
                window.ET.MCMF.UserName = Package.meteor.global.Accounts.connection._stores.users._getCollection()._collection._docs._map[Package.meteor.global.Accounts.connection._userId].username;
                try
                {
                    oDataTemp = Package.meteor.global.Accounts.connection._stores.combat._getCollection()._collection._docs._map;
                    window.ET.MCMF.CombatID = oDataTemp[Object.keys(oDataTemp)[0]]._id;
                }
                catch (err) { }

                window.ET.MCMF.AbilityManager = Package.meteor.global.Accounts.connection._stores.abilities._getCollection();
                window.ET.MCMF.AdventureManager = Package.meteor.global.Accounts.connection._stores.adventures._getCollection();
                window.ET.MCMF.ChatManager = Package.meteor.global.Accounts.connection._stores.simpleChats._getCollection();
                window.ET.MCMF.ItemManager = Package.meteor.global.Accounts.connection._stores.items._getCollection();

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

                // 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) console.log("<-- triggering ET:loaded -->");
                    window.ET.MCMF.EventTrigger("ET:loaded");
                    //jQ("div#ET_meancloud_bootstrap").trigger("ET:loaded");
                    window.ET.MCMF.FinishedLoading = true;
                }
            }
            catch (err)
            {
                // any errors and we retry setup
                setTimeout(window.ET.MCMF.Setup_remaining, 500);
            }
        },

        Loaded: function(fnCallback, sNote)
        {
            if (!window.ET.MCMF.FinishedLoading)
                window.ET.MCMF.EventSubscribe("loaded", fnCallback, sNote);
            else
                fnCallback();
        },

        Ready: function(fnCallback, sNote)
        {
            if (!window.ET.MCMF.Initialized)
                window.ET.MCMF.EventSubscribe("initialized", 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);
////////////////////////////////////////////////////////////////