Greasy Fork 支持简体中文。

[银河奶牛]食用工具

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

目前為 2025-03-02 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name [银河奶牛]食用工具
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.448
  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. // @grant GM_openInTab
  14. // @grant GM_setValue
  15. // @grant GM_getValue
  16. // ==/UserScript==
  17.  
  18. (async function() {
  19. 'use strict';
  20.  
  21. const itemSelector = '.ItemDictionary_drop__24I5f';
  22. const iconSelector = '.Icon_icon__2LtL_ use';
  23. 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';
  24. const MARKET_API_URL = "https://raw.githubusercontent.com/holychikenz/MWIApi/main/medianmarket.json";
  25. let timer = null;
  26. let formattedChestDropData = {};
  27. let battlePlayerFood = {};
  28. let battlePlayerLoot = {};
  29. let battleDuration;
  30. let item_icon_url
  31.  
  32. let marketData = JSON.parse(localStorage.getItem('MWITools_marketAPI_json'));
  33. const init_Client_Data = localStorage.getItem('initClientData');
  34. if (!init_Client_Data) return;
  35. let init_Client_Data_;
  36. try {
  37. init_Client_Data_ = JSON.parse(init_Client_Data);
  38. } catch (error) {
  39. console.error('数据解析失败:', error);
  40. return;
  41. }
  42. if (init_Client_Data_.type !== 'init_client_data') return;
  43. const item_hrid_to_name = init_Client_Data_.itemDetailMap;
  44. for (const key in item_hrid_to_name) {
  45. if (item_hrid_to_name[key] && typeof item_hrid_to_name[key] === 'object' && item_hrid_to_name[key].name) {
  46. item_hrid_to_name[key] = item_hrid_to_name[key].name;
  47. }
  48. }
  49. const item_name_to_hrid = Object.fromEntries(
  50. Object.entries(item_hrid_to_name).map(([key, value]) => [value, key])
  51. );
  52.  
  53. async function fetchMarketData() {
  54. return new Promise((resolve, reject) => {
  55. GM.xmlHttpRequest({
  56. method: 'GET',
  57. url: MARKET_API_URL,
  58. responseType: 'json',
  59. timeout: 5000,
  60. onload: function(response) {
  61. if (response.status === 200) {
  62. const data = JSON.parse(response.responseText);
  63. console.log('从API获取到的数据:', data);
  64. resolve(data);
  65. } else {
  66. console.error('获取数据失败。状态码:', response.status);
  67. reject(new Error('数据获取失败'));
  68. }
  69. },
  70. ontimeout: function() {
  71. console.error('请求超时:超过5秒未能获取到数据');
  72. reject(new Error('请求超时'));
  73. },
  74. onerror: function(error) {
  75. console.error('获取数据时发生错误:', error);
  76. reject(error);
  77. }
  78. });
  79. });
  80. }
  81.  
  82. hookWS();
  83. initObserver();
  84.  
  85. try {
  86. // 尝试从 API 获取数据
  87. marketData = await fetchMarketData();
  88. } catch (error) {
  89. console.error('从 API 获取数据失败,尝试从本地存储获取数据。', error);
  90.  
  91. // 从本地存储获取数据
  92. const marketDataStr = localStorage.getItem('MWITools_marketAPI_json');
  93. marketData = JSON.parse(marketDataStr);
  94.  
  95. if (!marketData) {
  96. alert('无法获取 market 数据');
  97. } else {
  98. console.log('从本地存储获取到的数据:', marketData);
  99. }
  100. }
  101.  
  102. function getSpecialItemPrice(itemName, priceType) {
  103. if (marketData?.market?.[itemName]) {
  104. const itemPrice = marketData.market[itemName][priceType];
  105. if (itemPrice !== undefined && itemPrice !== -1) {
  106. return itemPrice;
  107. }
  108. }
  109. console.error(`未找到物品 ${itemName} ${priceType} 价格信息`);
  110. return null;
  111. }
  112.  
  113. let specialItemPrices = {
  114. 'Coin': { ask: 1, bid: 1 }, // 默认的特殊物品价值,包括 ask 和 bid 价值
  115. 'Cowbell': {
  116. ask: getSpecialItemPrice('Bag Of 10 Cowbells', 'ask') / 10 || 50000,
  117. bid: getSpecialItemPrice('Bag Of 10 Cowbells', 'bid') / 10 || 50000
  118. },
  119. 'Chimerical Token': {
  120. ask: getSpecialItemPrice('Chimerical Essence', 'ask') || 3000,
  121. bid: getSpecialItemPrice('Chimerical Essence', 'bid') || 2850
  122. },
  123. 'Sinister Token': {
  124. ask: getSpecialItemPrice('Sinister Essence', 'ask') || 3000,
  125. bid: getSpecialItemPrice('Sinister Essence', 'bid') || 2900
  126. },
  127. 'Enchanted Token': {
  128. ask: getSpecialItemPrice('Enchanted Essence', 'ask') || 4000,
  129. bid: getSpecialItemPrice('Enchanted Essence', 'bid') || 4000
  130. },
  131. };
  132.  
  133. function getItemNameFromElement(element) {
  134. const itemNameRaw = element.getAttribute('href').split('#').pop();
  135. return formatItemName(itemNameRaw);
  136. }
  137.  
  138. function formatItemName(itemNameRaw) {
  139. return item_hrid_to_name[`/items/${itemNameRaw}`]
  140. }
  141.  
  142.  
  143. function formatPrice(value) {
  144. const isNegative = value < 0;
  145. value = Math.abs(value);
  146.  
  147. if (value >= 1e13) {
  148. return (isNegative ? '-' : '') + (value / 1e12).toFixed(1) + 'T';
  149. } else if (value >= 1e10) {
  150. return (isNegative ? '-' : '') + (value / 1e9).toFixed(1) + 'B';
  151. } else if (value >= 1e7) {
  152. return (isNegative ? '-' : '') + (value / 1e6).toFixed(1) + 'M';
  153. } else if (value >= 1e4) {
  154. return (isNegative ? '-' : '') + (value / 1e3).toFixed(1) + 'K';
  155. } else {
  156. return (isNegative ? '-' : '') + value.toFixed(0);
  157. }
  158. }
  159.  
  160.  
  161. function parseQuantityString(quantityStr) {
  162. const suffix = quantityStr.slice(-1);
  163. const base = parseFloat(quantityStr.slice(0, -1));
  164. if (suffix === 'K') {
  165. return base * 1000;
  166. } else if (suffix === 'M') {
  167. return base * 1000000;
  168. } else if (suffix === 'B') {
  169. return base * 1000000000;
  170. } else {
  171. return parseFloat(quantityStr);
  172. }
  173. }
  174.  
  175. function recordChestOpening(modalElement) {
  176. if (document.querySelector('.ChestStatistics')) {
  177. return;
  178. }
  179.  
  180. // 从本地存储读取数据
  181. let edibleTools = JSON.parse(localStorage.getItem('Edible_Tools')) || {};
  182. edibleTools.Chest_Open_Data = edibleTools.Chest_Open_Data || {};
  183.  
  184. let chestOpenData = edibleTools.Chest_Open_Data;
  185. const chestDropData = edibleTools.Chest_Drop_Data;
  186.  
  187. const chestNameElement = modalElement.querySelector("div.Modal_modal__1Jiep > div.Inventory_modalContent__3ObSx > div.Item_itemContainer__x7kH1 > div > div > div.Item_iconContainer__5z7j4 > svg > use");
  188. const chestCountElement = modalElement.querySelector("div.Modal_modal__1Jiep > div.Inventory_modalContent__3ObSx > div.Item_itemContainer__x7kH1 > div > div > div.Item_count__1HVvv");
  189.  
  190. if (chestNameElement && chestCountElement) {
  191. const chestName = getItemNameFromElement(chestNameElement);
  192. chestOpenData[chestName] = chestOpenData[chestName] || {};
  193. let chestData = chestOpenData[chestName];
  194. const chestCount = parseQuantityString(chestCountElement.textContent.trim());
  195. chestData["总计开箱数量"] = (chestData["总计开箱数量"] || 0) + chestCount;
  196. chestData["获得物品"] = chestData["获得物品"] || {};
  197. const itemsContainer = modalElement.querySelector('.Inventory_gainedItems___e9t9');
  198. const itemElements = itemsContainer.querySelectorAll('.Item_itemContainer__x7kH1');
  199.  
  200. let totalAskValue = 0;
  201. let totalBidValue = 0;
  202.  
  203. itemElements.forEach(itemElement => {
  204. const itemNameElement = itemElement.querySelector('.Item_iconContainer__5z7j4 use');
  205. const itemQuantityElement = itemElement.querySelector('.Item_count__1HVvv');
  206.  
  207. if (itemNameElement && itemQuantityElement) {
  208. const itemName = getItemNameFromElement(itemNameElement);
  209. const itemQuantity = parseQuantityString(itemQuantityElement.textContent.trim());
  210.  
  211. const itemData = chestDropData[chestName].item[itemName] || {};
  212. const itemAskValue = itemData["出售单价"] || 0;
  213. const itemBidValue = itemData["收购单价"] || 0;
  214. const color = itemData.Color || '';
  215.  
  216. itemQuantityElement.style.color = color;
  217.  
  218. const itemOpenTotalAskValue = itemAskValue * itemQuantity;
  219. const itemOpenTotalBidValue = itemBidValue * itemQuantity;
  220.  
  221. chestData["获得物品"][itemName] = chestData["获得物品"][itemName] || {};
  222. chestData["获得物品"][itemName]["数量"] = (chestData["获得物品"][itemName]["数量"] || 0) + itemQuantity;
  223. chestData["获得物品"][itemName]["总计Ask价值"] = (chestData["获得物品"][itemName]["总计Ask价值"] || 0) + itemOpenTotalAskValue;
  224. chestData["获得物品"][itemName]["总计Bid价值"] = (chestData["获得物品"][itemName]["总计Bid价值"] || 0) + itemOpenTotalBidValue;
  225.  
  226. totalAskValue += itemOpenTotalAskValue;
  227. totalBidValue += itemOpenTotalBidValue;
  228. }
  229. });
  230.  
  231. chestData["总计开箱Ask"] = (chestData["总计开箱Ask"] || 0) + totalAskValue;
  232. chestData["总计开箱Bid"] = (chestData["总计开箱Bid"] || 0) + totalBidValue;
  233.  
  234. // 计算本次开箱的偏差值
  235. const differenceValue = totalBidValue - chestDropData[chestName]["期望产出Bid"] * chestCount;
  236.  
  237. // 更新累计偏差值
  238. chestData["累计偏差值"] = (chestData["累计偏差值"] || 0) + differenceValue;
  239.  
  240. // 地牢开箱
  241. let profitRange = null;
  242. let profitColor = 'lime'; // 默认颜色
  243. const chestCosts = {
  244. "Chimerical Chest": {
  245. keyAsk: getSpecialItemPrice('Chimerical Chest Key', 'ask') || 2700e3,
  246. keyBid: getSpecialItemPrice('Chimerical Chest Key', 'bid') || 2600e3,
  247. entryAsk: getSpecialItemPrice('Chimerical Entry Key', 'ask') || 350e3,
  248. entryBid: getSpecialItemPrice('Chimerical Entry Key', 'bid') || 320e3
  249. },
  250. "Sinister Chest": {
  251. keyAsk: getSpecialItemPrice('Sinister Chest Key', 'ask') || 3800e3,
  252. keyBid: getSpecialItemPrice('Sinister Chest Key', 'bid') || 3500e3,
  253. entryAsk: getSpecialItemPrice('Sinister Entry Key', 'ask') || 450e3,
  254. entryBid: getSpecialItemPrice('Sinister Entry Key', 'bid') || 400e3
  255. },
  256. "Enchanted Chest": {
  257. keyAsk: getSpecialItemPrice('Enchanted Chest Key', 'ask') || 5600e3,
  258. keyBid: getSpecialItemPrice('Enchanted Chest Key', 'bid') || 5400e3,
  259. entryAsk: getSpecialItemPrice('Enchanted Entry Key', 'ask') || 420e3,
  260. entryBid: getSpecialItemPrice('Enchanted Entry Key', 'bid') || 400e3
  261. }
  262. };
  263.  
  264. if (chestCosts[chestName]) {
  265. const { keyAsk, keyBid, entryAsk, entryBid } = chestCosts[chestName];
  266. const minProfit = totalBidValue - (keyAsk + entryAsk) * chestCount;
  267. const maxProfit = totalAskValue - (keyBid + entryBid) * chestCount;
  268. profitRange = `${formatPrice(minProfit)}~${formatPrice(maxProfit)}`;
  269.  
  270. if (minProfit > 0 && maxProfit > 0) {
  271. profitColor = 'lime';
  272. } else if (minProfit < 0 && maxProfit < 0) {
  273. profitColor = 'red';
  274. } else {
  275. profitColor = 'orange';
  276. }
  277. }
  278.  
  279. // 显示
  280. const openChestElement = document.querySelector('.Inventory_modalContent__3ObSx');
  281.  
  282. const displayElement = document.createElement('div');
  283. displayElement.classList.add('ChestStatistics');
  284. displayElement.style.position = 'absolute';
  285. displayElement.style.left = `${openChestElement.offsetLeft}px`;
  286. displayElement.style.top = `${openChestElement.offsetTop}px`;
  287. displayElement.style.fontSize = '12px';
  288. displayElement.innerHTML = `
  289. 总计开箱次数:<br>
  290. ${chestData["总计开箱数量"]}<br>
  291. 本次开箱价值:<br>
  292. ${formatPrice(totalAskValue)}/${formatPrice(totalBidValue)}<br>
  293. 总计开箱价值:<br>
  294. ${formatPrice(chestData["总计开箱Ask"])}/${formatPrice(chestData["总计开箱Bid"])}<br>
  295. `;
  296.  
  297. const expectedOutputElement = document.createElement('div');
  298. expectedOutputElement.classList.add('ExpectedOutput');
  299. expectedOutputElement.style.position = 'absolute';
  300. expectedOutputElement.style.left = `${openChestElement.offsetLeft}px`;
  301. expectedOutputElement.style.bottom = `${openChestElement.offsetTop}px`;
  302. expectedOutputElement.style.fontSize = '12px';
  303. expectedOutputElement.innerHTML = `
  304. 预计产出价值:<br>
  305. ${formatPrice(chestDropData[chestName]["期望产出Ask"]*chestCount)}/${formatPrice(chestDropData[chestName]["期望产出Bid"]*chestCount)}<br>
  306. `;
  307.  
  308. const differenceOutputElement = document.createElement('div');
  309. differenceOutputElement.classList.add('DifferenceOutput');
  310. differenceOutputElement.style.position = 'absolute';
  311. differenceOutputElement.style.right = `${openChestElement.offsetLeft}px`;
  312. differenceOutputElement.style.bottom = `${openChestElement.offsetTop}px`;
  313. differenceOutputElement.style.fontSize = '12px';
  314. differenceOutputElement.style.color = differenceValue > 0 ? 'lime' : 'red';
  315. differenceOutputElement.innerHTML = `
  316. ${differenceValue > 0 ? '高于期望价值:' : '低于期望价值:'}<br>
  317. ${formatPrice(Math.abs(differenceValue))}<br>
  318. `;
  319.  
  320. // 创建并显示累计偏差值的元素
  321. const cumulativeDifferenceElement = document.createElement('div');
  322. cumulativeDifferenceElement.classList.add('CumulativeDifference');
  323. cumulativeDifferenceElement.style.position = 'absolute';
  324. cumulativeDifferenceElement.style.right = `${openChestElement.offsetLeft}px`;
  325. cumulativeDifferenceElement.style.top = `${openChestElement.offsetTop}px`;
  326. cumulativeDifferenceElement.style.fontSize = '12px';
  327. cumulativeDifferenceElement.style.color = chestData["累计偏差值"] > 0 ? 'lime' : 'red';
  328. cumulativeDifferenceElement.innerHTML = `
  329. <br><br>
  330. <span style="color: ${profitColor};">本次开箱利润</span><br>
  331. ${profitRange ? `<span style="color: ${profitColor};">${profitRange}</span>` : `<span style="color: ${profitColor};">${formatPrice(totalAskValue)}/${formatPrice(totalBidValue)}</span>`}<br>
  332. 累计${chestData["累计偏差值"] > 0 ? '高于期望:' : '低于期望:'}<br>
  333. ${formatPrice(Math.abs(chestData["累计偏差值"]))}<br>
  334. `;
  335.  
  336. openChestElement.appendChild(displayElement);
  337. openChestElement.appendChild(expectedOutputElement);
  338. openChestElement.appendChild(differenceOutputElement);
  339. openChestElement.appendChild(cumulativeDifferenceElement);
  340.  
  341. // 保存更新的数据到本地存储
  342. localStorage.setItem('Edible_Tools', JSON.stringify(edibleTools));
  343. }
  344. }
  345.  
  346.  
  347. function calculateTotalValues(itemElements) {
  348. let totalAskValue = 0;
  349. let totalBidValue = 0;
  350.  
  351. itemElements.forEach(itemElement => {
  352. const itemNameElement = itemElement.querySelector('.Item_iconContainer__5z7j4 use');
  353. const itemQuantityElement = itemElement.querySelector('.Item_count__1HVvv');
  354.  
  355. if (itemNameElement && itemQuantityElement) {
  356. const itemName = getItemNameFromElement(itemNameElement);
  357. const itemQuantity = parseQuantityString(itemQuantityElement.textContent.trim());
  358.  
  359. let askPrice = 0;
  360. let bidPrice = 0;
  361. let priceColor = '';
  362.  
  363. // 获取价格
  364. if (specialItemPrices[itemName] && specialItemPrices[itemName].ask) {
  365. askPrice = parseFloat(specialItemPrices[itemName].ask);
  366. bidPrice = parseFloat(specialItemPrices[itemName].bid);
  367. priceColor = '';
  368. } else if (marketData?.market?.[itemName]) {
  369. bidPrice = marketData.market[itemName].bid;
  370. askPrice = marketData.market[itemName].ask;
  371. } else {
  372. console.log(`${itemName} 的价格未找到`);
  373. }
  374. const itemTotalAskValue = askPrice * itemQuantity;
  375. const itemTotalBidValue = bidPrice * itemQuantity;
  376. totalAskValue += itemTotalAskValue;
  377. totalBidValue += itemTotalBidValue;
  378. }
  379. });
  380.  
  381. //console.log(totalAskValue);
  382. return { totalAskValue, totalBidValue };
  383. }
  384.  
  385. //更详细的战斗等级显示
  386. const updateCombatLevel = () => {
  387. const elements = document.querySelectorAll(".NavigationBar_currentExperience__3GDeX");
  388.  
  389. if (elements.length === 17) {
  390. const levels = Array.from(elements).slice(10, 17).map(el => {
  391. const levelText = parseInt(el.parentNode.parentNode.querySelector(".NavigationBar_textContainer__7TdaI .NavigationBar_level__3C7eR").textContent);
  392. const decimalPart = parseFloat(el.style.width) / 100;
  393. return { integerPart: levelText, decimalPart: decimalPart };
  394. });
  395.  
  396. let [endurance, intelligence, attack, strength, defense, ranged, magic] = levels;
  397.  
  398. let combatTypeMax = Math.max(
  399. 0.5 * (attack.integerPart + strength.integerPart),
  400. ranged.integerPart,
  401. magic.integerPart
  402. );
  403.  
  404. if (combatTypeMax !== 0.5 * (attack.integerPart + strength.integerPart)) {
  405. attack.decimalPart = 0;
  406. strength.decimalPart = 0;
  407. }
  408. if (combatTypeMax !== ranged.integerPart) ranged.decimalPart = 0;
  409. if (combatTypeMax !== magic.integerPart) magic.decimalPart = 0;
  410.  
  411. let combatLevel = 0.2 * (endurance.integerPart + intelligence.integerPart + defense.integerPart) + 0.4 * combatTypeMax;
  412. combatLevel = parseFloat(combatLevel.toFixed(2));
  413. //console.log("combatLevel",combatLevel)
  414. const integerPart = Math.floor(combatLevel);
  415. const decimalPart = combatLevel - integerPart;
  416. //console.log("integerPart",integerPart)
  417. const list1 = [
  418. endurance.decimalPart * 0.2,
  419. intelligence.decimalPart * 0.2,
  420. attack.decimalPart * 0.2,
  421. strength.decimalPart * 0.2,
  422. defense.decimalPart * 0.2,
  423. ranged.decimalPart * 0.2,
  424. magic.decimalPart * 0.2
  425. ];
  426.  
  427. const list2 = [
  428. endurance.decimalPart * 0.2,
  429. intelligence.decimalPart * 0.2,
  430. attack.decimalPart * 0.2,
  431. strength.decimalPart * 0.2,
  432. defense.decimalPart * 0.2,
  433. ranged.decimalPart * 0.2,
  434. magic.decimalPart * 0.2,
  435. ranged.decimalPart * 0.2,
  436. magic.decimalPart * 0.2
  437. ];
  438. //console.log("list1",list1,"\nlist2",list2)
  439. list1.sort((a, b) => b - a);
  440. list2.sort((a, b) => b - a);
  441.  
  442. if (decimalPart === 0.8) {
  443. combatLevel += list1[0];
  444. } else {
  445. let total = 0;
  446. const maxIterations = Math.floor((1 - decimalPart) / 0.2);
  447. let iterations = 0;
  448.  
  449. for (const i of list2) {
  450. if (iterations >= maxIterations) break;
  451.  
  452. if ((decimalPart + total + i) < 1) {
  453. total += i;
  454. } else {
  455. break;
  456. }
  457.  
  458. iterations++;
  459. }
  460. combatLevel = decimalPart + integerPart + total;
  461. }
  462.  
  463. elements[15].parentNode.parentNode.parentNode.parentNode.parentNode.querySelector(".NavigationBar_nav__3uuUl .NavigationBar_level__3C7eR").textContent = combatLevel.toFixed(2);
  464. }
  465. };
  466. window.setInterval(updateCombatLevel, 10000);
  467.  
  468. function OfflineStatistics(modalElement) {
  469. const itemsContainer = modalElement.querySelectorAll(".OfflineProgressModal_itemList__26h-Y");
  470.  
  471. let timeContainer = null;
  472. let getItemContainer = null;
  473. let spendItemContainer = null;
  474.  
  475.  
  476. itemsContainer.forEach(container => {
  477. const labelElement = container.querySelector('.OfflineProgressModal_label__2HwFG');
  478. if (labelElement) {
  479. const textContent = labelElement.textContent.trim();
  480. if (textContent.startsWith("You were offline for") || textContent.startsWith("你离线了")) {
  481. timeContainer = container;
  482. } else if (textContent.startsWith("Items gained:") || textContent.startsWith("获得物品:")) {
  483. getItemContainer = container;
  484. } else if (textContent.startsWith("You consumed:") || textContent.startsWith("你消耗了:")) {
  485. spendItemContainer = container;
  486. }
  487. }
  488. });
  489.  
  490. let TotalSec = null;
  491. if (timeContainer) {
  492. const textContent = timeContainer.textContent;
  493. const match = textContent.match(/(?:(\d+)d\s*)?(?:(\d+)h\s*)?(?:(\d+)m\s*)?(?:(\d+)s)/);
  494. if (match) {
  495. let days = parseInt(match[1], 10) || 0;
  496. let hours = parseInt(match[2], 10) || 0;
  497. let minutes = parseInt(match[3], 10) || 0;
  498. let seconds = parseInt(match[4], 10) || 0;
  499. TotalSec = days * 86400 + hours * 3600 + minutes * 60 + seconds;
  500. }
  501. }
  502.  
  503. let getitemtotalAskValue = 0;
  504. let getitemtotalBidValue = 0;
  505. if (getItemContainer) {
  506. const getitemElements = getItemContainer.querySelectorAll('.Item_itemContainer__x7kH1');
  507. const { totalAskValue, totalBidValue } = calculateTotalValues(getitemElements);
  508. getitemtotalAskValue = totalAskValue;
  509. getitemtotalBidValue = totalBidValue;
  510. }
  511.  
  512.  
  513. let spenditemtotalAskValue = 0;
  514. let spenditemtotalBidValue = 0;
  515. if (spendItemContainer) {
  516. const spenditemElements = spendItemContainer.querySelectorAll('.Item_itemContainer__x7kH1');
  517. const { totalAskValue, totalBidValue } = calculateTotalValues(spenditemElements);
  518. spenditemtotalAskValue = totalAskValue;
  519. spenditemtotalBidValue = totalBidValue;
  520. }
  521.  
  522. if (timeContainer) {
  523. const newElement = document.createElement('span');
  524. newElement.textContent = `利润: ${formatPrice(getitemtotalBidValue - spenditemtotalAskValue)} [${formatPrice((getitemtotalBidValue - spenditemtotalAskValue) / (TotalSec / 3600) * 24)}/天]`;
  525. newElement.style.float = 'right';
  526. newElement.style.color = 'gold';
  527. timeContainer.querySelector(':first-child').appendChild(newElement);
  528. }
  529. if (getItemContainer) {
  530. const newElement = document.createElement('span');
  531. newElement.textContent = `产出:[${formatPrice(getitemtotalAskValue)}/${formatPrice(getitemtotalBidValue)}]`;
  532. newElement.style.float = 'right';
  533. newElement.style.color = 'gold';
  534. getItemContainer.querySelector(':first-child').appendChild(newElement);
  535. }
  536. if (spendItemContainer) {
  537. const newElement = document.createElement('span');
  538. newElement.textContent = `成本:[${formatPrice(spenditemtotalAskValue)}/${formatPrice(spenditemtotalBidValue)}]`;
  539. newElement.style.float = 'right';
  540. newElement.style.color = 'gold';
  541. spendItemContainer.querySelector(':first-child').appendChild(newElement);
  542. }
  543. }
  544.  
  545.  
  546. function initObserver() {
  547. // 选择要观察的目标节点
  548. const targetNode = document.body;
  549.  
  550. // 观察器的配置(需要观察子节点的变化)
  551. const config = { childList: true, subtree: true };
  552.  
  553. // 创建一个观察器实例并传入回调函数
  554. const observer = new MutationObserver(mutationsList => {
  555. for (let mutation of mutationsList) {
  556. if (mutation.type === 'childList') {
  557. // 监听到子节点变化
  558. mutation.addedNodes.forEach(addedNode => {
  559. // 检查是否是我们关注的 Modal_modalContainer__3B80m 元素被添加
  560. if (addedNode.classList && addedNode.classList.contains('Modal_modalContainer__3B80m')) {
  561. // Modal_modalContainer__3B80m 元素被添加,执行处理函数
  562. //console.log("箱子被打开")
  563. ShowChestPrice();
  564. recordChestOpening(addedNode);
  565.  
  566. // 开始监听箱子图标的变化
  567. startIconObserver();
  568. }
  569. if (addedNode.classList && addedNode.classList.contains('OfflineProgressModal_modalContainer__knnk7')) {
  570. OfflineStatistics(addedNode);
  571. console.log("离线报告已创建!")
  572. }
  573. if (addedNode.classList && addedNode.classList.contains('MainPanel_subPanelContainer__1i-H9')) {
  574. if (addedNode.querySelector(".CombatPanel_combatPanel__QylPo")) {
  575. addBattlePlayerFoodButton();
  576. addBattlePlayerLootButton();
  577. }
  578. }
  579. });
  580.  
  581. mutation.removedNodes.forEach(removedNode => {
  582. // 检查是否是 Modal_modalContainer__3B80m 元素被移除
  583. if (removedNode.classList && removedNode.classList.contains('Modal_modalContainer__3B80m')) {
  584. // Modal_modalContainer__3B80m 元素被移除,停止监听箱子图标的变化
  585. stopIconObserver();
  586. }
  587. });
  588. }
  589. }
  590. });
  591.  
  592. // 以上述配置开始观察目标节点
  593. observer.observe(targetNode, config);
  594.  
  595. // 定义箱子图标变化的观察器
  596. let iconObserver = null;
  597.  
  598. // 开始监听箱子图标的变化
  599. function startIconObserver() {
  600. const chestNameElem = document.querySelector(chestNameSelector);
  601. if (!chestNameElem) return;
  602.  
  603. // 创建一个观察器实例来监听图标的变化
  604. iconObserver = new MutationObserver(() => {
  605. // 当箱子图标变化时,执行处理函数
  606. ShowChestPrice();
  607. });
  608.  
  609. // 配置观察器的选项
  610. const iconConfig = { attributes: true, attributeFilter: ['href'] };
  611.  
  612. // 以上述配置开始观察箱子图标节点
  613. iconObserver.observe(chestNameElem, iconConfig);
  614. }
  615.  
  616. // 停止监听箱子图标的变化
  617. function stopIconObserver() {
  618. if (iconObserver) {
  619. iconObserver.disconnect();
  620. iconObserver = null;
  621. }
  622. }
  623. }
  624.  
  625.  
  626. //公会部分代码
  627. const userLanguage = navigator.language || navigator.userLanguage;
  628. const isZH = userLanguage.startsWith("zh");
  629. const updataDealy = 24*60*60*1000; //数据更新时限
  630. let rateXPDayMap = {};
  631.  
  632. function hookWS() {
  633. const dataProperty = Object.getOwnPropertyDescriptor(MessageEvent.prototype, "data");
  634. const oriGet = dataProperty.get;
  635.  
  636. dataProperty.get = hookedGet;
  637. Object.defineProperty(MessageEvent.prototype, "data", dataProperty);
  638.  
  639. function hookedGet() {
  640. const socket = this.currentTarget;
  641. if (!(socket instanceof WebSocket)) {
  642. return oriGet.call(this);
  643. }
  644. if (socket.url.indexOf("api.milkywayidle.com/ws") <= -1 && socket.url.indexOf("api-test.milkywayidle.com/ws") <= -1) {
  645. return oriGet.call(this);
  646. }
  647.  
  648. const message = oriGet.call(this);
  649. Object.defineProperty(this, "data", { value: message }); // Anti-loop
  650.  
  651. return handleMessage(message);
  652. }
  653. }
  654.  
  655. function addStatisticsButton() {
  656. const waitForNavi = () => {
  657. const targetNode = document.querySelector("div.NavigationBar_minorNavigationLinks__dbxh7"); // 确认这个选择器是否适合你的环境
  658. if (targetNode) {
  659. // 创建统计窗口按钮
  660. let statsButton = document.createElement("div");
  661. statsButton.setAttribute("class", "NavigationBar_minorNavigationLink__31K7Y");
  662. statsButton.style.color = "gold";
  663. statsButton.innerHTML = isZH ? "开箱统计" : "Chest Statistics";
  664. statsButton.addEventListener("click", () => {
  665. const edibleTools = JSON.parse(localStorage.getItem('Edible_Tools')) || {};
  666. const openChestData = edibleTools.Chest_Open_Data || {};
  667. createVisualizationWindow(openChestData);
  668. });
  669.  
  670. // 创建食用工具按钮
  671. let edibleToolsButton = document.createElement("div");
  672. edibleToolsButton.setAttribute("class", "NavigationBar_minorNavigationLink__31K7Y");
  673. edibleToolsButton.style.color = "gold";
  674. edibleToolsButton.innerHTML = "食用工具";
  675. edibleToolsButton.addEventListener("click", () => {
  676. openSettings();
  677. });
  678.  
  679. // 将按钮添加到目标节点
  680. targetNode.insertAdjacentElement("afterbegin", statsButton);
  681. targetNode.insertAdjacentElement("afterbegin", edibleToolsButton);
  682.  
  683. //获取图标url格式模板
  684. item_icon_url = document.querySelector("div[class^='Item_itemContainer'] use")?.getAttribute("href")?.split("#")[0];
  685.  
  686. addBattlePlayerFoodButton();
  687. addBattlePlayerLootButton();
  688. } else {
  689. setTimeout(waitForNavi, 200);
  690. }
  691. };
  692.  
  693. waitForNavi(); // 开始等待目标节点出现
  694. }
  695.  
  696.  
  697.  
  698. //奶牛钉钉
  699. function handleMessage(message) {
  700. try {
  701. let obj = JSON.parse(message);
  702. if (obj && obj.type === "new_battle") {
  703. processCombatConsumables(obj);
  704. }
  705. if (obj && obj.type === "init_character_data") {
  706. processAndPrintData();
  707. addStatisticsButton();
  708. update_market_list(obj);
  709. }
  710. if (obj && obj.type === "guild_updated") {
  711. const Guild_ID = obj.guild.id;
  712. const edibleTools = JSON.parse(localStorage.getItem('Edible_Tools')) || {};
  713. edibleTools.Guild_Data = edibleTools.Guild_Data || {};
  714. let storedData = edibleTools.Guild_Data || {};
  715.  
  716. // 判断是否已经存在旧数据
  717. if (storedData[Guild_ID] && storedData[Guild_ID].guild_updated && storedData[Guild_ID].guild_updated.old.updatedAt) {
  718. const oldUpdatedAt = new Date(storedData[Guild_ID].guild_updated.new.updatedAt);
  719. const newUpdatedAt = new Date(obj.guild.updatedAt);
  720.  
  721. // 计算时间差(单位:毫秒)
  722. const timeDifference = newUpdatedAt - oldUpdatedAt;
  723.  
  724. if (timeDifference >= updataDealy) {
  725. // 更新老数据为新数据
  726. storedData[Guild_ID].guild_updated.old = storedData[Guild_ID].guild_updated.new;
  727. // 更新新数据为当前数据
  728. storedData[Guild_ID].guild_updated.new = {
  729. experience: obj.guild.experience,
  730. level: obj.guild.level,
  731. updatedAt: obj.guild.updatedAt
  732. };
  733. } else {
  734. // 仅更新新数据
  735. storedData[Guild_ID].guild_updated.new = {
  736. experience: obj.guild.experience,
  737. level: obj.guild.level,
  738. updatedAt: obj.guild.updatedAt
  739. };
  740. }
  741. //计算Δ
  742. const Delta = {
  743. Delta_Xp: storedData[Guild_ID].guild_updated.new.experience - storedData[Guild_ID].guild_updated.old.experience,
  744. Delta_Level: storedData[Guild_ID].guild_updated.new.level - storedData[Guild_ID].guild_updated.old.level,
  745. Delta_Time: (newUpdatedAt - new Date(storedData[Guild_ID].guild_updated.old.updatedAt)) / 1000, // 转换为秒
  746. 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)
  747. };
  748. storedData[Guild_ID].guild_updated.Delta = Delta;
  749.  
  750. const Guild_TotalXp_div = document.querySelectorAll(".GuildPanel_value__Hm2I9")[1];
  751. if (Guild_TotalXp_div) {
  752. const xpText = isZH ? "经验值 / 小时" : "XP / Hour";
  753.  
  754. Guild_TotalXp_div.insertAdjacentHTML(
  755. "afterend",
  756. `<div>${formatPrice(Delta.Rate_XP_Hours)} ${xpText}</div>`
  757. );
  758. const Guild_NeedXp_div = document.querySelectorAll(".GuildPanel_value__Hm2I9")[2];
  759. if (Guild_NeedXp_div) {
  760. const Guild_NeedXp = document.querySelectorAll(".GuildPanel_value__Hm2I9")[2].textContent.replace(/,/g, '');
  761. const Time = TimeReset(Guild_NeedXp/Delta.Rate_XP_Hours);
  762. Guild_NeedXp_div.insertAdjacentHTML(
  763. "afterend", // 使用 "afterend" 在元素的后面插入内容
  764. `<div>${Time}</div>`
  765. );
  766. }
  767. }
  768. } else {
  769. // 如果没有旧数据,则直接添加新数据
  770. storedData[Guild_ID] = {
  771. guild_name: obj.guild.name,
  772. guild_updated: {
  773. old: {
  774. experience: obj.guild.experience,
  775. level: obj.guild.level,
  776. updatedAt: obj.guild.updatedAt
  777. },
  778. new: {},
  779. }
  780. };
  781. }
  782.  
  783. // 存储更新后的数据到 localStorage
  784. edibleTools.Guild_Data = storedData;
  785. localStorage.setItem('Edible_Tools', JSON.stringify(edibleTools));
  786. } else if (obj && obj.type === "guild_characters_updated") {
  787. const edibleTools = JSON.parse(localStorage.getItem('Edible_Tools')) || {};
  788. edibleTools.Guild_Data = edibleTools.Guild_Data || {};
  789. let storedData = edibleTools.Guild_Data || {};
  790. for (const key in obj.guildSharableCharacterMap) {
  791. if (obj.guildSharableCharacterMap.hasOwnProperty(key)) {
  792. const Guild_ID = obj.guildCharacterMap[key].guildID;
  793. const name = obj.guildSharableCharacterMap[key].name;
  794. const newUpdatedAt = new Date();
  795. storedData[Guild_ID].guild_player = storedData[Guild_ID].guild_player || {};
  796. 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) {
  797. const oldUpdatedAt = new Date(storedData[Guild_ID].guild_player[name].old.updatedAt)
  798. const timeDifference = newUpdatedAt - oldUpdatedAt
  799. if (timeDifference >= updataDealy) {
  800. // 更新老数据为新数据
  801. storedData[Guild_ID].guild_player[name].old = storedData[Guild_ID].guild_player[name].new;
  802. // 更新新数据为当前数据
  803. storedData[Guild_ID].guild_player[name].new = {
  804. id: key,
  805. gameMode: obj.guildSharableCharacterMap[key].gameMode,
  806. guildExperience: obj.guildCharacterMap[key].guildExperience,
  807. updatedAt: newUpdatedAt,
  808. };
  809. } else {
  810. // 仅更新新数据
  811. storedData[Guild_ID].guild_player[name].new = {
  812. id: key,
  813. gameMode: obj.guildSharableCharacterMap[key].gameMode,
  814. guildExperience: obj.guildCharacterMap[key].guildExperience,
  815. updatedAt: newUpdatedAt,
  816. };
  817. }
  818. //计算Δ
  819. const Delta = {
  820. Delta_Time:(newUpdatedAt - new Date(storedData[Guild_ID].guild_player[name].old.updatedAt)) / 1000,
  821. Delta_Xp: storedData[Guild_ID].guild_player[name].new.guildExperience - storedData[Guild_ID].guild_player[name].old.guildExperience,
  822. 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)
  823. };
  824. storedData[Guild_ID].guild_player[name].Delta = Delta;
  825. rateXPDayMap[name] = Delta.Rate_XP_Day;
  826. }else {
  827. storedData[Guild_ID].guild_player[name] = {
  828. old: {
  829. id: key,
  830. gameMode: obj.guildSharableCharacterMap[key].gameMode,
  831. guildExperience: obj.guildCharacterMap[key].guildExperience,
  832. updatedAt: newUpdatedAt,
  833. },
  834. new:{}
  835. };
  836. }
  837. }
  838.  
  839. }
  840. //console.log("测试数据",storedData);
  841. //console.log("guild_characters_updated", obj);
  842. updateExperienceDisplay(rateXPDayMap);
  843. edibleTools.Guild_Data = storedData;
  844. localStorage.setItem('Edible_Tools', JSON.stringify(edibleTools));
  845. } else if (obj && obj.type === "market_listings_updated") {
  846. update_market_list(obj);
  847. }
  848.  
  849.  
  850.  
  851.  
  852. } catch (error) {
  853. console.error("Error processing message:", error);
  854. }
  855. return message;
  856. }
  857.  
  858. // 订单数据更新
  859. function update_market_list(date) {
  860. if (!date) return;
  861.  
  862. let market_list = JSON.parse(GM_getValue('market_list', '[]'));
  863.  
  864. // 通用更新
  865. function updateOrders(orders) {
  866. orders.forEach(newOrder => {
  867. const existingOrderIndex = market_list.findIndex(order => order.id === newOrder.id);
  868. if (existingOrderIndex !== -1) {
  869. market_list[existingOrderIndex] = newOrder;
  870. } else {
  871. market_list.push(newOrder);
  872. }
  873. // 给每个订单添加更新时间戳
  874. newOrder.lastUpdated = new Date().toISOString();
  875. });
  876. }
  877.  
  878. // 更新市场数据
  879. if (date.type === "init_character_data" && date.myMarketListings) {
  880. updateOrders(date.myMarketListings);
  881. } else if (date.type === "market_listings_updated" && date.endMarketListings) {
  882. updateOrders(date.endMarketListings);
  883. }
  884.  
  885. // 保存更新后的数据
  886. GM_setValue('market_list', JSON.stringify(market_list));
  887. }
  888.  
  889. function deleteOrdersBeforeDate() {
  890. const userInput = prompt("请输入要删除之前的日期 (格式:YYYY-MM-DD)", "");
  891.  
  892. if (!userInput) return;
  893.  
  894. // 转换用户输入的日期为 Date 对象
  895. const userDate = new Date(userInput);
  896.  
  897. if (isNaN(userDate)) {
  898. alert("无效的日期格式,请使用 YYYY-MM-DD");
  899. return;
  900. }
  901.  
  902. let market_list = JSON.parse(GM_getValue('market_list', '[]'));
  903.  
  904. // 过滤出所有在用户选择日期之前的订单
  905. const filteredMarketList = market_list.filter(order => {
  906. const orderDate = new Date(order.lastUpdated);
  907. return orderDate >= userDate;
  908. });
  909.  
  910. // 更新并保存新的数据
  911. GM_setValue('market_list', JSON.stringify(filteredMarketList));
  912.  
  913. alert("删除成功,已清理日期之前的数据。");
  914. }
  915.  
  916. function TimeReset(hours) {
  917. const totalMinutes = hours * 60;
  918. const days = Math.floor(totalMinutes / (24 * 60));
  919. const yudays = totalMinutes % (24 * 60);
  920. const hrs = Math.floor(yudays / 60);
  921. const minutes = Math.floor(yudays % 60);
  922. const dtext = isZH ? "天" : "d";
  923. const htext = isZH ? "时" : "h";
  924. const mtext = isZH ? "分" : "m";
  925. return `${days}${dtext} ${hrs}${htext} ${minutes}${mtext}`;
  926. }
  927.  
  928. function updateExperienceDisplay(rateXPDayMap) {
  929. const trElements = document.querySelectorAll(".GuildPanel_membersTable__1NwIX tbody tr");
  930. const idleuser_list = [];
  931. const dtext = isZH ? "天" : "d";
  932.  
  933. // 将 rateXPDayMap 转换为数组并排序
  934. const sortedMembers = Object.entries(rateXPDayMap)
  935. .map(([name, XPdata]) => ({ name, XPdata }))
  936. .sort((a, b) => b.XPdata - a.XPdata);
  937.  
  938. sortedMembers.forEach(({ name, XPdata }) => {
  939. trElements.forEach(tr => {
  940. const nameElement = tr.querySelector(".CharacterName_name__1amXp");
  941. const experienceElement = tr.querySelector("td:nth-child(3) > div");
  942. const activityElement = tr.querySelector('.GuildPanel_activity__9vshh');
  943.  
  944. if (nameElement && nameElement.textContent.trim() === name) {
  945. if (activityElement.childElementCount === 0) {
  946. idleuser_list.push(nameElement.textContent.trim());
  947. }
  948.  
  949. if (experienceElement) {
  950. const newDiv = document.createElement('div');
  951. newDiv.textContent = `${formatPrice(XPdata)}/${dtext}`;
  952.  
  953. // 计算颜色
  954. const rank = sortedMembers.findIndex(member => member.name === name);
  955. const hue = 120 - (rank * (120 / (sortedMembers.length - 1)));
  956. newDiv.style.color = `hsl(${hue}, 100%, 50%)`;
  957.  
  958. experienceElement.insertAdjacentElement('afterend', newDiv);
  959. }
  960. return;
  961. }
  962. });
  963. });
  964.  
  965. update_idleuser_tb(idleuser_list);
  966. }
  967.  
  968. function update_idleuser_tb(idleuser_list) {
  969. const targetElement = document.querySelector('.GuildPanel_noticeMessage__3Txji');
  970. if (!targetElement) {
  971. console.error('公会标语元素未找到!');
  972. return;
  973. }
  974. const clonedElement = targetElement.cloneNode(true);
  975.  
  976. const namesText = idleuser_list.join(', ');
  977. clonedElement.innerHTML = '';
  978. clonedElement.textContent = isZH ? `闲置的成员:${namesText}` : `Idle User : ${namesText}`;
  979. clonedElement.style.color = '#ffcc00';
  980.  
  981. // 设置复制元素的高度为原元素的25%
  982. const originalStyle = window.getComputedStyle(targetElement);
  983. const originalHeight = originalStyle.height;
  984. const originalMinHeight = originalStyle.minHeight;
  985. clonedElement.style.height = `25%`;
  986. clonedElement.style.minHeight = `25%`; // 也设置最小高度
  987. targetElement.parentElement.appendChild(clonedElement);
  988. }
  989.  
  990.  
  991. //箱子数据获取
  992. function processAndPrintData() {
  993. const initClientData = localStorage.getItem('initClientData');
  994. if (!initClientData) return;
  995. let obj;
  996. try {
  997. obj = JSON.parse(initClientData);
  998. } catch (error) {
  999. console.error('数据解析失败:', error);
  1000. return;
  1001. }
  1002. if (obj.type !== 'init_client_data') return;
  1003. const item_hrid_to_name = obj.itemDetailMap;
  1004. for (const key in item_hrid_to_name) {
  1005. if (item_hrid_to_name[key] && typeof item_hrid_to_name[key] === 'object' && item_hrid_to_name[key].name) {
  1006. item_hrid_to_name[key] = item_hrid_to_name[key].name;
  1007. }
  1008. }
  1009. const item_name_to_hrid = Object.fromEntries(
  1010. Object.entries(item_hrid_to_name).map(([key, value]) => [value, key])
  1011. );
  1012. const hrid2name = item_hrid_to_name;
  1013. let formattedShopData = {};
  1014.  
  1015. // 处理商店数据
  1016. for (let [key, details] of Object.entries(obj.shopItemDetailMap)) {
  1017. const { itemHrid, costs } = details;
  1018. const itemName = hrid2name[itemHrid] || formatItemName(itemHrid.split('/').pop());
  1019.  
  1020. costs.forEach(cost => {
  1021. const costItemName = hrid2name[cost.itemHrid] || formatItemName(cost.itemHrid.split('/').pop());
  1022. if (costItemName === "Coin") return;
  1023.  
  1024. const costCount = cost.count;
  1025.  
  1026. if (!formattedShopData[costItemName]) {
  1027. formattedShopData[costItemName] = { items: {}, 最挣钱: '', BID单价: 0 };
  1028. }
  1029.  
  1030. // 计算每种代币购买每个物品的收益
  1031. let bidValue = getSpecialItemPrice(itemName,"bid") || 0;
  1032. let profit = bidValue / costCount;
  1033.  
  1034. formattedShopData[costItemName].items[itemName] = {
  1035. 花费: costCount
  1036. };
  1037.  
  1038. // 更新最赚钱的物品信息
  1039. if (profit > formattedShopData[costItemName].BID单价) {
  1040. formattedShopData[costItemName].最挣钱 = itemName;
  1041. formattedShopData[costItemName].BID单价 = profit;
  1042. specialItemPrices[costItemName].ask = profit;
  1043. specialItemPrices[costItemName].bid = profit;
  1044. }
  1045. });
  1046. }
  1047. const mostProfitableItems = Object.values(formattedShopData).map(item => item.最挣钱).filter(Boolean);
  1048. //console.log(mostProfitableItems)
  1049. // 处理箱子掉落物数据
  1050.  
  1051. for (let iteration = 0; iteration < 4; iteration++) {
  1052. for (let [key, items] of Object.entries(obj.openableLootDropMap)) {
  1053. const boxName = hrid2name[key] || formatItemName(key.split('/').pop());
  1054.  
  1055. if (!formattedChestDropData[boxName]) {
  1056. formattedChestDropData[boxName] = { item: {} };
  1057. }
  1058. let TotalAsk = 0;
  1059. let TotalBid = 0;
  1060. let awa = 0;
  1061. items.forEach(item => {
  1062. const { itemHrid, dropRate, minCount, maxCount } = item;
  1063. const itemName = hrid2name[itemHrid] || formatItemName(itemHrid.split('/').pop());
  1064. const expectedYield = ((minCount + maxCount) / 2) * dropRate;
  1065. let bidPrice = -1;
  1066. let askPrice = -1;
  1067. let priceColor = '';
  1068.  
  1069. if (specialItemPrices[itemName] && specialItemPrices[itemName].ask) {
  1070. askPrice = parseFloat(specialItemPrices[itemName].ask);
  1071. bidPrice = parseFloat(specialItemPrices[itemName].bid);
  1072. priceColor = '';
  1073. } else if (marketData?.market?.[itemName]) {
  1074. bidPrice = marketData.market[itemName].bid;
  1075. askPrice = marketData.market[itemName].ask;
  1076. } else {
  1077. console.log(`${itemName} 的价格未找到`);
  1078. }
  1079.  
  1080. if (formattedChestDropData[boxName].item[itemName] && iteration === 0) {
  1081. // 如果物品已存在,更新期望掉落和相关价格
  1082. const existingItem = formattedChestDropData[boxName].item[itemName];
  1083. existingItem.期望掉落 += expectedYield;
  1084. } else if (iteration === 0) {
  1085. formattedChestDropData[boxName].item[itemName] = {
  1086. 期望掉落: expectedYield,
  1087. };
  1088. }
  1089.  
  1090. // 判断 itemName 是否在最挣钱物品列表中
  1091. if (mostProfitableItems.includes(itemName)) {
  1092. priceColor = '#FFb3E6';
  1093. } else if (askPrice === -1 && bidPrice === -1) {
  1094. priceColor = 'yellow';
  1095. } else if (askPrice === -1) {
  1096. askPrice = bidPrice;
  1097. priceColor = '#D95961';
  1098. } else if (bidPrice === -1) {
  1099. priceColor = '#2FC4A7';
  1100. }
  1101.  
  1102. const existingItem = formattedChestDropData[boxName].item[itemName];
  1103. existingItem.出售单价 = askPrice;
  1104. existingItem.收购单价 = bidPrice;
  1105. existingItem.出售总价 = (existingItem.出售单价 * existingItem.期望掉落).toFixed(2);
  1106. existingItem.收购总价 = (existingItem.收购单价 * existingItem.期望掉落).toFixed(2);
  1107. existingItem.Color = priceColor;
  1108.  
  1109. // 累计总价
  1110. TotalAsk += (askPrice * expectedYield);
  1111. TotalBid += (bidPrice * expectedYield);
  1112. });
  1113.  
  1114. formattedChestDropData[boxName] = {
  1115. ...formattedChestDropData[boxName],
  1116. 期望产出Ask: TotalAsk.toFixed(2),
  1117. 期望产出Bid: TotalBid.toFixed(2),
  1118. };
  1119.  
  1120. if (!specialItemPrices[boxName]) {
  1121. specialItemPrices[boxName] = {}
  1122. }
  1123.  
  1124. specialItemPrices[boxName].ask = formattedChestDropData[boxName].期望产出Ask;
  1125. specialItemPrices[boxName].bid = formattedChestDropData[boxName].期望产出Bid;
  1126. }
  1127. }
  1128.  
  1129. for (let itemName in specialItemPrices) {
  1130. if (specialItemPrices.hasOwnProperty(itemName)) {
  1131. marketData.market[itemName] = {
  1132. ask: specialItemPrices[itemName].ask,
  1133. bid: specialItemPrices[itemName].bid
  1134. };
  1135. }
  1136. }
  1137.  
  1138. localStorage.setItem('MWITools_marketAPI_json', JSON.stringify(marketData));
  1139. let edibleTools = JSON.parse(localStorage.getItem('Edible_Tools')) || {};
  1140. edibleTools.Chest_Drop_Data = formattedChestDropData;
  1141. localStorage.setItem('Edible_Tools', JSON.stringify(edibleTools));
  1142. // 打印结果
  1143. //console.log("特殊物品价格表:",specialItemPrices)
  1144. //console.log("箱子掉落物列表:", formattedChestDropData);
  1145. //console.log("地牢商店列表:", formattedShopData);
  1146. }
  1147.  
  1148. function ShowChestPrice() {
  1149. const modalContainer = document.querySelector(".Modal_modalContainer__3B80m");
  1150. if (!modalContainer) return; // 如果不存在 Modal_modalContainer__3B80m 元素,则直接返回
  1151.  
  1152. const chestNameElem = document.querySelector(chestNameSelector);
  1153. if (!chestNameElem) return;
  1154.  
  1155. const chestName = getItemNameFromElement(chestNameElem);
  1156. const items = document.querySelectorAll(itemSelector);
  1157.  
  1158. const dropListContainer = document.querySelector('.ItemDictionary_openToLoot__1krnv');
  1159. if (!dropListContainer) return; // 检查 dropListContainer 是否存在
  1160.  
  1161. const edibleTools = JSON.parse(localStorage.getItem('Edible_Tools'))
  1162. const formattedChestDropData = edibleTools.Chest_Drop_Data;
  1163.  
  1164. items.forEach(item => {
  1165. const itemName = getItemNameFromElement(item.querySelector(iconSelector));
  1166. if (!itemName) return; // 检查 itemName 是否存在
  1167.  
  1168. const itemData = formattedChestDropData[chestName].item[itemName];
  1169. if (!itemData) return; // 检查 itemData 是否存在
  1170.  
  1171. const itemColor = itemData.Color;
  1172. const itemNameElem = item.querySelector('.Item_name__2C42x');
  1173. if (itemNameElem && itemColor) {
  1174. itemNameElem.style.color = itemColor;
  1175. }
  1176. });
  1177.  
  1178. const askPrice = formattedChestDropData[chestName]["期望产出Ask"];
  1179. const bidPrice = formattedChestDropData[chestName]["期望产出Bid"];
  1180. if (askPrice && bidPrice) {
  1181.  
  1182. const previousResults = dropListContainer.querySelectorAll('.resultDiv');
  1183. previousResults.forEach(result => result.remove());
  1184.  
  1185. const createPriceOutput = (label, price) => {
  1186. const priceOutput = document.createElement('div');
  1187. priceOutput.className = 'resultDiv';
  1188. priceOutput.textContent = `${label}: ${formatPrice(price)}`;
  1189. priceOutput.style.color = 'gold';
  1190. priceOutput.style.fontSize = '14px';
  1191. priceOutput.style.fontWeight = '400';
  1192. priceOutput.style.paddingTop = '10px';
  1193. return priceOutput;
  1194. };
  1195.  
  1196. const minPriceOutput = createPriceOutput('期望产出 (最低买入价计算)', askPrice);
  1197. const maxPriceOutput = createPriceOutput('期望产出 (最高收购价计算)', bidPrice);
  1198.  
  1199. dropListContainer.appendChild(minPriceOutput);
  1200. dropListContainer.appendChild(maxPriceOutput);
  1201.  
  1202. }
  1203. }
  1204.  
  1205. function processCombatConsumables(obj) {
  1206. battlePlayerFood = {};
  1207. battlePlayerLoot = {};
  1208. battleDuration = (new Date() - new Date(obj.combatStartTime))/1000
  1209. obj.players.forEach(player => {
  1210. battlePlayerFood[player.character.name] = {};
  1211. battlePlayerLoot[player.character.name] = {};
  1212. let minTimeInDays = Infinity;
  1213. let minItemName = "";
  1214.  
  1215. player.combatConsumables.forEach(consumable => {
  1216. const itemname = item_hrid_to_name[consumable.itemHrid];
  1217. const timePerUnit = itemname.includes('Coffee') ? 5 : 1;
  1218. const totalTimeInMinutes = consumable.count * timePerUnit;
  1219. const totalTimeInDays = totalTimeInMinutes / (24 * 60);
  1220.  
  1221. if (totalTimeInDays < minTimeInDays) {
  1222. minTimeInDays = totalTimeInDays;
  1223. minItemName = itemname;
  1224. }
  1225.  
  1226. battlePlayerFood[player.character.name][itemname] = {
  1227. "数量": consumable.count,
  1228. "剩余时间": totalTimeInDays.toFixed(1) + '天',
  1229. "颜色": "Black"
  1230. };
  1231. });
  1232. Object.keys(battlePlayerFood[player.character.name]).forEach(item => {
  1233. if (item === minItemName) {
  1234. battlePlayerFood[player.character.name][item]["颜色"] = "red";
  1235. }
  1236. });
  1237. battlePlayerFood[player.character.name]['剩余时间'] = {
  1238. "数量": minTimeInDays < 1 ? (minTimeInDays * 24).toFixed(1) + '小时' : minTimeInDays.toFixed(1) + '天',
  1239. "颜色": "Black"
  1240. };
  1241.  
  1242. Object.values(player.totalLootMap).forEach(Loot => {
  1243. const itemname = item_hrid_to_name[Loot.itemHrid];
  1244. battlePlayerLoot[player.character.name][itemname] = {
  1245. "数量":Loot.count,
  1246. "ID":Loot.itemHrid
  1247. }
  1248. });
  1249.  
  1250. });
  1251. }
  1252.  
  1253.  
  1254. function addBattlePlayerFoodButton() {
  1255. //出警按钮父元素路径
  1256. 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(1) > div > div > div > div.TabsComponent_tabsContainer__3BDUp > div > div > div")
  1257. var referenceTab = tabsContainer ? tabsContainer.children[1] : null;
  1258.  
  1259. if (!tabsContainer || !referenceTab) {
  1260. return;
  1261. }
  1262.  
  1263. if (tabsContainer.querySelector('.Button_battlePlayerFood__custom')) {
  1264. console.log('按钮已存在');
  1265. return;
  1266. }
  1267.  
  1268. var battlePlayerFoodButton = document.createElement('div');
  1269. battlePlayerFoodButton.className = referenceTab.className + ' Button_battlePlayerFood__custom';
  1270. battlePlayerFoodButton.setAttribute('script_translatedfrom', 'New Action');
  1271. battlePlayerFoodButton.textContent = '出警';
  1272.  
  1273. battlePlayerFoodButton.addEventListener('click', function() {
  1274. let dataHtml = '<div style="display: flex; flex-wrap: nowrap;">';
  1275. for (let player in battlePlayerFood) {
  1276. dataHtml +=
  1277. `<div style="flex: 1 0 auto; min-width: 100px; margin: 10px; padding: 10px; border: 1px solid black;">
  1278. <h3>${player}</h3>`;
  1279. let consumables = battlePlayerFood[player];
  1280. for (let item in consumables) {
  1281. dataHtml += `<p style="color:${consumables[item].颜色};">${item}: ${consumables[item].数量}</p>`;
  1282. }
  1283. dataHtml += '</div>';
  1284. }
  1285. dataHtml += '</div>';
  1286.  
  1287. let popup = document.createElement('div');
  1288. popup.style.position = 'fixed';
  1289. popup.style.top = '50%';
  1290. popup.style.left = '50%';
  1291. popup.style.transform = 'translate(-50%, -50%)';
  1292. popup.style.backgroundColor = 'white';
  1293. popup.style.border = '1px solid black';
  1294. popup.style.padding = '10px';
  1295. popup.style.zIndex = '10000';
  1296. popup.style.maxWidth = '75%';
  1297. popup.style.overflowX = 'auto';
  1298. popup.style.whiteSpace = 'nowrap';
  1299. popup.innerHTML = dataHtml;
  1300.  
  1301. let closeButton = document.createElement('button');
  1302. closeButton.textContent = '关闭';
  1303. closeButton.style.display = 'block';
  1304. closeButton.style.margin = '20px auto 0 auto';
  1305. closeButton.onclick = function() {
  1306. document.body.removeChild(popup);
  1307. };
  1308. popup.appendChild(closeButton);
  1309.  
  1310. document.body.appendChild(popup);
  1311. });
  1312.  
  1313. var lastTab = tabsContainer.children[tabsContainer.children.length - 1];
  1314. tabsContainer.insertBefore(battlePlayerFoodButton, lastTab.nextSibling);
  1315.  
  1316. var style = document.createElement('style');
  1317. style.innerHTML = `
  1318. .Button_battlePlayerFood__custom {
  1319. background-color: #546ddb;
  1320. color: white;
  1321. }
  1322. `;
  1323. document.head.appendChild(style);
  1324. }
  1325.  
  1326.  
  1327. function addBattlePlayerLootButton() {
  1328. // 获取按钮父元素路径
  1329. 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(1) > div > div > div > div.TabsComponent_tabsContainer__3BDUp > div > div > div");
  1330. var referenceTab = tabsContainer ? tabsContainer.children[1] : null;
  1331.  
  1332. if (!tabsContainer || !referenceTab) {
  1333. return;
  1334. }
  1335.  
  1336. // 如果按钮已经存在,直接返回
  1337. if (tabsContainer.querySelector('.Button_battlePlayerLoot__custom')) {
  1338. console.log('分赃按钮已存在');
  1339. return;
  1340. }
  1341.  
  1342. // 创建按钮
  1343. var battlePlayerLootButton = document.createElement('div');
  1344. battlePlayerLootButton.className = referenceTab.className + ' Button_battlePlayerLoot__custom';
  1345. battlePlayerLootButton.setAttribute('script_translatedfrom', 'New Action');
  1346. battlePlayerLootButton.textContent = '分赃';
  1347.  
  1348. // 按钮点击事件
  1349. battlePlayerLootButton.addEventListener('click', function() {
  1350. let dataHtml = '<div style="display: flex; flex-wrap: nowrap; background-color: #131419; padding: 10px; border-radius: 10px; color: white;">';
  1351. const minPrice = 10000;
  1352.  
  1353. // 获取所有玩家的总计价格
  1354. let playerPrices = [];
  1355. for (let player in battlePlayerLoot) {
  1356. let totalPrice = 0;
  1357. let lootItems = battlePlayerLoot[player];
  1358. for (let item in lootItems) {
  1359. let bidPrice = getSpecialItemPrice(item,"bid") || 0;
  1360. totalPrice += bidPrice * lootItems[item].数量;
  1361. }
  1362. playerPrices.push({ player, totalPrice });
  1363. }
  1364.  
  1365. // 找到眉笔
  1366. let minTotalPricePlayer = null;
  1367. if (playerPrices.length > 1) {
  1368. minTotalPricePlayer = playerPrices.reduce((min, current) =>
  1369. current.totalPrice < min.totalPrice ? current : min
  1370. ).player;
  1371. }
  1372.  
  1373. // 显示高价值物品
  1374. for (let player in battlePlayerLoot) {
  1375. let totalPrice = 0;
  1376. dataHtml += `<div style="flex: 1 0 auto; min-width: 100px; margin: 10px; padding: 10px; border-radius: 10px; background-color: #1e1e2f; border: 1px solid #98a7e9;">`;
  1377. dataHtml += `<h3 style="color: white; margin-bottom: 10px;">${player}</h3>`;
  1378.  
  1379. // 计算总价格
  1380. let lootItems = battlePlayerLoot[player];
  1381. for (let item in lootItems) {
  1382. let bidPrice = getSpecialItemPrice(item,"bid") || 0;
  1383. totalPrice += bidPrice * lootItems[item].数量;
  1384. }
  1385.  
  1386. // 显示总计价格
  1387. if (totalPrice > 0) {
  1388. let color = '#4CAF50';
  1389. if (player === minTotalPricePlayer) {
  1390. color = '#FF0000';
  1391. }
  1392.  
  1393. // 计算每天价格
  1394. const pricePerDay = formatPrice((60 * 60 * 24 * totalPrice) / battleDuration);
  1395.  
  1396. dataHtml += `<p style="color: ${color}; font-weight: bold;">
  1397. 总计价值: ${formatPrice(totalPrice)}<br>
  1398. 每天收入: ${pricePerDay}</p>`;
  1399. }
  1400.  
  1401. // 获取经过筛选和排序后的物品列表
  1402. let sortedItems = Object.keys(lootItems)
  1403. .map(item => {
  1404. let bidPrice = getSpecialItemPrice(item, "bid") || 0;
  1405. return {
  1406. item,
  1407. bidPrice,
  1408. quantity: lootItems[item].数量
  1409. };
  1410. })
  1411. .filter(item => item.bidPrice >= 10000)
  1412. .sort((a, b) => b.bidPrice - a.bidPrice);
  1413.  
  1414. let maxQuantityLength = Math.max(...sortedItems.map(item => item.quantity.toString().length));
  1415.  
  1416. // 显示前十个物品
  1417. for (let i = 0; i < Math.min(sortedItems.length, 10); i++) {
  1418. let item = sortedItems[i].item;
  1419. let bidPrice = sortedItems[i].bidPrice;
  1420. let quantity = sortedItems[i].quantity;
  1421.  
  1422. // 创建图标
  1423. let svgIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
  1424. svgIcon.setAttribute('width', '20');
  1425. svgIcon.setAttribute('height', '20');
  1426. svgIcon.style.marginRight = '5px';
  1427. svgIcon.style.verticalAlign = 'middle';
  1428.  
  1429. let useElement = document.createElementNS('http://www.w3.org/2000/svg', 'use');
  1430. useElement.setAttribute('href', `${item_icon_url}#${lootItems[item].ID.split('/').pop()}`);
  1431. svgIcon.appendChild(useElement);
  1432.  
  1433. // 显示物品数量、图标和名称
  1434. dataHtml += `
  1435. <div style="display: flex; align-items: center; background-color: #2c2e45; border-radius: 5px; padding: 5px; margin-bottom: 5px; border: 1px solid white; white-space: nowrap; flex-shrink: 0;">
  1436. <span style="color: white; margin-right: 5px; min-width: ${maxQuantityLength * 10}px; text-align: center;">${quantity}</span>
  1437. ${svgIcon.outerHTML}
  1438. <span style="color: white; white-space: nowrap;">${item}</span>
  1439. </div>`;
  1440. }
  1441. dataHtml += '</div>';
  1442. }
  1443. dataHtml += '</div>';
  1444.  
  1445. // 创建弹窗
  1446. let popup = document.createElement('div');
  1447. popup.style.position = 'fixed';
  1448. popup.style.top = '50%';
  1449. popup.style.left = '50%';
  1450. popup.style.transform = 'translate(-50%, -50%)';
  1451. popup.style.backgroundColor = '#131419';
  1452. popup.style.border = '1px solid #98a7e9';
  1453. popup.style.padding = '20px';
  1454. popup.style.borderRadius = '10px';
  1455. popup.style.zIndex = '10000';
  1456. popup.style.maxWidth = '90%';
  1457. popup.style.overflowX = 'auto';
  1458. popup.style.whiteSpace = 'nowrap';
  1459. popup.innerHTML = dataHtml;
  1460.  
  1461. // 添加关闭按钮
  1462. let closeButton = document.createElement('button');
  1463. closeButton.textContent = '关闭';
  1464. closeButton.style.display = 'block';
  1465. closeButton.style.margin = '5px auto 0 auto';
  1466. closeButton.style.backgroundColor = '#4357af';
  1467. closeButton.style.color = 'white';
  1468. closeButton.style.border = 'none';
  1469. closeButton.style.padding = '10px 20px';
  1470. closeButton.style.borderRadius = '5px';
  1471. closeButton.style.cursor = 'pointer';
  1472. closeButton.onclick = function() {
  1473. document.body.removeChild(popup);
  1474. };
  1475. popup.appendChild(closeButton);
  1476.  
  1477. document.body.appendChild(popup);
  1478. });
  1479.  
  1480. // 将按钮插入到最后一个标签后面
  1481. var lastTab = tabsContainer.children[tabsContainer.children.length - 1];
  1482. tabsContainer.insertBefore(battlePlayerLootButton, lastTab.nextSibling);
  1483.  
  1484. // 添加按钮样式
  1485. var style = document.createElement('style');
  1486. style.innerHTML = `
  1487. .Button_battlePlayerLoot__custom {
  1488. background-color: #db5454;
  1489. color: white;
  1490. border-radius: 5px;
  1491. padding: 5px 10px;
  1492. cursor: pointer;
  1493. transition: background-color 0.3s ease;
  1494. }
  1495. .Button_battlePlayerLoot__custom:hover {
  1496. background-color: #ff6b6b;
  1497. }
  1498. `;
  1499. document.head.appendChild(style);
  1500. }
  1501.  
  1502. //菜单
  1503. GM_registerMenuCommand('打印所有箱子掉落物', function() {
  1504. console.log('箱子掉落物列表:', formattedChestDropData);
  1505. });
  1506.  
  1507. function createWindowBase() {
  1508. let windowDiv = document.createElement('div');
  1509. windowDiv.className = 'visualization-window';
  1510. windowDiv.style.position = 'fixed';
  1511. windowDiv.style.top = '50%';
  1512. windowDiv.style.left = '50%';
  1513. windowDiv.style.transform = 'translate(-50%, -50%)';
  1514. windowDiv.style.height = '60%';
  1515. windowDiv.style.backgroundColor = 'white';
  1516. windowDiv.style.border = '1px solid #000';
  1517. windowDiv.style.zIndex = '10000';
  1518. windowDiv.style.padding = '10px';
  1519. windowDiv.style.boxSizing = 'border-box';
  1520. windowDiv.style.display = 'flex';
  1521. windowDiv.style.flexDirection = 'column';
  1522. return windowDiv;
  1523. }
  1524.  
  1525. function createVisualizationWindow(chestData) {
  1526. let oldWindow = document.querySelector('.visualization-window');
  1527. if (oldWindow) {
  1528. oldWindow.remove();
  1529. }
  1530. let windowDiv = createWindowBase();
  1531.  
  1532. let title = document.createElement('h1');
  1533. title.innerText = '开箱记录';
  1534. title.style.textAlign = 'center';
  1535. windowDiv.appendChild(title);
  1536.  
  1537. let contentDiv = document.createElement('div');
  1538. contentDiv.style.flex = '1';
  1539. contentDiv.style.overflowY = 'auto';
  1540.  
  1541. // 添加箱子列表
  1542. for (let chestName in chestData) {
  1543. let chest = chestData[chestName];
  1544.  
  1545. // 外部容器(带边框)
  1546. let chestBox = document.createElement('div');
  1547. chestBox.style.display = 'flex';
  1548. chestBox.style.alignItems = 'center';
  1549. chestBox.style.border = '1px solid #000';
  1550. chestBox.style.borderRadius = '5px';
  1551. chestBox.style.marginBottom = '10px';
  1552. chestBox.style.padding = '5px';
  1553. chestBox.style.cursor = 'pointer';
  1554. chestBox.style.backgroundColor = '#f9f9f9';
  1555. chestBox.onmouseenter = () => (chestBox.style.backgroundColor = '#e0e0e0');
  1556. chestBox.onmouseleave = () => (chestBox.style.backgroundColor = '#f9f9f9');
  1557.  
  1558. // 点击事件绑定到 chestBox
  1559. chestBox.onclick = () => showChestDetails(chestName, chest);
  1560.  
  1561. let svgIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
  1562. svgIcon.setAttribute('width', '24');
  1563. svgIcon.setAttribute('height', '24');
  1564. svgIcon.style.marginRight = '10px';
  1565.  
  1566. let useElement = document.createElementNS('http://www.w3.org/2000/svg', 'use');
  1567. try {
  1568. let iconId = item_name_to_hrid[chestName].split('/').pop();
  1569. useElement.setAttribute('href', `${item_icon_url}#${iconId}`);
  1570. } catch (error) {
  1571. console.error(`无法找到宝箱 "${chestName}" 的图标ID:`, error);
  1572. useElement.setAttribute('href', `${item_icon_url}#coin`);
  1573. }
  1574. svgIcon.appendChild(useElement);
  1575.  
  1576. let chestText = document.createElement('span');
  1577. chestText.style.flex = '1';
  1578. chestText.style.textAlign = 'left';
  1579. chestText.innerText = `${chestName}: ${chest['总计开箱数量']}`;
  1580.  
  1581. chestBox.appendChild(svgIcon);
  1582. chestBox.appendChild(chestText);
  1583.  
  1584. contentDiv.appendChild(chestBox);
  1585. }
  1586.  
  1587. windowDiv.appendChild(contentDiv);
  1588.  
  1589. let footerDiv = document.createElement('div');
  1590. footerDiv.style.display = 'flex';
  1591. footerDiv.style.justifyContent = 'space-between';
  1592. footerDiv.style.marginTop = '10px';
  1593.  
  1594. // 关闭按钮
  1595. let closeButton = document.createElement('button');
  1596. closeButton.innerText = '关闭';
  1597. closeButton.onclick = () => document.body.removeChild(windowDiv);
  1598. footerDiv.appendChild(closeButton);
  1599.  
  1600. // 删除数据按钮
  1601. let deleteButton = document.createElement('button');
  1602. deleteButton.innerText = '删除数据';
  1603. deleteButton.onclick = () => {
  1604. if (confirm('确定要删除所有开箱数据吗?')) {
  1605. deleteOpenChestData(chestData);
  1606. document.body.removeChild(windowDiv);
  1607. }
  1608. };
  1609. footerDiv.appendChild(deleteButton);
  1610.  
  1611. windowDiv.appendChild(footerDiv);
  1612.  
  1613. document.body.appendChild(windowDiv);
  1614. }
  1615.  
  1616.  
  1617.  
  1618. function deleteOpenChestData() {
  1619. let edibleToolsData = JSON.parse(localStorage.getItem('Edible_Tools'));
  1620. if (edibleToolsData && edibleToolsData.Chest_Open_Data) {
  1621. edibleToolsData.Chest_Open_Data = {};
  1622. localStorage.setItem('Edible_Tools', JSON.stringify(edibleToolsData));
  1623. }
  1624. }
  1625.  
  1626.  
  1627. // 显示箱子详细信息
  1628. function showChestDetails(chestName, chestData) {
  1629. let oldWindow = document.querySelector('.visualization-window');
  1630. if (oldWindow) {
  1631. oldWindow.remove();
  1632. }
  1633.  
  1634. let detailsWindow = createWindowBase();
  1635. let title = document.createElement('h1');
  1636. title.innerText = chestName;
  1637. detailsWindow.appendChild(title);
  1638.  
  1639. let contentDiv = document.createElement('div');
  1640. contentDiv.style.flex = '1';
  1641. contentDiv.style.overflowY = 'auto';
  1642.  
  1643. // 显示总计信息
  1644. let totalStats = document.createElement('p');
  1645. totalStats.innerText = `总计开箱数量: ${chestData['总计开箱数量']}\n总计Ask: ${formatPrice(chestData['总计开箱Ask'])}\n总计Bid: ${formatPrice(chestData['总计开箱Bid'])}`;
  1646. contentDiv.appendChild(totalStats);
  1647.  
  1648. // 添加物品信息
  1649. for (let itemName in chestData['获得物品']) {
  1650. let item = chestData['获得物品'][itemName];
  1651.  
  1652. let itemBox = document.createElement('div');
  1653. itemBox.style.display = 'flex';
  1654. itemBox.style.alignItems = 'center';
  1655. itemBox.style.border = '1px solid #000';
  1656. itemBox.style.borderRadius = '5px';
  1657. itemBox.style.marginBottom = '5px';
  1658. itemBox.style.padding = '5px';
  1659.  
  1660. // 添加图标
  1661. let svgIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
  1662. svgIcon.setAttribute('width', '24');
  1663. svgIcon.setAttribute('height', '24');
  1664. svgIcon.style.marginRight = '10px';
  1665.  
  1666. let useElement = document.createElementNS('http://www.w3.org/2000/svg', 'use');
  1667. try {
  1668. let iconId = item_name_to_hrid[itemName].split('/').pop();
  1669. useElement.setAttribute('href', `${item_icon_url}#${iconId}`);
  1670. } catch (error) {
  1671. console.error(`无法找到物品 "${itemName}" 的图标ID:`, error);
  1672. useElement.setAttribute('href', `${item_icon_url}#coin`);
  1673. }
  1674. svgIcon.appendChild(useElement);
  1675.  
  1676. // 添加物品名称和数量
  1677. let itemText = document.createElement('span');
  1678. itemText.innerText = `${itemName}: ${formatPrice(item['数量'])}`;
  1679. itemText.style.flex = '1';
  1680.  
  1681. itemBox.appendChild(svgIcon);
  1682. itemBox.appendChild(itemText);
  1683.  
  1684. contentDiv.appendChild(itemBox);
  1685. }
  1686.  
  1687. detailsWindow.appendChild(contentDiv);
  1688.  
  1689. let footerDiv = document.createElement('div');
  1690. footerDiv.style.display = 'flex';
  1691. footerDiv.style.justifyContent = 'space-between';
  1692. footerDiv.style.marginTop = '10px';
  1693.  
  1694. // 返回按钮
  1695. let backButton = document.createElement('button');
  1696. backButton.innerText = '返回';
  1697. backButton.onclick = () => {
  1698. detailsWindow.remove();
  1699. createVisualizationWindow(JSON.parse(localStorage.getItem('Edible_Tools')).Chest_Open_Data);
  1700. };
  1701. footerDiv.appendChild(backButton);
  1702.  
  1703. // 关闭按钮
  1704. let closeButton = document.createElement('button');
  1705. closeButton.innerText = '关闭';
  1706. closeButton.onclick = () => detailsWindow.remove();
  1707. footerDiv.appendChild(closeButton);
  1708.  
  1709. detailsWindow.appendChild(footerDiv);
  1710.  
  1711. document.body.appendChild(detailsWindow);
  1712. }
  1713.  
  1714.  
  1715. GM_registerMenuCommand('打印全部开箱记录', function() {
  1716. const edibleTools = JSON.parse(localStorage.getItem('Edible_Tools')) || {};
  1717. const openChestData = edibleTools.Chest_Open_Data || {};
  1718. createVisualizationWindow(openChestData);
  1719. });
  1720.  
  1721. GM_registerMenuCommand('打印掉落物列表', function() {
  1722. let dataHtml = '<div style="display: flex; flex-wrap: nowrap;">';
  1723. const minPrice = 10000;
  1724. for (let player in battlePlayerLoot) {
  1725. let totalPrice = 0;
  1726. dataHtml += `<div style="flex: 1 0 auto; min-width: 100px; margin: 10px; padding: 10px; border: 1px solid black;">`;
  1727. dataHtml += `<h3>${player}</h3>`;
  1728.  
  1729. let lootItems = battlePlayerLoot[player];
  1730. for (let item in lootItems) {
  1731. let bidPrice = getSpecialItemPrice(item,"bid") || 0;
  1732. totalPrice += bidPrice*lootItems[item].数量
  1733. if (bidPrice > minPrice) {
  1734. dataHtml += `<p>${item}: ${lootItems[item].数量}</p>`;
  1735. }
  1736. }
  1737. if (totalPrice > 0) {
  1738. dataHtml += `<p>总计价格: ${formatPrice(totalPrice)}</p>`;
  1739. }
  1740. dataHtml += '</div>';
  1741. }
  1742. dataHtml += '</div>';
  1743.  
  1744. let popup = document.createElement('div');
  1745. popup.style.position = 'fixed';
  1746. popup.style.top = '50%';
  1747. popup.style.left = '50%';
  1748. popup.style.transform = 'translate(-50%, -50%)';
  1749. popup.style.backgroundColor = 'white';
  1750. popup.style.border = '1px solid black';
  1751. popup.style.padding = '10px';
  1752. popup.style.zIndex = '10000';
  1753. popup.style.maxWidth = '75%';
  1754. popup.style.overflowX = 'auto';
  1755. popup.style.whiteSpace = 'nowrap';
  1756. popup.innerHTML = dataHtml;
  1757.  
  1758. let closeButton = document.createElement('button');
  1759. closeButton.textContent = '关闭';
  1760. closeButton.style.display = 'block';
  1761. closeButton.style.margin = '20px auto 0 auto';
  1762. closeButton.onclick = function() {
  1763. document.body.removeChild(popup);
  1764. };
  1765. popup.appendChild(closeButton);
  1766.  
  1767. document.body.appendChild(popup);
  1768. });
  1769.  
  1770. function formatToChinesetime(timestamp) {
  1771. const date = new Date(timestamp);
  1772. const beijingOffset = 8 * 60;
  1773. date.setMinutes(date.getMinutes() + date.getTimezoneOffset() + beijingOffset);
  1774.  
  1775. const year = date.getFullYear();
  1776. const month = String(date.getMonth() + 1).padStart(2, '0');
  1777. const day = String(date.getDate()).padStart(2, '0');
  1778. const hours = String(date.getHours()).padStart(2, '0');
  1779. const minutes = String(date.getMinutes()).padStart(2, '0');
  1780.  
  1781. return `${year}/${month}/${day} ${hours}:${minutes}`;
  1782. }
  1783.  
  1784. function openSettings() {
  1785. const tran_market_list = {
  1786. "/market_listing_status/filled": "已完成",
  1787. "/market_listing_status/active": "进行中",
  1788. "/market_listing_status/cancelled": "取消",
  1789. "/market_listing_status/expired":"超时",
  1790. };
  1791. const market_List_Data = JSON.parse(GM_getValue('market_list', '[]'));
  1792. const hrid2name = item_hrid_to_name;
  1793.  
  1794. // 格式化数据
  1795. market_List_Data.forEach(item => {
  1796. item.itemName = hrid2name[item.itemHrid] || item.itemHrid;
  1797. if (item.enhancementLevel > 0) {
  1798. item.itemName = `${item.itemName} +${item.enhancementLevel}`;
  1799. }
  1800. item.status = tran_market_list[item.status] || item.status;
  1801. if (item.lastUpdated) {
  1802. item.format_lastUpdated = formatToChinesetime(item.lastUpdated);
  1803. }
  1804. });
  1805.  
  1806. const settingsContainer = document.createElement('div');
  1807. settingsContainer.style.position = 'fixed';
  1808. settingsContainer.style.top = '0';
  1809. settingsContainer.style.left = '0';
  1810. settingsContainer.style.width = '100%';
  1811. settingsContainer.style.height = '100%';
  1812. settingsContainer.style.backgroundColor = 'rgba(0, 0, 0, 0.7)';
  1813. settingsContainer.style.zIndex = '9999';
  1814. settingsContainer.style.display = 'flex';
  1815. settingsContainer.style.flexDirection = 'column';
  1816.  
  1817. // 页面内容
  1818. const Edible_Tools_HTML = `
  1819. <div style="flex: 1; overflow-y: auto; background-color: #fff; padding: 20px;">
  1820. <header style="background-color: #4CAF50; color: white; padding: 10px 20px; text-align: center;">
  1821. <h1>银河奶牛数据库</h1>
  1822. </header>
  1823. <div style="display: flex; flex: 1;">
  1824. <div style="width: 200px; background-color: #f4f4f4; padding: 10px;">
  1825. <button id="showMarketDataBtn" style="width: 100%; padding: 10px; margin: 5px 0; background-color: #ddd; border: none;">市场数据</button>
  1826. <button id="showOpenChestDataBtn" style="width: 100%; padding: 10px; margin: 5px 0; background-color: #ddd; border: none;">开箱数据</button>
  1827. <button id="showEnhancementDataBtn" style="width: 100%; padding: 10px; margin: 5px 0; background-color: #ddd; border: none;">强化数据</button>
  1828. </div>
  1829. <div style="flex: 1; padding: 20px; overflow-y: auto; display: block;" id="showMarketDataPage">
  1830. <h2 style="text-align: center;">市场数据</h2>
  1831. <table class="marketList-table" style="width: 100%; border-collapse: collapse;">
  1832. <thead>
  1833. <tr>
  1834. <th data-sort="id">订单ID</th>
  1835. <th data-sort="characterID">角色ID</th>
  1836. <th data-sort="status">状态</th>
  1837. <th data-sort="isSell">类型</th>
  1838. <th data-sort="itemName">物品</th>
  1839. <th data-sort="orderQuantity">数量</th>
  1840. <th data-sort="filledQuantity">已交易数量</th>
  1841. <th data-sort="price">单价</th>
  1842. <th data-sort="total">贸易额</th>
  1843. <th data-sort="format_lastUpdated">更新时间</th>
  1844. <th>操作</th>
  1845. </tr>
  1846. </thead>
  1847. <tbody id="marketDataTableBody">
  1848. <!-- 数据表会在这里插入 -->
  1849. </tbody>
  1850. </table>
  1851. </div>
  1852. <div style="flex: 1; padding: 20px; overflow-y: auto; display: none;" id="OpenChestDataPage">
  1853. <h2 style="text-align: center;">开箱数据(咕?)</h2>
  1854. </div>
  1855. <div style="flex: 1; padding: 20px; overflow-y: auto; display: none;" id="EnhancementDataPage">
  1856. <h2 style="text-align: center;">强化数据(咕咕~)</h2>
  1857. </div>
  1858. </div>
  1859. </div>
  1860. <button id="closeSettingsBtn" style="position: absolute; top: 10px; right: 10px; padding: 10px 20px; background-color: red; color: white; border: none;">关闭</button>
  1861. `;
  1862. settingsContainer.innerHTML = Edible_Tools_HTML;
  1863. document.body.appendChild(settingsContainer);
  1864. const marketDataPage = document.getElementById('showMarketDataPage');
  1865. const OpenChestDataPage = document.getElementById('OpenChestDataPage');
  1866. const EnhancementDataPage = document.getElementById('EnhancementDataPage');
  1867.  
  1868.  
  1869. function showMarketData() {
  1870. marketDataPage.style.display = 'block';
  1871. OpenChestDataPage.style.display = 'none';
  1872. EnhancementDataPage.style.display = 'none';
  1873.  
  1874. const tableBody = document.getElementById('marketDataTableBody');
  1875. tableBody.innerHTML = market_List_Data.map((row, index) => {
  1876. // 创建图标
  1877. let svgIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
  1878. svgIcon.setAttribute('width', '20');
  1879. svgIcon.setAttribute('height', '20');
  1880. svgIcon.style.marginRight = '10px';
  1881. svgIcon.style.verticalAlign = 'middle';
  1882.  
  1883. let useElement = document.createElementNS('http://www.w3.org/2000/svg', 'use');
  1884. try {
  1885. let iconId = row.itemHrid.split('/').pop();
  1886. useElement.setAttribute('href', `${item_icon_url}#${iconId}`);
  1887. } catch (error) {
  1888. console.error(`无法找到物品的图标ID:`, error);
  1889. useElement.setAttribute('href', `${item_icon_url}#coin`);
  1890. }
  1891.  
  1892. svgIcon.appendChild(useElement);
  1893.  
  1894. let itemNameWithIcon = `${svgIcon.outerHTML}${row.itemName}`;
  1895.  
  1896. return `
  1897. <tr data-index="${index}">
  1898. <td>${row.id}</td>
  1899. <td>${row.characterID}</td>
  1900. <td>${row.status}</td>
  1901. <td>${row.isSell ? '出售' : '收购'}</td>
  1902. <td>${itemNameWithIcon}</td>
  1903. <td>${(row.orderQuantity).toLocaleString()}</td>
  1904. <td>${(row.filledQuantity).toLocaleString()}</td>
  1905. <td>${(row.price).toLocaleString()}</td>
  1906. <td>${(row.price * row.filledQuantity).toLocaleString()}</td>
  1907. <td>${row.format_lastUpdated}</td>
  1908. <td><button class="delete-btn">删除</button></td>
  1909. </tr>
  1910. `;
  1911. }).join('');
  1912. }
  1913.  
  1914.  
  1915. function ShowOpenChestData() {
  1916. marketDataPage.style.display = 'none';
  1917. OpenChestDataPage.style.display = 'block';
  1918. EnhancementDataPage.style.display = 'none';
  1919. }
  1920.  
  1921. function ShowEnhancementData() {
  1922. marketDataPage.style.display = 'none';
  1923. OpenChestDataPage.style.display = 'none';
  1924. EnhancementDataPage.style.display = 'block';
  1925. }
  1926.  
  1927. showMarketData();
  1928.  
  1929. // 删除单行
  1930. function attachDeleteListeners() {
  1931. document.querySelectorAll('.delete-btn').forEach(button => {
  1932. button.addEventListener('click', (event) => {
  1933. const row = event.target.closest('tr');
  1934. const index = row.getAttribute('data-index');
  1935. market_List_Data.splice(index, 1);
  1936.  
  1937. GM_setValue('market_list', JSON.stringify(market_List_Data));// 更新存储的数据
  1938. showMarketData();// 重新渲染表格
  1939. attachDeleteListeners();// 重新绑定删除按钮事件
  1940. });
  1941. });
  1942. }
  1943.  
  1944. attachDeleteListeners();// 初始绑定删除按钮事件
  1945.  
  1946. // 排序功能
  1947. let sortOrder = { field: null, direction: 1 };// 1 是升序,-1 是降序
  1948.  
  1949. function sortTable(column) {
  1950. const field = column.getAttribute('data-sort');
  1951. const direction = sortOrder.field === field && sortOrder.direction === 1 ? -1 : 1;// 切换排序方向
  1952.  
  1953. market_List_Data.sort((a, b) => {
  1954. if (field === 'total') {
  1955. return (a.price * a.filledQuantity - b.price * b.filledQuantity) * direction;
  1956. }
  1957. if (typeof a[field] === 'string') {
  1958. return (a[field].localeCompare(b[field])) * direction;
  1959. }
  1960. return (a[field] - b[field]) * direction;
  1961. });
  1962.  
  1963. // 更新排序状态
  1964. document.querySelectorAll('th').forEach(th => {
  1965. th.classList.remove('sort-asc', 'sort-desc');
  1966. });
  1967. column.classList.add(direction === 1 ? 'sort-asc' : 'sort-desc');
  1968.  
  1969. sortOrder = { field, direction };
  1970.  
  1971. showMarketData();
  1972. attachDeleteListeners();
  1973. }
  1974.  
  1975. // 给每个表头添加点击事件监听器
  1976. document.querySelectorAll('th').forEach(th => {
  1977. th.addEventListener('click', () => {
  1978. sortTable(th);
  1979. });
  1980. });
  1981. // 切换数据库页面
  1982. document.getElementById('showMarketDataBtn').addEventListener('click', showMarketData);
  1983. document.getElementById('showOpenChestDataBtn').addEventListener('click', ShowOpenChestData);
  1984. document.getElementById('showEnhancementDataBtn').addEventListener('click', ShowEnhancementData);
  1985. // 关闭按钮
  1986. document.getElementById('closeSettingsBtn').addEventListener('click', () => {
  1987. document.body.removeChild(settingsContainer);
  1988. });
  1989.  
  1990. // 表格样式
  1991. const style = document.createElement('style');
  1992. style.innerHTML = `
  1993. .marketList-table {
  1994. width: 100%;
  1995. border-collapse: collapse;
  1996. }
  1997.  
  1998. .marketList-table, .marketList-table th, .marketList-table td {
  1999. border: 1px solid #ddd;
  2000. }
  2001.  
  2002. .marketList-table th, .marketList-table td {
  2003. padding: 10px;
  2004. text-align: center;
  2005. }
  2006.  
  2007. .marketList-table th {
  2008. background-color: #f2f2f2;
  2009. cursor: pointer;
  2010. }
  2011.  
  2012. .marketList-table th.sort-asc::after {
  2013. content: ' ▲';
  2014. }
  2015.  
  2016. .marketList-table th.sort-desc::after {
  2017. content: ' ▼';
  2018. }
  2019. `;
  2020. document.head.appendChild(style);
  2021. }
  2022.  
  2023.  
  2024.  
  2025. GM_registerMenuCommand('删除过时市场数据', deleteOrdersBeforeDate);
  2026. })();