Melvor Idle Item Dashboard (MIID)

Item fluctuations over time.

目前为 2021-02-28 提交的版本。查看 最新版本

// ==UserScript==
// @name        Melvor Idle Item Dashboard (MIID)
// @namespace   http://tampermonkey.net
// @match       https://melvoridle.com
// @match       https://melvoridle.com/*
// @match       https://www.melvoridle.com/*
// @match       https://test.melvoridle.com/*
// @grant       none
// @version     1.8
// @author      Gardens#3738
// @description Item fluctuations over time.
// ==/UserScript==

window.banked = function(itemID) {
    let qty = 0;
    if (checkBankForItem(itemID)) {
        let bankID = getBankId(itemID);
        qty = bank[bankID].qty;
    }
    return qty;
}

window.itemsOwned = function(silent = true) {
    let bulk = new Array(items.length).fill(0);
    // take everything in bank, pile it here
    for (let i = 0; i < bank.length; i++) {
        let stack = bank[i];
        bulk[stack.id] += stack.qty;
    }

    // check equipment sets, ignore golbin loadout
    for (let eqI = 0; eqI < 3; eqI++) {
        let equipmentSet = equipmentSets[eqI]["equipment"];
        for (let slot = 0; slot < equipmentSet.length; slot++) {
            let gearID = equipmentSet[slot];
            let qty = 0;
            // malcs, nobody uses logs in battle
            // malcs please just use -1 for no item
            if (gearID !== 0) {
                qty = (slot === CONSTANTS.equipmentSlot.Quiver) ? equipmentSets[eqI].ammo : 1;
                !silent && console.log(`gear item:${items[gearID].name} qty ${qty}`)
            }
            bulk[gearID] += qty;
        }
    }
    // tally food, ignore golbin food at equippedFood[3]
    for (let i = 0; i < 3; i++) {
        let foodID = equippedFood[i].itemID;
        let qty = equippedFood[i].qty;
        if (qty > 0) {
            !silent && console.log(`food item:${items[foodID].name} qty ${qty}`);
            bulk[foodID] += qty;
        }
    }
    if (!silent) {

        for (let i = 0; i < bulk.length - 1; i++) {
            if (bulk[i] > 0) {
                console.log(`has item:${items[i].name} qty ${bulk[i]}`);
            }
        }
    }
    return bulk;
}

window.effHp = function() {
    // return equippedFood.map(({ itemID, qty }) => (getFoodHealValue(itemID) || 0) * qty).reduce((a, b) => a + b)
    let foodID = equippedFood[currentCombatFood].itemID
    let foodQty = equippedFood[currentCombatFood].qty;
    if (foodQty == 0) return combatData.player.hitpoints;
    return equippedFood[currentCombatFood].qty * getFoodHealValue(foodID) + combatData.player.hitpoints;
}

window.totalKills = function() {
    return monsterStats.map(mon => mon.stats[2]).reduce((a, b) => a + b);
}

window.toggleIntervalSize = function() {
    if (itemTracker.itemTracked != -1) {
        itemTracker.itemTracked = -1;
        itemTracker.intervalTracked = 0;
    }
    itemTracker.intervalTracked = (itemTracker.intervalTracked + 1) % itemTracker.trackIntervals.length;
}

window.getTimeString = function(sec) {
    let s = Math.round(sec)
    let h = Math.floor(s / 3600)
    let m = Math.floor((s - h * 3600) / 60)
    s = s - 3600 * h - 60 * m;
    h = (h < 10) ? "0" + h : h;
    m = (m < 10) ? "0" + m : m;
    s = (s < 10) ? "0" + s : s;
    // if (h == 0) {
    //     if (m == 0) {
    //         return `${s}s`;
    //     } else return `${m}m${s}s`;
    // } else return `${h}h${m}m${s}s`;
    // let timeString = ` h: ${h} m: ${m} s: ${s}`;
    return `${h}:${m}:${s}`;
}

