您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
死亡提醒、强制刷新MWITools的价格、私信提醒音、自动任务排序、显示购买预付金/出售可获金/待领取金额、显示任务价值、默哀法师助手
当前为
- // ==UserScript==
- // @name Ranged Way Idle
- // @namespace http://tampermonkey.net/
- // @version 2.2
- // @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
- // ==/UserScript==
- (function () {
- const config = {
- notifyDeath: {enable: true, desc: "战斗中角色死亡时发送通知"},
- forceUpdateMarketPrice: {enable: true, desc: "进入市场时,强制更新MWITools的市场价格"},
- notifyWhisperMessages: {enable: false, desc: "接受到私信时播放提醒音"},
- listenKeywordMessages: {enable: false, desc: "中文频道消息含有关键词时播放提醒音"},
- autoTaskSort: {enable: true, desc: "自动点击MWI TaskManager的任务排序按钮"},
- showMarketListingsFunds: {enable: true, desc: "显示购买预付金/出售可获金/待领取金额"},
- mournForMagicWayIdle: {enable: true, desc: "在控制台默哀法师助手"},
- showTaskValue: {enable: true, desc: "显示任务代币的价值"},
- 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.enable = false;
- }
- // 更新市场价格需要MWITools支持
- if (!('MWITools_marketAPI_json' in localStorage)) {
- config.forceUpdateMarketPrice.enable = false;
- }
- globalVariable.whisperAudio.volume = 0.4;
- globalVariable.keywordAudio.volume = 0.4;
- let observer = new MutationObserver(function () {
- if (config.showMarketListingsFunds.enable) showMarketListingsFunds();
- if (config.autoTaskSort.enable) autoClickTaskSortButton();
- if (config.showTaskValue.enable) showTaskValue();
- showConfigMenu();
- });
- observer.observe(document, {childList: true, subtree: true});
- globalVariable.task.taskTokenValueData = getTaskTokenValue();
- if (config.mournForMagicWayIdle.enable) {
- console.log("为法师助手默哀");
- }
- const oriGet = Object.getOwnPropertyDescriptor(MessageEvent.prototype, "data").get;
- function hookedGet() {
- const socket = this.currentTarget;
- if (!(socket instanceof WebSocket) || !socket.url ||
- (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);
- return handleMessage(message);
- }
- Object.defineProperty(MessageEvent.prototype, "data", {
- get: hookedGet,
- configurable: true,
- enumerable: true
- });
- }
- 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) && key !== 'keywords') {
- config[key].enable = localConfigObj[key];
- }
- }
- config.keywords = localConfigObj.keywords;
- }
- }
- function saveConfig() {
- // 仅保存enable开关和keywords
- const saveConfigObj = {};
- const configMenu = document.querySelectorAll("div#ranged_way_idle_config_menu input");
- if (configMenu.length === 0) return;
- for (const checkbox of configMenu) {
- config[checkbox.id].isTrue = checkbox.checked;
- saveConfigObj[checkbox.id] = checkbox.checked;
- }
- saveConfigObj.keywords = config.keywords;
- localStorage.setItem("ranged_way_idle_config", JSON.stringify(saveConfigObj));
- }
- function showConfigMenu() {
- const targetNode = document.querySelector("div.SettingsPanel_profileTab__214Bj");
- if (targetNode) {
- if (!targetNode.querySelector("#ranged_way_idle_config_menu")) {
- // enable开关部分
- targetNode.insertAdjacentHTML("beforeend", `<div id="ranged_way_idle_config_menu"></div>`);
- const insertElem = targetNode.querySelector("div#ranged_way_idle_config_menu");
- insertElem.insertAdjacentHTML(
- "beforeend",
- `<div style="float: left;" id="ranged_way_idle_config">${
- "Ranged Way Idle 设置"
- }</div></br>`
- );
- for (let key in config) {
- if (key === 'keywords') continue;
- insertElem.insertAdjacentHTML(
- "beforeend",
- `<div style="float: left;">
- <input type="checkbox" id="${key}" ${config[key].enable ? "checked" : ""}>${config[key].desc}
- </div></br>`
- );
- }
- insertElem.addEventListener("change", saveConfig);
- // 控制 keywords 列表
- const container = document.createElement('div');
- container.style.marginTop = '20px';
- container.classList.add("ranged_way_idle_keywords_config_menu")
- const input = document.createElement('input');
- input.type = 'text';
- input.style.width = '200px';
- input.placeholder = 'Ranged Way Idle 监听关键词';
- const button = document.createElement('button');
- button.textContent = '添加';
- const listContainer = document.createElement('div');
- listContainer.style.marginTop = '10px';
- container.appendChild(input);
- container.appendChild(button);
- container.appendChild(listContainer);
- targetNode.insertBefore(container, targetNode.nextSibling);
- function renderList() {
- listContainer.innerHTML = '';
- config.keywords.forEach((item, index) => {
- const itemDiv = document.createElement('div');
- itemDiv.textContent = item;
- itemDiv.style.margin = 'auto';
- itemDiv.style.width = '200px';
- itemDiv.style.cursor = 'pointer';
- itemDiv.addEventListener('click', () => {
- config.keywords.splice(index, 1);
- renderList();
- });
- listContainer.appendChild(itemDiv);
- });
- saveConfig();
- }
- renderList();
- button.addEventListener('click', () => {
- const newItem = input.value.trim();
- if (newItem) {
- config.keywords.push(newItem);
- input.value = '';
- saveConfig();
- renderList();
- }
- });
- }
- }
- }
- function handleMessage(message) {
- try {
- const obj = JSON.parse(message);
- if (!obj) return message;
- switch (obj.type) {
- case "init_character_data":
- globalVariable.market.sellListings = {};
- globalVariable.market.buyListings = {};
- updateMarketListings(obj.myMarketListings);
- break;
- case "market_listings_updated":
- updateMarketListings(obj.endMarketListings);
- break;
- case "new_battle":
- if (config.notifyDeath.enable) initBattle(obj);
- break;
- case "battle_updated":
- if (config.notifyDeath.enable) checkDeath(obj);
- break;
- case "market_item_order_books_updated":
- if (config.forceUpdateMarketPrice.enable) 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;
- }
- } catch (e) {
- console.error(e);
- }
- return message;
- }
- function notifyDeath(name) {
- // 如果间隔小于60秒,强制不播报
- 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 (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.marketData[itemName]) {
- jsonObj.marketData[itemName].ask = ask;
- jsonObj.marketData[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.enable) {
- globalVariable.whisperAudio.play();
- }
- } else if (obj.message.chan === "/chat_channel_types/chinese") {
- if (config.listenKeywordMessages.enable) {
- for (let keyword of config.keywords) {
- if (obj.message.m.includes(keyword)) {
- globalVariable.keywordAudio.play();
- }
- }
- }
- }
- }
- function autoClickTaskSortButton() {
- // 点击MWI TaskManager的任务排序按钮
- const targetElement = document.querySelector('#TaskSort');
- if (targetElement && targetElement.textContent !== '手动排序') {
- targetElement.click();
- targetElement.textContent = '手动排序';
- }
- }
- function formatCoinValue(num) {
- if (isNaN(num)) return "NaN";
- 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 = `<span style="color: rgb(102,204,255); font-weight: bold;">${text}</span>`;
- 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)
- }
- // bid和ask的最佳兑换选项
- res.bidLoots = lootsName[bidValueList.indexOf(res.bidValue)];
- res.askLoots = lootsName[askValueList.indexOf(res.askValue)];
- // bid和ask的任务代币价值
- 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.enable) {
- const marketJSON = JSON.parse(localStorage.getItem("MWITools_marketAPI_json"));
- marketJSON.marketData["/items/task_token"].ask = res.askValue;
- marketJSON.marketData["/items/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)";
- newDiv.classList.add("rewardValue");
- node.querySelector("div.RandomTask_action__3eC6o").appendChild(newDiv);
- globalVariable.task.taskValueElements.push(newDiv);
- });
- }
- })();