Ranged Way Idle

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

当前为 2025-05-14 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Ranged Way Idle
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.9
  5. // @description 死亡提醒、强制刷新MWITools的价格、私信提醒音、自动任务排序、显示购买预付金/出售可获金/待领取金额、显示任务价值、默哀法师助手
  6. // @author AlphB
  7. // @match https://www.milkywayidle.com/*
  8. // @match https://test.milkywayidle.com/*
  9. // @grant GM_notification
  10. // @icon https://www.google.com/s2/favicons?sz=64&domain=milkywayidle.com
  11. // @grant none
  12. // @license CC-BY-NC-SA-4.0
  13. // ==/UserScript==
  14.  
  15. (function () {
  16. const config = {
  17. notifyDeath: true,
  18. forceUpdateMarketPrice: true,
  19. notifyWhisperMessages: false,
  20. listenKeywordMessages: false,
  21. autoTaskSort: true,
  22. showMarketListingsFunds: true,
  23. mournForMagicWayIdle: true,
  24. showTaskValue: true
  25. }
  26. const globalVariable = {
  27. battleData: {
  28. players: null
  29. },
  30. itemDetailMap: JSON.parse(localStorage.getItem("initClientData")).itemDetailMap,
  31. whisperAudio: new Audio(`https://upload.thbwiki.cc/d/d1/se_bonus2.mp3`),
  32. keywordAudio: new Audio(`https://upload.thbwiki.cc/c/c9/se_pldead00.mp3`),
  33. keywords: [],
  34. market: {
  35. hasFundsElement: false,
  36. sellValue: null,
  37. buyValue: null,
  38. unclaimedValue: null,
  39. sellListings: null,
  40. buyListings: null
  41. },
  42. task: {
  43. taskTokenValueData: null,
  44. hasTaskValueElement: false,
  45. taskValueElements: [],
  46. }
  47. };
  48. init();
  49.  
  50. function init() {
  51. if (!('Edible_Tools' in localStorage)) {
  52. config.showTaskValue = false;
  53. }
  54. globalVariable.whisperAudio.volume = 0.4;
  55. globalVariable.keywordAudio.volume = 0.4;
  56. let observer = new MutationObserver(function (mutationsList, observer) {
  57. if (config.showMarketListingsFunds) showMarketListingsFunds();
  58. if (config.autoTaskSort) autoClickTaskSortButton();
  59. if (config.showTaskValue) showTaskValue();
  60. });
  61. observer.observe(document, {childList: true, subtree: true});
  62. const dataProperty = Object.getOwnPropertyDescriptor(MessageEvent.prototype, "data");
  63. const oriGet = dataProperty.get;
  64. dataProperty.get = hookedGet;
  65. Object.defineProperty(MessageEvent.prototype, "data", dataProperty);
  66.  
  67. function hookedGet() {
  68. const socket = this.currentTarget;
  69. if (!(socket instanceof WebSocket)) {
  70. return oriGet.call(this);
  71. }
  72. if (socket.url.indexOf("api.milkywayidle.com/ws") <= -1 && socket.url.indexOf("api-test.milkywayidle.com/ws") <= -1) {
  73. return oriGet.call(this);
  74. }
  75. const message = oriGet.call(this);
  76. Object.defineProperty(this, "data", {value: message});
  77. return handleMessage(message);
  78. }
  79.  
  80. globalVariable.task.taskTokenValueData = getTaskTokenValue();
  81.  
  82. if (config.mournForMagicWayIdle) {
  83. console.log("为法师助手默哀");
  84. }
  85. }
  86.  
  87.  
  88. function handleMessage(message) {
  89. const obj = JSON.parse(message);
  90. if (!obj) return message;
  91. switch (obj.type) {
  92. case "init_character_data":
  93. globalVariable.market.sellListings = {};
  94. globalVariable.market.buyListings = {};
  95. globalVariable.keywords.push(obj.character.name.toLowerCase());
  96. updateMarketListings(obj.myMarketListings);
  97. break;
  98. case "market_listings_updated":
  99. updateMarketListings(obj.endMarketListings);
  100. break;
  101. case "new_battle":
  102. if (config.notifyDeath) initBattle(obj);
  103. break;
  104. case "battle_updated":
  105. if (config.notifyDeath) checkDeath(obj);
  106. break;
  107. case "market_item_order_books_updated":
  108. if (config.forceUpdateMarketPrice) marketPriceUpdate(obj);
  109. break;
  110. case "quests_updated":
  111. for (let e of globalVariable.task.taskValueElements) {
  112. e.remove();
  113. }
  114. globalVariable.task.taskValueElements.length = 0;
  115. globalVariable.task.hasTaskValueElement = false;
  116. break;
  117. case "chat_message_received":
  118. handleChatMessage(obj);
  119. break;
  120. }
  121. return message;
  122. }
  123.  
  124. function notifyDeath(name) {
  125. new Notification('🎉🎉🎉喜报🎉🎉🎉', {body: `${name} 死了!`});
  126. }
  127.  
  128. function initBattle(obj) {
  129. globalVariable.battleData.players = [];
  130. for (let player of obj.players) {
  131. globalVariable.battleData.players.push({
  132. name: player.name, isAlive: player.currentHitpoints > 0,
  133. });
  134. if (player.currentHitpoints === 0) {
  135. notifyDeath(player.name);
  136. }
  137. }
  138. }
  139.  
  140. function checkDeath(obj) {
  141. for (let key in obj.pMap) {
  142. const index = parseInt(key);
  143. if (globalVariable.battleData.players[index].isAlive && obj.pMap[key].cHP === 0) {
  144. globalVariable.battleData.players[index].isAlive = false;
  145. notifyDeath(globalVariable.battleData.players[index].name);
  146. } else if (!globalVariable.battleData.players[index].isAlive && obj.pMap[key].cHP > 0) {
  147. globalVariable.battleData.players[index].isAlive = true;
  148. }
  149. }
  150. }
  151.  
  152. function marketPriceUpdate(obj) {
  153. globalVariable.task.taskTokenValueData = getTaskTokenValue();
  154. // 本函数的代码复制自Magic Way Idle
  155. let itemDetailMap = globalVariable.itemDetailMap;
  156. let itemName = itemDetailMap[obj.marketItemOrderBooks.itemHrid].name;
  157. let ask = -1;
  158. let bid = -1;
  159. // 读取ask最低报价
  160. if (obj.marketItemOrderBooks.orderBooks[0].asks && obj.marketItemOrderBooks.orderBooks[0].asks.length > 0) {
  161. ask = obj.marketItemOrderBooks.orderBooks[0].asks[0].price;
  162. }
  163. // 读取bid最高报价
  164. if (obj.marketItemOrderBooks.orderBooks[0].bids && obj.marketItemOrderBooks.orderBooks[0].bids.length > 0) {
  165. bid = obj.marketItemOrderBooks.orderBooks[0].bids[0].price;
  166. }
  167. // 读取所有物品价格
  168. let jsonObj = JSON.parse(localStorage.getItem("MWITools_marketAPI_json"));
  169. // 修改当前查看物品价格
  170. if (jsonObj.market[itemName]) {
  171. jsonObj.market[itemName].ask = ask;
  172. jsonObj.market[itemName].bid = bid;
  173. }
  174. // 将修改后结果写回marketAPI缓存,完成对marketAPI价格的强制修改
  175. localStorage.setItem("MWITools_marketAPI_json", JSON.stringify(jsonObj));
  176. }
  177.  
  178. function handleChatMessage(obj) {
  179. if (obj.message.chan === "/chat_channel_types/whisper") {
  180. if (config.notifyWhisperMessages) {
  181. globalVariable.whisperAudio.play();
  182. }
  183. } else if (obj.message.chan === "/chat_channel_types/chinese") {
  184. if (config.listenKeywordMessages) {
  185. for (let keyword of globalVariable.keywords) {
  186. if (obj.message.m.toLowerCase().includes(keyword)) {
  187. globalVariable.keywordAudio.play();
  188. }
  189. }
  190. }
  191. }
  192. }
  193.  
  194. function autoClickTaskSortButton() {
  195. const targetElement = document.querySelector('#TaskSort');
  196. if (targetElement && targetElement.textContent !== '手动排序') {
  197. targetElement.click();
  198. targetElement.textContent = '手动排序';
  199. }
  200. }
  201.  
  202. function formatCoinValue(num) {
  203. if (num >= 1e13) {
  204. return Math.floor(num / 1e12) + "T";
  205. } else if (num >= 1e10) {
  206. return Math.floor(num / 1e9) + "B";
  207. } else if (num >= 1e7) {
  208. return Math.floor(num / 1e6) + "M";
  209. } else if (num >= 1e4) {
  210. return Math.floor(num / 1e3) + "K";
  211. }
  212. return num.toString();
  213. }
  214.  
  215. function updateMarketListings(obj) {
  216. for (let listing of obj) {
  217. if (listing.status === "/market_listing_status/cancelled") {
  218. delete globalVariable.market[listing.isSell ? "sellListings" : "buyListings"][listing.id];
  219. continue
  220. }
  221. globalVariable.market[listing.isSell ? "sellListings" : "buyListings"][listing.id] = {
  222. itemHrid: listing.itemHrid,
  223. price: (listing.orderQuantity - listing.filledQuantity) * (listing.isSell ? Math.ceil(listing.price * 0.98) : listing.price),
  224. unclaimedCoinCount: listing.unclaimedCoinCount,
  225. }
  226. }
  227. globalVariable.market.buyValue = 0;
  228. globalVariable.market.sellValue = 0;
  229. globalVariable.market.unclaimedValue = 0;
  230. for (let id in globalVariable.market.buyListings) {
  231. const listing = globalVariable.market.buyListings[id];
  232. globalVariable.market.buyValue += listing.price;
  233. globalVariable.market.unclaimedValue += listing.unclaimedCoinCount;
  234. }
  235. for (let id in globalVariable.market.sellListings) {
  236. const listing = globalVariable.market.sellListings[id];
  237. globalVariable.market.sellValue += listing.price;
  238. globalVariable.market.unclaimedValue += listing.unclaimedCoinCount;
  239. }
  240. globalVariable.market.hasFundsElement = false;
  241. }
  242.  
  243. function showMarketListingsFunds() {
  244. function makeNode(innerHTML, value, targetNode, style) {
  245. let node = targetNode.cloneNode(true);
  246. node.classList.add("fundsElement");
  247. const countNode = node.querySelector("div.Item_count__1HVvv");
  248. const textNode = node.querySelector("div.Item_name__2C42x");
  249. if (countNode) countNode.textContent = formatCoinValue(value);
  250. if (textNode) textNode.innerHTML = innerHTML;
  251. node.style.left = style[0];
  252. node.style.top = style[1];
  253. targetNode.parentNode.insertBefore(node, targetNode.nextSibling);
  254. }
  255.  
  256. if (globalVariable.market.hasFundsElement) return;
  257. const targetNode = document.querySelector("div.MarketplacePanel_coinStack__1l0UD");
  258. if (targetNode) {
  259. targetNode.style.top = "0px";
  260. targetNode.style.left = "0px";
  261. let fundsElement = document.querySelector("div.fundsElement");
  262. while (fundsElement) {
  263. fundsElement.remove();
  264. fundsElement = document.querySelector("div.fundsElement");
  265. }
  266. makeNode('<span style="color: rgb(102,204,255); font-weight: bold;">购买预付金</span>', globalVariable.market.buyValue, targetNode, ["125px", "0px"]);
  267. makeNode('<span style="color: rgb(102,204,255); font-weight: bold;">出售可获金</span>', globalVariable.market.sellValue, targetNode, ["125px", "22px"]);
  268. makeNode('<span style="color: rgb(102,204,255); font-weight: bold;">待领取金额</span>', globalVariable.market.unclaimedValue, targetNode, ["0px", "22px"]);
  269. globalVariable.market.hasFundsElement = true;
  270. }
  271. }
  272.  
  273. function getTaskTokenValue() {
  274. const chestDropData = JSON.parse(localStorage.getItem("Edible_Tools")).Chest_Drop_Data;
  275. const lootsName = ["大陨石舱", "大工匠匣", "大宝箱"];
  276. const bidValueList = [
  277. parseFloat(chestDropData["Large Meteorite Cache"]["期望产出Bid"]),
  278. parseFloat(chestDropData["Large Artisan's Crate"]["期望产出Bid"]),
  279. parseFloat(chestDropData["Large Treasure Chest"]["期望产出Bid"]),
  280. ]
  281. const askValueList = [
  282. parseFloat(chestDropData["Large Meteorite Cache"]["期望产出Ask"]),
  283. parseFloat(chestDropData["Large Artisan's Crate"]["期望产出Ask"]),
  284. parseFloat(chestDropData["Large Treasure Chest"]["期望产出Ask"]),
  285. ]
  286. const res = {
  287. bidValue: Math.max(...bidValueList),
  288. askValue: Math.max(...askValueList)
  289. }
  290. res.bidLoots = lootsName[bidValueList.indexOf(res.bidValue)];
  291. res.askLoots = lootsName[askValueList.indexOf(res.askValue)];
  292. res.bidValue = Math.round(res.bidValue / 30);
  293. res.askValue = Math.round(res.askValue / 30);
  294. res.giftValueBid = Math.round(parseFloat(chestDropData["Purple's Gift"]["期望产出Bid"]));
  295. res.giftValueAsk = Math.round(parseFloat(chestDropData["Purple's Gift"]["期望产出Ask"]));
  296. if (config.forceUpdateMarketPrice) {
  297. const marketJSON = JSON.parse(localStorage.getItem("MWITools_marketAPI_json"));
  298. marketJSON.market["Task Token"].ask = res.askValue;
  299. marketJSON.market["Task Token"].bid = res.bidValue;
  300. localStorage.setItem("MWITools_marketAPI_json", JSON.stringify(marketJSON));
  301. }
  302. return res;
  303. }
  304.  
  305. function showTaskValue() {
  306. const isInTask = document.querySelector("div.TasksPanel_taskSlotCount__nfhgS");
  307. if (!isInTask) {
  308. for (let e of globalVariable.task.taskValueElements) {
  309. e.remove();
  310. }
  311. globalVariable.task.taskValueElements.length = 0;
  312. globalVariable.task.hasTaskValueElement = false;
  313. return;
  314. }
  315. if (globalVariable.task.hasTaskValueElement) return;
  316. globalVariable.task.hasTaskValueElement = true;
  317. const list = document.querySelector("div.TasksPanel_taskList__2xh4k");
  318. const taskNodes = [...list.querySelectorAll("div.RandomTask_randomTask__3B9fA")];
  319.  
  320. function convertKEndStringToNumber(str) {
  321. if (str.endsWith('K') || str.endsWith('k')) {
  322. return Number(str.slice(0, -1)) * 1000;
  323. } else {
  324. return Number(str);
  325. }
  326. }
  327.  
  328. const tokenValueBid = globalVariable.task.taskTokenValueData.bidValue + globalVariable.task.taskTokenValueData.giftValueBid / 50;
  329. const tokenValueAsk = globalVariable.task.taskTokenValueData.askValue + globalVariable.task.taskTokenValueData.giftValueAsk / 50;
  330.  
  331. taskNodes.forEach(function (node) {
  332. let reward = node.querySelector("div.RandomTask_rewards__YZk7D");
  333. let coin = convertKEndStringToNumber(reward.querySelectorAll("div.Item_count__1HVvv")[0].innerText);
  334. let tokenCount = Number(reward.querySelectorAll("div.Item_count__1HVvv")[1].innerText);
  335. let newDiv = document.createElement("div");
  336. newDiv.textContent = `奖励期望收益:
  337. ${formatCoinValue(coin + tokenCount * tokenValueAsk)} /
  338. ${formatCoinValue(coin + tokenCount * tokenValueBid)}`;
  339. newDiv.style.color = "rgb(248,0,248)";
  340. node.querySelector("div.RandomTask_action__3eC6o").appendChild(newDiv);
  341. globalVariable.task.taskValueElements.push(newDiv);
  342. });
  343. }
  344. })();

QingJ © 2025

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