// let itemTracker = {
//     start: {
//         bulkItems: [
//             0: 34,
//             1: 356
//         ],
//         gp: 2444,
//         prayerPoints: 54243,
//     },
//     curr: {... }
window.getSnapshot = function() {
    let resources = {
        date: (new Date()).getTime(),
        bulkItems: itemsOwned(),
        prayerPoints: prayerPoints,
        slayerCoins: slayerCoins,
        gp: gp,
        hp: effHp(),
        kills: totalKills(),
    };
    return resources;
}

window.resetItemTracker = function() {
    window.itemTracker = {
        start: getSnapshot(),
        curr: getSnapshot(),
        itemTracked: -1,
        intervalTracked: 1,
        trackIntervals: [
            [0, 'reset'],
            [1, 'sec'],
            [60, 'min'],
            [3600, 'hour'],
            [3600 * 24, 'day']
        ]
    }
    trackerTick();
}

// resetItemTracker();

window.setupItemTracker = function() {
    let localCopy = localStorage.getItem("itemTracker")
    if (localCopy == null) {
        resetItemTracker();
    } else {
        window.itemTracker = JSON.parse(localCopy);
    }
}

roundCustom = function(nr, roundDigits = 0, letters = true) {
    // 12345.6789 --> roundDecs:
    // 12345
    if (Math.abs(nr) < 1000 || !letters) {
        return Math.round(nr * Math.pow(10, roundDigits)) / Math.pow(10, roundDigits);
    } else if (Math.abs(nr) < 1000000) {
        // 32415 -> 32.4k
        return Math.round(nr / (Math.pow(10, 3 - roundDigits))) / Math.pow(10, roundDigits) + "k";
    } else {
        // 12345678 -> 12.3m
        return Math.round(nr / (Math.pow(10, 6 - roundDigits))) / Math.pow(10, roundDigits) + "m";
    }
}

setItemTracked = function(itemID = -1) {
    itemTracker.itemTracked = itemID;
}

