// ==UserScript== // @name Ranged Way Idle // @namespace http://tampermonkey.net/ // @version 1.12 // @description 死亡提醒、强制刷新MWITools的价格、私信提醒音、自动任务排序、显示购买预付金/出售可获金/待领取金额、显示任务价值、默哀法师助手 // @author AlphB // @match https://www.milkywayidle.com/* // @match https://test.milkywayidle.com/* // @grant GM_notification // @grant GM_getValue // @grant GM_setValue // @icon https://www.google.com/s2/favicons?sz=64&domain=milkywayidle.com // @grant none // @license CC-BY-NC-SA-4.0 // @downloadURL https://update.greasyfork.icu/scripts/535671/Ranged%20Way%20Idle.user.js // @updateURL https://update.greasyfork.icu/scripts/535671/Ranged%20Way%20Idle.meta.js // ==/UserScript== (function () { const config = { notifyDeath: true, forceUpdateMarketPrice: true, notifyWhisperMessages: false, listenKeywordMessages: false, autoTaskSort: true, showMarketListingsFunds: true, mournForMagicWayIdle: true, showTaskValue: true, keywords: [], } const globalVariable = { battleData: { players: null, lastNotifyTime: 0, }, 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`), market: { hasFundsElement: false, sellValue: null, buyValue: null, unclaimedValue: null, sellListings: null, buyListings: null }, task: { taskListElement: null, taskTokenValueData: null, hasTaskValueElement: false, taskValueElements: [], tokenValue: { Bid: null, Ask: null } } }; init(); function init() { // readConfig(); if (!('Edible_Tools' in localStorage)) { config.showTaskValue = false; } globalVariable.whisperAudio.volume = 0.4; globalVariable.keywordAudio.volume = 0.4; let observer = new MutationObserver(function () { if (config.showMarketListingsFunds) showMarketListingsFunds(); if (config.autoTaskSort) autoClickTaskSortButton(); if (config.showTaskValue) showTaskValue(); }); 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); globalVariable.task.taskTokenValueData = getTaskTokenValue(); if (config.mournForMagicWayIdle) { console.log("为法师助手默哀"); } 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); } // function readConfig() { // const localConfig = localStorage.getItem("ranged_way_idle_config"); // if (localConfig) { // const localConfigObj = JSON.parse(localConfig); // for (let key in localConfigObj) { // if (config.hasOwnProperty(key)) { // config[key] = localConfigObj[key]; // } // } // } // } } function handleMessage(message) { const obj = JSON.parse(message); if (!obj) return message; switch (obj.type) { case "init_character_data": globalVariable.market.sellListings = {}; globalVariable.market.buyListings = {}; config.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 "quests_updated": for (let e of globalVariable.task.taskValueElements) { e.remove(); } globalVariable.task.taskValueElements = []; globalVariable.task.hasTaskValueElement = false; break; case "chat_message_received": handleChatMessage(obj); break; } return message; } function notifyDeath(name) { const nowTime = Date.now(); if (nowTime - globalVariable.battleData.lastNotifyTime < 60000) return; globalVariable.battleData.lastNotifyTime = nowTime; 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) { if (!globalVariable.battleData.players) return; 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) { globalVariable.task.taskTokenValueData = getTaskTokenValue(); // 本函数的代码复制自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 config.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 Math.floor(num / 1e12) + "T"; } else if (num >= 1e10) { return Math.floor(num / 1e9) + "B"; } else if (num >= 1e7) { return Math.floor(num / 1e6) + "M"; } else if (num >= 1e4) { return Math.floor(num / 1e3) + "K"; } return num.toString(); } function updateMarketListings(obj) { for (let 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 coinStackElement = document.querySelector("div.MarketplacePanel_coinStack__1l0UD"); if (coinStackElement) { coinStackElement.style.top = "0px"; coinStackElement.style.left = "0px"; let fundsElement = coinStackElement.parentNode.querySelector("div.fundsElement"); while (fundsElement) { fundsElement.remove(); fundsElement = coinStackElement.parentNode.querySelector("div.fundsElement"); } makeNode("购买预付金", globalVariable.market.buyValue, ["125px", "0px"]); makeNode("出售可获金", globalVariable.market.sellValue, ["125px", "22px"]); makeNode("待领取金额", globalVariable.market.unclaimedValue, ["0px", "22px"]); globalVariable.market.hasFundsElement = true; } function makeNode(text, value, style) { let node = coinStackElement.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 = `${text}`; node.style.left = style[0]; node.style.top = style[1]; coinStackElement.parentNode.insertBefore(node, coinStackElement.nextSibling); } } function getTaskTokenValue() { const chestDropData = JSON.parse(localStorage.getItem("Edible_Tools")).Chest_Drop_Data; const lootsName = ["大陨石舱", "大工匠匣", "大宝箱"]; const bidValueList = [ parseFloat(chestDropData["Large Meteorite Cache"]["期望产出Bid"]), parseFloat(chestDropData["Large Artisan's Crate"]["期望产出Bid"]), parseFloat(chestDropData["Large Treasure Chest"]["期望产出Bid"]), ] const askValueList = [ parseFloat(chestDropData["Large Meteorite Cache"]["期望产出Ask"]), parseFloat(chestDropData["Large Artisan's Crate"]["期望产出Ask"]), parseFloat(chestDropData["Large Treasure Chest"]["期望产出Ask"]), ] const res = { bidValue: Math.max(...bidValueList), askValue: Math.max(...askValueList) } res.bidLoots = lootsName[bidValueList.indexOf(res.bidValue)]; res.askLoots = lootsName[askValueList.indexOf(res.askValue)]; res.bidValue = Math.round(res.bidValue / 30); res.askValue = Math.round(res.askValue / 30); res.giftValueBid = Math.round(parseFloat(chestDropData["Purple's Gift"]["期望产出Bid"])); res.giftValueAsk = Math.round(parseFloat(chestDropData["Purple's Gift"]["期望产出Ask"])); if (config.forceUpdateMarketPrice) { const marketJSON = JSON.parse(localStorage.getItem("MWITools_marketAPI_json")); marketJSON.market["Task Token"].ask = res.askValue; marketJSON.market["Task Token"].bid = res.bidValue; localStorage.setItem("MWITools_marketAPI_json", JSON.stringify(marketJSON)); } res.rewardValueBid = res.bidValue + res.giftValueBid / 50; res.rewardValueAsk = res.askValue + res.giftValueAsk / 50; return res; } function showTaskValue() { globalVariable.task.taskListElement = document.querySelector("div.TasksPanel_taskList__2xh4k"); if (!globalVariable.task.taskListElement) { globalVariable.task.taskValueElements = []; globalVariable.task.hasTaskValueElement = false; globalVariable.task.taskListElement = null; return; } if (globalVariable.task.hasTaskValueElement) return; globalVariable.task.hasTaskValueElement = true; const taskNodes = [...globalVariable.task.taskListElement.querySelectorAll("div.RandomTask_randomTask__3B9fA")]; function convertKEndStringToNumber(str) { if (str.endsWith('K') || str.endsWith('k')) { return Number(str.slice(0, -1)) * 1000; } else { return Number(str); } } taskNodes.forEach(function (node) { const reward = node.querySelector("div.RandomTask_rewards__YZk7D"); const coin = convertKEndStringToNumber(reward.querySelectorAll("div.Item_count__1HVvv")[0].innerText); const tokenCount = Number(reward.querySelectorAll("div.Item_count__1HVvv")[1].innerText); const newDiv = document.createElement("div"); newDiv.textContent = `奖励期望收益: ${formatCoinValue(coin + tokenCount * globalVariable.task.taskTokenValueData.rewardValueAsk)} / ${formatCoinValue(coin + tokenCount * globalVariable.task.taskTokenValueData.rewardValueBid)}`; newDiv.style.color = "rgb(248,0,248)"; node.querySelector("div.RandomTask_action__3eC6o").appendChild(newDiv); globalVariable.task.taskValueElements.push(newDiv); }); } })();