Elethor General Purpose

Provides some general additions to Elethor

As of 2021-01-13. See the latest version.

// ==UserScript==
// @name         Elethor General Purpose
// @description  Provides some general additions to Elethor
// @namespace    https://www.elethor.com/
// @version      1.3.1
// @author       Anders Morgan Larsen (Xortrox)
// @match        https://elethor.com/*
// @match        https://www.elethor.com/*
// @run-at       document-start
// @grant        none
// ==/UserScript==

(function() {
    const currentUserData = {};

    const moduleName = 'Elethor General Purpose';
    const version = '1.3.1'

    const playerHealthBarElementDefinition = '.progress.is-medium.is-danger';
    const playerHealthBarTextElementDefinition = '.is-fight-health';

    function getBattleHealthPercentage() {
        const playerHealth = document.querySelector(playerHealthBarElementDefinition);

        if (!playerHealth) {
            return 0;
        }

        return playerHealth.value / playerHealth.max * 100;
    }

    function highlightKills() {
        document.querySelectorAll('.has-combat-log>li').forEach((e) => {
            if (!e || !e.setAttribute || !e.innerText) {
                return;
            }

            if (e.innerText.includes('You killed')) {
                e.setAttribute('style', 'color:lime');
            } else if (e.innerText.includes('The monster did')){
                e.setAttribute('style', 'color:red');
            } else {
                e.setAttribute('style', '');
            }
        });
    }

    function initializeUpdateBattleHealthPercentage() {
        if (window.elethorExtrasInterval) {
            clearInterval(window.elethorExtrasInterval);
        }

        window.elethorExtrasInterval = setInterval(() => {
            const playerHealthText = document.querySelector(playerHealthBarTextElementDefinition);
            const healthPercentage = getBattleHealthPercentage();

            if (playerHealthText && healthPercentage && !isNaN(healthPercentage)) {
                let percentageDisplay;
                if (playerHealthText.children.length === 0) {
                    percentageDisplay = document.createElement('span');

                    playerHealthText.appendChild(percentageDisplay);
                } else {
                    percentageDisplay = playerHealthText.children[0];

                }

                if (percentageDisplay) {
                    percentageDisplay.innerText = ` (${healthPercentage.toFixed(3)}%)`;
                }
            }

            highlightKills();
        }, 500);


        console.log(`[${moduleName} v${version}] Battle Percentage initialized.`);
    }

    function initializeToastKiller() {
        document.addEventListener('click', function(e) {
            if (e.target
                && e.target.className
                && e.target.className.includes
                && e.target.className.includes('toasted')
            ) {
                e.target.remove();
            }
        });

        console.log(`[${moduleName} v${version}] Toast Killer initialized.`);
    }

    function initializeXHRHook() {
        let rawSend = XMLHttpRequest.prototype.send;

        XMLHttpRequest.prototype.send = function() {
            if (!this._hooked) {
                this._hooked = true;

                this.addEventListener('readystatechange', function() {
                    if (this.readyState === XMLHttpRequest.DONE) {
                        setupHook(this);
                    }
                }, false);
            }
            rawSend.apply(this, arguments);
        }

        function setupHook(xhr) {
            if (window.elethorGeneralPurposeOnXHR) {
                const e = new Event('EGPXHR');
                e.xhr = xhr;

                window.elethorGeneralPurposeOnXHR.dispatchEvent(e);
            }
        }
        window.elethorGeneralPurposeOnXHR = new EventTarget();

        console.log(`[${moduleName} v${version}] XHR Hook initialized.`);
    }

    function initializeUserLoadListener() {
        elethorGeneralPurposeOnXHR.addEventListener('EGPXHR', function (e) {
            if (e && e.xhr
                && e.xhr.responseURL
                && e.xhr.responseURL.endsWith
                && e.xhr.responseURL.endsWith('/game/user')
            ) {
                try {
                    const userData = JSON.parse(e.xhr.responseText);
                    console.log(`[${moduleName} v${version}] User Data hook:`, userData);

                    if (userData) {
                        for (const key of Object.keys(userData)) {
                            currentUserData[key] = userData[key];
                        }

                        console.log(`[${moduleName} v${version}] User Data loaded:`, currentUserData);
                    }
                } catch (e) {
                    console.log(`[${moduleName} v${version}] Error parsing userData:`, e);
                }

            }
        });

        console.log(`[${moduleName} v${version}] User Load Listener initialized.`);
    }

    function initializeInventoryStatsLoadListener() {
        elethorGeneralPurposeOnXHR.addEventListener('EGPXHR', function (e) {
            if (e && e.xhr
                && e.xhr.responseURL
                && e.xhr.responseURL.endsWith
                && e.xhr.responseURL.endsWith('/game/inventory/stats')
            ) {
                setTimeout(() => {
                    updateEquipmentPercentageSummary();

                    setTimeout(updateInventoryStatsPercentages, 1000);
                });
            }
        });

        console.log(`[${moduleName} v${version}] Inventory Stats Load Listener initialized.`);
    }

    function updateEquipmentPercentageSummary() {
        document.querySelector('.is-equipment>div>div').setAttribute('style', 'width: 50%')
        let percentagesTable = document.querySelector('#egpPercentagesSummary')
        if (!percentagesTable){
            percentagesTable = document.querySelector('.is-equipment>div>div:nth-child(2)').cloneNode(true);
            percentagesTable.setAttribute('style', 'width: 25%');
            percentagesTable.id='egpPercentagesSummary';
            document.querySelector('.is-equipment>div').appendChild(percentagesTable);

            for (const child of percentagesTable.children[0].children) {
                if (child && child.children && child.children[0]) {
                    child.children[0].remove();
                }
            }

            document.querySelector('#egpPercentagesSummary>table>tr:nth-child(8)').setAttribute('style', 'height:43px');
        }
    }

    function getStatSummary(equipment) {
        const summary = {
            base: {},
            energizements: {}
        };

        if (equipment) {
            for (const key of Object.keys(equipment)) {
                const item = equipment[key];

                /**
                 * Sums base attributes by name
                 * */
                if (item && item.attributes) {
                    for (const attributeName of Object.keys(item.attributes)) {
                        const attributeValue = item.attributes[attributeName];

                        if (!summary.base[attributeName]) {
                            summary.base[attributeName] = 0;
                        }

                        summary.base[attributeName] += attributeValue;
                    }
                }

                /**
                 * Sums energizements by stat name
                 * */
                if (item && item.upgrade && item.upgrade.energizements) {
                    for (const energizement of item.upgrade.energizements) {
                        if (!summary.energizements[energizement.stat]) {
                            summary.energizements[energizement.stat] = 0;
                        }

                        summary.energizements[energizement.stat] += Number(energizement.boost);
                    }
                }
            }
        }

        return summary;
    }

    function updateInventoryStatsPercentages() {
        let percentagesTable = document.querySelector('#egpPercentagesSummary')
        if (percentagesTable && currentUserData && currentUserData.equipment){
            const statSummary = getStatSummary(currentUserData.equipment);

            const baseKeys = Object.keys(statSummary.base);
            const energizementKeys = Object.keys(statSummary.energizements);

            let allKeys = baseKeys.concat(energizementKeys);
            const filterUniques = {};
            for (const key of allKeys){
                filterUniques[key] = true;
            }
            allKeys = Object.keys(filterUniques);
            allKeys.sort();

            allKeys.push('actions');

            const tableRows = percentagesTable.children[0].children;

            for(const row of tableRows) {
                if (row
                    && row.children
                    && row.children[0]
                    && row.children[0].children[0]
                ) {
                    const rowText = row.children[0].children[0];
                    rowText.innerText = '';
                }
            }

            let rowIndex = 0;
            for (const key of allKeys) {
                if (key === 'puncture') {
                    continue;
                }

                const row = tableRows[rowIndex];
                if (row
                    && row.children
                    && row.children[0]
                    && row.children[0].children[0]
                ) {
                    const rowText = row.children[0].children[0];

                    const rowBase = statSummary.base[key] || 0;
                    const rowEnergizement = (statSummary.energizements[key] || 0);
                    const rowEnergizementPercentage = (statSummary.energizements[key] || 0) * 100;

                    if (key.startsWith('+')) {
                        rowText.innerText = `${key} per 10 levels: ${rowEnergizement}`;
                    } else if (key === 'actions') {
                        const actions = currentUserData.user.bonus_actions || 0;
                        rowText.innerText = `Bonus Actions: ${actions}`;
                    } else {
                        rowText.innerText = `${key}: ${rowBase} (${rowEnergizementPercentage.toFixed(0)}%)`;
                    }

                    rowIndex++;
                }
            }
        }
    }

    (function run() {
        initializeUpdateBattleHealthPercentage();
        initializeToastKiller();
        initializeXHRHook();
        initializeUserLoadListener();
        initializeInventoryStatsLoadListener();

        console.log(`[${moduleName} v${version}] Loaded.`);
    })();

    (async function loadRerollDisableButtonModule() {
        async function waitForEcho() {
            return new Promise((resolve, reject) => {
                const interval = setInterval(() => {
                    if (window.Echo) {
                        clearInterval(interval);
                        resolve();
                    }
                }, 100);
            });
        }

        async function waitForUser() {
            return new Promise((resolve, reject) => {
                const interval = setInterval(() => {
                    if (currentUserData.user && currentUserData.user.id !== undefined) {
                        clearInterval(interval);
                        resolve();
                    }
                }, 100);
            });
        }

        await waitForEcho();
        await waitForUser();

        elethorGeneralPurposeOnXHR.addEventListener('EGPXHR', async function (e) {
            if (e && e.xhr && e.xhr.responseURL) {
                if(e.xhr.responseURL.includes('/game/energize')) {
                    const itemID = e.xhr.responseURL.substr(e.xhr.responseURL.lastIndexOf('/')+1);
                    window.lastEnergizeID = Number(itemID);
                }
            }
        });

        function initializeDisableEnergizementButtonOnReroll() {
            const privateRoom = `App.User.${currentUserData.user.id}`;
            console.log(`[${moduleName} v${version}] Binding to private room ${privateRoom}`);
            window.Echo.private(privateRoom).listen(".App\\Domain\\Inventory\\Events\\UpdateItem", (data) => {
                if (data && data.item.id === window.lastEnergizeID){
                    enableEnergizementButton();
                }
            });

            document.addEventListener('click', function(e) {
                try {
                    if (!e
                        || !e.target
                        || !e.target.parentElement
                        || !e.target.className
                        || !e.target.parentElement.className
                        || !e.target.innerText
                        || !e.target.innerText.includes
                    ) {
                        return;
                    }

                    if ((e.target.innerText.includes('Reroll Energizements') || e.target.innerText.includes('Costs 1 Standard Energizing Shard'))
                        && (e.target.className.includes('button') || e.target.parentElement.className.includes('button'))
                    ) {
                        disableEnergizementButton();
                    }
                } catch (e) {
                    console.warn(`[${moduleName} v${version}] Error during document click:`, e);
                }
            });
        }
        initializeDisableEnergizementButtonOnReroll();

        function disableEnergizementButton() {
            const button = getEnergizementButton();

            if (!button) {
                return;
            }

            button.setAttribute('style', 'pointer-events: none');
            button.setAttribute('disabled', 'true');
        }

        function enableEnergizementButton() {
            const button = getEnergizementButton();

            if (!button) {
                return;
            }

            button.removeAttribute('style');
            button.removeAttribute('disabled');
        }

        function getEnergizementButton() {
            let button;
            document.querySelectorAll('.buttons>.button.is-multiline.is-info>span:first-child').forEach((e) => {
                if (e && e.innerText && e.innerText === 'Reroll Energizements') {
                    button = e.parentElement;
                }
            });
            return button;
        }
    })();

    (function loadResourceNodeUpdater() {
        function updateExperienceRates() {
            document.querySelectorAll('.is-resource-node').forEach(visualizeResourceNodeExperienceRates);

            function visualizeResourceNodeExperienceRates(node) {
                const purity = getNodePurityPercentage(node);
                const density = getNodeDensityPercentage(node);
                const experience = getNodeExperience(node);
                const ore = 16;
                const experienceRate = getExperienceRate(density, experience);
                const oreRate = getOreRate(density, purity, ore);

                node.children[0].setAttribute('data-after', `${experienceRate} xp/h ${oreRate} ore/h`);
            }

            function getNodePurityPercentage(node) {
                const column = node.children[0].children[2];
                const label = column.getElementsByClassName('has-text-weight-bold')[0].parentElement;
                const percentage = Number(label.innerText.replace('%','').split(':')[1])
                return percentage;
            }

            function getNodeDensityPercentage(node) {
                const column = node.children[0].children[1];
                const label = column.getElementsByClassName('has-text-weight-bold')[0].parentElement;
                const percentage = Number(label.innerText.replace('%','').split(':')[1])
                return percentage;
            }

            function getNodeExperience(node) {
                const column = node.children[0].children[3];
                const label = column.getElementsByClassName('has-text-weight-bold')[0].parentElement;
                const value = Number(label.innerText.replace('%','').split(':')[1])
                return value;
            }

            function getExperienceRate(density, experience) {
                return Number((3600 / (60 / (density / 100)) * experience).toFixed(3));
            }

            function getOreRate(density, purity, ore) {
                return Number((3600 / (60 / (density / 100)) * (purity / 100) * ore).toFixed(3));
            }
        }

        updateExperienceRates();
        window.elethorResourceInterval = setInterval(updateExperienceRates, 500);
        initializeResourceNodeView();

        async function initializeResourceNodeView() {
            await waitForField(document, 'head');

            var css = '.columns.is-mobile.is-size-7-mobile::after { content: attr(data-after); padding: 12px;}',
                head = document.head || document.getElementsByTagName('head')[0],
                style = document.createElement('style');

            head.appendChild(style);

            style.type = 'text/css';
            if (style.styleSheet){
                // This is required for IE8 and below.
                style.styleSheet.cssText = css;
            } else {
                style.appendChild(document.createTextNode(css));
            }
        }

        async function waitForField(target, field) {
            return new Promise((resolve, reject) => {
                const interval = setInterval(() => {
                    if (target[field] !== undefined) {
                        clearInterval(interval);
                        resolve();
                    }
                }, 100);
            });
        }
    })();
})();