window.resDiff = {};
trackerTick = function(silent = true) {
    itemTracker.curr = getSnapshot();
    let { start, curr } = itemTracker;
    let timePassed = (curr.date - start.date) / 1000;
    // save tracker
    localStorage.setItem("itemTracker", JSON.stringify(itemTracker));
    // why did I do this?
    // itemTracker = JSON.parse(localStorage.getItem("itemTracker"));

    window.resDiff = {
        timePassed,
        intervalDur: -1,
        intervalLabel: "default",
        itemChange: new Array(items.length).fill(0),
        worthChange: new Array(items.length).fill(0),
        itemRate: new Array(items.length).fill(0),
        worthRate: new Array(items.length).fill(0),
        itemRound: 2,
        goldRound: 0,

        generalItemRate: 0,
        generalItemChange: 0,
        generalWorthChange: 0,
        generalWorthRate: 0,
        generalChanges: false,

        farmingItemRate: 0,
        farmingItemChange: 0,
        farmingWorthChange: 0,
        farmingWorthRate: 0,
        farmingChanges: false,

        totalWorthChange: 0,
        totalWorthRate: 0,

        pointRate: {},
        pointTimeLeft: {},

        lossChanges: false,
        timeLeft: new Array(items.length).fill(0),
    };


    let rateFactor, intervalDur, intervalLabel;
    if (itemTracker.itemTracked == -1) {
        // time-based tracking
        let interval = itemTracker.trackIntervals[itemTracker.intervalTracked];
        resDiff.intervalDur = interval[0];
        resDiff.intervalLabel = interval[1];
        if (resDiff.intervalDur == 0) {
            rateFactor = 1;
        } else {
            rateFactor = timePassed / resDiff.intervalDur;
        }
    } else {
        // let itemTrackedChange = scrt
        if (isNaN(itemTracker.itemTracked)) {
            let pointNames = ["gp", "prayerPoints", "slayerCoins", "hp", "kills"];
            if (pointNames.indexOf(itemTracker.itemTracked) != -1) {
                rateFactor = curr[itemTracker.itemTracked] - start[itemTracker.itemTracked];
                resDiff.intervalLabel = itemTracker.itemTracked;
            } else
                console.log(`Error tracking by ${itemTracker.itemTracked}`);
            // point tracking
        } else {
            // track relative to a specific item's change
            rateFactor = itemTracker.curr.bulkItems[itemTracker.itemTracked] -
                itemTracker.start.bulkItems[itemTracker.itemTracked]

            resDiff.intervalLabel = items[itemTracker.itemTracked].name;
            !silent && console.log(`Tracking by ${itemTracker.itemTracked}`)
        }
    }

    // items
    for (let itemID = 0; itemID < items.length; itemID++) {
        let startQty = start.bulkItems[itemID] || 0;
        let currQty = curr.bulkItems[itemID] || 0;
        let change = currQty - startQty;
        if (change == 0) continue;

        // absolute change, interval rate, time left
        resDiff.itemChange[itemID] = change;
        resDiff.itemRate[itemID] = change / rateFactor;

        // register change
        !silent && console.log(`${items[itemID].name} changed by ${resDiff.itemRate[itemID]} / ${resDiff.intervalLabel}`);
        let worthChange = change * items[itemID].sellsFor;
        resDiff.worthChange[itemID] = worthChange;
        resDiff.worthRate[itemID] = worthChange / rateFactor;
        // split by farming
        // I miss you horsie
        // Daisy, where are you now
        if (items[itemID].category == "Farming") {
            resDiff.farmingChanges = true;
            resDiff.farmingItemChange += change;
            resDiff.farmingWorthChange += worthChange;
        } else {
            resDiff.generalChanges = true;
            resDiff.generalItemChange += change;
            resDiff.generalWorthChange += worthChange;
        }
        if (change < 0 && currQty > 0) {
            resDiff.lossChanges = true;
            let timeLeft = currQty / (-change / timePassed);
            resDiff.timeLeft[itemID] = timeLeft;
            !silent && console.log(`${items[itemID].name} running out in ${resDiff.timeLeft[itemID]}`);
        }
    }
    resDiff.generalItemRate = resDiff.generalItemChange / rateFactor;
    resDiff.generalWorthRate = resDiff.generalWorthChange / rateFactor;

    resDiff.farmingItemRate = resDiff.farmingItemChange / rateFactor;
    resDiff.farmingWorthRate = resDiff.farmingWorthChange / rateFactor;

    resDiff.totalWorthChange = resDiff.generalWorthChange + resDiff.farmingWorthChange;
    resDiff.totalWorthRate = resDiff.totalWorthChange / rateFactor;

    resDiff.netWealthChange = (resDiff.totalWorthChange + curr.gp - start.gp) / timePassed;
    // points
    let pointNames = ["gp", "prayerPoints", "slayerCoins", "hp", "kills"];
    let trackTimeLeft = { "prayerPoints": true, "hp": true }
    for (let pointName of pointNames) {
        let startQty = start[pointName];
        let currQty = curr[pointName];
        let change = currQty - startQty;
        let rate = change / rateFactor;
        resDiff.pointRate[pointName] = rate;
        if (!silent && change != 0) console.log(pointName, " differ by", rate, "/", intervalLabel);
        if (currQty > 0 && change < 0 && trackTimeLeft[pointName]) {
            let timeLeft = currQty / (-change / timePassed);
            resDiff.pointTimeLeft[pointName] = timeLeft;
            !silent && console.log(`${pointName} point running out in ${resDiff.pointTimeLeft[pointName]}`);
        }
    }
    resDiff.rateFactor = rateFactor;
    // glove charges (todo)
    if (document.getElementById("dashWealthChange") != null) {
        $("#dashWealthChange").text(`${roundCustom(resDiff.netWealthChange, 0)}/s`);
    }
    return resDiff;
}

checkPreservation = function(obj) {
    return (JSON.stringify(obj) === JSON.stringify(JSON.parse(JSON.stringify(obj))))
}

function blerg() {
    console.log("blerg!")
}

