Melvor Virtual Levels

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

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

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址