WWW XP Time Calculator (for MWICombatSimulator)

Shows time remaining to level up each skill based on current XP rates

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         WWW XP Time Calculator (for MWICombatSimulator)
// @namespace    http://tampermonkey.net/
// @version      0.2
// @description  Shows time remaining to level up each skill based on current XP rates
// @author       WekizZ
// @match        https://shykai.github.io/MWICombatSimulatorTest/dist/*
// @match        https://www.milkywayidle.com/*
// @grant        GM_setValue
// @grant        GM_getValue
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    const expTable = {
        1: 0, 2: 33, 3: 43, 4: 56, 5: 70, 6: 84, 7: 100, 8: 117, 9: 134, 10: 154,
        11: 173, 12: 195, 13: 218, 14: 243, 15: 271, 16: 301, 17: 333, 18: 368, 19: 407, 20: 450,
        21: 497, 22: 548, 23: 605, 24: 668, 25: 737, 26: 813, 27: 899, 28: 991, 29: 1096, 30: 1210,
        31: 1337, 32: 1478, 33: 1633, 34: 1806, 35: 1996, 36: 2207, 37: 2441, 38: 2699, 39: 2985, 40: 3301,
        41: 3649, 42: 4035, 43: 4461, 44: 4931, 45: 5449, 46: 6021, 47: 6652, 48: 7347, 49: 8113, 50: 8956,
        51: 9884, 52: 10905, 53: 12027, 54: 13263, 55: 14619, 56: 16109, 57: 17745, 58: 19540, 59: 21511, 60: 23670,
        61: 26039, 62: 28633, 63: 31475, 64: 34587, 65: 37993, 66: 41719, 67: 45794, 68: 50250, 69: 55119, 70: 60439,
        71: 66249, 72: 72591, 73: 79513, 74: 87065, 75: 95301, 76: 104282, 77: 114068, 78: 124732, 79: 136347, 80: 148994,
        81: 162762, 82: 177743, 83: 194042, 84: 211767, 85: 231039, 86: 251986, 87: 274748, 88: 299474, 89: 326328, 90: 355482,
        91: 387129, 92: 421467, 93: 458722, 94: 499124, 95: 542934, 96: 590424, 97: 641889, 98: 697651, 99: 758052, 100: 823463,
        101: 1404976, 102: 1499591, 103: 1609833, 104: 1727680, 105: 1853622, 106: 1988184, 107: 2131922, 108: 2285422, 109: 2449310, 110: 2624247,
        111: 2810934, 112: 3010117, 113: 3222582, 114: 3449164, 115: 3690748, 116: 3948271, 117: 4222725, 118: 4515161, 119: 4826690, 120: 5158491,
        121: 5511809, 122: 5887961, 123: 6288343, 124: 6714431, 125: 7167786, 126: 14406130, 127: 15712264, 128: 17201262, 129: 18827962, 130: 20604810,
        131: 22545343, 132: 24664301, 133: 26977715, 134: 29503027, 135: 32259214, 136: 35266910, 137: 38548557, 138: 42128558, 139: 46033441, 140: 50292044,
        141: 54935714, 142: 59998511, 143: 65517455, 144: 71532759, 145: 78088116, 146: 85230981, 147: 93012897, 148: 101489833, 149: 110722569, 150: 120777085,
        151: 131725012, 152: 143644096, 153: 156618719, 154: 170740445, 155: 186108628, 156: 202831060, 157: 221024671, 158: 240816292, 159: 262343476, 160: 285755393,
        161: 311213781, 162: 338894002, 163: 368986151, 164: 401696287, 165: 437247736, 166: 475882521, 167: 517862896, 168: 563473004, 169: 613020673, 170: 666839361,
        171: 725290240, 172: 788764470, 173: 857685635, 174: 932512394, 175: 1013741327, 176: 1101910017, 177: 1197600375, 178: 1301442241, 179: 1414117248, 180: 1536363024,
        181: 1668977702, 182: 1812824804, 183: 1968838501, 184: 2138029305, 185: 2321490193, 186: 2520403225, 187: 2736046691, 188: 2969802806, 189: 3223166015, 190: 3497751968,
        191: 3795307178, 192: 4117719444, 193: 4467029099, 194: 4845441132, 195: 5255338257, 196: 5699295007, 197: 6180092921, 198: 6700736933, 199: 7264473008, 200: 7874807178
    };

    let skillData = GM_getValue('mwi_skill_data', {});
    let isProcessing = false;

    function getSkillInfo(skillName) {
        const skillContainers = Array.from(document.querySelectorAll('.NavigationBar_nav__3uuUl'));
        const skillContainer = skillContainers.find(container => {
            const label = container.querySelector('.NavigationBar_label__1uH-y');
            return label && label.textContent.toLowerCase() === skillName.toLowerCase();
        });

        if (!skillContainer) return null;

        const levelElement = skillContainer.querySelector('.NavigationBar_level__3C7eR');
        const level = levelElement ? parseFloat(levelElement.textContent) : null;

        const progressBar = skillContainer.querySelector('.NavigationBar_currentExperience__3GDeX');
        const percent = progressBar ? parseFloat(progressBar.style.width) : null;

        return {
            level: Math.floor(level),
            percent: percent
        };
    }

    function getRemainingExp(skillName) {
        const info = getSkillInfo(skillName);
        if (!info) return null;

        const neededForLevelUp = expTable[info.level + 1];
        if (!neededForLevelUp) return 0;

        const gainedExp = neededForLevelUp * (info.percent / 100);
        return Math.floor(neededForLevelUp - gainedExp);
    }

    function formatTime(hours) {
        if (hours < 1) {
            return `${Math.round(hours * 60)}m`;
        } else if (hours < 24) {
            return `${Math.floor(hours)}h ${Math.round((hours % 1) * 60)}m`;
        } else {
            const days = Math.floor(hours / 24);
            const remainingHours = Math.floor(hours % 24);
            return `${days}d ${remainingHours}h`;
        }
    }

    function trackSkills() {
        const skills = ['Stamina', 'Intelligence', 'Attack', 'Melee', 'Defense'];

        skills.forEach(skill => {
            const remainingXP = getRemainingExp(skill);
            if (remainingXP !== null) {
                skillData[skill.toLowerCase()] = {
                    xpToLevel: remainingXP,
                    timestamp: Date.now()
                };
            }
        });

        GM_setValue('mwi_skill_data', skillData);
    }

    function processResults() {
        const resultsDiv = document.getElementById('simulationResultExperienceGain');
        if (!resultsDiv || isProcessing) return;

        isProcessing = true;
        const progressBar = document.getElementById('simulationProgressBar');

        if (progressBar && progressBar.style.width !== '100%') {
            setTimeout(processResults, 100);
            return;
        }

        try {
            const results = {};
            resultsDiv.querySelectorAll('.row').forEach(row => {
                const cols = row.children;
                if (cols.length >= 2) {
                    const skillName = cols[0].textContent.trim();
                    const xpPerHour = parseInt(cols[1].textContent.replace(/,/g, '')) || 0;
                    results[skillName.toLowerCase()] = xpPerHour;
                }
            });

            GM_setValue('last_simulation_results', results);
            addTimeToLevel(results);
        } catch (e) {
            console.error('Error processing results:', e);
        } finally {
            isProcessing = false;
        }
    }

    function addTimeToLevel(results) {
        const resultsDiv = document.getElementById('simulationResultExperienceGain');
        if (!resultsDiv) return;

        resultsDiv.querySelectorAll('.row').forEach(row => {
            row.style.cssText = `
                display: flex;
                justify-content: space-between;
                align-items: center;
                padding: 2px 0;
            `;

            const cols = row.children;
            if (cols.length >= 2) {
                cols[0].style.cssText = `
                    width: 120px;
                    flex-shrink: 0;
                `;

                cols[1].style.cssText = `
                    width: 80px;
                    text-align: right;
                    margin-right: 20px;
                    flex-shrink: 0;
                `;

                if (!row.querySelector('.time-to-level')) {
                    const timeCol = document.createElement('div');
                    timeCol.className = 'col text-end time-to-level';
                    timeCol.style.cssText = `
                        flex-grow: 1;
                        text-align: right;
                        white-space: nowrap;
                    `;

                    if (cols[0].textContent.includes('Total')) {
                        timeCol.textContent = 'Time to Level';
                        timeCol.id = 'timeToLevelHeader';
                    } else {
                        const skillName = cols[0].textContent.trim().toLowerCase();
                        const xpPerHour = results[skillName] || 0;

                        if (skillData[skillName] && xpPerHour > 0) {
                            const xpNeeded = skillData[skillName].xpToLevel;
                            const timeToLevel = xpNeeded / xpPerHour;
                            timeCol.textContent = `${formatTime(timeToLevel)} (${xpNeeded.toLocaleString()} xp)`;
                        } else {
                            timeCol.textContent = 'Reading...';
                            timeCol.style.color = '#999';
                        }
                    }
                    row.appendChild(timeCol);
                }
            }
        });
    }

    if (window.location.hostname === 'www.milkywayidle.com') {
        setInterval(trackSkills, 5000);
        trackSkills();
    }

    const simulationObserver = new MutationObserver((mutations) => {
        const progressBar = document.getElementById('simulationProgressBar');
        if (progressBar && progressBar.style.width === '100%') {
            processResults();
        }
    });

    setInterval(() => {
        const progressBar = document.getElementById('simulationProgressBar');
        if (progressBar) {
            simulationObserver.observe(progressBar, {
                attributes: true,
                characterData: true,
                subtree: true
            });
        }

        const lastResults = GM_getValue('last_simulation_results', null);
        if (lastResults) {
            addTimeToLevel(lastResults);
        }
    }, 1000);

})();