window.getDashContent = function() {
    let compact = $(window).width() < 1250;
    // use curr and start to generate some item rows, and change #dashItems for it
    let generalContent = ``
    let farmingContent = ``;
    let lossContent = ``;
    let pointContent = ``;
    let content = ``;

    // each item row
    for (let itemID = 0; itemID < items.length; itemID++) {
        let change = resDiff.itemChange[itemID];

        // each item's change, put in the right content chunk
        if (change !== 0) {
            let itemRate = roundCustom(resDiff.itemRate[itemID], resDiff.itemRound);
            let worthRate = roundCustom(resDiff.worthRate[itemID], resDiff.goldRound);
            let row;
            // create item row
            if (compact) {
                row = `
                <div class="pointer-enabled" onClick="setItemTracked(${itemID})">
                    <img width="32" height="32" src="${items[itemID].media}"></img>
                    <span>
                        ${itemRate}
                    </span>
                    <br>
                </div`;
            } else {
                row = `
                <div class="row">
                    <div class="col-4 pointer-enabled" onClick="setItemTracked(${itemID})">
                        <img class="nav-img" src="${items[itemID].media}"></img>
                        ${items[itemID].name}
                    </div>
                    <div class="col-4">${itemRate}</div>
                    <div class="col-4">
                        <img class="nav-img" src="https://cdn.melvor.net/core/v018/assets/media/main/coins.svg"></img>
                        ${worthRate}
                    </div>
                </div>`;
            }
            if (items[itemID].category == "Farming") {
                farmingContent += row;
            } else {
                generalContent += row;
            }
            // Items running out --> lossContent
            // add row to loss content
            if (resDiff.timeLeft[itemID] > 0) {
                let timeString = getTimeString(resDiff.timeLeft[itemID]);
                let lossRow = ``;
                if (compact) {
                    lossRow = `
                    <div class="pointer-enabled" onClick="setItemTracked(${itemID})">
                        <img width="32" height="32" src="${items[itemID].media}"></img>
                        <span>
                            ${timeString} left
                        </span>
                        <br>
                    </div`;
                } else {
                    lossRow = `
                    <div class="row pointer-enabled" onClick="setItemTracked(${itemID})">
                        <div class="col-6">
                            <img class="nav-img" src="${items[itemID].media}"></img>
                            ${roundCustom(itemTracker.curr.bulkItems[itemID], 1)}
                        </div>
                        <div class="col-6">${timeString} left</div>
                    </div>`;
                }
                lossContent += lossRow;
            }
        }
    }
    // assemble general, farming, loss content with headers
    if (compact) {
        generalContent = `
            <br>
            <div class = "row no-gutters">
                <h5 class = "font-w700 text-center text-combat-smoke col"> General items </h5>
            </div>` + generalContent;
        farmingContent = `
            <br>
            <div class = "row no-gutters">
                <h5 class = "font-w700 text-center text-combat-smoke col"> Farming items </h5>
            </div>` + farmingContent;
        lossContent = `
            <br>
            <div class = "row no-gutters">
                <h5 class = "font-w700 text-center text-combat-smoke col"> Losing items </h5>
            </div>` + lossContent;
    } else {
        generalContent = `
            <br>
            <div class = "row no-gutters">
                <h5 class = "font-w700 text-center text-combat-smoke col"> General items </h5>
                <h5 class = "font-w700 text-center text-combat-smoke col"> Change </h5>
                <h5 class = "font-w700 text-center text-combat-smoke col"> Gold Worth </h5>
            </div>` + generalContent;
        farmingContent = `
            <br>
            <div class = "row no-gutters">
                <h5 class = "font-w700 text-center text-combat-smoke col"> Farming items </h5>
                <h5 class = "font-w700 text-center text-combat-smoke col"> Change </h5>
                <h5 class = "font-w700 text-center text-combat-smoke col"> Gold Worth </h5>
            </div>` + farmingContent;
        lossContent = `
                <br>
                <div class = "row no-gutters">
                    <h5 class = "font-w700 text-center text-combat-smoke col"> Losing items </h5>
                    <h5 class = "font-w700 text-center text-combat-smoke col"> Time left </h5>
                </div>` + lossContent;
    }
    // item category rate and worth rate
    if (compact) {
        generalContent += `
        <br>
        <h5> General items </h5>
        <img width="32" height="32" src="https://cdn.melvor.net/core/v018/assets/media/main/bank_header.svg"></img>
        <br>
        <span>${roundCustom(resDiff.generalItemRate, resDiff.itemRound)}</span>
        <br>
        <h5> Item worth </h5>
        <img width="32" height="32" src="https://cdn.melvor.net/core/v018/assets/media/main/coins.svg"></img>
        <br>
        <span>${roundCustom(resDiff.generalWorthRate, resDiff.goldRound)}</span>
        <br>`
        farmingContent += `
        <br>
        <h5> Farming items </h5>
        <img width="32" height="32" src="https://cdn.melvor.net/core/v018/assets/media/skills/farming/farming.svg"></img>
        <br>
        <span>${roundCustom(resDiff.farmingItemRate, resDiff.itemRound)}</span>
        <br>
        <h5> Item worth </h5>
        <img width="32" height="32" src="https://cdn.melvor.net/core/v018/assets/media/main/coins.svg"></img>
        <br>
        <span>${roundCustom(resDiff.farmingWorthRate, resDiff.goldRound)}</span>
        <br>`
    } else {
        // total item changes
        generalContent += `
        <div class="row">
            <div class="col-4">
                <img class="nav-img" src="https://cdn.melvor.net/core/v018/assets/media/main/bank_header.svg"></img>
                General Items
            </div>
            <div class="col-4">${roundCustom(resDiff.generalItemRate, resDiff.itemRound)}</div>
            <div class="col-4">
                <img class="nav-img" src="https://cdn.melvor.net/core/v018/assets/media/main/coins.svg"></img>
                ${roundCustom(resDiff.generalWorthRate, resDiff.goldRound)}
            </div>
        </div>`;
        farmingContent += `
        <div class="row">
            <div class="col-4">
                <img class="nav-img" src="https://cdn.melvor.net/core/v018/assets/media/skills/farming/farming.svg"></img>
                Farming Items
            </div>
            <div class="col-4">${roundCustom(resDiff.farmingItemRate, resDiff.itemRound)}</div>
            <div class="col-4">
                <img class="nav-img" src="https://cdn.melvor.net/core/v018/assets/media/main/coins.svg"></img>
                ${roundCustom(resDiff.farmingWorthRate, resDiff.goldRound)}
            </div>
        </div>`;
    }

    if (resDiff.generalChanges)
        content += generalContent
    if (resDiff.farmingChanges)
        content += farmingContent
    if (resDiff.lossChanges)
        content += lossContent

    // points
    pointContent += `<br/>
    <div class="row no-gutters">
        <h5 class="font-w700 text-center text-combat-smoke col-sm">Others</h5>
    </div>`;

    pointContent += makePointRow(compact, "gp", "https://cdn.melvor.net/core/v018/assets/media/main/coins.svg");
    pointContent += makePointRow(compact, "hp", "https://cdn.melvor.net/core/v018/assets/media/skills/combat/hitpoints.svg", true);
    pointContent += makePointRow(compact, "kills", "https://cdn.melvor.net/core/v018/assets/media/skills/combat/combat.svg");
    pointContent += makePointRow(compact, "prayerPoints", "https://cdn.melvor.net/core/v018/assets/media/skills/prayer/prayer.svg", true);
    pointContent += makePointRow(compact, "slayerCoins", "https://cdn.melvor.net/core/v018/assets/media/main/slayer_coins.svg");
    content += pointContent;
    return content;
}

