Greasy Fork 支持简体中文。

[银河奶牛]食用工具

开箱记录、箱子期望、离线统计、公会钉钉、食物警察、掉落追踪

目前為 2024-09-30 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name [银河奶牛]食用工具
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.422
  5. // @description 开箱记录、箱子期望、离线统计、公会钉钉、食物警察、掉落追踪
  6. // @author Truth_Light
  7. // @license Truth_Light
  8. // @match https://www.milkywayidle.com/*
  9. // @match https://test.milkywayidle.com/*
  10. // @icon https://www.google.com/s2/favicons?sz=64&domain=milkywayidle.com
  11. // @grant GM.xmlHttpRequest
  12. // @grant GM_registerMenuCommand
  13. // ==/UserScript==
  14.  
  15. (async function() {
  16. 'use strict';
  17.  
  18. const itemSelector = '.ItemDictionary_drop__24I5f';
  19. const iconSelector = '.Icon_icon__2LtL_ use';
  20. const chestNameSelector = '#root > div > div > div.Modal_modalContainer__3B80m > div.Modal_modal__1Jiep > div.ItemDictionary_modalContent__WvEBY > div.ItemDictionary_itemAndDescription__28_he > div.Item_itemContainer__x7kH1 > div > div > div > svg > use';
  21. const MARKET_API_URL = "https://raw.githubusercontent.com/holychikenz/MWIApi/main/medianmarket.json";
  22. let marketData = null;
  23. let timer = null;
  24. let formattedChestDropData = {};
  25. let battlePlayerFood = {};
  26. let battlePlayerLoot = {};
  27. async function fetchMarketData() {
  28. return new Promise((resolve, reject) => {
  29. GM.xmlHttpRequest({
  30. method: 'GET',
  31. url: MARKET_API_URL,
  32. responseType: 'json',
  33. timeout: 5000,
  34. onload: function(response) {
  35. if (response.status === 200) {
  36. const data = JSON.parse(response.responseText);
  37. console.log('从API获取到的数据:', data);
  38. resolve(data);
  39. } else {
  40. console.error('获取数据失败。状态码:', response.status);
  41. reject(new Error('数据获取失败'));
  42. }
  43. },
  44. ontimeout: function() {
  45. console.error('请求超时:超过5秒未能获取到数据');
  46. reject(new Error('请求超时'));
  47. },
  48. onerror: function(error) {
  49. console.error('获取数据时发生错误:', error);
  50. reject(error);
  51. }
  52. });
  53. });
  54. }
  55.  
  56. try {
  57. // 尝试从 API 获取数据
  58. marketData = await fetchMarketData();
  59. } catch (error) {
  60. console.error('从 API 获取数据失败,尝试从本地存储获取数据。', error);
  61.  
  62. // 从本地存储获取数据
  63. const marketDataStr = localStorage.getItem('MWITools_marketAPI_json');
  64. marketData = JSON.parse(marketDataStr);
  65.  
  66. if (!marketData) {
  67. alert('无法获取 market 数据');
  68. } else {
  69. console.log('从本地存储获取到的数据:', marketData);
  70. }
  71. }
  72.  
  73. function getSpecialItemPrice(itemName,priceType) {
  74. if (marketData?.market?.[itemName]) {
  75. const itemPrice = marketData.market[itemName][priceType];
  76. if (itemPrice !== undefined && itemPrice !== -1) {
  77. return itemPrice;
  78. }
  79. }
  80. console.error(`未找到物品 ${itemName} ${priceType} 价格信息`);
  81. return null;
  82. }
  83.  
  84. let specialItemPrices = {
  85. 'Coin': { ask: 1, bid: 1 }, // 默认的特殊物品价值,包括 ask 和 bid 价值
  86. 'Cowbell': {
  87. ask: getSpecialItemPrice('Bag Of 10 Cowbells', 'ask') / 10 || 30000,
  88. bid: getSpecialItemPrice('Bag Of 10 Cowbells', 'bid') / 10 || 25000
  89. },
  90. 'Chimerical Token': {
  91. ask: getSpecialItemPrice('Chimerical Essence', 'ask') || 1300,
  92. bid: getSpecialItemPrice('Chimerical Essence', 'bid') || 1100
  93. },
  94. 'Sinister Token': {
  95. ask: getSpecialItemPrice('Sinister Essence', 'ask') || 1400,
  96. bid: getSpecialItemPrice('Sinister Essence', 'bid') || 1200
  97. },
  98. 'Enchanted Token': {
  99. ask: getSpecialItemPrice('Enchanted Essence', 'ask') || 3900,
  100. bid: getSpecialItemPrice('Enchanted Essence', 'bid') || 3500
  101. },
  102. };
  103.  
  104. function getItemNameFromElement(element) {
  105. const itemNameRaw = element.getAttribute('href').split('#').pop();
  106. return formatItemName(itemNameRaw);
  107. }
  108.  
  109. function formatItemName(itemNameRaw) {
  110. let formattedName = itemNameRaw.replace('#', '').replace(/_/g, ' ').replace(/\b\w/g, char => char.toUpperCase());
  111.  
  112. const specialStrings = {
  113. 'Artisans': 'Artisan\'s',
  114. };
  115.  
  116. const blacklist = [
  117. 'Swiftness Coffee',
  118. ];
  119.  
  120. if (blacklist.includes(formattedName)) {
  121. return formattedName.split(' ').map(word => specialStrings[word] || word).join(' ');
  122. }
  123.  
  124. if (formattedName.includes(' ')) {
  125. const words = formattedName.split(' ');
  126. let firstWord = words[0];
  127. const restOfName = words.slice(1).join(' ');
  128.  
  129. if (firstWord.endsWith('s') && !firstWord.endsWith("'s")) {
  130. firstWord = `${firstWord.slice(0, -1)}'${firstWord.slice(-1)}`;
  131. }
  132. formattedName = `${firstWord}${restOfName ? " " + restOfName : ""}`;
  133. }
  134.  
  135. return formattedName.split(' ').map(word => specialStrings[word] || word).join(' ');
  136. }
  137.  
  138.  
  139. function formatPrice(value) {
  140. const isNegative = value < 0;
  141. value = Math.abs(value);
  142.  
  143. if (value >= 1000000) {
  144. return (isNegative ? '-' : '') + (value / 1000000).toFixed(1) + 'M';
  145. } else if (value >= 1000) {
  146. return (isNegative ? '-' : '') + (value / 1000).toFixed(1) + 'K';
  147. } else {
  148. return (isNegative ? '-' : '') + value.toFixed(0);
  149. }
  150. }
  151.  
  152.  
  153. function parseQuantityString(quantityStr) {
  154. const suffix = quantityStr.slice(-1);
  155. const base = parseFloat(quantityStr.slice(0, -1));
  156. if (suffix === 'K') {
  157. return base * 1000;
  158. } else if (suffix === 'M') {
  159. return base * 1000000;
  160. } else if (suffix === 'B') {
  161. return base * 1000000000;
  162. } else {
  163. return parseFloat(quantityStr);
  164. }
  165. }
  166.  
  167. function recordChestOpening(modalElement) {
  168.  
  169. if (document.querySelector('.ChestStatistics')) {
  170. return;
  171. }
  172.  
  173. // 从本地存储读取数据
  174. let edibleTools = JSON.parse(localStorage.getItem('Edible_Tools')) || {};
  175. edibleTools.Chest_Open_Data = edibleTools.Chest_Open_Data || {};
  176.  
  177. let chestOpenData = edibleTools.Chest_Open_Data;
  178. const chestDropData = edibleTools.Chest_Drop_Data;
  179.  
  180. const chestNameElement = modalElement.querySelector("div.Modal_modal__1Jiep > div.Inventory_modalContent__3ObSx > div.Item_itemContainer__x7kH1 > div > div > div.Item_iconContainer__5z7j4 > svg > use");
  181. const chestCountElement = modalElement.querySelector("div.Modal_modal__1Jiep > div.Inventory_modalContent__3ObSx > div.Item_itemContainer__x7kH1 > div > div > div.Item_count__1HVvv");
  182.  
  183. if (chestNameElement && chestCountElement) {
  184.  
  185. const chestName = getItemNameFromElement(chestNameElement);
  186. chestOpenData[chestName] = chestOpenData[chestName] || {};
  187. let chestData = chestOpenData[chestName];
  188. const chestCount = parseQuantityString(chestCountElement.textContent.trim());
  189. chestData["总计开箱数量"] = (chestData["总计开箱数量"] || 0) + chestCount;
  190. chestData["获得物品"] = chestData["获得物品"] || {};
  191. const itemsContainer = modalElement.querySelector('.Inventory_gainedItems___e9t9');
  192. const itemElements = itemsContainer.querySelectorAll('.Item_itemContainer__x7kH1');
  193.  
  194. let totalAskValue = 0;
  195. let totalBidValue = 0;
  196.  
  197. itemElements.forEach(itemElement => {
  198. const itemNameElement = itemElement.querySelector('.Item_iconContainer__5z7j4 use');
  199. const itemQuantityElement = itemElement.querySelector('.Item_count__1HVvv');
  200.  
  201. if (itemNameElement && itemQuantityElement) {
  202. const itemName = getItemNameFromElement(itemNameElement);
  203. const itemQuantity = parseQuantityString(itemQuantityElement.textContent.trim());
  204.  
  205. const itemData = chestDropData[chestName].item[itemName] || {};
  206. const itemAskValue = itemData["出售单价"] || 0;
  207. const itemBidValue = itemData["收购单价"] || 0;
  208. const color = itemData.Color || '';
  209.  
  210. itemQuantityElement.style.color = color;
  211.  
  212. const itemOpenTotalAskValue = itemAskValue * itemQuantity;
  213. const itemOpenTotalBidValue = itemBidValue * itemQuantity;
  214.  
  215. chestData["获得物品"][itemName] = chestData["获得物品"][itemName] || {};
  216. chestData["获得物品"][itemName]["数量"] = (chestData["获得物品"][itemName]["数量"] || 0) + itemQuantity;
  217. chestData["获得物品"][itemName]["总计Ask价值"] = (chestData["获得物品"][itemName]["总计Ask价值"] || 0) + itemOpenTotalAskValue;
  218. chestData["获得物品"][itemName]["总计Bid价值"] = (chestData["获得物品"][itemName]["总计Bid价值"] || 0) + itemOpenTotalBidValue;
  219.  
  220. totalAskValue += itemOpenTotalAskValue;
  221. totalBidValue += itemOpenTotalBidValue;
  222. }
  223. });
  224.  
  225. chestData["总计开箱Ask"] = (chestData["总计开箱Ask"] || 0) + totalAskValue;
  226. chestData["总计开箱Bid"] = (chestData["总计开箱Bid"] || 0) + totalBidValue;
  227.  
  228. //显示
  229. const openChestElement = document.querySelector('.Inventory_modalContent__3ObSx');
  230.  
  231. const displayElement = document.createElement('div');
  232. displayElement.classList.add('ChestStatistics');
  233. displayElement.style.position = 'absolute';
  234. displayElement.style.left = `${openChestElement.offsetLeft}px`;
  235. displayElement.style.top = `${openChestElement.offsetTop}px`;
  236. displayElement.style.fontSize = '12px';
  237. displayElement.innerHTML = `
  238. 总计开箱次数:<br>
  239. ${chestData["总计开箱数量"]}<br>
  240. 本次开箱价值:<br>
  241. ${formatPrice(totalAskValue)}/${formatPrice(totalBidValue)}<br>
  242. 总计开箱价值:<br>
  243. ${formatPrice(chestData["总计开箱Ask"])}/${formatPrice(chestData["总计开箱Bid"])}<br>
  244. `;
  245.  
  246. const expectedOutputElement = document.createElement('div');
  247. expectedOutputElement.classList.add('ExpectedOutput');
  248. expectedOutputElement.style.position = 'absolute';
  249. expectedOutputElement.style.left = `${openChestElement.offsetLeft}px`;
  250. expectedOutputElement.style.bottom = `${openChestElement.offsetTop}px`;
  251. expectedOutputElement.style.fontSize = '12px';
  252. expectedOutputElement.innerHTML = `
  253. 预计产出价值:<br>
  254. ${formatPrice(chestDropData[chestName]["期望产出Ask"]*chestCount)}/${formatPrice(chestDropData[chestName]["期望产出Bid"]*chestCount)}<br>
  255. `;
  256.  
  257. const differenceValue = totalBidValue - chestDropData[chestName]["期望产出Bid"] * chestCount;
  258.  
  259. const differenceOutputElement = document.createElement('div');
  260. differenceOutputElement.classList.add('DifferenceOutput');
  261. differenceOutputElement.style.position = 'absolute';
  262. differenceOutputElement.style.right = `${openChestElement.offsetLeft}px`;
  263. differenceOutputElement.style.bottom = `${openChestElement.offsetTop}px`;
  264. differenceOutputElement.style.fontSize = '12px';
  265. differenceOutputElement.style.color = differenceValue > 0 ? 'lime' : 'red';
  266. differenceOutputElement.innerHTML = `
  267. ${differenceValue > 0 ? '高于期望价值:' : '低于期望价值:'}<br>
  268. ${formatPrice(Math.abs(differenceValue))}<br>
  269. `;
  270.  
  271. openChestElement.appendChild(displayElement);
  272. openChestElement.appendChild(expectedOutputElement);
  273. openChestElement.appendChild(differenceOutputElement);
  274.  
  275. // 保存更新的数据到本地存储
  276. localStorage.setItem('Edible_Tools', JSON.stringify(edibleTools));
  277. }
  278. }
  279.  
  280.  
  281. function calculateTotalValues(itemElements) {
  282. let totalAskValue = 0;
  283. let totalBidValue = 0;
  284.  
  285. itemElements.forEach(itemElement => {
  286. const itemNameElement = itemElement.querySelector('.Item_iconContainer__5z7j4 use');
  287. const itemQuantityElement = itemElement.querySelector('.Item_count__1HVvv');
  288.  
  289. if (itemNameElement && itemQuantityElement) {
  290. const itemName = getItemNameFromElement(itemNameElement);
  291. const itemQuantity = parseQuantityString(itemQuantityElement.textContent.trim());
  292.  
  293. let askPrice = 0;
  294. let bidPrice = 0;
  295. let priceColor = '';
  296.  
  297. // 获取价格
  298. if (specialItemPrices[itemName] && specialItemPrices[itemName].ask) {
  299. askPrice = parseFloat(specialItemPrices[itemName].ask);
  300. bidPrice = parseFloat(specialItemPrices[itemName].bid);
  301. priceColor = '';
  302. } else if (marketData?.market?.[itemName]) {
  303. bidPrice = marketData.market[itemName].bid;
  304. askPrice = marketData.market[itemName].ask;
  305. } else {
  306. console.log(`${itemName} 的价格未找到`);
  307. }
  308.  
  309. const itemTotalAskValue = askPrice * itemQuantity;
  310. const itemTotalBidValue = bidPrice * itemQuantity;
  311. totalAskValue += itemTotalAskValue;
  312. totalBidValue += itemTotalBidValue;
  313. }
  314. });
  315.  
  316. console.log(totalAskValue);
  317. return { totalAskValue, totalBidValue };
  318. }
  319.  
  320. //更详细的战斗等级显示
  321. const updateCombatLevel = () => {
  322. const elements = document.querySelectorAll(".NavigationBar_currentExperience__3GDeX");
  323.  
  324. if (elements.length === 16) {
  325. const levels = Array.from(elements).slice(9, 16).map(el => {
  326. const levelText = parseInt(el.parentNode.parentNode.querySelector(".NavigationBar_textContainer__7TdaI .NavigationBar_level__3C7eR").textContent);
  327. const decimalPart = parseFloat(el.style.width) / 100;
  328. return { integerPart: levelText, decimalPart: decimalPart };
  329. });
  330.  
  331. let [endurance, intelligence, attack, strength, defense, ranged, magic] = levels;
  332.  
  333. let combatTypeMax = Math.max(
  334. 0.5 * (attack.integerPart + strength.integerPart),
  335. ranged.integerPart,
  336. magic.integerPart
  337. );
  338.  
  339. if (combatTypeMax !== 0.5 * (attack.integerPart + strength.integerPart)) {
  340. attack.decimalPart = 0;
  341. strength.decimalPart = 0;
  342. }
  343. if (combatTypeMax !== ranged.integerPart) ranged.decimalPart = 0;
  344. if (combatTypeMax !== magic.integerPart) magic.decimalPart = 0;
  345.  
  346. let combatLevel = 0.2 * (endurance.integerPart + intelligence.integerPart + defense.integerPart) + 0.4 * combatTypeMax;
  347. combatLevel = parseFloat(combatLevel.toFixed(2));
  348. console.log("combatLevel",combatLevel)
  349. const integerPart = Math.floor(combatLevel);
  350. const decimalPart = combatLevel - integerPart;
  351. console.log("integerPart",integerPart)
  352. const list1 = [
  353. endurance.decimalPart * 0.2,
  354. intelligence.decimalPart * 0.2,
  355. attack.decimalPart * 0.2,
  356. strength.decimalPart * 0.2,
  357. defense.decimalPart * 0.2,
  358. ranged.decimalPart * 0.2,
  359. magic.decimalPart * 0.2
  360. ];
  361.  
  362. const list2 = [
  363. endurance.decimalPart * 0.2,
  364. intelligence.decimalPart * 0.2,
  365. attack.decimalPart * 0.2,
  366. strength.decimalPart * 0.2,
  367. defense.decimalPart * 0.2,
  368. ranged.decimalPart * 0.4,
  369. magic.decimalPart * 0.4
  370. ];
  371. //console.log("list1",list1,"\nlist2",list2)
  372. list1.sort((a, b) => b - a);
  373. list2.sort((a, b) => b - a);
  374.  
  375. if (decimalPart === 0.8) {
  376. combatLevel += list1[0];
  377. } else {
  378. let total = 0;
  379. const maxIterations = Math.floor((1 - decimalPart) / 0.2);
  380. let iterations = 0;
  381.  
  382. for (const i of list2) {
  383. if (iterations >= maxIterations) break;
  384.  
  385. if ((decimalPart + total + i) < 1) {
  386. total += i;
  387. } else {
  388. break;
  389. }
  390.  
  391. iterations++;
  392. }
  393. combatLevel = decimalPart + integerPart + total;
  394. }
  395.  
  396. elements[15].parentNode.parentNode.parentNode.parentNode.parentNode.querySelector(".NavigationBar_nav__3uuUl .NavigationBar_level__3C7eR").textContent = combatLevel.toFixed(2);
  397. }
  398. };
  399. window.setInterval(updateCombatLevel, 10000);
  400.  
  401. function OfflineStatistics(modalElement) {
  402. const itemsContainer = modalElement.querySelectorAll(".OfflineProgressModal_itemList__26h-Y");
  403.  
  404. let timeContainer = null;
  405. let getItemContainer = null;
  406. let spendItemContainer = null;
  407.  
  408.  
  409. itemsContainer.forEach(container => {
  410. const labelElement = container.querySelector('.OfflineProgressModal_label__2HwFG');
  411. if (labelElement) {
  412. const textContent = labelElement.textContent.trim();
  413. if (textContent.startsWith("You were offline for") || textContent.startsWith("你离线了")) {
  414. timeContainer = container;
  415. } else if (textContent.startsWith("Items gained:") || textContent.startsWith("获得物品:")) {
  416. getItemContainer = container;
  417. } else if (textContent.startsWith("You consumed:") || textContent.startsWith("你消耗了:")) {
  418. spendItemContainer = container;
  419. }
  420. }
  421. });
  422.  
  423. let TotalSec = null;
  424. if (timeContainer) {
  425. const textContent = timeContainer.textContent;
  426. const match = textContent.match(/(?:(\d+)d\s*)?(?:(\d+)h\s*)?(?:(\d+)m\s*)?(?:(\d+)s)/);
  427. if (match) {
  428. let days = parseInt(match[1], 10) || 0;
  429. let hours = parseInt(match[2], 10) || 0;
  430. let minutes = parseInt(match[3], 10) || 0;
  431. let seconds = parseInt(match[4], 10) || 0;
  432. TotalSec = days * 86400 + hours * 3600 + minutes * 60 + seconds;
  433. }
  434. }
  435.  
  436. let getitemtotalAskValue = 0;
  437. let getitemtotalBidValue = 0;
  438. if (getItemContainer) {
  439. const getitemElements = getItemContainer.querySelectorAll('.Item_itemContainer__x7kH1');
  440. const { totalAskValue, totalBidValue } = calculateTotalValues(getitemElements);
  441. getitemtotalAskValue = totalAskValue;
  442. getitemtotalBidValue = totalBidValue;
  443. }
  444.  
  445.  
  446. let spenditemtotalAskValue = 0;
  447. let spenditemtotalBidValue = 0;
  448. if (spendItemContainer) {
  449. const spenditemElements = spendItemContainer.querySelectorAll('.Item_itemContainer__x7kH1');
  450. const { totalAskValue, totalBidValue } = calculateTotalValues(spenditemElements);
  451. spenditemtotalAskValue = totalAskValue;
  452. spenditemtotalBidValue = totalBidValue;
  453. }
  454.  
  455. if (timeContainer) {
  456. const newElement = document.createElement('span');
  457. newElement.textContent = `利润:[${formatPrice(getitemtotalBidValue - spenditemtotalAskValue)}/${formatPrice((getitemtotalBidValue - spenditemtotalAskValue) / (TotalSec / 3600) * 24)}]/天`;
  458. newElement.style.float = 'right';
  459. newElement.style.color = 'gold';
  460. timeContainer.querySelector(':first-child').appendChild(newElement);
  461. }
  462. if (getItemContainer) {
  463. const newElement = document.createElement('span');
  464. newElement.textContent = `产出:[${formatPrice(getitemtotalAskValue)}/${formatPrice(getitemtotalBidValue)}]`;
  465. newElement.style.float = 'right';
  466. newElement.style.color = 'gold';
  467. getItemContainer.querySelector(':first-child').appendChild(newElement);
  468. }
  469. if (spendItemContainer) {
  470. const newElement = document.createElement('span');
  471. newElement.textContent = `成本:[${formatPrice(spenditemtotalAskValue)}/${formatPrice(spenditemtotalBidValue)}]`;
  472. newElement.style.float = 'right';
  473. newElement.style.color = 'gold';
  474. spendItemContainer.querySelector(':first-child').appendChild(newElement);
  475. }
  476. }
  477.  
  478.  
  479. function initObserver() {
  480. // 选择要观察的目标节点
  481. const targetNode = document.body;
  482.  
  483. // 观察器的配置(需要观察子节点的变化)
  484. const config = { childList: true, subtree: true };
  485.  
  486. // 创建一个观察器实例并传入回调函数
  487. const observer = new MutationObserver(mutationsList => {
  488. for (let mutation of mutationsList) {
  489. if (mutation.type === 'childList') {
  490. // 监听到子节点变化
  491. mutation.addedNodes.forEach(addedNode => {
  492. // 检查是否是我们关注的 Modal_modalContainer__3B80m 元素被添加
  493. if (addedNode.classList && addedNode.classList.contains('Modal_modalContainer__3B80m')) {
  494. // Modal_modalContainer__3B80m 元素被添加,执行处理函数
  495. //console.log("箱子被打开")
  496. ShowChestPrice();
  497. recordChestOpening(addedNode);
  498.  
  499. // 开始监听箱子图标的变化
  500. startIconObserver();
  501. }
  502. if (addedNode.classList && addedNode.classList.contains('OfflineProgressModal_modalContainer__knnk7')) {
  503. OfflineStatistics(addedNode);
  504. console.log("离线报告已创建!")
  505. }
  506. if (addedNode.classList && addedNode.classList.contains('MainPanel_subPanelContainer__1i-H9')) {
  507. if (addedNode.querySelector(".CombatPanel_combatPanel__QylPo")) {
  508. addBattlePlayerFoodButton();
  509. }
  510. }
  511. });
  512.  
  513. mutation.removedNodes.forEach(removedNode => {
  514. // 检查是否是 Modal_modalContainer__3B80m 元素被移除
  515. if (removedNode.classList && removedNode.classList.contains('Modal_modalContainer__3B80m')) {
  516. // Modal_modalContainer__3B80m 元素被移除,停止监听箱子图标的变化
  517. stopIconObserver();
  518. }
  519. });
  520. }
  521. }
  522. });
  523.  
  524. // 以上述配置开始观察目标节点
  525. observer.observe(targetNode, config);
  526.  
  527. // 定义箱子图标变化的观察器
  528. let iconObserver = null;
  529.  
  530. // 开始监听箱子图标的变化
  531. function startIconObserver() {
  532. const chestNameElem = document.querySelector(chestNameSelector);
  533. if (!chestNameElem) return;
  534.  
  535. // 创建一个观察器实例来监听图标的变化
  536. iconObserver = new MutationObserver(() => {
  537. // 当箱子图标变化时,执行处理函数
  538. ShowChestPrice();
  539. });
  540.  
  541. // 配置观察器的选项
  542. const iconConfig = { attributes: true, attributeFilter: ['href'] };
  543.  
  544. // 以上述配置开始观察箱子图标节点
  545. iconObserver.observe(chestNameElem, iconConfig);
  546. }
  547.  
  548. // 停止监听箱子图标的变化
  549. function stopIconObserver() {
  550. if (iconObserver) {
  551. iconObserver.disconnect();
  552. iconObserver = null;
  553. }
  554. }
  555. }
  556.  
  557.  
  558.  
  559.  
  560. initObserver();
  561.  
  562.  
  563. //公会部分代码
  564. const userLanguage = navigator.language || navigator.userLanguage;
  565. const isZH = userLanguage.startsWith("zh");
  566. const updataDealy = 24*60*60*1000; //数据更新时限
  567. let rateXPDayMap = {};
  568.  
  569. function hookWS() {
  570. const dataProperty = Object.getOwnPropertyDescriptor(MessageEvent.prototype, "data");
  571. const oriGet = dataProperty.get;
  572.  
  573. dataProperty.get = hookedGet;
  574. Object.defineProperty(MessageEvent.prototype, "data", dataProperty);
  575.  
  576. function hookedGet() {
  577. const socket = this.currentTarget;
  578. if (!(socket instanceof WebSocket)) {
  579. return oriGet.call(this);
  580. }
  581. if (socket.url.indexOf("api.milkywayidle.com/ws") <= -1 && socket.url.indexOf("api-test.milkywayidle.com/ws") <= -1) {
  582. return oriGet.call(this);
  583. }
  584.  
  585. const message = oriGet.call(this);
  586. Object.defineProperty(this, "data", { value: message }); // Anti-loop
  587.  
  588. return handleMessage(message);
  589. }
  590. }
  591.  
  592. function addStatisticsButton() {
  593. const waitForNavi = () => {
  594. const targetNode = document.querySelector("div.NavigationBar_minorNavigationLinks__dbxh7"); // 确认这个选择器是否适合你的环境
  595. if (targetNode) {
  596. // 创建统计窗口按钮
  597. let statsButton = document.createElement("div");
  598. statsButton.setAttribute("class", "NavigationBar_minorNavigationLink__31K7Y");
  599. statsButton.style.color = "gold";
  600. statsButton.innerHTML = isZH ? "开箱统计" : "Chest Statistics";
  601. statsButton.addEventListener("click", () => {
  602. const edibleTools = JSON.parse(localStorage.getItem('Edible_Tools')) || {};
  603. const openChestData = edibleTools.Chest_Open_Data || {};
  604. createVisualizationWindow(openChestData);
  605. });
  606.  
  607. // 将按钮添加到目标节点
  608. targetNode.insertAdjacentElement("afterbegin", statsButton);
  609. } else {
  610. setTimeout(addStatisticsButton, 200);
  611. }
  612. };
  613.  
  614. waitForNavi(); // 开始等待目标节点出现
  615. }
  616.  
  617.  
  618. //奶牛钉钉
  619. function handleMessage(message) {
  620. try {
  621. let obj = JSON.parse(message);
  622. if (obj && obj.type === "new_battle") {
  623. processCombatConsumables(obj);
  624. }
  625. if (obj && obj.type === "init_client_data") {
  626. processAndPrintData(obj,marketData);
  627. addStatisticsButton();
  628. }
  629. if (obj && obj.type === "guild_updated") {
  630. const Guild_ID = obj.guild.id;
  631. const edibleTools = JSON.parse(localStorage.getItem('Edible_Tools')) || {};
  632. edibleTools.Guild_Data = edibleTools.Guild_Data || {};
  633. let storedData = edibleTools.Guild_Data || {};
  634.  
  635. // 判断是否已经存在旧数据
  636. if (storedData[Guild_ID] && storedData[Guild_ID].guild_updated && storedData[Guild_ID].guild_updated.old.updatedAt) {
  637. const oldUpdatedAt = new Date(storedData[Guild_ID].guild_updated.new.updatedAt);
  638. const newUpdatedAt = new Date(obj.guild.updatedAt);
  639.  
  640. // 计算时间差(单位:毫秒)
  641. const timeDifference = newUpdatedAt - oldUpdatedAt;
  642.  
  643. if (timeDifference >= updataDealy) {
  644. // 更新老数据为新数据
  645. storedData[Guild_ID].guild_updated.old = storedData[Guild_ID].guild_updated.new;
  646. // 更新新数据为当前数据
  647. storedData[Guild_ID].guild_updated.new = {
  648. experience: obj.guild.experience,
  649. level: obj.guild.level,
  650. updatedAt: obj.guild.updatedAt
  651. };
  652. } else {
  653. // 仅更新新数据
  654. storedData[Guild_ID].guild_updated.new = {
  655. experience: obj.guild.experience,
  656. level: obj.guild.level,
  657. updatedAt: obj.guild.updatedAt
  658. };
  659. }
  660. //计算Δ
  661. const Delta = {
  662. Delta_Xp: storedData[Guild_ID].guild_updated.new.experience - storedData[Guild_ID].guild_updated.old.experience,
  663. Delta_Level: storedData[Guild_ID].guild_updated.new.level - storedData[Guild_ID].guild_updated.old.level,
  664. Delta_Time: (newUpdatedAt - new Date(storedData[Guild_ID].guild_updated.old.updatedAt)) / 1000, // 转换为秒
  665. Rate_XP_Hours: (3600*(obj.guild.experience - storedData[Guild_ID].guild_updated.old.experience)/((newUpdatedAt - new Date(storedData[Guild_ID].guild_updated.old.updatedAt)) / 1000)).toFixed(2)
  666. };
  667. storedData[Guild_ID].guild_updated.Delta = Delta;
  668.  
  669. const Guild_TotalXp_div = document.querySelectorAll(".GuildPanel_value__Hm2I9")[1];
  670. if (Guild_TotalXp_div) {
  671. const xpText = isZH ? "经验值 / 小时" : "XP / Hour";
  672.  
  673. Guild_TotalXp_div.insertAdjacentHTML(
  674. "afterend",
  675. `<div>${formatPrice(Delta.Rate_XP_Hours)} ${xpText}</div>`
  676. );
  677. const Guild_NeedXp_div = document.querySelectorAll(".GuildPanel_value__Hm2I9")[2];
  678. if (Guild_NeedXp_div) {
  679. const Guild_NeedXp = document.querySelectorAll(".GuildPanel_value__Hm2I9")[2].textContent.replace(/,/g, '');
  680. const Time = TimeReset(Guild_NeedXp/Delta.Rate_XP_Hours);
  681. Guild_NeedXp_div.insertAdjacentHTML(
  682. "afterend", // 使用 "afterend" 在元素的后面插入内容
  683. `<div>${Time}</div>`
  684. );
  685. }
  686. }
  687. } else {
  688. // 如果没有旧数据,则直接添加新数据
  689. storedData[Guild_ID] = {
  690. guild_name: obj.guild.name,
  691. guild_updated: {
  692. old: {
  693. experience: obj.guild.experience,
  694. level: obj.guild.level,
  695. updatedAt: obj.guild.updatedAt
  696. },
  697. new: {},
  698. }
  699. };
  700. }
  701.  
  702. // 存储更新后的数据到 localStorage
  703. edibleTools.Guild_Data = storedData;
  704. localStorage.setItem('Edible_Tools', JSON.stringify(edibleTools));
  705. } else if (obj && obj.type === "guild_characters_updated") {
  706. const edibleTools = JSON.parse(localStorage.getItem('Edible_Tools')) || {};
  707. edibleTools.Guild_Data = edibleTools.Guild_Data || {};
  708. let storedData = edibleTools.Guild_Data || {};
  709. for (const key in obj.guildSharableCharacterMap) {
  710. if (obj.guildSharableCharacterMap.hasOwnProperty(key)) {
  711. const Guild_ID = obj.guildCharacterMap[key].guildID;
  712. const name = obj.guildSharableCharacterMap[key].name;
  713. const newUpdatedAt = new Date();
  714. storedData[Guild_ID].guild_player = storedData[Guild_ID].guild_player || {};
  715. if (storedData[Guild_ID] && storedData[Guild_ID].guild_player && storedData[Guild_ID].guild_player[name] && storedData[Guild_ID].guild_player[name].old && storedData[Guild_ID].guild_player[name].old.updatedAt) {
  716. const oldUpdatedAt = new Date(storedData[Guild_ID].guild_player[name].old.updatedAt)
  717. const timeDifference = newUpdatedAt - oldUpdatedAt
  718. if (timeDifference >= updataDealy) {
  719. // 更新老数据为新数据
  720. storedData[Guild_ID].guild_player[name].old = storedData[Guild_ID].guild_player[name].new;
  721. // 更新新数据为当前数据
  722. storedData[Guild_ID].guild_player[name].new = {
  723. id: key,
  724. gameMode: obj.guildSharableCharacterMap[key].gameMode,
  725. guildExperience: obj.guildCharacterMap[key].guildExperience,
  726. updatedAt: newUpdatedAt,
  727. };
  728. } else {
  729. // 仅更新新数据
  730. storedData[Guild_ID].guild_player[name].new = {
  731. id: key,
  732. gameMode: obj.guildSharableCharacterMap[key].gameMode,
  733. guildExperience: obj.guildCharacterMap[key].guildExperience,
  734. updatedAt: newUpdatedAt,
  735. };
  736. }
  737. //计算Δ
  738. const Delta = {
  739. Delta_Time:(newUpdatedAt - new Date(storedData[Guild_ID].guild_player[name].old.updatedAt)) / 1000,
  740. Delta_Xp: storedData[Guild_ID].guild_player[name].new.guildExperience - storedData[Guild_ID].guild_player[name].old.guildExperience,
  741. Rate_XP_Day: (24*3600*(obj.guildCharacterMap[key].guildExperience - storedData[Guild_ID].guild_player[name].old.guildExperience)/((newUpdatedAt - new Date(storedData[Guild_ID].guild_player[name].old.updatedAt)) / 1000)).toFixed(2)
  742. };
  743. storedData[Guild_ID].guild_player[name].Delta = Delta;
  744. rateXPDayMap[name] = Delta.Rate_XP_Day;
  745. }else {
  746. storedData[Guild_ID].guild_player[name] = {
  747. old: {
  748. id: key,
  749. gameMode: obj.guildSharableCharacterMap[key].gameMode,
  750. guildExperience: obj.guildCharacterMap[key].guildExperience,
  751. updatedAt: newUpdatedAt,
  752. },
  753. new:{}
  754. };
  755. }
  756. }
  757.  
  758. }
  759. //console.log("测试数据",storedData);
  760. //console.log("guild_characters_updated", obj);
  761. updateExperienceDisplay(rateXPDayMap);
  762. edibleTools.Guild_Data = storedData;
  763. localStorage.setItem('Edible_Tools', JSON.stringify(edibleTools));
  764. }
  765.  
  766.  
  767.  
  768.  
  769. } catch (error) {
  770. console.error("Error processing message:", error);
  771. }
  772. return message;
  773. }
  774.  
  775.  
  776. function TimeReset(hours) {
  777. const totalMinutes = hours * 60;
  778. const days = Math.floor(totalMinutes / (24 * 60));
  779. const yudays = totalMinutes % (24 * 60);
  780. const hrs = Math.floor(yudays / 60);
  781. const minutes = Math.floor(yudays % 60);
  782. const dtext = isZH ? "天" : "d";
  783. const htext = isZH ? "时" : "h";
  784. const mtext = isZH ? "分" : "m";
  785. return `${days}${dtext} ${hrs}${htext} ${minutes}${mtext}`;
  786. }
  787.  
  788. function updateExperienceDisplay(rateXPDayMap) {
  789. const trElements = document.querySelectorAll(".GuildPanel_membersTable__1NwIX tbody tr");
  790. const idleuser_list = [];
  791. const dtext = isZH ? "天" : "d";
  792.  
  793. // 将 rateXPDayMap 转换为数组并排序
  794. const sortedMembers = Object.entries(rateXPDayMap)
  795. .map(([name, XPdata]) => ({ name, XPdata }))
  796. .sort((a, b) => b.XPdata - a.XPdata);
  797.  
  798. sortedMembers.forEach(({ name, XPdata }) => {
  799. trElements.forEach(tr => {
  800. const nameElement = tr.querySelector(".CharacterName_name__1amXp");
  801. const experienceElement = tr.querySelector("td:nth-child(3) > div");
  802. const activityElement = tr.querySelector('.GuildPanel_activity__9vshh');
  803.  
  804. if (nameElement && nameElement.textContent.trim() === name) {
  805. if (activityElement.childElementCount === 0) {
  806. idleuser_list.push(nameElement.textContent.trim());
  807. }
  808.  
  809. if (experienceElement) {
  810. const newDiv = document.createElement('div');
  811. newDiv.textContent = `${formatPrice(XPdata)}/${dtext}`;
  812.  
  813. // 计算颜色
  814. const rank = sortedMembers.findIndex(member => member.name === name);
  815. const hue = 120 - (rank * (120 / (sortedMembers.length - 1)));
  816. newDiv.style.color = `hsl(${hue}, 100%, 50%)`;
  817.  
  818. experienceElement.insertAdjacentElement('afterend', newDiv);
  819. }
  820. return;
  821. }
  822. });
  823. });
  824.  
  825. update_idleuser_tb(idleuser_list);
  826. }
  827.  
  828. function update_idleuser_tb(idleuser_list) {
  829. const targetElement = document.querySelector('.GuildPanel_noticeMessage__3Txji');
  830. if (!targetElement) {
  831. console.error('公会标语元素未找到!');
  832. return;
  833. }
  834. const clonedElement = targetElement.cloneNode(true);
  835.  
  836. const namesText = idleuser_list.join(', ');
  837. clonedElement.innerHTML = '';
  838. clonedElement.textContent = isZH ? `闲置的成员:${namesText}` : `Idle User : ${namesText}`;
  839. clonedElement.style.color = '#ffcc00';
  840.  
  841. // 设置复制元素的高度为原元素的25%
  842. const originalStyle = window.getComputedStyle(targetElement);
  843. const originalHeight = originalStyle.height;
  844. const originalMinHeight = originalStyle.minHeight;
  845. clonedElement.style.height = `25%`;
  846. clonedElement.style.minHeight = `25%`; // 也设置最小高度
  847. targetElement.parentElement.appendChild(clonedElement);
  848. }
  849.  
  850.  
  851. hookWS();
  852.  
  853.  
  854. //箱子数据获取
  855. function processAndPrintData(obj) {
  856. let formattedShopData = {};
  857.  
  858. // 处理商店数据
  859. for (let [key, details] of Object.entries(obj.shopItemDetailMap)) {
  860. const { itemHrid, costs } = details;
  861. const itemName = formatItemName(itemHrid.split('/').pop());
  862.  
  863. costs.forEach(cost => {
  864. const costItemName = formatItemName(cost.itemHrid.split('/').pop());
  865.  
  866. if (costItemName === "Coin") return;
  867.  
  868. const costCount = cost.count;
  869.  
  870. if (!formattedShopData[costItemName]) {
  871. formattedShopData[costItemName] = { items: {}, 最挣钱: '', BID单价: 0 };
  872. }
  873.  
  874. // 计算每种代币购买每个物品的收益
  875. let bidValue = marketData?.market?.[itemName]?.bid || 0;
  876. let profit = bidValue / costCount;
  877.  
  878. formattedShopData[costItemName].items[itemName] = {
  879. 花费: costCount
  880. };
  881.  
  882. // 更新最赚钱的物品信息
  883. if (profit > formattedShopData[costItemName].BID单价) {
  884. formattedShopData[costItemName].最挣钱 = itemName;
  885. formattedShopData[costItemName].BID单价 = profit;
  886. specialItemPrices[costItemName].ask = profit;
  887. specialItemPrices[costItemName].bid = profit;
  888. }
  889. });
  890. }
  891. const mostProfitableItems = Object.values(formattedShopData).map(item => item.最挣钱).filter(Boolean);
  892. console.log(mostProfitableItems)
  893. // 处理箱子掉落物数据
  894.  
  895. for (let iteration = 0; iteration < 4; iteration++) {
  896. for (let [key, items] of Object.entries(obj.openableLootDropMap)) {
  897. const boxName = formatItemName(key.split('/').pop());
  898.  
  899. if (!formattedChestDropData[boxName]) {
  900. formattedChestDropData[boxName] = { item: {} };
  901. }
  902. let TotalAsk = 0;
  903. let TotalBid = 0;
  904. let awa = 0;
  905. items.forEach(item => {
  906. const { itemHrid, dropRate, minCount, maxCount } = item;
  907. const itemName = formatItemName(itemHrid.split('/').pop());
  908. const expectedYield = ((minCount + maxCount) / 2) * dropRate;
  909. let bidPrice = -1;
  910. let askPrice = -1;
  911. let priceColor = '';
  912.  
  913. if (specialItemPrices[itemName] && specialItemPrices[itemName].ask) {
  914. askPrice = parseFloat(specialItemPrices[itemName].ask);
  915. bidPrice = parseFloat(specialItemPrices[itemName].bid);
  916. priceColor = '';
  917. } else if (marketData?.market?.[itemName]) {
  918. bidPrice = marketData.market[itemName].bid;
  919. askPrice = marketData.market[itemName].ask;
  920. } else {
  921. console.log(`${itemName} 的价格未找到`);
  922. }
  923.  
  924. if (formattedChestDropData[boxName].item[itemName] && iteration === 0) {
  925. // 如果物品已存在,更新期望掉落和相关价格
  926. const existingItem = formattedChestDropData[boxName].item[itemName];
  927. existingItem.期望掉落 += expectedYield;
  928. } else if (iteration === 0) {
  929. formattedChestDropData[boxName].item[itemName] = {
  930. 期望掉落: expectedYield,
  931. };
  932. }
  933.  
  934. // 判断 itemName 是否在最挣钱物品列表中,并执行相应代码
  935. if (mostProfitableItems.includes(itemName)) {
  936. priceColor = '#FFb3E6';
  937. } else if (askPrice === -1 && bidPrice === -1) {
  938. priceColor = 'yellow';
  939. } else if (askPrice === -1) {
  940. askPrice = bidPrice;
  941. priceColor = '#D95961';
  942. } else if (bidPrice === -1) {
  943. priceColor = '#2FC4A7';
  944. }
  945.  
  946. const existingItem = formattedChestDropData[boxName].item[itemName];
  947. existingItem.出售单价 = askPrice;
  948. existingItem.收购单价 = bidPrice;
  949. existingItem.出售总价 = (existingItem.出售单价 * existingItem.期望掉落).toFixed(2);
  950. existingItem.收购总价 = (existingItem.收购单价 * existingItem.期望掉落).toFixed(2);
  951. existingItem.Color = priceColor;
  952.  
  953. // 累计总价
  954. TotalAsk += (askPrice * expectedYield);
  955. TotalBid += (bidPrice * expectedYield);
  956. });
  957.  
  958. formattedChestDropData[boxName] = {
  959. ...formattedChestDropData[boxName],
  960. 期望产出Ask: TotalAsk.toFixed(2),
  961. 期望产出Bid: TotalBid.toFixed(2),
  962. };
  963.  
  964. if (!specialItemPrices[boxName]) {
  965. specialItemPrices[boxName] = {}
  966. }
  967.  
  968. specialItemPrices[boxName].ask = formattedChestDropData[boxName].期望产出Ask;
  969. specialItemPrices[boxName].bid = formattedChestDropData[boxName].期望产出Bid;
  970. }
  971. }
  972.  
  973. for (let itemName in specialItemPrices) {
  974. if (specialItemPrices.hasOwnProperty(itemName)) {
  975. marketData.market[itemName] = {
  976. ask: specialItemPrices[itemName].ask,
  977. bid: specialItemPrices[itemName].bid
  978. };
  979. }
  980. }
  981.  
  982. localStorage.setItem('MWITools_marketAPI_json', JSON.stringify(marketData));
  983. let edibleTools = JSON.parse(localStorage.getItem('Edible_Tools')) || {};
  984. edibleTools.Chest_Drop_Data = formattedChestDropData;
  985. localStorage.setItem('Edible_Tools', JSON.stringify(edibleTools));
  986. // 打印结果
  987. //console.log("特殊物品价格表:",specialItemPrices)
  988. //console.log("箱子掉落物列表:", formattedChestDropData);
  989. //console.log("地牢商店列表:", formattedShopData);
  990. }
  991.  
  992. function ShowChestPrice() {
  993. const modalContainer = document.querySelector(".Modal_modalContainer__3B80m");
  994. if (!modalContainer) return; // 如果不存在 Modal_modalContainer__3B80m 元素,则直接返回
  995.  
  996. const chestNameElem = document.querySelector(chestNameSelector);
  997. if (!chestNameElem) return;
  998.  
  999. const chestName = getItemNameFromElement(chestNameElem);
  1000. const items = document.querySelectorAll(itemSelector);
  1001.  
  1002. const dropListContainer = document.querySelector('.ItemDictionary_openToLoot__1krnv');
  1003. if (!dropListContainer) return; // 检查 dropListContainer 是否存在
  1004.  
  1005. const edibleTools = JSON.parse(localStorage.getItem('Edible_Tools'))
  1006. const formattedChestDropData = edibleTools.Chest_Drop_Data;
  1007.  
  1008. items.forEach(item => {
  1009. const itemName = getItemNameFromElement(item.querySelector(iconSelector));
  1010. if (!itemName) return; // 检查 itemName 是否存在
  1011.  
  1012. const itemData = formattedChestDropData[chestName].item[itemName];
  1013. if (!itemData) return; // 检查 itemData 是否存在
  1014.  
  1015. const itemColor = itemData.Color;
  1016. const itemNameElem = item.querySelector('.Item_name__2C42x');
  1017. if (itemNameElem && itemColor) {
  1018. itemNameElem.style.color = itemColor;
  1019. }
  1020. });
  1021.  
  1022. const askPrice = formattedChestDropData[chestName]["期望产出Ask"];
  1023. const bidPrice = formattedChestDropData[chestName]["期望产出Bid"];
  1024. if (askPrice && bidPrice) {
  1025.  
  1026. const previousResults = dropListContainer.querySelectorAll('.resultDiv');
  1027. previousResults.forEach(result => result.remove());
  1028.  
  1029. const createPriceOutput = (label, price) => {
  1030. const priceOutput = document.createElement('div');
  1031. priceOutput.className = 'resultDiv';
  1032. priceOutput.textContent = `${label}: ${formatPrice(price)}`;
  1033. priceOutput.style.color = 'gold';
  1034. priceOutput.style.fontSize = '14px';
  1035. priceOutput.style.fontWeight = '400';
  1036. priceOutput.style.paddingTop = '10px';
  1037. return priceOutput;
  1038. };
  1039.  
  1040. const minPriceOutput = createPriceOutput('期望产出 (最低买入价计算)', askPrice);
  1041. const maxPriceOutput = createPriceOutput('期望产出 (最高收购价计算)', bidPrice);
  1042.  
  1043. dropListContainer.appendChild(minPriceOutput);
  1044. dropListContainer.appendChild(maxPriceOutput);
  1045.  
  1046. }
  1047. }
  1048.  
  1049. function processCombatConsumables(obj) {
  1050. battlePlayerFood = {};
  1051. battlePlayerLoot = {};
  1052. obj.players.forEach(player => {
  1053. battlePlayerFood[player.character.name] = {};
  1054. battlePlayerLoot[player.character.name] = {};
  1055. let minTimeInDays = Infinity;
  1056. let minItemName = "";
  1057.  
  1058. player.combatConsumables.forEach(consumable => {
  1059. const itemname = formatItemName(consumable.itemHrid.split('/').pop());
  1060. const timePerUnit = itemname.includes('Coffee') ? 5 : 1;
  1061. const totalTimeInMinutes = consumable.count * timePerUnit;
  1062. const totalTimeInDays = totalTimeInMinutes / (24 * 60);
  1063.  
  1064. if (totalTimeInDays < minTimeInDays) {
  1065. minTimeInDays = totalTimeInDays;
  1066. minItemName = itemname;
  1067. }
  1068.  
  1069. battlePlayerFood[player.character.name][itemname] = {
  1070. "数量": consumable.count,
  1071. "剩余时间": totalTimeInDays.toFixed(1) + '天',
  1072. "颜色": "Black"
  1073. };
  1074. });
  1075. Object.keys(battlePlayerFood[player.character.name]).forEach(item => {
  1076. if (item === minItemName) {
  1077. battlePlayerFood[player.character.name][item]["颜色"] = "red";
  1078. }
  1079. });
  1080. battlePlayerFood[player.character.name]['剩余时间'] = {
  1081. "数量": minTimeInDays < 1 ? (minTimeInDays * 24).toFixed(1) + '小时' : minTimeInDays.toFixed(1) + '天',
  1082. "颜色": "Black"
  1083. };
  1084.  
  1085. Object.values(player.totalLootMap).forEach(Loot => {
  1086. const itemname = formatItemName(Loot.itemHrid.split('/').pop());
  1087. battlePlayerLoot[player.character.name][itemname] = {
  1088. "数量":Loot.count
  1089. }
  1090. });
  1091.  
  1092. });
  1093. }
  1094.  
  1095.  
  1096. function addBattlePlayerFoodButton() {
  1097.  
  1098. var tabsContainer = document.querySelector("#root > div > div > div.GamePage_gamePanel__3uNKN > div.GamePage_contentPanel__Zx4FH > div.GamePage_middlePanel__uDts7 > div.GamePage_mainPanel__2njyb > div > div:nth-child(2) > div > div.CombatPanel_tabsComponentContainer__GsQlg > div > div.TabsComponent_tabsContainer__3BDUp > div > div > div");
  1099. var referenceTab = tabsContainer ? tabsContainer.children[1] : null;
  1100.  
  1101. if (!tabsContainer || !referenceTab) {
  1102. return;
  1103. }
  1104.  
  1105. if (tabsContainer.querySelector('.Button_battlePlayerFood__custom')) {
  1106. console.log('按钮已存在');
  1107. return;
  1108. }
  1109.  
  1110. var battlePlayerFoodButton = document.createElement('div');
  1111. battlePlayerFoodButton.className = referenceTab.className + ' Button_battlePlayerFood__custom';
  1112. battlePlayerFoodButton.setAttribute('script_translatedfrom', 'New Action');
  1113. battlePlayerFoodButton.textContent = '出警';
  1114.  
  1115. battlePlayerFoodButton.addEventListener('click', function() {
  1116. let dataHtml = '<div style="display: flex; flex-wrap: nowrap;">';
  1117. for (let player in battlePlayerFood) {
  1118. dataHtml +=
  1119. `<div style="flex: 1 0 auto; min-width: 100px; margin: 10px; padding: 10px; border: 1px solid black;">
  1120. <h3>${player}</h3>`;
  1121. let consumables = battlePlayerFood[player];
  1122. for (let item in consumables) {
  1123. dataHtml += `<p style="color:${consumables[item].颜色};">${item}: ${consumables[item].数量}</p>`;
  1124. }
  1125. dataHtml += '</div>';
  1126. }
  1127. dataHtml += '</div>';
  1128.  
  1129. let popup = document.createElement('div');
  1130. popup.style.position = 'fixed';
  1131. popup.style.top = '50%';
  1132. popup.style.left = '50%';
  1133. popup.style.transform = 'translate(-50%, -50%)';
  1134. popup.style.backgroundColor = 'white';
  1135. popup.style.border = '1px solid black';
  1136. popup.style.padding = '10px';
  1137. popup.style.zIndex = '10000';
  1138. popup.style.maxWidth = '75%';
  1139. popup.style.overflowX = 'auto';
  1140. popup.style.whiteSpace = 'nowrap';
  1141. popup.innerHTML = dataHtml;
  1142.  
  1143. let closeButton = document.createElement('button');
  1144. closeButton.textContent = '关闭';
  1145. closeButton.style.display = 'block';
  1146. closeButton.style.margin = '20px auto 0 auto';
  1147. closeButton.onclick = function() {
  1148. document.body.removeChild(popup);
  1149. };
  1150. popup.appendChild(closeButton);
  1151.  
  1152. document.body.appendChild(popup);
  1153. });
  1154.  
  1155. var lastTab = tabsContainer.children[tabsContainer.children.length - 1];
  1156. tabsContainer.insertBefore(battlePlayerFoodButton, lastTab.nextSibling);
  1157.  
  1158. var style = document.createElement('style');
  1159. style.innerHTML = `
  1160. .Button_battlePlayerFood__custom {
  1161. background-color: #546ddb;
  1162. color: white;
  1163. }
  1164. `;
  1165. document.head.appendChild(style);
  1166. }
  1167.  
  1168.  
  1169.  
  1170.  
  1171. //菜单
  1172. GM_registerMenuCommand('打印所有箱子掉落物', function() {
  1173. console.log('箱子掉落物列表:', formattedChestDropData);
  1174. });
  1175.  
  1176. function createWindowBase() {
  1177. let windowDiv = document.createElement('div');
  1178. windowDiv.className = 'visualization-window';
  1179. windowDiv.style.position = 'fixed';
  1180. windowDiv.style.top = '50%';
  1181. windowDiv.style.left = '50%';
  1182. windowDiv.style.transform = 'translate(-50%, -50%)';
  1183. windowDiv.style.height = '60%';
  1184. windowDiv.style.backgroundColor = 'white';
  1185. windowDiv.style.border = '1px solid #000';
  1186. windowDiv.style.zIndex = '10000';
  1187. windowDiv.style.padding = '10px';
  1188. windowDiv.style.boxSizing = 'border-box';
  1189. windowDiv.style.display = 'flex';
  1190. windowDiv.style.flexDirection = 'column';
  1191. return windowDiv;
  1192. }
  1193.  
  1194. function createVisualizationWindow(chestData) {
  1195. let oldWindow = document.querySelector('.visualization-window');
  1196. if (oldWindow) {
  1197. oldWindow.remove();
  1198. }
  1199. let windowDiv = createWindowBase();
  1200.  
  1201. let title = document.createElement('h1');
  1202. title.innerText = '开箱记录';
  1203. title.style.textAlign = 'center';
  1204. windowDiv.appendChild(title);
  1205.  
  1206. let contentDiv = document.createElement('div');
  1207. contentDiv.style.flex = '1';
  1208. contentDiv.style.overflowY = 'auto';
  1209.  
  1210. // 添加箱子列表
  1211. for (let chestName in chestData) {
  1212. let chest = chestData[chestName];
  1213.  
  1214. let chestButton = document.createElement('button');
  1215. chestButton.style.display = 'block';
  1216. chestButton.style.width = '100%';
  1217. chestButton.style.textAlign = 'left';
  1218. chestButton.style.marginBottom = '5px';
  1219. chestButton.innerText = `${chestName}: ${chest['总计开箱数量']}`;
  1220. chestButton.onclick = () => showChestDetails(chestName, chest);
  1221.  
  1222. contentDiv.appendChild(chestButton);
  1223. }
  1224.  
  1225. windowDiv.appendChild(contentDiv);
  1226.  
  1227. let footerDiv = document.createElement('div');
  1228. footerDiv.style.display = 'flex';
  1229. footerDiv.style.justifyContent = 'space-between';
  1230. footerDiv.style.marginTop = '10px';
  1231.  
  1232. // 关闭按钮
  1233. let closeButton = document.createElement('button');
  1234. closeButton.innerText = '关闭';
  1235. closeButton.onclick = () => document.body.removeChild(windowDiv);
  1236. footerDiv.appendChild(closeButton);
  1237.  
  1238. // 删除数据按钮
  1239. let deleteButton = document.createElement('button');
  1240. deleteButton.innerText = '删除数据';
  1241. deleteButton.onclick = () => {
  1242. if (confirm('确定要删除所有开箱数据吗?')) {
  1243. deleteOpenChestData(chestData);
  1244. document.body.removeChild(windowDiv);
  1245. }
  1246. };
  1247. footerDiv.appendChild(deleteButton);
  1248.  
  1249. windowDiv.appendChild(footerDiv);
  1250.  
  1251. document.body.appendChild(windowDiv);
  1252. }
  1253.  
  1254. function deleteOpenChestData() {
  1255. let edibleToolsData = JSON.parse(localStorage.getItem('Edible_Tools'));
  1256. if (edibleToolsData && edibleToolsData.Chest_Open_Data) {
  1257. edibleToolsData.Chest_Open_Data = {};
  1258. localStorage.setItem('Edible_Tools', JSON.stringify(edibleToolsData));
  1259. }
  1260. }
  1261.  
  1262.  
  1263. // 显示箱子详细信息
  1264. function showChestDetails(chestName, chestData) {
  1265. let oldWindow = document.querySelector('.visualization-window');
  1266. if (oldWindow) {
  1267. oldWindow.remove();
  1268. }
  1269.  
  1270. let detailsWindow = createWindowBase();
  1271. let title = document.createElement('h1');
  1272. title.innerText = chestName;
  1273. detailsWindow.appendChild(title);
  1274.  
  1275. let contentDiv = document.createElement('div');
  1276. contentDiv.style.flex = '1';
  1277. contentDiv.style.overflowY = 'auto';
  1278.  
  1279. let totalStats = document.createElement('p');
  1280. totalStats.innerText = `总计开箱数量: ${chestData['总计开箱数量']}\n总计Ask: ${formatPrice(chestData['总计开箱Ask'])}\n总计Bid: ${formatPrice(chestData['总计开箱Bid'])}`;
  1281. contentDiv.appendChild(totalStats);
  1282.  
  1283. // 添加物品信息
  1284. for (let itemName in chestData['获得物品']) {
  1285. let item = chestData['获得物品'][itemName];
  1286. let itemElement = document.createElement('p');
  1287. itemElement.innerText = `${itemName}: ${formatPrice(item['数量'])}`;
  1288. contentDiv.appendChild(itemElement);
  1289. }
  1290.  
  1291. detailsWindow.appendChild(contentDiv);
  1292.  
  1293. let footerDiv = document.createElement('div');
  1294. footerDiv.style.display = 'flex';
  1295. footerDiv.style.justifyContent = 'space-between';
  1296. footerDiv.style.marginTop = '10px';
  1297.  
  1298. let backButton = document.createElement('button');
  1299. backButton.innerText = '返回';
  1300. backButton.onclick = () => {
  1301. detailsWindow.remove();
  1302. createVisualizationWindow(JSON.parse(localStorage.getItem('Edible_Tools')).Chest_Open_Data);
  1303. };
  1304. footerDiv.appendChild(backButton);
  1305.  
  1306. let closeButton = document.createElement('button');
  1307. closeButton.innerText = '关闭';
  1308. closeButton.onclick = () => detailsWindow.remove();
  1309. footerDiv.appendChild(closeButton);
  1310.  
  1311. detailsWindow.appendChild(footerDiv);
  1312.  
  1313. document.body.appendChild(detailsWindow);
  1314. }
  1315.  
  1316. GM_registerMenuCommand('打印全部开箱记录', function() {
  1317. const edibleTools = JSON.parse(localStorage.getItem('Edible_Tools')) || {};
  1318. const openChestData = edibleTools.Chest_Open_Data || {};
  1319. createVisualizationWindow(openChestData);
  1320. });
  1321.  
  1322. GM_registerMenuCommand('打印掉落物列表', function() {
  1323. let dataHtml = '<div style="display: flex; flex-wrap: nowrap;">';
  1324. const minPrice = 10000;
  1325. for (let player in battlePlayerLoot) {
  1326. let totalPrice = 0;
  1327. dataHtml += `<div style="flex: 1 0 auto; min-width: 100px; margin: 10px; padding: 10px; border: 1px solid black;">`;
  1328. dataHtml += `<h3>${player}</h3>`;
  1329.  
  1330. let lootItems = battlePlayerLoot[player];
  1331. for (let item in lootItems) {
  1332. let bidPrice = marketData.market[item].bid;
  1333. totalPrice += bidPrice*lootItems[item].数量
  1334. if (bidPrice > minPrice) {
  1335. dataHtml += `<p>${item}: ${lootItems[item].数量}</p>`;
  1336. }
  1337. }
  1338. if (totalPrice > 0) {
  1339. dataHtml += `<p>总计价格: ${formatPrice(totalPrice)}</p>`;
  1340. }
  1341. dataHtml += '</div>';
  1342. }
  1343. dataHtml += '</div>';
  1344.  
  1345. let popup = document.createElement('div');
  1346. popup.style.position = 'fixed';
  1347. popup.style.top = '50%';
  1348. popup.style.left = '50%';
  1349. popup.style.transform = 'translate(-50%, -50%)';
  1350. popup.style.backgroundColor = 'white';
  1351. popup.style.border = '1px solid black';
  1352. popup.style.padding = '10px';
  1353. popup.style.zIndex = '10000';
  1354. popup.style.maxWidth = '75%';
  1355. popup.style.overflowX = 'auto';
  1356. popup.style.whiteSpace = 'nowrap';
  1357. popup.innerHTML = dataHtml;
  1358.  
  1359. let closeButton = document.createElement('button');
  1360. closeButton.textContent = '关闭';
  1361. closeButton.style.display = 'block';
  1362. closeButton.style.margin = '20px auto 0 auto';
  1363. closeButton.onclick = function() {
  1364. document.body.removeChild(popup);
  1365. };
  1366. popup.appendChild(closeButton);
  1367.  
  1368. document.body.appendChild(popup);
  1369. });
  1370. })();