Ranged Way Idle

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

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

  1. // ==UserScript==
  2. // @name Ranged Way Idle
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.2
  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. }
  25. const globalVariable = {
  26. battleData: {
  27. players: null
  28. },
  29. itemDetailMap: JSON.parse(localStorage.getItem("initClientData")).itemDetailMap,
  30. whisperAudio: new Audio(`https://upload.thbwiki.cc/d/d1/se_bonus2.mp3`),
  31. keywordAudio: new Audio(`https://upload.thbwiki.cc/c/c9/se_pldead00.mp3`),
  32. keywords: [],
  33. market: {
  34. hasFundsElement: false,
  35. sellValue: null,
  36. buyValue: null,
  37. unclaimedValue: null,
  38. sellListings: null,
  39. buyListings: null
  40. }
  41. };
  42. init();
  43.  
  44. function init() {
  45. globalVariable.whisperAudio.volume = 0.4;
  46. globalVariable.keywordAudio.volume = 0.4;
  47. let observer = new MutationObserver(function (mutationsList, observer) {
  48. if (config.showMarketListingsFunds) showMarketListingsFunds();
  49. if (config.autoTaskSort) autoClickTaskSortButton();
  50. });
  51. observer.observe(document, {childList: true, subtree: true});
  52. const dataProperty = Object.getOwnPropertyDescriptor(MessageEvent.prototype, "data");
  53. const oriGet = dataProperty.get;
  54. dataProperty.get = hookedGet;
  55. Object.defineProperty(MessageEvent.prototype, "data", dataProperty);
  56.  
  57. function hookedGet() {
  58. const socket = this.currentTarget;
  59. if (!(socket instanceof WebSocket)) {
  60. return oriGet.call(this);
  61. }
  62. if (socket.url.indexOf("api.milkywayidle.com/ws") <= -1 && socket.url.indexOf("api-test.milkywayidle.com/ws") <= -1) {
  63. return oriGet.call(this);
  64. }
  65. const message = oriGet.call(this);
  66. Object.defineProperty(this, "data", {value: message});
  67. return handleMessage(message);
  68. }
  69.  
  70. if (config.mournForMagicWayIdle) {
  71. console.log("为法师助手默哀")
  72. }
  73. }
  74.  
  75.  
  76. function handleMessage(message) {
  77. const obj = JSON.parse(message);
  78. if (!obj) return message;
  79. switch (obj.type) {
  80. case "init_character_data":
  81. globalVariable.market.sellListings = {};
  82. globalVariable.market.buyListings = {};
  83. globalVariable.keywords.push(obj.character.name.toLowerCase());
  84. updateMarketListings(obj.myMarketListings);
  85. break;
  86. case "market_listings_updated":
  87. updateMarketListings(obj.endMarketListings);
  88. break;
  89. case "new_battle":
  90. if (config.notifyDeath) initBattle(obj);
  91. break;
  92. case "battle_updated":
  93. if (config.notifyDeath) checkDeath(obj);
  94. break;
  95. case "market_item_order_books_updated":
  96. if (config.forceUpdateMarketPrice) marketPriceUpdate(obj);
  97. break;
  98. case "chat_message_received":
  99. handleChatMessage(obj);
  100. break;
  101. }
  102. return message;
  103. }
  104.  
  105. function notifyDeath(name) {
  106. new Notification('🎉🎉🎉喜报🎉🎉🎉', {body: `${name} 死了!`});
  107. }
  108.  
  109. function initBattle(obj) {
  110. globalVariable.battleData.players = [];
  111. for (let player of obj.players) {
  112. globalVariable.battleData.players.push({
  113. name: player.name, isAlive: player.currentHitpoints > 0,
  114. });
  115. if (player.currentHitpoints === 0) {
  116. notifyDeath(player.name);
  117. }
  118. }
  119. }
  120.  
  121. function checkDeath(obj) {
  122. for (let key in obj.pMap) {
  123. const index = parseInt(key);
  124. if (globalVariable.battleData.players[index].isAlive && obj.pMap[key].cHP === 0) {
  125. globalVariable.battleData.players[index].isAlive = false;
  126. notifyDeath(globalVariable.battleData.players[index].name);
  127. } else if (!globalVariable.battleData.players[index].isAlive && obj.pMap[key].cHP > 0) {
  128. globalVariable.battleData.players[index].isAlive = true;
  129. }
  130. }
  131. }
  132.  
  133. function marketPriceUpdate(obj) {
  134. // 本函数的代码复制自Magic Way Idle
  135. let itemDetailMap = globalVariable.itemDetailMap;
  136. let itemName = itemDetailMap[obj.marketItemOrderBooks.itemHrid].name;
  137. let ask = -1;
  138. let bid = -1;
  139. // 读取ask最低报价
  140. if (obj.marketItemOrderBooks.orderBooks[0].asks && obj.marketItemOrderBooks.orderBooks[0].asks.length > 0) {
  141. ask = obj.marketItemOrderBooks.orderBooks[0].asks[0].price;
  142. }
  143. // 读取bid最高报价
  144. if (obj.marketItemOrderBooks.orderBooks[0].bids && obj.marketItemOrderBooks.orderBooks[0].bids.length > 0) {
  145. bid = obj.marketItemOrderBooks.orderBooks[0].bids[0].price;
  146. }
  147. // 读取所有物品价格
  148. let jsonObj = JSON.parse(localStorage.getItem("MWITools_marketAPI_json"));
  149. // 修改当前查看物品价格
  150. if (jsonObj.market[itemName]) {
  151. jsonObj.market[itemName].ask = ask;
  152. jsonObj.market[itemName].bid = bid;
  153. }
  154. // 将修改后结果写回marketAPI缓存,完成对marketAPI价格的强制修改
  155. localStorage.setItem("MWITools_marketAPI_json", JSON.stringify(jsonObj));
  156. }
  157.  
  158. function handleChatMessage(obj) {
  159. if (obj.message.chan === "/chat_channel_types/whisper") {
  160. if (config.notifyWhisperMessages) {
  161. globalVariable.whisperAudio.play();
  162. }
  163. } else if (obj.message.chan === "/chat_channel_types/chinese") {
  164. if (config.listenKeywordMessages) {
  165. for (let keyword of globalVariable.keywords) {
  166. if (obj.message.m.toLowerCase().includes(keyword)) {
  167. globalVariable.keywordAudio.play();
  168. }
  169. }
  170. }
  171. }
  172. }
  173.  
  174. function autoClickTaskSortButton() {
  175. const targetElement = document.querySelector('#TaskSort');
  176. if (targetElement && targetElement.textContent !== '手动排序') {
  177. targetElement.click();
  178. targetElement.textContent = '手动排序';
  179. }
  180. }
  181.  
  182. function formatCoinValue(num) {
  183. if (num >= 1e13) {
  184. return (num / 1e12).toFixed(0) + "T";
  185. } else if (num >= 1e10) {
  186. return (num / 1e9).toFixed(0) + "B";
  187. } else if (num >= 1e7) {
  188. return (num / 1e6).toFixed(0) + "M";
  189. } else if (num >= 1e4) {
  190. return (num / 1e3).toFixed(0) + "K";
  191. }
  192. return num.toString();
  193. }
  194.  
  195. function updateMarketListings(obj) {
  196. for (listing of obj) {
  197. if (listing.status === "/market_listing_status/cancelled") {
  198. delete globalVariable.market[listing.isSell ? "sellListings" : "buyListings"][listing.id];
  199. continue
  200. }
  201. globalVariable.market[listing.isSell ? "sellListings" : "buyListings"][listing.id] = {
  202. itemHrid: listing.itemHrid,
  203. price: (listing.orderQuantity - listing.filledQuantity) * (listing.isSell ? Math.ceil(listing.price * 0.98) : listing.price),
  204. unclaimedCoinCount: listing.unclaimedCoinCount,
  205. }
  206. }
  207. globalVariable.market.buyValue = 0;
  208. globalVariable.market.sellValue = 0;
  209. globalVariable.market.unclaimedValue = 0;
  210. for (let id in globalVariable.market.buyListings) {
  211. const listing = globalVariable.market.buyListings[id];
  212. globalVariable.market.buyValue += listing.price;
  213. globalVariable.market.unclaimedValue += listing.unclaimedCoinCount;
  214. }
  215. for (let id in globalVariable.market.sellListings) {
  216. const listing = globalVariable.market.sellListings[id];
  217. globalVariable.market.sellValue += listing.price;
  218. globalVariable.market.unclaimedValue += listing.unclaimedCoinCount;
  219. }
  220. globalVariable.market.hasFundsElement = false;
  221. }
  222.  
  223. function showMarketListingsFunds() {
  224. if (globalVariable.market.hasFundsElement) return;
  225. const targetNode = document.querySelector("div.MarketplacePanel_coinStack__1l0UD");
  226. if (targetNode) {
  227. targetNode.style.top = "0px";
  228. targetNode.style.left = "0px";
  229. let fundsElement = document.querySelector("div.fundsElement");
  230. while (fundsElement) {
  231. fundsElement.remove();
  232. fundsElement = document.querySelector("div.fundsElement");
  233. }
  234. makeNode('<span style="color: rgb(102,204,255); font-weight: bold;">购买预付金</span>', globalVariable.market.buyValue, targetNode, ["125px", "0px"]);
  235. makeNode('<span style="color: rgb(102,204,255); font-weight: bold;">出售可获金</span>', globalVariable.market.sellValue, targetNode, ["125px", "22px"]);
  236. makeNode('<span style="color: rgb(102,204,255); font-weight: bold;">待领取金额</span>', globalVariable.market.unclaimedValue, targetNode, ["0px", "22px"]);
  237. globalVariable.market.hasFundsElement = true;
  238. }
  239. }
  240.  
  241. function makeNode(innerHTML, value, targetNode, style) {
  242. let node = targetNode.cloneNode(true);
  243. node.classList.add("fundsElement");
  244. const countNode = node.querySelector("div.Item_count__1HVvv");
  245. const textNode = node.querySelector("div.Item_name__2C42x");
  246. if (countNode) countNode.textContent = formatCoinValue(value);
  247. if (textNode) textNode.innerHTML = innerHTML;
  248. node.style.left = style[0];
  249. node.style.top = style[1];
  250. targetNode.parentNode.insertBefore(node, targetNode.nextSibling);
  251. }
  252. })();