makePointRow = function(compact, pointName, src) {
    let desc = {
        "hp": "Hitpoints",
        "prayerPoints": "Prayer Points",
        "slayerCoins": "Slayer Coins",
        "gp": "Cash",
        "kills": "Kills"
    }
    let pointRow = ``;
    if (compact) {
        pointRow = `
        <div class="pointer-enabled" onClick="setItemTracked('${pointName}')">
            <img width="32" height="32" src="${src}"></img>
            <span>${roundCustom(resDiff.pointRate[pointName], 1)}</span>
        </div>`
        if (resDiff.pointTimeLeft[pointName]) {
            pointRow += `<div><span>${getTimeString(resDiff.pointTimeLeft[pointName])} left</span></div`
        }
    } else {
        pointRow = `
        <div class="row no-gutters pointer-enabled" onClick="setItemTracked('${pointName}')">
            <div class="col-4">
                <img class="nav-img" src="${src}"></img>
                ${desc[pointName]}
            </div>
            <div class="col-8">
                ${roundCustom(resDiff.pointRate[pointName], 2)}
            </div>
        </div>`
        if (resDiff.pointTimeLeft[pointName]) {
            pointRow += `
            <div class="row no-gutters">
                <div class="col-4">
                    <img class="nav-img" src="${src}"></img>
                    ${roundCustom(itemTracker.curr[pointName], 2)}
                </div>
                <div class="col-8">
                    ${getTimeString(resDiff.pointTimeLeft[pointName])} left
                </div>
            </div>`
        }
    }
    return pointRow;
}

