[银河奶牛]食用工具

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

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

  1. // ==UserScript==
  2. // @name [银河奶牛]食用工具
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.421
  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 * (endurance.integerPart + strength.integerPart),
  335. ranged.integerPart,
  336. magic.integerPart
  337. );
  338.  
  339. if (combatTypeMax !== 0.5 * (endurance.integerPart + strength.integerPart)) {
  340. endurance.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. const integerPart = Math.floor(combatLevel);
  349. const decimalPart = combatLevel - integerPart;
  350.  
  351. const list1 = [
  352. endurance.decimalPart * 0.2,
  353. intelligence.decimalPart * 0.2,
  354. attack.decimalPart * 0.2,
  355. strength.decimalPart * 0.2,
  356. defense.decimalPart * 0.2,
  357. ranged.decimalPart * 0.2,
  358. magic.decimalPart * 0.2
  359. ];
  360.  
  361. const list2 = [
  362. endurance.decimalPart * 0.2,
  363. intelligence.decimalPart * 0.2,
  364. attack.decimalPart * 0.2,
  365. strength.decimalPart * 0.2,
  366. defense.decimalPart * 0.2,
  367. ranged.decimalPart * 0.4,
  368. magic.decimalPart * 0.4
  369. ];
  370. //console.log("list1",list1,"\nlist2",list2)
  371. list1.sort((a, b) => b - a);
  372. list2.sort((a, b) => b - a);
  373.  
  374. if (decimalPart === 0.8) {
  375. combatLevel += list1[0];
  376. } else {
  377. let total = 0;
  378. const maxIterations = Math.floor((1 - decimalPart) / 0.2);
  379. let iterations = 0;
  380.  
  381. for (const i of list2) {
  382. if (iterations >= maxIterations) break;
  383.  
  384. if ((decimalPart + total + i) < 1) {
  385. total += i;
  386. } else {
  387. break;
  388. }
  389.  
  390. iterations++;
  391. }
  392. combatLevel = decimalPart + integerPart + total;
  393. }
  394.  
  395. elements[15].parentNode.parentNode.parentNode.parentNode.parentNode.querySelector(".NavigationBar_nav__3uuUl .NavigationBar_level__3C7eR").textContent = combatLevel.toFixed(2);
  396. }
  397. };
  398. window.setInterval(updateCombatLevel, 10000);
  399.  
  400. function OfflineStatistics(modalElement) {
  401. const itemsContainer = modalElement.querySelectorAll(".OfflineProgressModal_itemList__26h-Y");
  402.  
  403. let timeContainer = null;
  404. let getItemContainer = null;
  405. let spendItemContainer = null;
  406.  
  407.  
  408. itemsContainer.forEach(container => {
  409. const labelElement = container.querySelector('.OfflineProgressModal_label__2HwFG');
  410. if (labelElement) {
  411. const textContent = labelElement.textContent.trim();
  412. if (textContent.startsWith("You were offline for") || textContent.startsWith("你离线了")) {
  413. timeContainer = container;
  414. } else if (textContent.startsWith("Items gained:") || textContent.startsWith("获得物品:")) {
  415. getItemContainer = container;
  416. } else if (textContent.startsWith("You consumed:") || textContent.startsWith("你消耗了:")) {
  417. spendItemContainer = container;
  418. }
  419. }
  420. });
  421.  
  422. let TotalSec = null;
  423. if (timeContainer) {
  424. const textContent = timeContainer.textContent;
  425. const match = textContent.match(/(?:(\d+)d\s*)?(?:(\d+)h\s*)?(?:(\d+)m\s*)?(?:(\d+)s)/);
  426. if (match) {
  427. let days = parseInt(match[1], 10) || 0;
  428. let hours = parseInt(match[2], 10) || 0;
  429. let minutes = parseInt(match[3], 10) || 0;
  430. let seconds = parseInt(match[4], 10) || 0;
  431. TotalSec = days * 86400 + hours * 3600 + minutes * 60 + seconds;
  432. }
  433. }
  434.  
  435. let getitemtotalAskValue = 0;
  436. let getitemtotalBidValue = 0;
  437. if (getItemContainer) {
  438. const getitemElements = getItemContainer.querySelectorAll('.Item_itemContainer__x7kH1');
  439. const { totalAskValue, totalBidValue } = calculateTotalValues(getitemElements);
  440. getitemtotalAskValue = totalAskValue;
  441. getitemtotalBidValue = totalBidValue;
  442. }
  443.  
  444.  
  445. let spenditemtotalAskValue = 0;
  446. let spenditemtotalBidValue = 0;
  447. if (spendItemContainer) {
  448. const spenditemElements = spendItemContainer.querySelectorAll('.Item_itemContainer__x7kH1');
  449. const { totalAskValue, totalBidValue } = calculateTotalValues(spenditemElements);
  450. spenditemtotalAskValue = totalAskValue;
  451. spenditemtotalBidValue = totalBidValue;
  452. }
  453.  
  454. if (timeContainer) {
  455. const newElement = document.createElement('span');
  456. newElement.textContent = `利润:[${formatPrice(getitemtotalBidValue - spenditemtotalAskValue)}/${formatPrice((getitemtotalBidValue - spenditemtotalAskValue) / (TotalSec / 3600) * 24)}]/天`;
  457. newElement.style.float = 'right';
  458. newElement.style.color = 'gold';
  459. timeContainer.querySelector(':first-child').appendChild(newElement);
  460. }
  461. if (getItemContainer) {
  462. const newElement = document.createElement('span');
  463. newElement.textContent = `产出:[${formatPrice(getitemtotalAskValue)}/${formatPrice(getitemtotalBidValue)}]`;
  464. newElement.style.float = 'right';
  465. newElement.style.color = 'gold';
  466. getItemContainer.querySelector(':first-child').appendChild(newElement);
  467. }
  468. if (spendItemContainer) {
  469. const newElement = document.createElement('span');
  470. newElement.textContent = `成本:[${formatPrice(spenditemtotalAskValue)}/${formatPrice(spenditemtotalBidValue)}]`;
  471. newElement.style.float = 'right';
  472. newElement.style.color = 'gold';
  473. spendItemContainer.querySelector(':first-child').appendChild(newElement);
  474. }
  475. }
  476.  
  477.  
  478. function initObserver() {
  479. // 选择要观察的目标节点
  480. const targetNode = document.body;
  481.  
  482. // 观察器的配置(需要观察子节点的变化)
  483. const config = { childList: true, subtree: true };
  484.  
  485. // 创建一个观察器实例并传入回调函数
  486. const observer = new MutationObserver(mutationsList => {
  487. for (let mutation of mutationsList) {
  488. if (mutation.type === 'childList') {
  489. // 监听到子节点变化
  490. mutation.addedNodes.forEach(addedNode => {
  491. // 检查是否是我们关注的 Modal_modalContainer__3B80m 元素被添加
  492. if (addedNode.classList && addedNode.classList.contains('Modal_modalContainer__3B80m')) {
  493. // Modal_modalContainer__3B80m 元素被添加,执行处理函数
  494. //console.log("箱子被打开")
  495. ShowChestPrice();
  496. recordChestOpening(addedNode);
  497.  
  498. // 开始监听箱子图标的变化
  499. startIconObserver();
  500. }
  501. if (addedNode.classList && addedNode.classList.contains('OfflineProgressModal_modalContainer__knnk7')) {
  502. OfflineStatistics(addedNode);
  503. console.log("离线报告已创建!")
  504. }
  505. if (addedNode.classList && addedNode.classList.contains('MainPanel_subPanelContainer__1i-H9')) {
  506. if (addedNode.querySelector(".CombatPanel_combatPanel__QylPo")) {
  507. addBattlePlayerFoodButton();
  508. }
  509. }
  510. });
  511.  
  512. mutation.removedNodes.forEach(removedNode => {
  513. // 检查是否是 Modal_modalContainer__3B80m 元素被移除
  514. if (removedNode.classList && removedNode.classList.contains('Modal_modalContainer__3B80m')) {
  515. // Modal_modalContainer__3B80m 元素被移除,停止监听箱子图标的变化
  516. stopIconObserver();
  517. }
  518. });
  519. }
  520. }
  521. });
  522.  
  523. // 以上述配置开始观察目标节点
  524. observer.observe(targetNode, config);
  525.  
  526. // 定义箱子图标变化的观察器
  527. let iconObserver = null;
  528.  
  529. // 开始监听箱子图标的变化
  530. function startIconObserver() {
  531. const chestNameElem = document.querySelector(chestNameSelector);
  532. if (!chestNameElem) return;
  533.  
  534. // 创建一个观察器实例来监听图标的变化
  535. iconObserver = new MutationObserver(() => {
  536. // 当箱子图标变化时,执行处理函数
  537. ShowChestPrice();
  538. });
  539.  
  540. // 配置观察器的选项
  541. const iconConfig = { attributes: true, attributeFilter: ['href'] };
  542.  
  543. // 以上述配置开始观察箱子图标节点
  544. iconObserver.observe(chestNameElem, iconConfig);
  545. }
  546.  
  547. // 停止监听箱子图标的变化
  548. function stopIconObserver() {
  549. if (iconObserver) {
  550. iconObserver.disconnect();
  551. iconObserver = null;
  552. }
  553. }
  554. }
  555.  
  556.  
  557.  
  558.  
  559. initObserver();
  560.  
  561.  
  562. //公会部分代码
  563. const userLanguage = navigator.language || navigator.userLanguage;
  564. const isZH = userLanguage.startsWith("zh");
  565. const updataDealy = 24*60*60*1000; //数据更新时限
  566. let rateXPDayMap = {};
  567.  
  568. function hookWS() {
  569. const dataProperty = Object.getOwnPropertyDescriptor(MessageEvent.prototype, "data");
  570. const oriGet = dataProperty.get;
  571.  
  572. dataProperty.get = hookedGet;
  573. Object.defineProperty(MessageEvent.prototype, "data", dataProperty);
  574.  
  575. function hookedGet() {
  576. const socket = this.currentTarget;
  577. if (!(socket instanceof WebSocket)) {
  578. return oriGet.call(this);
  579. }
  580. if (socket.url.indexOf("api.milkywayidle.com/ws") <= -1 && socket.url.indexOf("api-test.milkywayidle.com/ws") <= -1) {
  581. return oriGet.call(this);
  582. }
  583.  
  584. const message = oriGet.call(this);
  585. Object.defineProperty(this, "data", { value: message }); // Anti-loop
  586.  
  587. return handleMessage(message);
  588. }
  589. }
  590.  
  591. function addStatisticsButton() {
  592. const waitForNavi = () => {
  593. const targetNode = document.querySelector("div.NavigationBar_minorNavigationLinks__dbxh7"); // 确认这个选择器是否适合你的环境
  594. if (targetNode) {
  595. // 创建统计窗口按钮
  596. let statsButton = document.createElement("div");
  597. statsButton.setAttribute("class", "NavigationBar_minorNavigationLink__31K7Y");
  598. statsButton.style.color = "gold";
  599. statsButton.innerHTML = isZH ? "开箱统计" : "Chest Statistics";
  600. statsButton.addEventListener("click", () => {
  601. const edibleTools = JSON.parse(localStorage.getItem('Edible_Tools')) || {};
  602. const openChestData = edibleTools.Chest_Open_Data || {};
  603. createVisualizationWindow(openChestData);
  604. });
  605.  
  606. // 将按钮添加到目标节点
  607. targetNode.insertAdjacentElement("afterbegin", statsButton);
  608. } else {
  609. setTimeout(addStatisticsButton, 200);
  610. }
  611. };
  612.  
  613. waitForNavi(); // 开始等待目标节点出现
  614. }
  615.  
  616.  
  617. //奶牛钉钉
  618. function handleMessage(message) {
  619. try {
  620. let obj = JSON.parse(message);
  621. if (obj && obj.type === "new_battle") {
  622. processCombatConsumables(obj);
  623. }
  624. if (obj && obj.type === "init_client_data") {
  625. processAndPrintData(obj,marketData);
  626. addStatisticsButton();
  627. }
  628. if (obj && obj.type === "guild_updated") {
  629. const Guild_ID = obj.guild.id;
  630. const edibleTools = JSON.parse(localStorage.getItem('Edible_Tools')) || {};
  631. edibleTools.Guild_Data = edibleTools.Guild_Data || {};
  632. let storedData = edibleTools.Guild_Data || {};
  633.  
  634. // 判断是否已经存在旧数据
  635. if (storedData[Guild_ID] && storedData[Guild_ID].guild_updated && storedData[Guild_ID].guild_updated.old.updatedAt) {
  636. const oldUpdatedAt = new Date(storedData[Guild_ID].guild_updated.new.updatedAt);
  637. const newUpdatedAt = new Date(obj.guild.updatedAt);
  638.  
  639. // 计算时间差(单位:毫秒)
  640. const timeDifference = newUpdatedAt - oldUpdatedAt;
  641.  
  642. if (timeDifference >= updataDealy) {
  643. // 更新老数据为新数据
  644. storedData[Guild_ID].guild_updated.old = storedData[Guild_ID].guild_updated.new;
  645. // 更新新数据为当前数据
  646. storedData[Guild_ID].guild_updated.new = {
  647. experience: obj.guild.experience,
  648. level: obj.guild.level,
  649. updatedAt: obj.guild.updatedAt
  650. };
  651. } else {
  652. // 仅更新新数据
  653. storedData[Guild_ID].guild_updated.new = {
  654. experience: obj.guild.experience,
  655. level: obj.guild.level,
  656. updatedAt: obj.guild.updatedAt
  657. };
  658. }
  659. //计算Δ
  660. const Delta = {
  661. Delta_Xp: storedData[Guild_ID].guild_updated.new.experience - storedData[Guild_ID].guild_updated.old.experience,
  662. Delta_Level: storedData[Guild_ID].guild_updated.new.level - storedData[Guild_ID].guild_updated.old.level,
  663. Delta_Time: (newUpdatedAt - new Date(storedData[Guild_ID].guild_updated.old.updatedAt)) / 1000, // 转换为秒
  664. 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)
  665. };
  666. storedData[Guild_ID].guild_updated.Delta = Delta;
  667.  
  668. const Guild_TotalXp_div = document.querySelectorAll(".GuildPanel_value__Hm2I9")[1];
  669. if (Guild_TotalXp_div) {
  670. const xpText = isZH ? "经验值 / 小时" : "XP / Hour";
  671.  
  672. Guild_TotalXp_div.insertAdjacentHTML(
  673. "afterend",
  674. `<div>${formatPrice(Delta.Rate_XP_Hours)} ${xpText}</div>`
  675. );
  676. const Guild_NeedXp_div = document.querySelectorAll(".GuildPanel_value__Hm2I9")[2];
  677. if (Guild_NeedXp_div) {
  678. const Guild_NeedXp = document.querySelectorAll(".GuildPanel_value__Hm2I9")[2].textContent.replace(/,/g, '');
  679. const Time = TimeReset(Guild_NeedXp/Delta.Rate_XP_Hours);
  680. Guild_NeedXp_div.insertAdjacentHTML(
  681. "afterend", // 使用 "afterend" 在元素的后面插入内容
  682. `<div>${Time}</div>`
  683. );
  684. }
  685. }
  686. } else {
  687. // 如果没有旧数据,则直接添加新数据
  688. storedData[Guild_ID] = {
  689. guild_name: obj.guild.name,
  690. guild_updated: {
  691. old: {
  692. experience: obj.guild.experience,
  693. level: obj.guild.level,
  694. updatedAt: obj.guild.updatedAt
  695. },
  696. new: {},
  697. }
  698. };
  699. }
  700.  
  701. // 存储更新后的数据到 localStorage
  702. edibleTools.Guild_Data = storedData;
  703. localStorage.setItem('Edible_Tools', JSON.stringify(edibleTools));
  704. } else if (obj && obj.type === "guild_characters_updated") {
  705. const edibleTools = JSON.parse(localStorage.getItem('Edible_Tools')) || {};
  706. edibleTools.Guild_Data = edibleTools.Guild_Data || {};
  707. let storedData = edibleTools.Guild_Data || {};
  708. for (const key in obj.guildSharableCharacterMap) {
  709. if (obj.guildSharableCharacterMap.hasOwnProperty(key)) {
  710. const Guild_ID = obj.guildCharacterMap[key].guildID;
  711. const name = obj.guildSharableCharacterMap[key].name;
  712. const newUpdatedAt = new Date();
  713. storedData[Guild_ID].guild_player = storedData[Guild_ID].guild_player || {};
  714. 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) {
  715. const oldUpdatedAt = new Date(storedData[Guild_ID].guild_player[name].old.updatedAt)
  716. const timeDifference = newUpdatedAt - oldUpdatedAt
  717. if (timeDifference >= updataDealy) {
  718. // 更新老数据为新数据
  719. storedData[Guild_ID].guild_player[name].old = storedData[Guild_ID].guild_player[name].new;
  720. // 更新新数据为当前数据
  721. storedData[Guild_ID].guild_player[name].new = {
  722. id: key,
  723. gameMode: obj.guildSharableCharacterMap[key].gameMode,
  724. guildExperience: obj.guildCharacterMap[key].guildExperience,
  725. updatedAt: newUpdatedAt,
  726. };
  727. } else {
  728. // 仅更新新数据
  729. storedData[Guild_ID].guild_player[name].new = {
  730. id: key,
  731. gameMode: obj.guildSharableCharacterMap[key].gameMode,
  732. guildExperience: obj.guildCharacterMap[key].guildExperience,
  733. updatedAt: newUpdatedAt,
  734. };
  735. }
  736. //计算Δ
  737. const Delta = {
  738. Delta_Time:(newUpdatedAt - new Date(storedData[Guild_ID].guild_player[name].old.updatedAt)) / 1000,
  739. Delta_Xp: storedData[Guild_ID].guild_player[name].new.guildExperience - storedData[Guild_ID].guild_player[name].old.guildExperience,
  740. 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)
  741. };
  742. storedData[Guild_ID].guild_player[name].Delta = Delta;
  743. rateXPDayMap[name] = Delta.Rate_XP_Day;
  744. }else {
  745. storedData[Guild_ID].guild_player[name] = {
  746. old: {
  747. id: key,
  748. gameMode: obj.guildSharableCharacterMap[key].gameMode,
  749. guildExperience: obj.guildCharacterMap[key].guildExperience,
  750. updatedAt: newUpdatedAt,
  751. },
  752. new:{}
  753. };
  754. }
  755. }
  756.  
  757. }
  758. //console.log("测试数据",storedData);
  759. //console.log("guild_characters_updated", obj);
  760. updateExperienceDisplay(rateXPDayMap);
  761. edibleTools.Guild_Data = storedData;
  762. localStorage.setItem('Edible_Tools', JSON.stringify(edibleTools));
  763. }
  764.  
  765.  
  766.  
  767.  
  768. } catch (error) {
  769. console.error("Error processing message:", error);
  770. }
  771. return message;
  772. }
  773.  
  774.  
  775. function TimeReset(hours) {
  776. const totalMinutes = hours * 60;
  777. const days = Math.floor(totalMinutes / (24 * 60));
  778. const yudays = totalMinutes % (24 * 60);
  779. const hrs = Math.floor(yudays / 60);
  780. const minutes = Math.floor(yudays % 60);
  781. const dtext = isZH ? "天" : "d";
  782. const htext = isZH ? "时" : "h";
  783. const mtext = isZH ? "分" : "m";
  784. return `${days}${dtext} ${hrs}${htext} ${minutes}${mtext}`;
  785. }
  786.  
  787. function updateExperienceDisplay(rateXPDayMap) {
  788. const trElements = document.querySelectorAll(".GuildPanel_membersTable__1NwIX tbody tr");
  789. const idleuser_list = [];
  790. const dtext = isZH ? "天" : "d";
  791.  
  792. // 将 rateXPDayMap 转换为数组并排序
  793. const sortedMembers = Object.entries(rateXPDayMap)
  794. .map(([name, XPdata]) => ({ name, XPdata }))
  795. .sort((a, b) => b.XPdata - a.XPdata);
  796.  
  797. sortedMembers.forEach(({ name, XPdata }) => {
  798. trElements.forEach(tr => {
  799. const nameElement = tr.querySelector(".CharacterName_name__1amXp");
  800. const experienceElement = tr.querySelector("td:nth-child(3) > div");
  801. const activityElement = tr.querySelector('.GuildPanel_activity__9vshh');
  802.  
  803. if (nameElement && nameElement.textContent.trim() === name) {
  804. if (activityElement.childElementCount === 0) {
  805. idleuser_list.push(nameElement.textContent.trim());
  806. }
  807.  
  808. if (experienceElement) {
  809. const newDiv = document.createElement('div');
  810. newDiv.textContent = `${formatPrice(XPdata)}/${dtext}`;
  811.  
  812. // 计算颜色
  813. const rank = sortedMembers.findIndex(member => member.name === name);
  814. const hue = 120 - (rank * (120 / (sortedMembers.length - 1)));
  815. newDiv.style.color = `hsl(${hue}, 100%, 50%)`;
  816.  
  817. experienceElement.insertAdjacentElement('afterend', newDiv);
  818. }
  819. return;
  820. }
  821. });
  822. });
  823.  
  824. update_idleuser_tb(idleuser_list);
  825. }
  826.  
  827. function update_idleuser_tb(idleuser_list) {
  828. const targetElement = document.querySelector('.GuildPanel_noticeMessage__3Txji');
  829. if (!targetElement) {
  830. console.error('公会标语元素未找到!');
  831. return;
  832. }
  833. const clonedElement = targetElement.cloneNode(true);
  834.  
  835. const namesText = idleuser_list.join(', ');
  836. clonedElement.innerHTML = '';
  837. clonedElement.textContent = isZH ? `闲置的成员:${namesText}` : `Idle User : ${namesText}`;
  838. clonedElement.style.color = '#ffcc00';
  839.  
  840. // 设置复制元素的高度为原元素的25%
  841. const originalStyle = window.getComputedStyle(targetElement);
  842. const originalHeight = originalStyle.height;
  843. const originalMinHeight = originalStyle.minHeight;
  844. clonedElement.style.height = `25%`;
  845. clonedElement.style.minHeight = `25%`; // 也设置最小高度
  846. targetElement.parentElement.appendChild(clonedElement);
  847. }
  848.  
  849.  
  850. hookWS();
  851.  
  852.  
  853. //箱子数据获取
  854. function processAndPrintData(obj) {
  855. let formattedShopData = {};
  856.  
  857. // 处理商店数据
  858. for (let [key, details] of Object.entries(obj.shopItemDetailMap)) {
  859. const { itemHrid, costs } = details;
  860. const itemName = formatItemName(itemHrid.split('/').pop());
  861.  
  862. costs.forEach(cost => {
  863. const costItemName = formatItemName(cost.itemHrid.split('/').pop());
  864.  
  865. if (costItemName === "Coin") return;
  866.  
  867. const costCount = cost.count;
  868.  
  869. if (!formattedShopData[costItemName]) {
  870. formattedShopData[costItemName] = { items: {}, 最挣钱: '', BID单价: 0 };
  871. }
  872.  
  873. // 计算每种代币购买每个物品的收益
  874. let bidValue = marketData?.market?.[itemName]?.bid || 0;
  875. let profit = bidValue / costCount;
  876.  
  877. formattedShopData[costItemName].items[itemName] = {
  878. 花费: costCount
  879. };
  880.  
  881. // 更新最赚钱的物品信息
  882. if (profit > formattedShopData[costItemName].BID单价) {
  883. formattedShopData[costItemName].最挣钱 = itemName;
  884. formattedShopData[costItemName].BID单价 = profit;
  885. specialItemPrices[costItemName].ask = profit;
  886. specialItemPrices[costItemName].bid = profit;
  887. }
  888. });
  889. }
  890. const mostProfitableItems = Object.values(formattedShopData).map(item => item.最挣钱).filter(Boolean);
  891. console.log(mostProfitableItems)
  892. // 处理箱子掉落物数据
  893.  
  894. for (let iteration = 0; iteration < 4; iteration++) {
  895. for (let [key, items] of Object.entries(obj.openableLootDropMap)) {
  896. const boxName = formatItemName(key.split('/').pop());
  897.  
  898. if (!formattedChestDropData[boxName]) {
  899. formattedChestDropData[boxName] = { item: {} };
  900. }
  901. let TotalAsk = 0;
  902. let TotalBid = 0;
  903. let awa = 0;
  904. items.forEach(item => {
  905. const { itemHrid, dropRate, minCount, maxCount } = item;
  906. const itemName = formatItemName(itemHrid.split('/').pop());
  907. const expectedYield = ((minCount + maxCount) / 2) * dropRate;
  908. let bidPrice = -1;
  909. let askPrice = -1;
  910. let priceColor = '';
  911.  
  912. if (specialItemPrices[itemName] && specialItemPrices[itemName].ask) {
  913. askPrice = parseFloat(specialItemPrices[itemName].ask);
  914. bidPrice = parseFloat(specialItemPrices[itemName].bid);
  915. priceColor = '';
  916. } else if (marketData?.market?.[itemName]) {
  917. bidPrice = marketData.market[itemName].bid;
  918. askPrice = marketData.market[itemName].ask;
  919. } else {
  920. console.log(`${itemName} 的价格未找到`);
  921. }
  922.  
  923. if (formattedChestDropData[boxName].item[itemName] && iteration === 0) {
  924. // 如果物品已存在,更新期望掉落和相关价格
  925. const existingItem = formattedChestDropData[boxName].item[itemName];
  926. existingItem.期望掉落 += expectedYield;
  927. } else if (iteration === 0) {
  928. formattedChestDropData[boxName].item[itemName] = {
  929. 期望掉落: expectedYield,
  930. };
  931. }
  932.  
  933. // 判断 itemName 是否在最挣钱物品列表中,并执行相应代码
  934. if (mostProfitableItems.includes(itemName)) {
  935. priceColor = '#FFb3E6';
  936. } else if (askPrice === -1 && bidPrice === -1) {
  937. priceColor = 'yellow';
  938. } else if (askPrice === -1) {
  939. askPrice = bidPrice;
  940. priceColor = '#D95961';
  941. } else if (bidPrice === -1) {
  942. priceColor = '#2FC4A7';
  943. }
  944.  
  945. const existingItem = formattedChestDropData[boxName].item[itemName];
  946. existingItem.出售单价 = askPrice;
  947. existingItem.收购单价 = bidPrice;
  948. existingItem.出售总价 = (existingItem.出售单价 * existingItem.期望掉落).toFixed(2);
  949. existingItem.收购总价 = (existingItem.收购单价 * existingItem.期望掉落).toFixed(2);
  950. existingItem.Color = priceColor;
  951.  
  952. // 累计总价
  953. TotalAsk += (askPrice * expectedYield);
  954. TotalBid += (bidPrice * expectedYield);
  955. });
  956.  
  957. formattedChestDropData[boxName] = {
  958. ...formattedChestDropData[boxName],
  959. 期望产出Ask: TotalAsk.toFixed(2),
  960. 期望产出Bid: TotalBid.toFixed(2),
  961. };
  962.  
  963. if (!specialItemPrices[boxName]) {
  964. specialItemPrices[boxName] = {}
  965. }
  966.  
  967. specialItemPrices[boxName].ask = formattedChestDropData[boxName].期望产出Ask;
  968. specialItemPrices[boxName].bid = formattedChestDropData[boxName].期望产出Bid;
  969. }
  970. }
  971.  
  972. for (let itemName in specialItemPrices) {
  973. if (specialItemPrices.hasOwnProperty(itemName)) {
  974. marketData.market[itemName] = {
  975. ask: specialItemPrices[itemName].ask,
  976. bid: specialItemPrices[itemName].bid
  977. };
  978. }
  979. }
  980.  
  981. localStorage.setItem('MWITools_marketAPI_json', JSON.stringify(marketData));
  982. let edibleTools = JSON.parse(localStorage.getItem('Edible_Tools')) || {};
  983. edibleTools.Chest_Drop_Data = formattedChestDropData;
  984. localStorage.setItem('Edible_Tools', JSON.stringify(edibleTools));
  985. // 打印结果
  986. //console.log("特殊物品价格表:",specialItemPrices)
  987. //console.log("箱子掉落物列表:", formattedChestDropData);
  988. //console.log("地牢商店列表:", formattedShopData);
  989. }
  990.  
  991. function ShowChestPrice() {
  992. const modalContainer = document.querySelector(".Modal_modalContainer__3B80m");
  993. if (!modalContainer) return; // 如果不存在 Modal_modalContainer__3B80m 元素,则直接返回
  994.  
  995. const chestNameElem = document.querySelector(chestNameSelector);
  996. if (!chestNameElem) return;
  997.  
  998. const chestName = getItemNameFromElement(chestNameElem);
  999. const items = document.querySelectorAll(itemSelector);
  1000.  
  1001. const dropListContainer = document.querySelector('.ItemDictionary_openToLoot__1krnv');
  1002. if (!dropListContainer) return; // 检查 dropListContainer 是否存在
  1003.  
  1004. const edibleTools = JSON.parse(localStorage.getItem('Edible_Tools'))
  1005. const formattedChestDropData = edibleTools.Chest_Drop_Data;
  1006.  
  1007. items.forEach(item => {
  1008. const itemName = getItemNameFromElement(item.querySelector(iconSelector));
  1009. if (!itemName) return; // 检查 itemName 是否存在
  1010.  
  1011. const itemData = formattedChestDropData[chestName].item[itemName];
  1012. if (!itemData) return; // 检查 itemData 是否存在
  1013.  
  1014. const itemColor = itemData.Color;
  1015. const itemNameElem = item.querySelector('.Item_name__2C42x');
  1016. if (itemNameElem && itemColor) {
  1017. itemNameElem.style.color = itemColor;
  1018. }
  1019. });
  1020.  
  1021. const askPrice = formattedChestDropData[chestName]["期望产出Ask"];
  1022. const bidPrice = formattedChestDropData[chestName]["期望产出Bid"];
  1023. if (askPrice && bidPrice) {
  1024.  
  1025. const previousResults = dropListContainer.querySelectorAll('.resultDiv');
  1026. previousResults.forEach(result => result.remove());
  1027.  
  1028. const createPriceOutput = (label, price) => {
  1029. const priceOutput = document.createElement('div');
  1030. priceOutput.className = 'resultDiv';
  1031. priceOutput.textContent = `${label}: ${formatPrice(price)}`;
  1032. priceOutput.style.color = 'gold';
  1033. priceOutput.style.fontSize = '14px';
  1034. priceOutput.style.fontWeight = '400';
  1035. priceOutput.style.paddingTop = '10px';
  1036. return priceOutput;
  1037. };
  1038.  
  1039. const minPriceOutput = createPriceOutput('期望产出 (最低买入价计算)', askPrice);
  1040. const maxPriceOutput = createPriceOutput('期望产出 (最高收购价计算)', bidPrice);
  1041.  
  1042. dropListContainer.appendChild(minPriceOutput);
  1043. dropListContainer.appendChild(maxPriceOutput);
  1044.  
  1045. }
  1046. }
  1047.  
  1048. function processCombatConsumables(obj) {
  1049. battlePlayerFood = {};
  1050. battlePlayerLoot = {};
  1051. obj.players.forEach(player => {
  1052. battlePlayerFood[player.character.name] = {};
  1053. battlePlayerLoot[player.character.name] = {};
  1054. let minTimeInDays = Infinity;
  1055. let minItemName = "";
  1056.  
  1057. player.combatConsumables.forEach(consumable => {
  1058. const itemname = formatItemName(consumable.itemHrid.split('/').pop());
  1059. const timePerUnit = itemname.includes('Coffee') ? 5 : 1;
  1060. const totalTimeInMinutes = consumable.count * timePerUnit;
  1061. const totalTimeInDays = totalTimeInMinutes / (24 * 60);
  1062.  
  1063. if (totalTimeInDays < minTimeInDays) {
  1064. minTimeInDays = totalTimeInDays;
  1065. minItemName = itemname;
  1066. }
  1067.  
  1068. battlePlayerFood[player.character.name][itemname] = {
  1069. "数量": consumable.count,
  1070. "剩余时间": totalTimeInDays.toFixed(1) + '天',
  1071. "颜色": "Black"
  1072. };
  1073. });
  1074. Object.keys(battlePlayerFood[player.character.name]).forEach(item => {
  1075. if (item === minItemName) {
  1076. battlePlayerFood[player.character.name][item]["颜色"] = "red";
  1077. }
  1078. });
  1079. battlePlayerFood[player.character.name]['剩余时间'] = {
  1080. "数量": minTimeInDays < 1 ? (minTimeInDays * 24).toFixed(1) + '小时' : minTimeInDays.toFixed(1) + '天',
  1081. "颜色": "Black"
  1082. };
  1083.  
  1084. Object.values(player.totalLootMap).forEach(Loot => {
  1085. const itemname = formatItemName(Loot.itemHrid.split('/').pop());
  1086. battlePlayerLoot[player.character.name][itemname] = {
  1087. "数量":Loot.count
  1088. }
  1089. });
  1090.  
  1091. });
  1092. }
  1093.  
  1094.  
  1095. function addBattlePlayerFoodButton() {
  1096.  
  1097. 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");
  1098. var referenceTab = tabsContainer ? tabsContainer.children[1] : null;
  1099.  
  1100. if (!tabsContainer || !referenceTab) {
  1101. return;
  1102. }
  1103.  
  1104. if (tabsContainer.querySelector('.Button_battlePlayerFood__custom')) {
  1105. console.log('按钮已存在');
  1106. return;
  1107. }
  1108.  
  1109. var battlePlayerFoodButton = document.createElement('div');
  1110. battlePlayerFoodButton.className = referenceTab.className + ' Button_battlePlayerFood__custom';
  1111. battlePlayerFoodButton.setAttribute('script_translatedfrom', 'New Action');
  1112. battlePlayerFoodButton.textContent = '出警';
  1113.  
  1114. battlePlayerFoodButton.addEventListener('click', function() {
  1115. let dataHtml = '<div style="display: flex; flex-wrap: nowrap;">';
  1116. for (let player in battlePlayerFood) {
  1117. dataHtml +=
  1118. `<div style="flex: 1 0 auto; min-width: 100px; margin: 10px; padding: 10px; border: 1px solid black;">
  1119. <h3>${player}</h3>`;
  1120. let consumables = battlePlayerFood[player];
  1121. for (let item in consumables) {
  1122. dataHtml += `<p style="color:${consumables[item].颜色};">${item}: ${consumables[item].数量}</p>`;
  1123. }
  1124. dataHtml += '</div>';
  1125. }
  1126. dataHtml += '</div>';
  1127.  
  1128. let popup = document.createElement('div');
  1129. popup.style.position = 'fixed';
  1130. popup.style.top = '50%';
  1131. popup.style.left = '50%';
  1132. popup.style.transform = 'translate(-50%, -50%)';
  1133. popup.style.backgroundColor = 'white';
  1134. popup.style.border = '1px solid black';
  1135. popup.style.padding = '10px';
  1136. popup.style.zIndex = '10000';
  1137. popup.style.maxWidth = '75%';
  1138. popup.style.overflowX = 'auto';
  1139. popup.style.whiteSpace = 'nowrap';
  1140. popup.innerHTML = dataHtml;
  1141.  
  1142. let closeButton = document.createElement('button');
  1143. closeButton.textContent = '关闭';
  1144. closeButton.style.display = 'block';
  1145. closeButton.style.margin = '20px auto 0 auto';
  1146. closeButton.onclick = function() {
  1147. document.body.removeChild(popup);
  1148. };
  1149. popup.appendChild(closeButton);
  1150.  
  1151. document.body.appendChild(popup);
  1152. });
  1153.  
  1154. var lastTab = tabsContainer.children[tabsContainer.children.length - 1];
  1155. tabsContainer.insertBefore(battlePlayerFoodButton, lastTab.nextSibling);
  1156.  
  1157. var style = document.createElement('style');
  1158. style.innerHTML = `
  1159. .Button_battlePlayerFood__custom {
  1160. background-color: #546ddb;
  1161. color: white;
  1162. }
  1163. `;
  1164. document.head.appendChild(style);
  1165. }
  1166.  
  1167.  
  1168.  
  1169.  
  1170. //菜单
  1171. GM_registerMenuCommand('打印所有箱子掉落物', function() {
  1172. console.log('箱子掉落物列表:', formattedChestDropData);
  1173. });
  1174.  
  1175. function createWindowBase() {
  1176. let windowDiv = document.createElement('div');
  1177. windowDiv.className = 'visualization-window';
  1178. windowDiv.style.position = 'fixed';
  1179. windowDiv.style.top = '50%';
  1180. windowDiv.style.left = '50%';
  1181. windowDiv.style.transform = 'translate(-50%, -50%)';
  1182. windowDiv.style.height = '60%';
  1183. windowDiv.style.backgroundColor = 'white';
  1184. windowDiv.style.border = '1px solid #000';
  1185. windowDiv.style.zIndex = '10000';
  1186. windowDiv.style.padding = '10px';
  1187. windowDiv.style.boxSizing = 'border-box';
  1188. windowDiv.style.display = 'flex';
  1189. windowDiv.style.flexDirection = 'column';
  1190. return windowDiv;
  1191. }
  1192.  
  1193. function createVisualizationWindow(chestData) {
  1194. let oldWindow = document.querySelector('.visualization-window');
  1195. if (oldWindow) {
  1196. oldWindow.remove();
  1197. }
  1198. let windowDiv = createWindowBase();
  1199.  
  1200. let title = document.createElement('h1');
  1201. title.innerText = '开箱记录';
  1202. title.style.textAlign = 'center';
  1203. windowDiv.appendChild(title);
  1204.  
  1205. let contentDiv = document.createElement('div');
  1206. contentDiv.style.flex = '1';
  1207. contentDiv.style.overflowY = 'auto';
  1208.  
  1209. // 添加箱子列表
  1210. for (let chestName in chestData) {
  1211. let chest = chestData[chestName];
  1212.  
  1213. let chestButton = document.createElement('button');
  1214. chestButton.style.display = 'block';
  1215. chestButton.style.width = '100%';
  1216. chestButton.style.textAlign = 'left';
  1217. chestButton.style.marginBottom = '5px';
  1218. chestButton.innerText = `${chestName}: ${chest['总计开箱数量']}`;
  1219. chestButton.onclick = () => showChestDetails(chestName, chest);
  1220.  
  1221. contentDiv.appendChild(chestButton);
  1222. }
  1223.  
  1224. windowDiv.appendChild(contentDiv);
  1225.  
  1226. let footerDiv = document.createElement('div');
  1227. footerDiv.style.display = 'flex';
  1228. footerDiv.style.justifyContent = 'space-between';
  1229. footerDiv.style.marginTop = '10px';
  1230.  
  1231. // 关闭按钮
  1232. let closeButton = document.createElement('button');
  1233. closeButton.innerText = '关闭';
  1234. closeButton.onclick = () => document.body.removeChild(windowDiv);
  1235. footerDiv.appendChild(closeButton);
  1236.  
  1237. // 删除数据按钮
  1238. let deleteButton = document.createElement('button');
  1239. deleteButton.innerText = '删除数据';
  1240. deleteButton.onclick = () => {
  1241. if (confirm('确定要删除所有开箱数据吗?')) {
  1242. deleteOpenChestData(chestData);
  1243. document.body.removeChild(windowDiv);
  1244. }
  1245. };
  1246. footerDiv.appendChild(deleteButton);
  1247.  
  1248. windowDiv.appendChild(footerDiv);
  1249.  
  1250. document.body.appendChild(windowDiv);
  1251. }
  1252.  
  1253. function deleteOpenChestData() {
  1254. let edibleToolsData = JSON.parse(localStorage.getItem('Edible_Tools'));
  1255. if (edibleToolsData && edibleToolsData.Chest_Open_Data) {
  1256. edibleToolsData.Chest_Open_Data = {};
  1257. localStorage.setItem('Edible_Tools', JSON.stringify(edibleToolsData));
  1258. }
  1259. }
  1260.  
  1261.  
  1262. // 显示箱子详细信息
  1263. function showChestDetails(chestName, chestData) {
  1264. let oldWindow = document.querySelector('.visualization-window');
  1265. if (oldWindow) {
  1266. oldWindow.remove();
  1267. }
  1268.  
  1269. let detailsWindow = createWindowBase();
  1270. let title = document.createElement('h1');
  1271. title.innerText = chestName;
  1272. detailsWindow.appendChild(title);
  1273.  
  1274. let contentDiv = document.createElement('div');
  1275. contentDiv.style.flex = '1';
  1276. contentDiv.style.overflowY = 'auto';
  1277.  
  1278. let totalStats = document.createElement('p');
  1279. totalStats.innerText = `总计开箱数量: ${chestData['总计开箱数量']}\n总计Ask: ${formatPrice(chestData['总计开箱Ask'])}\n总计Bid: ${formatPrice(chestData['总计开箱Bid'])}`;
  1280. contentDiv.appendChild(totalStats);
  1281.  
  1282. // 添加物品信息
  1283. for (let itemName in chestData['获得物品']) {
  1284. let item = chestData['获得物品'][itemName];
  1285. let itemElement = document.createElement('p');
  1286. itemElement.innerText = `${itemName}: ${formatPrice(item['数量'])}`;
  1287. contentDiv.appendChild(itemElement);
  1288. }
  1289.  
  1290. detailsWindow.appendChild(contentDiv);
  1291.  
  1292. let footerDiv = document.createElement('div');
  1293. footerDiv.style.display = 'flex';
  1294. footerDiv.style.justifyContent = 'space-between';
  1295. footerDiv.style.marginTop = '10px';
  1296.  
  1297. let backButton = document.createElement('button');
  1298. backButton.innerText = '返回';
  1299. backButton.onclick = () => {
  1300. detailsWindow.remove();
  1301. createVisualizationWindow(JSON.parse(localStorage.getItem('Edible_Tools')).Chest_Open_Data);
  1302. };
  1303. footerDiv.appendChild(backButton);
  1304.  
  1305. let closeButton = document.createElement('button');
  1306. closeButton.innerText = '关闭';
  1307. closeButton.onclick = () => detailsWindow.remove();
  1308. footerDiv.appendChild(closeButton);
  1309.  
  1310. detailsWindow.appendChild(footerDiv);
  1311.  
  1312. document.body.appendChild(detailsWindow);
  1313. }
  1314.  
  1315. GM_registerMenuCommand('打印全部开箱记录', function() {
  1316. const edibleTools = JSON.parse(localStorage.getItem('Edible_Tools')) || {};
  1317. const openChestData = edibleTools.Chest_Open_Data || {};
  1318. createVisualizationWindow(openChestData);
  1319. });
  1320.  
  1321. GM_registerMenuCommand('打印掉落物列表', function() {
  1322. let dataHtml = '<div style="display: flex; flex-wrap: nowrap;">';
  1323. const minPrice = 10000;
  1324. for (let player in battlePlayerLoot) {
  1325. let totalPrice = 0;
  1326. dataHtml += `<div style="flex: 1 0 auto; min-width: 100px; margin: 10px; padding: 10px; border: 1px solid black;">`;
  1327. dataHtml += `<h3>${player}</h3>`;
  1328.  
  1329. let lootItems = battlePlayerLoot[player];
  1330. for (let item in lootItems) {
  1331. let bidPrice = marketData.market[item].bid;
  1332. totalPrice += bidPrice*lootItems[item].数量
  1333. if (bidPrice > minPrice) {
  1334. dataHtml += `<p>${item}: ${lootItems[item].数量}</p>`;
  1335. }
  1336. }
  1337. if (totalPrice > 0) {
  1338. dataHtml += `<p>总计价格: ${formatPrice(totalPrice)}</p>`;
  1339. }
  1340. dataHtml += '</div>';
  1341. }
  1342. dataHtml += '</div>';
  1343.  
  1344. let popup = document.createElement('div');
  1345. popup.style.position = 'fixed';
  1346. popup.style.top = '50%';
  1347. popup.style.left = '50%';
  1348. popup.style.transform = 'translate(-50%, -50%)';
  1349. popup.style.backgroundColor = 'white';
  1350. popup.style.border = '1px solid black';
  1351. popup.style.padding = '10px';
  1352. popup.style.zIndex = '10000';
  1353. popup.style.maxWidth = '75%';
  1354. popup.style.overflowX = 'auto';
  1355. popup.style.whiteSpace = 'nowrap';
  1356. popup.innerHTML = dataHtml;
  1357.  
  1358. let closeButton = document.createElement('button');
  1359. closeButton.textContent = '关闭';
  1360. closeButton.style.display = 'block';
  1361. closeButton.style.margin = '20px auto 0 auto';
  1362. closeButton.onclick = function() {
  1363. document.body.removeChild(popup);
  1364. };
  1365. popup.appendChild(closeButton);
  1366.  
  1367. document.body.appendChild(popup);
  1368. });
  1369. })();