Greasy Fork

Greasy Fork is available in English.

Ranged Way Idle

死亡提醒、强制刷新MWITools的价格、私信提醒音、自动任务排序、显示购买预付金/出售可获金/待领取金额、默哀法师助手

目前为 2025-05-11 提交的版本。查看 最新版本

// ==UserScript==
// @name         Ranged Way Idle
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  死亡提醒、强制刷新MWITools的价格、私信提醒音、自动任务排序、显示购买预付金/出售可获金/待领取金额、默哀法师助手
// @author       AlphB
// @match        https://www.milkywayidle.com/*
// @match        https://test.milkywayidle.com/*
// @grant        GM_notification
// @icon         https://www.google.com/s2/favicons?sz=64&domain=milkywayidle.com
// @grant        none
// @license      CC-BY-NC-SA-4.0
// ==/UserScript==

(function () {
    const config = {
        notifyDeath: true,
        forceUpdateMarketPrice: true,
        notifyWhisperMessages: true,
        listenKeywordMessages: true,
        autoTaskSort: true,
        showMarketListingsFunds: true,
        mournForMagicWayIdle: true
    }
    const globalVariable = {
        battleData: {
            players: null
        },
        itemDetailMap: JSON.parse(localStorage.getItem("initClientData")).itemDetailMap,
        whisperAudio: new Audio(`https://upload.thbwiki.cc/d/d1/se_bonus2.mp3`),
        keywordAudio: new Audio(`https://upload.thbwiki.cc/c/c9/se_pldead00.mp3`),
        keywords: [],
        market: {
            hasFundsElement: false,
            sellValue: null,
            buyValue: null,
            unclaimedValue: null,
            sellListings: {},
            buyListings: {}
        }
    };
    init();

    function init() {
        globalVariable.whisperAudio.volume = 0.4;
        globalVariable.keywordAudio.volume = 0.4;
        let observer = new MutationObserver(function (mutationsList, observer) {
            if (config.showMarketListingsFunds) showMarketListingsFunds();
            if (config.autoTaskSort) autoClickTaskSortButton();
        });
        observer.observe(document, {childList: true, subtree: true});
        const dataProperty = Object.getOwnPropertyDescriptor(MessageEvent.prototype, "data");
        const oriGet = dataProperty.get;
        dataProperty.get = hookedGet;
        Object.defineProperty(MessageEvent.prototype, "data", dataProperty);

        function hookedGet() {
            const socket = this.currentTarget;
            if (!(socket instanceof WebSocket)) {
                return oriGet.call(this);
            }
            if (socket.url.indexOf("api.milkywayidle.com/ws") <= -1 && socket.url.indexOf("api-test.milkywayidle.com/ws") <= -1) {
                return oriGet.call(this);
            }
            const message = oriGet.call(this);
            Object.defineProperty(this, "data", {value: message});
            return handleMessage(message);
        }

        if (config.mournForMagicWayIdle) {
            console.log("为法师助手默哀")
        }
    }


    function handleMessage(message) {
        const obj = JSON.parse(message);
        if (!obj) return message;
        switch (obj.type) {
            case "init_character_data":
                globalVariable.keywords.push(obj.character.name.toLowerCase());
                updateMarketListings(obj.myMarketListings);
                break;
            case "market_listings_updated":
                updateMarketListings(obj.endMarketListings);
                break;
            case "new_battle":
                if (config.notifyDeath) initBattle(obj);
                break;
            case "battle_updated":
                if (config.notifyDeath) checkDeath(obj);
                break;
            case "market_item_order_books_updated":
                if (config.forceUpdateMarketPrice) marketPriceUpdate(obj);
                break;
            case "chat_message_received":
                handleChatMessage(obj);
                break;
        }
        return message;
    }

    function notifyDeath(name) {
        new Notification('🎉🎉🎉喜报🎉🎉🎉', {body: `${name} 死了!`});
    }

    function initBattle(obj) {
        globalVariable.battleData.players = [];
        for (let player of obj.players) {
            globalVariable.battleData.players.push({
                name: player.name, isAlive: player.currentHitpoints > 0,
            });
            if (player.currentHitpoints === 0) {
                notifyDeath(player.name);
            }
        }
    }

    function checkDeath(obj) {
        for (let key in obj.pMap) {
            const index = parseInt(key);
            if (globalVariable.battleData.players[index].isAlive && obj.pMap[key].cHP === 0) {
                globalVariable.battleData.players[index].isAlive = false;
                notifyDeath(globalVariable.battleData.players[index].name);
            } else if (!globalVariable.battleData.players[index].isAlive && obj.pMap[key].cHP > 0) {
                globalVariable.battleData.players[index].isAlive = true;
            }
        }
    }

    function marketPriceUpdate(obj) {
        // 本函数的代码复制自Magic Way Idle
        let itemDetailMap = globalVariable.itemDetailMap;
        let itemName = itemDetailMap[obj.marketItemOrderBooks.itemHrid].name;
        let ask = -1;
        let bid = -1;
        // 读取ask最低报价
        if (obj.marketItemOrderBooks.orderBooks[0].asks && obj.marketItemOrderBooks.orderBooks[0].asks.length > 0) {
            ask = obj.marketItemOrderBooks.orderBooks[0].asks[0].price;
        }
        // 读取bid最高报价
        if (obj.marketItemOrderBooks.orderBooks[0].bids && obj.marketItemOrderBooks.orderBooks[0].bids.length > 0) {
            bid = obj.marketItemOrderBooks.orderBooks[0].bids[0].price;
        }
        // 读取所有物品价格
        let jsonObj = JSON.parse(localStorage.getItem("MWITools_marketAPI_json"));
        // 修改当前查看物品价格
        if (jsonObj.market[itemName]) {
            jsonObj.market[itemName].ask = ask;
            jsonObj.market[itemName].bid = bid;
        }
        // 将修改后结果写回marketAPI缓存,完成对marketAPI价格的强制修改
        localStorage.setItem("MWITools_marketAPI_json", JSON.stringify(jsonObj));
    }

    function handleChatMessage(obj) {
        if (obj.message.chan === "/chat_channel_types/whisper") {
            if (config.notifyWhisperMessages) {
                globalVariable.whisperAudio.play();
            }
        } else if (obj.message.chan === "/chat_channel_types/chinese") {
            if (config.listenKeywordMessages) {
                for (let keyword of globalVariable.keywords) {
                    if (obj.message.m.toLowerCase().includes(keyword)) {
                        globalVariable.keywordAudio.play();
                    }
                }
            }
        }
    }

    function autoClickTaskSortButton() {
        const targetElement = document.querySelector('#TaskSort');
        if (targetElement && targetElement.textContent !== '手动排序') {
            targetElement.click();
            targetElement.textContent = '手动排序';
        }
    }

    function formatCoinValue(num) {
        if (num >= 1e13) {
            return (num / 1e12).toFixed(0) + "T";
        } else if (num >= 1e10) {
            return (num / 1e9).toFixed(0) + "B";
        } else if (num >= 1e7) {
            return (num / 1e6).toFixed(0) + "M";
        } else if (num >= 1e4) {
            return (num / 1e3).toFixed(0) + "K";
        }
        return num.toString();
    }

    function updateMarketListings(obj) {
        for (listing of obj) {
            if (listing.status === "/market_listing_status/cancelled") {
                delete globalVariable.market[listing.isSell ? "sellListings" : "buyListings"][listing.id];
                continue
            }
            globalVariable.market[listing.isSell ? "sellListings" : "buyListings"][listing.id] = {
                itemHrid: listing.itemHrid,
                price: (listing.orderQuantity - listing.filledQuantity) * (listing.isSell ? Math.ceil(listing.price * 0.98) : listing.price),
                unclaimedCoinCount: listing.unclaimedCoinCount,
            }
        }
        globalVariable.market.buyValue = 0;
        globalVariable.market.sellValue = 0;
        globalVariable.market.unclaimedValue = 0;
        for (let id in globalVariable.market.buyListings) {
            const listing = globalVariable.market.buyListings[id];
            globalVariable.market.buyValue += listing.price;
            globalVariable.market.unclaimedValue += listing.unclaimedCoinCount;
        }
        for (let id in globalVariable.market.sellListings) {
            const listing = globalVariable.market.sellListings[id];
            globalVariable.market.sellValue += listing.price;
            globalVariable.market.unclaimedValue += listing.unclaimedCoinCount;
        }
        globalVariable.market.hasFundsElement = false;
    }

    function showMarketListingsFunds() {
        if (globalVariable.market.hasFundsElement) return;
        const targetNode = document.querySelector("div.MarketplacePanel_coinStack__1l0UD");
        if (targetNode) {
            targetNode.style.top = "0px";
            targetNode.style.left = "0px";
            let fundsElement = document.querySelector("div.fundsElement");
            while (fundsElement) {
                fundsElement.remove();
                fundsElement = document.querySelector("div.fundsElement");
            }
            makeNode('<span style="color: rgb(102,204,255); font-weight: bold;">购买预付金</span>', globalVariable.market.buyValue, targetNode, ["125px", "0px"]);
            makeNode('<span style="color: rgb(102,204,255); font-weight: bold;">出售可获金</span>', globalVariable.market.sellValue, targetNode, ["125px", "22px"]);
            makeNode('<span style="color: rgb(102,204,255); font-weight: bold;">待领取金额</span>', globalVariable.market.unclaimedValue, targetNode, ["0px", "22px"]);
            globalVariable.market.hasFundsElement = true;
        }
    }

    function makeNode(innerHTML, value, targetNode, style) {
        let node = targetNode.cloneNode(true);
        node.classList.add("fundsElement");
        const countNode = node.querySelector("div.Item_count__1HVvv");
        const textNode = node.querySelector("div.Item_name__2C42x");
        if (countNode) countNode.textContent = formatCoinValue(value);
        if (textNode) textNode.innerHTML = innerHTML;
        node.style.left = style[0];
        node.style.top = style[1];
        targetNode.parentNode.insertBefore(node, targetNode.nextSibling);
    }
})();