// window.dashItemRows = "";
// window.dashPointRows = "";
// window.dashContent = "";

window.updateDash = function() {
    let interval = itemTracker.trackIntervals[itemTracker.intervalTracked];
    let intervalLabel = interval[1];
    let content = getDashContent();
    let buttonLabel = (resDiff.intervalLabel == "reset") ? "Since last" : "Per:";
    // <button type="button" class="swal2-confirm swal2-styled onClick="resetItemTracker()">Reset</button>`
    if (document.getElementById("dashContent") != null) {
        $("#dashContent").html(`
        <p>Time tracked: ${getTimeString(resDiff.timePassed)}</p>
        <button type="button" onClick="toggleIntervalSize()" class="swal2-confirm swal2-styled" aria-label="" style="display: inline-block; background-color: rgb(55, 200, 55); border-left-color: rgb(48, 133, 214); border-right-color: rgb(48, 133, 214);">
            ${buttonLabel} ${resDiff.intervalLabel}
        </button>
        ${content}
        `);
    }
}

window.openItemDash = function() {
    Swal.fire({
        title: 'Items',
        html: `<div id="dashContent"></div>`,
        width: "50%",
        // openItemDash();
        confirmButtonColor: '#3085d6',
        cancelButtonColor: '#d33',
        confirmButtonText: 'Reset Tracker'
    }).then((result) => {
        if (result.value) {
            console.log("Resetting item tracker")
            resetItemTracker();
            setTimeout(openItemDash, 100);
        }
    })
}

function injectItemTrackerButton() {
    if (document.getElementById("dashWealthChange") == null) {
        let dashButton = `
        <li class="nav-main-item">
        <div class="nav-main-link nav-compact pointer-enabled" onclick="openItemDash();">
        <img class="nav-img" src="https://cdn.melvor.net/core/v018/assets/media/main/statistics_header.svg">
        <span class="nav-main-link-name">Item Dash</span>
        <img src="https://cdn.melvor.net/core/v018/assets/media/main/coins.svg" style="margin-right: 4px;" width="16px" height="16px">
        <small id="dashWealthChange" class="text-warning" data-toggle="tooltip" data-html="true" data-placement="bottom" title="" data-original-title="TEST"></small>
        </div>
        </li>`
        $("#nav-menu-show").before(dashButton);
        setupItemTracker();
        window.itemTrackBot = setInterval(() => {
            trackerTick();
            updateDash();
            // HACK for initial ticker:
            // $("#dashItems").html(getDashItemRows())
        }, 1000);
    }
}

function loadItemDashboard() {
    // if ((window.isLoaded && !window.currentlyCatchingUp) ||
    //     (typeof unsafeWindow !== 'undefined' && unsafeWindow.isLoaded && !unsafeWindow.currentlyCatchingUp) ||
    //     document.getElementById("nav-menu-show") == null ||
    if (!window.isLoaded || window.currentlyCatchingUp ||
        document.getElementById("nav-menu-show") == null
        // equipmentSets[1] == undefined
    ) {
        // console.log("Retrying...")
        setTimeout(loadItemDashboard, 300);
        return;
    } else {
        injectItemTrackerButton();
    }
}
loadItemDashboard();
// window.dashboardLoaderInterval = setInterval()

QingJ © 2025

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