Pete's Eternity Tower Stats UI

Adds stats to the UI for the Eternity Tower game

As of 2017-11-13. See the latest version.

// ==UserScript==
// @name          Pete's Eternity Tower Stats UI
// @icon          https://www.eternitytower.net/favicon.png
// @namespace     http://mean.cloud/
// @version       1.07
// @description   Adds stats to the UI for the Eternity Tower game
// @match         http*://*.eternitytower.net/*
// @copyright     2017, MeanCloud
// @run-at        document-end
// ==/UserScript==

function addJQuery(callback)
{
    var script = document.createElement("script");
    script.setAttribute("src", "//ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js");
    script.addEventListener('load', function()
    {
        var script = document.createElement("script");
        script.textContent = "window.jQ=jQuery.noConflict(true);(" + callback.toString() + ")();";
        document.body.appendChild(script);
    }, false);
    document.body.appendChild(script);
}

document.ET_Stat_UserID = "";
document.ET_Stat_CombatID = "";
document.ET_Stat_CritChance = 0;
document.ET_Stat_CritDamage = 0;
document.ET_Stat_HealingPower = 0;
document.ET_Stat_DamageTaken = 0;

function main()
{
	// Set background tasks
	setTimeout(document.ET_fnDPSShow, 1000);

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

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

                document.ET_Stat_UserID = oMeteorData.id;
            }

            if (oMeteorData.collection == "combat")
            {
                if ((oMeteorData.fields.owner == document.ET_Stat_UserID) || (document.ET_Stat_UserID === ""))
                {
                    document.ET_Stat_CombatID = oMeteorData.id;
                }
            }

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

                    document.ET_Stat_CritChance = oMeteorData.fields.stats.criticalChance;
                    document.ET_Stat_CritDamage = oMeteorData.fields.stats.criticalDamage;
                    document.ET_Stat_HealingPower = oMeteorData.fields.stats.healingPower;
                    document.ET_Stat_DamageTaken = oMeteorData.fields.stats.damageTaken;
                }
            }
        }
        catch (err) { }
    });
}

document.ET_JustExpanded = false;
document.ET_LastModal = null;
document.ET_LastForm = null;

document.ET_fnDPSShow = 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;


    ///////////////////////////////////////////////////////////////////////////////////////
    //
    //  Stat Descriptions
    //
    jQ(".PeteUI_TooltipStat").remove();
    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>");
    //
    ///////////////////////////////////////////////////////////////////////////////////////


    ///////////////////////////////////////////////////////////////////////////////////////
    //
    //  Crafting Recipes
    //
	try
	{
		if (jQ("a.show-stats").length > 0)
		{
			document.ET_LastForm = jQ("a.show-stats").parent().parent();
			document.ET_LastModal = document.ET_LastForm.parent().parent().parent();

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

		if ((document.ET_LastModal !== null) && (document.ET_LastModal.hasClass("show")) && (document.ET_JustExpanded === true))
		{
			var oForm = document.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*******************");

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

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

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

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

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

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

				dAttackSpeed = document.CDbl(sBareAttackSpeed);
				dCriticalChance = document.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"
                    );
                }

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


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

			for (i = 0; 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*******************");

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

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

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

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

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

			dActualDPSAverageQuality = dAverageDamageAvgQuality * dAttackSpeed;

            if (!isNaN(dActualDPSAverageQuality))
            {
                if (jQ("div#PeteUI_TooltipExp").length === 0)
                {
                    jQ("div.item-tooltip-content > div").append
                    (
                        "<div id=\"PeteUI_TooltipExp\"><div class=\"d-flex flex-wrap\"></div><b>Rated Damage</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></div>\r\n"
                    );
                }
            }
		}
	}
	catch (err) { console.log("ERROR: " + err); }
    //
    ///////////////////////////////////////////////////////////////////////////////////////


    ///////////////////////////////////////////////////////////////////////////////////////
    //
    //  Combat Stats page (personal)
    //
	try
	{
		if (jQ("div.stats-row").length > 0)
		{
			sBareDamageRange = document.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 = document.ChopperBlank(sBareDamageRange, "", " - ");
			sDamagePartMax = document.ChopperBlank(sBareDamageRange, " - ", "");

			dDamageMin = document.CDbl(sDamagePartMin);
			dDamageMax = document.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 = document.CDbl(sBareAttackSpeed);
			dCriticalChance = document.CDbl(document.ET_Stat_CritChance);

			if (dCriticalChance > 0.0)
				dAverageDamageAvgQuality += dAverageDamageAvgQuality * (dCriticalChance / 100.0); // note: not using document.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 (document.CDbl(document.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" + document.ET_Stat_CritChance.toFixed(1) + " critical chance for " + document.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 (document.CDbl(document.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" + document.ET_Stat_HealingPower.toFixed(1) + "% " + ((document.CDbl(document.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");
            }
		}
	}
	catch (err) { console.log("ERROR: " + err); }
    //
    ///////////////////////////////////////////////////////////////////////////////////////

	setTimeout(document.ET_fnDPSShow, 1000);
};

String.prototype.replaceAll = function(search, replace)
{
    if (replace === undefined)
        return this.toString();

    return this.replace(new RegExp('[' + search + ']', 'g'), replace);
};

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

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

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

addJQuery(main);