MWI Level-Up Timer + Target Level Input

Time-to-Level-Up mit Wochen/Tagen/Stunden/Minuten/Sekunden, Number-Input für Ziel-Level, nur sichtbar wenn BattlePanel geöffnet

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

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

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name         MWI Level-Up Timer + Target Level Input
// @namespace    http://tampermonkey.net/
// @version      0.9
// @description  Time-to-Level-Up mit Wochen/Tagen/Stunden/Minuten/Sekunden, Number-Input für Ziel-Level, nur sichtbar wenn BattlePanel geöffnet
// @match        *://milkywayidle.com/*
// @match        *://*.milkywayidle.com/*
// @grant        none
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    const SKILL_NAMES = ["Stamina","Intelligence","Attack","Defense","Melee","Ranged","Magic"];
    let intervalId = null;

    // XP-Tabelle 1-200 (reale Werte)
    const XP_TABLE = [
        0,33,76,132,202,286,386,503,637,791,964,1159,1377,1620,1891,2192,2525,2893,3300,3750,
        4247,4795,5400,6068,6805,7618,8517,9508,10604,11814,13151,14629,16262,18068,20064,22271,
        24712,27411,30396,33697,37346,41381,45842,50773,56222,62243,68895,76242,84355,93311,103195,
        114100,126127,139390,154009,170118,187863,207403,228914,252584,278623,307256,338731,373318,411311,453030,
        498824,549074,604193,664632,730881,803472,882985,970050,1065351,1169633,1283701,1408433,1544780,1693774,1856536,
        2034279,2228321,2440088,2671127,2923113,3197861,3497335,3823663,4179145,4566274,4987741,5446463,5945587,6488521,
        7078945,7720834,8418485,9176537,10000000,11404976,12904567,14514400,16242080,18095702,20083886,22215808,24501230,
        26950540,29574787,32385721,35395838,38618420,42067584,45758332,49706603,53929328,58444489,63271179,68429670,73941479,
        79829440,86117783,92832214,100000000,114406130,130118394,147319656,166147618,186752428,209297771,233962072,260939787,
        290442814,322702028,357968938,396517495,438646053,484679494,534971538,589907252,649905763,715423218,786955977,865044093,
        950275074,1043287971,1144777804,1255500373,1376277458,1508002470,1651646566,1808265285,1979005730,2165114358,2367945418,
        2588970089,2829786381,3092129857,3377885250,3689099031,4027993033,4396979184,4798675471,5235923207,5711805728,6229668624,
        6793141628,7406162301,8073001662,8798291902,9587056372,10444742007,11377254401,12390995728,13492905745,14690506120,
        15991948361,17406065609,18942428633,20611406335,22424231139,24393069640,26531098945,28852589138,31372992363,34109039054,
        37078841860,40302007875,43799759843,47595067021,51712786465,56179815564,61025256696,66280594953,71979889960,78159982881,
        84860719814,92125192822,100000000000
    ];

    function parseSkills() {
        const skills = {};
        document.querySelectorAll(".NavigationBar_subSkills__37qWb .NavigationBar_nav__3uuUl").forEach(nav => {
            const nameEl = nav.querySelector(".NavigationBar_label__1uH-y");
            const percentEl = nav.querySelector(".insertedSpan");
            const levelEl = nav.querySelector(".NavigationBar_level__3C7eR");
            if(nameEl && percentEl && levelEl){
                const name = nameEl.textContent.trim();
                const percent = parseFloat(percentEl.textContent.replace("%","").replace(",","").trim());
                const level = parseInt(levelEl.textContent.replace(/\./g,""),10);
                skills[name] = { level, percent };
            }
        });
        return skills;
    }

    function parseBattleXP(){
        const xpData = {};
        document.querySelectorAll(".BattlePanel_skillExp__2uMMD").forEach(div=>{
            const svg = div.querySelector("use");
            if(!svg) return;
            const skillNameFromHref = svg.getAttribute("href").split("#")[1];
            let xpText = div.textContent.trim().replace(/\./g,"").replace("K","000").replace("M","000000").replace(",","");
            const xp = parseInt(xpText);
            if(!isNaN(xp)){
                xpData[skillNameFromHref.toLowerCase()] = xp;
            }
        });
        return xpData;
    }

    function parseCombatInfo() {
        const infoDiv = document.querySelector(".BattlePanel_combatInfo__sHGCe");
        if(!infoDiv) return { combatSec:0, battles:0 };

        const text = infoDiv.textContent;
        const dayMatch = text.match(/(\d+)d/);
        const hourMatch = text.match(/(\d+)h/);
        const minMatch = text.match(/(\d+)m/);
        const secMatch = text.match(/(\d+)s/);
        const battlesMatch = text.match(/Battles:\s*(\d+)/);

        const days = dayMatch ? parseInt(dayMatch[1]) : 0;
        const hours = hourMatch ? parseInt(hourMatch[1]) : 0;
        const minutes = minMatch ? parseInt(minMatch[1]) : 0;
        const seconds = secMatch ? parseInt(secMatch[1]) : 0;
        const battles = battlesMatch ? parseInt(battlesMatch[1]) : 1;

        return { combatSec: days*86400 + hours*3600 + minutes*60 + seconds, battles };
    }

    function xpToNextLevel(level, percent){
        const currentXP = XP_TABLE[level-1] || 0;
        const nextXP = XP_TABLE[level] || currentXP*1.1;
        const remaining = (1 - percent/100) * (nextXP - currentXP);
        return remaining;
    }

    function xpToTargetLevel(level, targetLevel, percent){
        let currentXP = XP_TABLE[level-1] || 0;
        currentXP += percent/100 * (XP_TABLE[level] - currentXP);
        let total = 0;
        for(let l = level; l < targetLevel; l++){
            const nextXP = XP_TABLE[l] || (XP_TABLE[l-1]*1.1);
            if(l === level){
                total += nextXP - currentXP;
            } else {
                total += nextXP - XP_TABLE[l-1];
            }
        }
        return total;
    }

    function formatTimeWithSeconds(hours){
        let totalSeconds = Math.floor(hours * 3600);
        const weeks = Math.floor(totalSeconds / (7*24*3600));
        totalSeconds %= 7*24*3600;
        const days = Math.floor(totalSeconds / 86400);
        totalSeconds %= 86400;
        const h = Math.floor(totalSeconds / 3600);
        totalSeconds %= 3600;
        const m = Math.floor(totalSeconds / 60);
        const s = totalSeconds % 60;

        let result = "";
        if(weeks > 0) result += `${weeks}w `;
        if(days > 0) result += `${days}d `;
        result += `${h}h ${m}m ${s}s`;
        return result;
    }

    let skillTargetLevels = {};

    function updateTimer(){
        const skills = parseSkills();
        const battleXP = parseBattleXP();
        const {combatSec, battles} = parseCombatInfo();
        if(combatSec <= 0 || battles <= 0) return;

        const combatHours = combatSec / 3600;

        let box = document.getElementById("mwi_levelup_timer_box");
        if(!box){
            box = document.createElement("div");
            box.id = "mwi_levelup_timer_box";
            box.style = `
                position: fixed;
                bottom: 10px;
                right: 10px;
                background: rgba(0,0,0,0.8);
                color: #fff;
                padding: 8px;
                border-radius: 5px;
                font-size: 14px;
                max-height: 80vh;
                overflow-y: auto;
                z-index: 9999;
            `;
            document.body.appendChild(box);
        }

        let html = "<b>Time-to-Level-Up (Current XP)</b><br>";

        for(const skill of SKILL_NAMES){
            const sk = skills[skill];
            if(!sk) continue;

            const skillKey = skill.toLowerCase();
            const gainedXP = battleXP[skillKey];
            if(!gainedXP) continue;

            const xpPerHour = gainedXP / combatHours;
            const xpPerBattle = gainedXP / battles;
            const remXP = xpToNextLevel(sk.level, sk.percent);
            const hours = remXP / xpPerHour;
            const neededBattles = Math.ceil(remXP / xpPerBattle);

            html += `${skill}: ~${formatTimeWithSeconds(hours)} (~${neededBattles} Battles)<br>`;
        }

        html += "<br><b>Target Level Preview</b><br>";

        for(const skill of SKILL_NAMES){
            const sk = skills[skill];
            if(!sk) continue;
            const skillKey = skill.toLowerCase();
            const gainedXP = battleXP[skillKey];
            if(!gainedXP) continue;

            if(!skillTargetLevels[skill]) skillTargetLevels[skill] = sk.level + 1;
            const inputId = `target_level_${skillKey}`;

            html += `${skill}: <input type="number" id="${inputId}" min="${sk.level+1}" max="200" value="${skillTargetLevels[skill]}" style="width:50px"> → Time: <span id="time_${skillKey}"></span><br>`;
        }

        box.innerHTML = html;

        for(const skill of SKILL_NAMES){
            const sk = skills[skill];
            if(!sk) continue;
            const skillKey = skill.toLowerCase();
            const inputEl = document.getElementById(`target_level_${skillKey}`);
            const timeEl = document.getElementById(`time_${skillKey}`);
            if(!inputEl || !timeEl) continue;

            const gainedXP = battleXP[skillKey];
            const xpPerHour = gainedXP / combatHours;

            function updateTargetTime(){
                let targetLevel = parseInt(inputEl.value);
                if(isNaN(targetLevel)) targetLevel = sk.level+1;
                skillTargetLevels[skill] = targetLevel;
                const remXP = xpToTargetLevel(sk.level, targetLevel, sk.percent);
                const hours = remXP / xpPerHour;
                timeEl.textContent = formatTimeWithSeconds(hours);
            }

            inputEl.addEventListener("input", updateTargetTime);
            updateTargetTime();
        }
    }

    const observer = new MutationObserver(() => {
        const panel = document.querySelector(".BattlePanel_combatInfo__sHGCe");
        const boxExists = !!document.getElementById("mwi_levelup_timer_box");

        if(panel && !boxExists){
            updateTimer();
            intervalId = setInterval(updateTimer, 2000);
        } else if(!panel && boxExists){
            document.getElementById("mwi_levelup_timer_box").remove();
            clearInterval(intervalId);
            intervalId = null;
        }
    });

    observer.observe(document.body, { childList: true, subtree: true });

})();