Melvor Virtual Levels

Display level and xp numbers for virtual levels, similar to regular levels.

K instalaci tototo skriptu si budete muset nainstalovat rozšíření jako Tampermonkey, Greasemonkey nebo Violentmonkey.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Violentmonkey.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Violentmonkey.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Userscripts.

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

K instalaci tohoto skriptu si budete muset nainstalovat manažer uživatelských skriptů.

(Už mám manažer uživatelských skriptů, nechte mě ho nainstalovat!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Už mám manažer uživatelských stylů, nechte mě ho nainstalovat!)

// ==UserScript==
// @name        Melvor Virtual Levels
// @namespace   github.com/gmiclotte
// @version		0.1.7
// @description Display level and xp numbers for virtual levels, similar to regular levels.
// @author		GMiclotte
// @include		https://melvoridle.com/*
// @include		https://*.melvoridle.com/*
// @exclude		https://melvoridle.com/index.php
// @exclude		https://*.melvoridle.com/index.php
// @exclude		https://wiki.melvoridle.com/*
// @exclude		https://*.wiki.melvoridle.com/*
// @inject-into page
// @noframes
// @grant		none
// ==/UserScript==

((main) => {
    const script = document.createElement('script');
    script.textContent = `try { (${main})(); } catch (e) { console.log(e); }`;
    document.body.appendChild(script).parentNode.removeChild(script);
})(() => {

    function addSummoningProgess() {
        // add summoning to combat skill progress menu
        const table = document.getElementById('combat-skill-progress-menu').children[0];
        const skillID = Skills.Summoning + '-1';
        const body = document.createElement('tbody');
        const row = body.insertRow();
        // header
        const header = document.createElement('th');
        header.className = 'text-center';
        header.scope = 'row';
        const headerImg = document.createElement('img');
        headerImg.className = 'skill-icon-xs';
        headerImg.src = 'https://cdn.melvor.net/core/v018/assets/media/skills/summoning/summoning.svg';
        header.appendChild(headerImg);
        row.appendChild(header);
        // level
        const level = document.createElement('td');
        level.className = 'font-w600 font-size-sm';
        const levelSmall = document.createElement('small');
        levelSmall.id = `skill-progress-level-${skillID}`;
        level.appendChild(levelSmall);
        row.appendChild(level);
        // progress %
        const percent = document.createElement('td');
        percent.className = 'font-w600 font-size-sm';
        const percentSmall = document.createElement('small');
        percentSmall.id = `skill-progress-percent-${skillID}`;
        percent.appendChild(percentSmall);
        row.appendChild(percent);
        // xp
        const xp = document.createElement('td');
        xp.className = 'font-w600 font-size-sm d-none d-md-table-cell';
        const xpSmall = document.createElement('small');
        xpSmall.id = `skill-progress-xp-${skillID}`;
        xp.appendChild(xpSmall);
        row.appendChild(xp);
        // progress bar
        const bar = document.createElement('td');
        const tooltipBar = document.createElement('div');
        tooltipBar.className = 'progress active';
        tooltipBar.style = 'height: 8px';
        tooltipBar.id = `skill-progress-xp-tooltip-${skillID}`;
        const barInnerDiv = document.createElement('div');
        barInnerDiv.className = 'progress-bar bg-info';
        barInnerDiv.id = `skill-progress-bar-${skillID}`;
        barInnerDiv.role = 'progressbar';
        barInnerDiv.style = 'width: 0%;';
        barInnerDiv.ariaValuenow = '0';
        barInnerDiv.ariaValuemin = '0';
        barInnerDiv.ariaValuemax = '100';
        tooltipBar.appendChild(barInnerDiv);
        bar.appendChild(tooltipBar);
        row.appendChild(bar);
        // add row to table
        table.appendChild(body);
        // add new Summoning elements to `skillProgressDisplay.elems`
        const elems = skillProgressDisplay.elems.get(Skills.Summoning)
        elems.level.push(levelSmall);
        elems.percent.push(percentSmall);
        elems.progress.push(barInnerDiv);
        elems.tooltip.push(skillProgressDisplay.createTooltip(tooltipBar, ''));
        elems.xp.push(xpSmall);
    }

    function startVirtualLevels() {
        addSummoningProgess();

        window.virtualLevels = {
            navLevelCap: undefined,
            pageLevelCap: undefined,
            xpCap: undefined,
            // method to save data to local storage
            save: () => {
                window.localStorage['virtualLevels'] = window.JSON.stringify(window.virtualLevels)
            },
            // method to update all skill displays
            update: () => {
                for (let id in SKILLS) {
                    updateSkillVisuals(Number(id));
                }
            },
            // set caps
            setCaps(level, xp, nav = undefined) {
                window.virtualLevels.navLevelCap = nav ?? level;
                window.virtualLevels.pageLevelCap = level;
                window.virtualLevels.xpCap = xp;
                window.virtualLevels.save();
                window.virtualLevels.update();
            },
        }

        if (window.localStorage['virtualLevels'] !== undefined) {
            const stored = window.JSON.parse(window.localStorage['virtualLevels']);
            Object.getOwnPropertyNames(stored).forEach(x => {
                window.virtualLevels[x] = stored[x];
            });
        }

        window.virtualLevels.save();

        //////////////////////////////////
        //show exp to next level past 99//
        //////////////////////////////////

        // skill page
        let updateSkillString = skillProgressDisplay.updateSkill.toString();
        // replace this with object name
        updateSkillString = updateSkillString.replaceAll(
            'this',
            'skillProgressDisplay',
        );
        // ignore showVirtualLevels
        updateSkillString = updateSkillString.replace(
            'showVirtualLevels',
            'true',
        );
        // always use the <99 display mode that shows xp
        updateSkillString = updateSkillString.replace(
            'level<99',
            'true',
        );
        // limit xp to xpCap if given
        updateSkillString = updateSkillString.replace(
            '`${xpText} / ${numberWithCommas(exp.level_to_xp(level+1))}`',
            'xp >= (virtualLevels.xpCap ?? Infinity) ? numberWithCommas(Math.floor(virtualLevels.xpCap)) : `${xpText} / ${numberWithCommas(exp.level_to_xp(level + 1))}`',
        );
        // limit level display to pageLevelCap if given
        updateSkillString = updateSkillString.replace(
            '`${level} / 99`',
            '`${Math.min(virtualLevels.pageLevelCap ?? Infinity, level)} / 99`',
        );
        // compute next level progress, if pageLevelCap set it to 100
        updateSkillString = updateSkillString.replace(
            'nextLevelProgress[skillID]',
            'level > virtualLevels.pageLevelCap ? 100 : 100 * (xp - exp.level_to_xp(level)) / (exp.level_to_xp(level + 1) - exp.level_to_xp(level))',
        );
        // hookup
        updateSkillString = updateSkillString.replace(
            'updateSkill(skillID){',
            'skillProgressDisplay.updateSkill = (skillID) => {',
        );
        // evaluate
        eval(updateSkillString);

        // navigation
        let updateNavString = skillNav.updateNav.toString();
        // replace this with object name
        updateNavString = updateNavString.replaceAll(
            'this',
            'skillNav',
        );
        // limit level display to navLevelCap if given
        updateNavString = updateNavString.replace(
            'showVirtualLevels?exp.xp_to_level(xp)-1:level',
            'Math.min(virtualLevels.navLevelCap ?? Infinity, exp.xp_to_level(xp)-1)',
        );
        // hookup
        updateNavString = updateNavString.replace(
            'updateNav(skillID){',
            'skillNav.updateNav = (skillID) => {',
        );
        // evaluate
        eval(updateNavString);

        // one-time update of all skill windows after a slight delay
        setTimeout(window.virtualLevels.update, 5000);

        ///////
        //log//
        ///////
        console.log("Melvor Virtual Levels Loaded");
    }

    function loadScript() {
        if (typeof confirmedLoaded !== typeof undefined && confirmedLoaded) {
            // Only load script after game has opened
            clearInterval(scriptLoader);
            startVirtualLevels();
        }
    }

    const scriptLoader = setInterval(loadScript, 200);
});