Ranged Way Idle

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

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

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