Greasy Fork is available in English.

Pendoria - Chance to die

Implements some extra information per finished fight in the battle view on pendoria.

// ==UserScript==
// @name         Pendoria - Chance to die
// @description  Implements some extra information per finished fight in the battle view on pendoria.
// @namespace    http://pendoria.net/
// @version      0.0.33
// @author       Xortrox
// @contributor  Tester: euphone
// @contributor  Tester: fourkade
// @contributor  Coder: Kidel
// @match        http://pendoria.net/game
// @match        https://pendoria.net/game
// @match        http://www.pendoria.net/game
// @match        https://www.pendoria.net/game
// @grant        none
// ==/UserScript==

(function () {
    function showChanceToDie(data) {
        let playerHealth = $('#php-value')[0];

        if (!playerHealth) {
            return console.log('playerHealth element was not found.');
        }

        let fightDiv = $('#fight')[0];

        if (!fightDiv) {
            return console.log('fightDiv element was not found.');
        }

        let playerHPBackground = $('#php-background')[0];

        if (!playerHPBackground) {
            return console.log('playerHPBackground element was not found.');
        }

        function createHistoricallyLowestHealthTrackerIfNotExists() {
            let historicallyLowestHPTracker = $('#ctdInfoHistoriallyLowestDiv')[0];

            if(!historicallyLowestHPTracker) {
                let infoDiv = document.createElement('div');
                infoDiv.id = 'ctdInfoHistoriallyLowestDiv';
                playerHPBackground.parentElement.prepend(infoDiv);
            }

            return historicallyLowestHPTracker;
        }

        function createInfoDivIfNotExists() {
            let ctdInfoDiv = $('#ctdInfoDiv')[0];

            if (!ctdInfoDiv) {
                let infoDiv = document.createElement('div');
                infoDiv.id = 'ctdInfoDiv';
                fightDiv.parentElement.appendChild(infoDiv);
            }

            return ctdInfoDiv;
        }

        let enemyHitChanceFactor = data.monsterChanceToHit / 100.0;

        let playerMaxHealth = data.playerMaxLife;
        let enemyAverageDamage = data.damageTaken / data.hitsTaken;

        let enemyHitsToKill = Math.ceil(playerMaxHealth / enemyAverageDamage);

        let playerChanceFactorDeath = enemyHitChanceFactor ** enemyHitsToKill;

        let playerHitChanceFactor = data.playerChanceToHit / 100.0;


        let playerAverageDamage = data.damageDone / data.timesHit;

        let enemyMaxHealth = data.monsterMaxLife;

        let enemyMaxHealthWithChanceFactor = (enemyMaxHealth / playerHitChanceFactor);
        let playerHitsToKillEnemy = Math.ceil(enemyMaxHealthWithChanceFactor / playerAverageDamage);
        let playerHitsToKillEnemyNoChanceFactor = Math.ceil(enemyMaxHealth / playerAverageDamage);

        // console.log('enemyHitsToKill:', enemyHitsToKill);
        // console.log('playerHitChanceFactor:', playerHitChanceFactor);
        // console.log('(1 - (1 - playerHitChanceFactor) ** enemyHitsToKill) * 100');
        // let playerWinChance = (1 - (1 - playerHitChanceFactor) ** playerHitsToKillEnemyNoChanceFactor) * 100

        let enemyChanceFactorDeath = playerHitChanceFactor ** playerHitsToKillEnemy;

        let likelinessPrediction = ``;

        // TODO: Make this based on some amount of % within proximity of max healths and damages.
        if (playerHitsToKillEnemy < enemyHitsToKill) {
            likelinessPrediction +=
                `It seems <b style="color:lightgreen">unlikely</b> that you will die as the enemy is required to hit you ${enemyHitsToKill} times, but you need to hit it roughly ${playerHitsToKillEnemy} times.<br>
                <br>
                `;
        } else if (playerHitsToKillEnemy >= enemyHitsToKill){
            likelinessPrediction +=
                `It seems <b style="color:red">likely</b> that you will die as the enemy needs to hit you ${enemyHitsToKill} times, but you need to hit it roughly ${playerHitsToKillEnemy} times.<br>
                <br>
                `;
        }

        let ctdInfoDiv = createInfoDivIfNotExists();

        let diffHits = enemyHitsToKill - playerHitsToKillEnemy;

        if (ctdInfoDiv){
            ctdInfoDiv.innerHTML =
                `
                <div style="border-bottom: 1px solid white; margin-bottom: 3px;">
                  Chance to die<button id="ctdClearHistoricalTracker" style="padding-top: 0; padding-bottom: 0; height:21px; border: 1px solid white; background: transparent; border-radius: 0; float:right; line-height: 21px;">Reset lowest health tracker</button>
                </div>
                ${likelinessPrediction}
                You would die after <b style="color:red;">${enemyHitsToKill}</b>~ rounds of battle if the enemy is still alive.<br>
                You would win after <b style="color:lightgreen;">${playerHitsToKillEnemy}</b>~ rounds of battle.<br>
                <br>
                You have a chance of ${(((1 - playerHitChanceFactor) ** diffHits) * 100).toPrecision(3)}%~ to miss enough in order to receive ${enemyHitsToKill} hits from this enemy, which would kill you.<br>
                `
        }

        /**
         * Bind click event of ctdClearHistoricalTracker
         * */
        $('#ctdClearHistoricalTracker').click(() => {
            let pendCtdSettings = window.localStorage.getItem('PendoriaChanceToDie');
            if (pendCtdSettings) {
               pendCtdSettings = JSON.parse(pendCtdSettings);
            }

            delete(pendCtdSettings.historiallyLowestHealth);

            localStorage.setItem('PendoriaChanceToDie', JSON.stringify(pendCtdSettings));
        });

        playerCurrentHealth = data.playerLife;
        if (playerCurrentHealth < 0) {
            playerCurrentHealth = 0;
        }

        // console.log('playerCurrentHealth:', playerCurrentHealth);

        let ctdInfoHistoriallyLowestDiv = createHistoricallyLowestHealthTrackerIfNotExists();

        // TODO: Also show how many hits you took and for what average damage the hits were, in the tooltip
        if(ctdInfoHistoriallyLowestDiv) {
            let pendCtdSettings = window.localStorage.getItem('PendoriaChanceToDie');
            if (!pendCtdSettings) {
                pendCtdSettings = {
                    historiallyLowestHealth: playerCurrentHealth
                };

                localStorage.setItem('PendoriaChanceToDie', JSON.stringify(pendCtdSettings));
            } else {
                pendCtdSettings = JSON.parse(pendCtdSettings);

                if (pendCtdSettings.historiallyLowestHealth === undefined) {
                    pendCtdSettings.historiallyLowestHealth = playerCurrentHealth
                    localStorage.setItem('PendoriaChanceToDie', JSON.stringify(pendCtdSettings));
                }
            }

            if (playerCurrentHealth < pendCtdSettings.historiallyLowestHealth) {
                pendCtdSettings.historiallyLowestHealth = playerCurrentHealth;
                localStorage.setItem('PendoriaChanceToDie', JSON.stringify(pendCtdSettings))
            }

            let playerHealthPercentage = (pendCtdSettings.historiallyLowestHealth / playerMaxHealth * 100);

            /**
             * Only update qtip if the contents changed.
             **/
            if (ctdInfoHistoriallyLowestDiv.innerText !== commarize(pendCtdSettings.historiallyLowestHealth)) {
                setTimeout(() => {
                    $('#php-value').qtip({
                        style: {
                            classes: 'qtip-dark'
                        },
                        position: {
                            target: $('#ctdInfoHistoriallyLowestDiv'),
                            at: 'bottom left'
                        },
                        content: {
                            text: pendCtdSettings.historiallyLowestHealth,
                            // title: 'Historically lowest health'
                        }
                    });
                })
            }

            ctdInfoHistoriallyLowestDiv.innerText = commarize(pendCtdSettings.historiallyLowestHealth);
            ctdInfoHistoriallyLowestDiv.style =
                `
                z-index: 9999;
                position: absolute;
                background-color: #14723c;
                height: 25px;
                line-height: 26px;
                padding-left: 5px;
                border-radius: 10px;
                font-size: 12px;
                width: ${playerHealthPercentage}%;
                `;

            playerHealth.style.zIndex = '9999';
        }
    }

    $(function () {
        function load() {
            if(window.socket) {
                socket.on('battle data', (data) => {
                    showChanceToDie(data);
                });
            } else {
                setTimeout(load, 500);
            }
        }
        load();
    });


    /**
     * Appends a suffix to the number and shortens it generally.
     * */
    function commarize(number) {
        if(number < 999999) { return number; }
        var sizes = [
            '',
            '',
            'Mill',
            'Bill',
            'Trill',
            'Quad',
            'Quint',
            'Sext',
            'Sept',
            'Oct'
        ];
        if (number == 0) return '0 Byte';
        var i = parseInt(Math.floor(Math.log(number) / Math.log(1000)));
        return (number / Math.pow(1000, i)).toPrecision(4) + ' ' + sizes[i];
    }

    /**
     * Shows the full precision of a number up to 100 digits.
     * */
    function fullZeroes(number) {
        return number.toFixed(100).replace(/.?0+$/,"")     // 0.0000005
    }

    /**
     * Takes a number string
     * */
    function fourLastDigits(numberString) {
        let index = 0;
        let theValAfterDot = numberString.substr(numberString.indexOf('.') + 1);

        while(theValAfterDot[index] === '0'){
            index++;
        }
        return '0.' + theValAfterDot.substr(0, index + 3)
    }
}());