// ==UserScript==
// @name [银河奶牛]食用工具
// @namespace http://tampermonkey.net/
// @version 0.443
// @description 开箱记录、箱子期望、离线统计、公会钉钉、食物警察、掉落追踪
// @author Truth_Light
// @license Truth_Light
// @match https://www.milkywayidle.com/*
// @match https://test.milkywayidle.com/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=milkywayidle.com
// @grant GM.xmlHttpRequest
// @grant GM_registerMenuCommand
// @grant GM_openInTab
// @grant GM_setValue
// @grant GM_getValue
// ==/UserScript==
(async function() {
'use strict';
const itemSelector = '.ItemDictionary_drop__24I5f';
const iconSelector = '.Icon_icon__2LtL_ use';
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';
const MARKET_API_URL = "https://raw.githubusercontent.com/holychikenz/MWIApi/main/medianmarket.json";
let marketData = null;
let timer = null;
let formattedChestDropData = {};
let battlePlayerFood = {};
let battlePlayerLoot = {};
let item_icon_url
const init_Client_Data = localStorage.getItem('initClientData');
if (!init_Client_Data) return;
let init_Client_Data_;
try {
init_Client_Data_ = JSON.parse(init_Client_Data);
} catch (error) {
console.error('数据解析失败:', error);
return;
}
if (init_Client_Data_.type !== 'init_client_data') return;
const item_hrid_to_name = init_Client_Data_.itemDetailMap;
for (const key in item_hrid_to_name) {
if (item_hrid_to_name[key] && typeof item_hrid_to_name[key] === 'object' && item_hrid_to_name[key].name) {
item_hrid_to_name[key] = item_hrid_to_name[key].name;
}
}
const item_name_to_hrid = Object.fromEntries(
Object.entries(item_hrid_to_name).map(([key, value]) => [value, key])
);
const MWITools_marketAPI_json = JSON.parse(localStorage.getItem('MWITools_marketAPI_json'));
async function fetchMarketData() {
return new Promise((resolve, reject) => {
GM.xmlHttpRequest({
method: 'GET',
url: MARKET_API_URL,
responseType: 'json',
timeout: 5000,
onload: function(response) {
if (response.status === 200) {
const data = JSON.parse(response.responseText);
console.log('从API获取到的数据:', data);
resolve(data);
} else {
console.error('获取数据失败。状态码:', response.status);
reject(new Error('数据获取失败'));
}
},
ontimeout: function() {
console.error('请求超时:超过5秒未能获取到数据');
reject(new Error('请求超时'));
},
onerror: function(error) {
console.error('获取数据时发生错误:', error);
reject(error);
}
});
});
}
try {
// 尝试从 API 获取数据
marketData = await fetchMarketData();
} catch (error) {
console.error('从 API 获取数据失败,尝试从本地存储获取数据。', error);
// 从本地存储获取数据
const marketDataStr = localStorage.getItem('MWITools_marketAPI_json');
marketData = JSON.parse(marketDataStr);
if (!marketData) {
alert('无法获取 market 数据');
} else {
console.log('从本地存储获取到的数据:', marketData);
}
}
function getSpecialItemPrice(itemName, priceType) {
if (marketData?.market?.[itemName]) {
const itemPrice = marketData.market[itemName][priceType];
if (itemPrice !== undefined && itemPrice !== -1) {
return itemPrice;
}
}
if (MWITools_marketAPI_json?.market?.[itemName]) {
const itemPrice = MWITools_marketAPI_json.market[itemName][priceType];
if (itemPrice !== undefined && itemPrice !== -1) {
return itemPrice;
}
}
console.error(`未找到物品 ${itemName} 的 ${priceType} 价格信息`);
return null;
}
let specialItemPrices = {
'Coin': { ask: 1, bid: 1 }, // 默认的特殊物品价值,包括 ask 和 bid 价值
'Cowbell': {
ask: getSpecialItemPrice('Bag Of 10 Cowbells', 'ask') / 10 || 50000,
bid: getSpecialItemPrice('Bag Of 10 Cowbells', 'bid') / 10 || 50000
},
'Chimerical Token': {
ask: getSpecialItemPrice('Chimerical Essence', 'ask') || 3000,
bid: getSpecialItemPrice('Chimerical Essence', 'bid') || 2850
},
'Sinister Token': {
ask: getSpecialItemPrice('Sinister Essence', 'ask') || 3000,
bid: getSpecialItemPrice('Sinister Essence', 'bid') || 2900
},
'Enchanted Token': {
ask: getSpecialItemPrice('Enchanted Essence', 'ask') || 4000,
bid: getSpecialItemPrice('Enchanted Essence', 'bid') || 4000
},
};
function getItemNameFromElement(element) {
const itemNameRaw = element.getAttribute('href').split('#').pop();
return formatItemName(itemNameRaw);
}
function formatItemName(itemNameRaw) {
return item_hrid_to_name[`/items/${itemNameRaw}`]
}
function formatPrice(value) {
const isNegative = value < 0;
value = Math.abs(value);
if (value >= 1e13) {
return (isNegative ? '-' : '') + (value / 1e12).toFixed(1) + 'T';
} else if (value >= 1e10) {
return (isNegative ? '-' : '') + (value / 1e9).toFixed(1) + 'B';
} else if (value >= 1e7) {
return (isNegative ? '-' : '') + (value / 1e6).toFixed(1) + 'M';
} else if (value >= 1e4) {
return (isNegative ? '-' : '') + (value / 1e3).toFixed(1) + 'K';
} else {
return (isNegative ? '-' : '') + value.toFixed(0);
}
}
function parseQuantityString(quantityStr) {
const suffix = quantityStr.slice(-1);
const base = parseFloat(quantityStr.slice(0, -1));
if (suffix === 'K') {
return base * 1000;
} else if (suffix === 'M') {
return base * 1000000;
} else if (suffix === 'B') {
return base * 1000000000;
} else {
return parseFloat(quantityStr);
}
}
function recordChestOpening(modalElement) {
if (document.querySelector('.ChestStatistics')) {
return;
}
// 从本地存储读取数据
let edibleTools = JSON.parse(localStorage.getItem('Edible_Tools')) || {};
edibleTools.Chest_Open_Data = edibleTools.Chest_Open_Data || {};
let chestOpenData = edibleTools.Chest_Open_Data;
const chestDropData = edibleTools.Chest_Drop_Data;
const chestNameElement = modalElement.querySelector("div.Modal_modal__1Jiep > div.Inventory_modalContent__3ObSx > div.Item_itemContainer__x7kH1 > div > div > div.Item_iconContainer__5z7j4 > svg > use");
const chestCountElement = modalElement.querySelector("div.Modal_modal__1Jiep > div.Inventory_modalContent__3ObSx > div.Item_itemContainer__x7kH1 > div > div > div.Item_count__1HVvv");
if (chestNameElement && chestCountElement) {
const chestName = getItemNameFromElement(chestNameElement);
chestOpenData[chestName] = chestOpenData[chestName] || {};
let chestData = chestOpenData[chestName];
const chestCount = parseQuantityString(chestCountElement.textContent.trim());
chestData["总计开箱数量"] = (chestData["总计开箱数量"] || 0) + chestCount;
chestData["获得物品"] = chestData["获得物品"] || {};
const itemsContainer = modalElement.querySelector('.Inventory_gainedItems___e9t9');
const itemElements = itemsContainer.querySelectorAll('.Item_itemContainer__x7kH1');
let totalAskValue = 0;
let totalBidValue = 0;
itemElements.forEach(itemElement => {
const itemNameElement = itemElement.querySelector('.Item_iconContainer__5z7j4 use');
const itemQuantityElement = itemElement.querySelector('.Item_count__1HVvv');
if (itemNameElement && itemQuantityElement) {
const itemName = getItemNameFromElement(itemNameElement);
const itemQuantity = parseQuantityString(itemQuantityElement.textContent.trim());
const itemData = chestDropData[chestName].item[itemName] || {};
const itemAskValue = itemData["出售单价"] || 0;
const itemBidValue = itemData["收购单价"] || 0;
const color = itemData.Color || '';
itemQuantityElement.style.color = color;
const itemOpenTotalAskValue = itemAskValue * itemQuantity;
const itemOpenTotalBidValue = itemBidValue * itemQuantity;
chestData["获得物品"][itemName] = chestData["获得物品"][itemName] || {};
chestData["获得物品"][itemName]["数量"] = (chestData["获得物品"][itemName]["数量"] || 0) + itemQuantity;
chestData["获得物品"][itemName]["总计Ask价值"] = (chestData["获得物品"][itemName]["总计Ask价值"] || 0) + itemOpenTotalAskValue;
chestData["获得物品"][itemName]["总计Bid价值"] = (chestData["获得物品"][itemName]["总计Bid价值"] || 0) + itemOpenTotalBidValue;
totalAskValue += itemOpenTotalAskValue;
totalBidValue += itemOpenTotalBidValue;
}
});
chestData["总计开箱Ask"] = (chestData["总计开箱Ask"] || 0) + totalAskValue;
chestData["总计开箱Bid"] = (chestData["总计开箱Bid"] || 0) + totalBidValue;
// 计算本次开箱的偏差值
const differenceValue = totalBidValue - chestDropData[chestName]["期望产出Bid"] * chestCount;
// 更新累计偏差值
chestData["累计偏差值"] = (chestData["累计偏差值"] || 0) + differenceValue;
// 地牢开箱
let profitRange = null;
let profitColor = 'lime'; // 默认颜色
const chestCosts = {
"Chimerical Chest": {
keyAsk: getSpecialItemPrice('Chimerical Chest Key', 'ask') || 2700e3,
keyBid: getSpecialItemPrice('Chimerical Chest Key', 'bid') || 2600e3,
entryAsk: getSpecialItemPrice('Chimerical Entry Key', 'ask') || 350e3,
entryBid: getSpecialItemPrice('Chimerical Entry Key', 'bid') || 320e3
},
"Sinister Chest": {
keyAsk: getSpecialItemPrice('Sinister Chest Key', 'ask') || 3800e3,
keyBid: getSpecialItemPrice('Sinister Chest Key', 'bid') || 3500e3,
entryAsk: getSpecialItemPrice('Sinister Entry Key', 'ask') || 450e3,
entryBid: getSpecialItemPrice('Sinister Entry Key', 'bid') || 400e3
},
"Enchanted Chest": {
keyAsk: getSpecialItemPrice('Enchanted Chest Key', 'ask') || 5600e3,
keyBid: getSpecialItemPrice('Enchanted Chest Key', 'bid') || 5400e3,
entryAsk: getSpecialItemPrice('Enchanted Entry Key', 'ask') || 420e3,
entryBid: getSpecialItemPrice('Enchanted Entry Key', 'bid') || 400e3
}
};
if (chestCosts[chestName]) {
const { keyAsk, keyBid, entryAsk, entryBid } = chestCosts[chestName];
const minProfit = totalBidValue - (keyAsk + entryAsk) * chestCount;
const maxProfit = totalAskValue - (keyBid + entryBid) * chestCount;
profitRange = `${formatPrice(minProfit)}~${formatPrice(maxProfit)}`;
if (minProfit > 0 && maxProfit > 0) {
profitColor = 'lime';
} else if (minProfit < 0 && maxProfit < 0) {
profitColor = 'red';
} else {
profitColor = 'orange';
}
}
// 显示
const openChestElement = document.querySelector('.Inventory_modalContent__3ObSx');
const displayElement = document.createElement('div');
displayElement.classList.add('ChestStatistics');
displayElement.style.position = 'absolute';
displayElement.style.left = `${openChestElement.offsetLeft}px`;
displayElement.style.top = `${openChestElement.offsetTop}px`;
displayElement.style.fontSize = '12px';
displayElement.innerHTML = `
总计开箱次数:<br>
${chestData["总计开箱数量"]}<br>
本次开箱价值:<br>
${formatPrice(totalAskValue)}/${formatPrice(totalBidValue)}<br>
总计开箱价值:<br>
${formatPrice(chestData["总计开箱Ask"])}/${formatPrice(chestData["总计开箱Bid"])}<br>
`;
const expectedOutputElement = document.createElement('div');
expectedOutputElement.classList.add('ExpectedOutput');
expectedOutputElement.style.position = 'absolute';
expectedOutputElement.style.left = `${openChestElement.offsetLeft}px`;
expectedOutputElement.style.bottom = `${openChestElement.offsetTop}px`;
expectedOutputElement.style.fontSize = '12px';
expectedOutputElement.innerHTML = `
预计产出价值:<br>
${formatPrice(chestDropData[chestName]["期望产出Ask"]*chestCount)}/${formatPrice(chestDropData[chestName]["期望产出Bid"]*chestCount)}<br>
`;
const differenceOutputElement = document.createElement('div');
differenceOutputElement.classList.add('DifferenceOutput');
differenceOutputElement.style.position = 'absolute';
differenceOutputElement.style.right = `${openChestElement.offsetLeft}px`;
differenceOutputElement.style.bottom = `${openChestElement.offsetTop}px`;
differenceOutputElement.style.fontSize = '12px';
differenceOutputElement.style.color = differenceValue > 0 ? 'lime' : 'red';
differenceOutputElement.innerHTML = `
${differenceValue > 0 ? '高于期望价值:' : '低于期望价值:'}<br>
${formatPrice(Math.abs(differenceValue))}<br>
`;
// 创建并显示累计偏差值的元素
const cumulativeDifferenceElement = document.createElement('div');
cumulativeDifferenceElement.classList.add('CumulativeDifference');
cumulativeDifferenceElement.style.position = 'absolute';
cumulativeDifferenceElement.style.right = `${openChestElement.offsetLeft}px`;
cumulativeDifferenceElement.style.top = `${openChestElement.offsetTop}px`;
cumulativeDifferenceElement.style.fontSize = '12px';
cumulativeDifferenceElement.style.color = chestData["累计偏差值"] > 0 ? 'lime' : 'red';
cumulativeDifferenceElement.innerHTML = `
<br><br>
<span style="color: ${profitColor};">本次开箱利润</span><br>
${profitRange ? `<span style="color: ${profitColor};">${profitRange}</span>` : `<span style="color: ${profitColor};">${formatPrice(totalAskValue)}/${formatPrice(totalBidValue)}</span>`}<br>
累计${chestData["累计偏差值"] > 0 ? '高于期望:' : '低于期望:'}<br>
${formatPrice(Math.abs(chestData["累计偏差值"]))}<br>
`;
openChestElement.appendChild(displayElement);
openChestElement.appendChild(expectedOutputElement);
openChestElement.appendChild(differenceOutputElement);
openChestElement.appendChild(cumulativeDifferenceElement);
// 保存更新的数据到本地存储
localStorage.setItem('Edible_Tools', JSON.stringify(edibleTools));
}
}
function calculateTotalValues(itemElements) {
let totalAskValue = 0;
let totalBidValue = 0;
itemElements.forEach(itemElement => {
const itemNameElement = itemElement.querySelector('.Item_iconContainer__5z7j4 use');
const itemQuantityElement = itemElement.querySelector('.Item_count__1HVvv');
if (itemNameElement && itemQuantityElement) {
const itemName = getItemNameFromElement(itemNameElement);
const itemQuantity = parseQuantityString(itemQuantityElement.textContent.trim());
let askPrice = 0;
let bidPrice = 0;
let priceColor = '';
// 获取价格
if (specialItemPrices[itemName] && specialItemPrices[itemName].ask) {
askPrice = parseFloat(specialItemPrices[itemName].ask);
bidPrice = parseFloat(specialItemPrices[itemName].bid);
priceColor = '';
} else if (marketData?.market?.[itemName]) {
bidPrice = marketData.market[itemName].bid;
askPrice = marketData.market[itemName].ask;
} else if (MWITools_marketAPI_json?.market?.[itemName]) {
bidPrice = MWITools_marketAPI_json.market[itemName].bid;
askPrice = MWITools_marketAPI_json.market[itemName].ask;
} else {
console.log(`${itemName} 的价格未找到`);
}
const itemTotalAskValue = askPrice * itemQuantity;
const itemTotalBidValue = bidPrice * itemQuantity;
totalAskValue += itemTotalAskValue;
totalBidValue += itemTotalBidValue;
}
});
//console.log(totalAskValue);
return { totalAskValue, totalBidValue };
}
//更详细的战斗等级显示
const updateCombatLevel = () => {
const elements = document.querySelectorAll(".NavigationBar_currentExperience__3GDeX");
if (elements.length === 17) {
const levels = Array.from(elements).slice(10, 17).map(el => {
const levelText = parseInt(el.parentNode.parentNode.querySelector(".NavigationBar_textContainer__7TdaI .NavigationBar_level__3C7eR").textContent);
const decimalPart = parseFloat(el.style.width) / 100;
return { integerPart: levelText, decimalPart: decimalPart };
});
let [endurance, intelligence, attack, strength, defense, ranged, magic] = levels;
let combatTypeMax = Math.max(
0.5 * (attack.integerPart + strength.integerPart),
ranged.integerPart,
magic.integerPart
);
if (combatTypeMax !== 0.5 * (attack.integerPart + strength.integerPart)) {
attack.decimalPart = 0;
strength.decimalPart = 0;
}
if (combatTypeMax !== ranged.integerPart) ranged.decimalPart = 0;
if (combatTypeMax !== magic.integerPart) magic.decimalPart = 0;
let combatLevel = 0.2 * (endurance.integerPart + intelligence.integerPart + defense.integerPart) + 0.4 * combatTypeMax;
combatLevel = parseFloat(combatLevel.toFixed(2));
//console.log("combatLevel",combatLevel)
const integerPart = Math.floor(combatLevel);
const decimalPart = combatLevel - integerPart;
//console.log("integerPart",integerPart)
const list1 = [
endurance.decimalPart * 0.2,
intelligence.decimalPart * 0.2,
attack.decimalPart * 0.2,
strength.decimalPart * 0.2,
defense.decimalPart * 0.2,
ranged.decimalPart * 0.2,
magic.decimalPart * 0.2
];
const list2 = [
endurance.decimalPart * 0.2,
intelligence.decimalPart * 0.2,
attack.decimalPart * 0.2,
strength.decimalPart * 0.2,
defense.decimalPart * 0.2,
ranged.decimalPart * 0.2,
magic.decimalPart * 0.2,
ranged.decimalPart * 0.2,
magic.decimalPart * 0.2
];
//console.log("list1",list1,"\nlist2",list2)
list1.sort((a, b) => b - a);
list2.sort((a, b) => b - a);
if (decimalPart === 0.8) {
combatLevel += list1[0];
} else {
let total = 0;
const maxIterations = Math.floor((1 - decimalPart) / 0.2);
let iterations = 0;
for (const i of list2) {
if (iterations >= maxIterations) break;
if ((decimalPart + total + i) < 1) {
total += i;
} else {
break;
}
iterations++;
}
combatLevel = decimalPart + integerPart + total;
}
elements[15].parentNode.parentNode.parentNode.parentNode.parentNode.querySelector(".NavigationBar_nav__3uuUl .NavigationBar_level__3C7eR").textContent = combatLevel.toFixed(2);
}
};
window.setInterval(updateCombatLevel, 10000);
function OfflineStatistics(modalElement) {
const itemsContainer = modalElement.querySelectorAll(".OfflineProgressModal_itemList__26h-Y");
let timeContainer = null;
let getItemContainer = null;
let spendItemContainer = null;
itemsContainer.forEach(container => {
const labelElement = container.querySelector('.OfflineProgressModal_label__2HwFG');
if (labelElement) {
const textContent = labelElement.textContent.trim();
if (textContent.startsWith("You were offline for") || textContent.startsWith("你离线了")) {
timeContainer = container;
} else if (textContent.startsWith("Items gained:") || textContent.startsWith("获得物品:")) {
getItemContainer = container;
} else if (textContent.startsWith("You consumed:") || textContent.startsWith("你消耗了:")) {
spendItemContainer = container;
}
}
});
let TotalSec = null;
if (timeContainer) {
const textContent = timeContainer.textContent;
const match = textContent.match(/(?:(\d+)d\s*)?(?:(\d+)h\s*)?(?:(\d+)m\s*)?(?:(\d+)s)/);
if (match) {
let days = parseInt(match[1], 10) || 0;
let hours = parseInt(match[2], 10) || 0;
let minutes = parseInt(match[3], 10) || 0;
let seconds = parseInt(match[4], 10) || 0;
TotalSec = days * 86400 + hours * 3600 + minutes * 60 + seconds;
}
}
let getitemtotalAskValue = 0;
let getitemtotalBidValue = 0;
if (getItemContainer) {
const getitemElements = getItemContainer.querySelectorAll('.Item_itemContainer__x7kH1');
const { totalAskValue, totalBidValue } = calculateTotalValues(getitemElements);
getitemtotalAskValue = totalAskValue;
getitemtotalBidValue = totalBidValue;
}
let spenditemtotalAskValue = 0;
let spenditemtotalBidValue = 0;
if (spendItemContainer) {
const spenditemElements = spendItemContainer.querySelectorAll('.Item_itemContainer__x7kH1');
const { totalAskValue, totalBidValue } = calculateTotalValues(spenditemElements);
spenditemtotalAskValue = totalAskValue;
spenditemtotalBidValue = totalBidValue;
}
if (timeContainer) {
const newElement = document.createElement('span');
newElement.textContent = `利润: ${formatPrice(getitemtotalBidValue - spenditemtotalAskValue)} [${formatPrice((getitemtotalBidValue - spenditemtotalAskValue) / (TotalSec / 3600) * 24)}/天]`;
newElement.style.float = 'right';
newElement.style.color = 'gold';
timeContainer.querySelector(':first-child').appendChild(newElement);
}
if (getItemContainer) {
const newElement = document.createElement('span');
newElement.textContent = `产出:[${formatPrice(getitemtotalAskValue)}/${formatPrice(getitemtotalBidValue)}]`;
newElement.style.float = 'right';
newElement.style.color = 'gold';
getItemContainer.querySelector(':first-child').appendChild(newElement);
}
if (spendItemContainer) {
const newElement = document.createElement('span');
newElement.textContent = `成本:[${formatPrice(spenditemtotalAskValue)}/${formatPrice(spenditemtotalBidValue)}]`;
newElement.style.float = 'right';
newElement.style.color = 'gold';
spendItemContainer.querySelector(':first-child').appendChild(newElement);
}
}
function initObserver() {
// 选择要观察的目标节点
const targetNode = document.body;
// 观察器的配置(需要观察子节点的变化)
const config = { childList: true, subtree: true };
// 创建一个观察器实例并传入回调函数
const observer = new MutationObserver(mutationsList => {
for (let mutation of mutationsList) {
if (mutation.type === 'childList') {
// 监听到子节点变化
mutation.addedNodes.forEach(addedNode => {
// 检查是否是我们关注的 Modal_modalContainer__3B80m 元素被添加
if (addedNode.classList && addedNode.classList.contains('Modal_modalContainer__3B80m')) {
// Modal_modalContainer__3B80m 元素被添加,执行处理函数
//console.log("箱子被打开")
ShowChestPrice();
recordChestOpening(addedNode);
// 开始监听箱子图标的变化
startIconObserver();
}
if (addedNode.classList && addedNode.classList.contains('OfflineProgressModal_modalContainer__knnk7')) {
OfflineStatistics(addedNode);
console.log("离线报告已创建!")
}
if (addedNode.classList && addedNode.classList.contains('MainPanel_subPanelContainer__1i-H9')) {
if (addedNode.querySelector(".CombatPanel_combatPanel__QylPo")) {
addBattlePlayerFoodButton();
addBattlePlayerLootButton();
}
}
});
mutation.removedNodes.forEach(removedNode => {
// 检查是否是 Modal_modalContainer__3B80m 元素被移除
if (removedNode.classList && removedNode.classList.contains('Modal_modalContainer__3B80m')) {
// Modal_modalContainer__3B80m 元素被移除,停止监听箱子图标的变化
stopIconObserver();
}
});
}
}
});
// 以上述配置开始观察目标节点
observer.observe(targetNode, config);
// 定义箱子图标变化的观察器
let iconObserver = null;
// 开始监听箱子图标的变化
function startIconObserver() {
const chestNameElem = document.querySelector(chestNameSelector);
if (!chestNameElem) return;
// 创建一个观察器实例来监听图标的变化
iconObserver = new MutationObserver(() => {
// 当箱子图标变化时,执行处理函数
ShowChestPrice();
});
// 配置观察器的选项
const iconConfig = { attributes: true, attributeFilter: ['href'] };
// 以上述配置开始观察箱子图标节点
iconObserver.observe(chestNameElem, iconConfig);
}
// 停止监听箱子图标的变化
function stopIconObserver() {
if (iconObserver) {
iconObserver.disconnect();
iconObserver = null;
}
}
}
initObserver();
//公会部分代码
const userLanguage = navigator.language || navigator.userLanguage;
const isZH = userLanguage.startsWith("zh");
const updataDealy = 24*60*60*1000; //数据更新时限
let rateXPDayMap = {};
function hookWS() {
const dataProperty = Object.getOwnPropertyDescriptor(MessageEvent.prototype, "data");
const oriGet = dataProperty.get;
dataProperty.get = hookedGet;
Object.defineProperty(MessageEvent.prototype, "data", dataProperty);
function hookedGet() {
const socket = this.currentTarget;
if (!(socket instanceof WebSocket)) {
return oriGet.call(this);
}
if (socket.url.indexOf("api.milkywayidle.com/ws") <= -1 && socket.url.indexOf("api-test.milkywayidle.com/ws") <= -1) {
return oriGet.call(this);
}
const message = oriGet.call(this);
Object.defineProperty(this, "data", { value: message }); // Anti-loop
return handleMessage(message);
}
}
function addStatisticsButton() {
const waitForNavi = () => {
const targetNode = document.querySelector("div.NavigationBar_minorNavigationLinks__dbxh7"); // 确认这个选择器是否适合你的环境
if (targetNode) {
// 创建统计窗口按钮
let statsButton = document.createElement("div");
statsButton.setAttribute("class", "NavigationBar_minorNavigationLink__31K7Y");
statsButton.style.color = "gold";
statsButton.innerHTML = isZH ? "开箱统计" : "Chest Statistics";
statsButton.addEventListener("click", () => {
const edibleTools = JSON.parse(localStorage.getItem('Edible_Tools')) || {};
const openChestData = edibleTools.Chest_Open_Data || {};
createVisualizationWindow(openChestData);
});
// 创建食用工具按钮
let edibleToolsButton = document.createElement("div");
edibleToolsButton.setAttribute("class", "NavigationBar_minorNavigationLink__31K7Y");
edibleToolsButton.style.color = "gold";
edibleToolsButton.innerHTML = "食用工具";
edibleToolsButton.addEventListener("click", () => {
openSettings();
});
// 将按钮添加到目标节点
targetNode.insertAdjacentElement("afterbegin", statsButton);
targetNode.insertAdjacentElement("afterbegin", edibleToolsButton);
//获取图标url格式模板
item_icon_url = document.querySelector("div[class^='Item_itemContainer'] use")?.getAttribute("href")?.split("#")[0];
} else {
setTimeout(waitForNavi, 200);
}
};
waitForNavi(); // 开始等待目标节点出现
}
//奶牛钉钉
function handleMessage(message) {
try {
let obj = JSON.parse(message);
if (obj && obj.type === "new_battle") {
processCombatConsumables(obj);
}
if (obj && obj.type === "init_character_data") {
processAndPrintData();
addStatisticsButton();
update_market_list(obj);
}
if (obj && obj.type === "guild_updated") {
const Guild_ID = obj.guild.id;
const edibleTools = JSON.parse(localStorage.getItem('Edible_Tools')) || {};
edibleTools.Guild_Data = edibleTools.Guild_Data || {};
let storedData = edibleTools.Guild_Data || {};
// 判断是否已经存在旧数据
if (storedData[Guild_ID] && storedData[Guild_ID].guild_updated && storedData[Guild_ID].guild_updated.old.updatedAt) {
const oldUpdatedAt = new Date(storedData[Guild_ID].guild_updated.new.updatedAt);
const newUpdatedAt = new Date(obj.guild.updatedAt);
// 计算时间差(单位:毫秒)
const timeDifference = newUpdatedAt - oldUpdatedAt;
if (timeDifference >= updataDealy) {
// 更新老数据为新数据
storedData[Guild_ID].guild_updated.old = storedData[Guild_ID].guild_updated.new;
// 更新新数据为当前数据
storedData[Guild_ID].guild_updated.new = {
experience: obj.guild.experience,
level: obj.guild.level,
updatedAt: obj.guild.updatedAt
};
} else {
// 仅更新新数据
storedData[Guild_ID].guild_updated.new = {
experience: obj.guild.experience,
level: obj.guild.level,
updatedAt: obj.guild.updatedAt
};
}
//计算Δ
const Delta = {
Delta_Xp: storedData[Guild_ID].guild_updated.new.experience - storedData[Guild_ID].guild_updated.old.experience,
Delta_Level: storedData[Guild_ID].guild_updated.new.level - storedData[Guild_ID].guild_updated.old.level,
Delta_Time: (newUpdatedAt - new Date(storedData[Guild_ID].guild_updated.old.updatedAt)) / 1000, // 转换为秒
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)
};
storedData[Guild_ID].guild_updated.Delta = Delta;
const Guild_TotalXp_div = document.querySelectorAll(".GuildPanel_value__Hm2I9")[1];
if (Guild_TotalXp_div) {
const xpText = isZH ? "经验值 / 小时" : "XP / Hour";
Guild_TotalXp_div.insertAdjacentHTML(
"afterend",
`<div>${formatPrice(Delta.Rate_XP_Hours)} ${xpText}</div>`
);
const Guild_NeedXp_div = document.querySelectorAll(".GuildPanel_value__Hm2I9")[2];
if (Guild_NeedXp_div) {
const Guild_NeedXp = document.querySelectorAll(".GuildPanel_value__Hm2I9")[2].textContent.replace(/,/g, '');
const Time = TimeReset(Guild_NeedXp/Delta.Rate_XP_Hours);
Guild_NeedXp_div.insertAdjacentHTML(
"afterend", // 使用 "afterend" 在元素的后面插入内容
`<div>${Time}</div>`
);
}
}
} else {
// 如果没有旧数据,则直接添加新数据
storedData[Guild_ID] = {
guild_name: obj.guild.name,
guild_updated: {
old: {
experience: obj.guild.experience,
level: obj.guild.level,
updatedAt: obj.guild.updatedAt
},
new: {},
}
};
}
// 存储更新后的数据到 localStorage
edibleTools.Guild_Data = storedData;
localStorage.setItem('Edible_Tools', JSON.stringify(edibleTools));
} else if (obj && obj.type === "guild_characters_updated") {
const edibleTools = JSON.parse(localStorage.getItem('Edible_Tools')) || {};
edibleTools.Guild_Data = edibleTools.Guild_Data || {};
let storedData = edibleTools.Guild_Data || {};
for (const key in obj.guildSharableCharacterMap) {
if (obj.guildSharableCharacterMap.hasOwnProperty(key)) {
const Guild_ID = obj.guildCharacterMap[key].guildID;
const name = obj.guildSharableCharacterMap[key].name;
const newUpdatedAt = new Date();
storedData[Guild_ID].guild_player = storedData[Guild_ID].guild_player || {};
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) {
const oldUpdatedAt = new Date(storedData[Guild_ID].guild_player[name].old.updatedAt)
const timeDifference = newUpdatedAt - oldUpdatedAt
if (timeDifference >= updataDealy) {
// 更新老数据为新数据
storedData[Guild_ID].guild_player[name].old = storedData[Guild_ID].guild_player[name].new;
// 更新新数据为当前数据
storedData[Guild_ID].guild_player[name].new = {
id: key,
gameMode: obj.guildSharableCharacterMap[key].gameMode,
guildExperience: obj.guildCharacterMap[key].guildExperience,
updatedAt: newUpdatedAt,
};
} else {
// 仅更新新数据
storedData[Guild_ID].guild_player[name].new = {
id: key,
gameMode: obj.guildSharableCharacterMap[key].gameMode,
guildExperience: obj.guildCharacterMap[key].guildExperience,
updatedAt: newUpdatedAt,
};
}
//计算Δ
const Delta = {
Delta_Time:(newUpdatedAt - new Date(storedData[Guild_ID].guild_player[name].old.updatedAt)) / 1000,
Delta_Xp: storedData[Guild_ID].guild_player[name].new.guildExperience - storedData[Guild_ID].guild_player[name].old.guildExperience,
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)
};
storedData[Guild_ID].guild_player[name].Delta = Delta;
rateXPDayMap[name] = Delta.Rate_XP_Day;
}else {
storedData[Guild_ID].guild_player[name] = {
old: {
id: key,
gameMode: obj.guildSharableCharacterMap[key].gameMode,
guildExperience: obj.guildCharacterMap[key].guildExperience,
updatedAt: newUpdatedAt,
},
new:{}
};
}
}
}
//console.log("测试数据",storedData);
//console.log("guild_characters_updated", obj);
updateExperienceDisplay(rateXPDayMap);
edibleTools.Guild_Data = storedData;
localStorage.setItem('Edible_Tools', JSON.stringify(edibleTools));
} else if (obj && obj.type === "market_listings_updated") {
update_market_list(obj);
}
} catch (error) {
console.error("Error processing message:", error);
}
return message;
}
// 订单数据更新
function update_market_list(date) {
if (!date) return;
let market_list = JSON.parse(GM_getValue('market_list', '[]'));
// 通用更新
function updateOrders(orders) {
orders.forEach(newOrder => {
const existingOrderIndex = market_list.findIndex(order => order.id === newOrder.id);
if (existingOrderIndex !== -1) {
market_list[existingOrderIndex] = newOrder;
} else {
market_list.push(newOrder);
}
// 给每个订单添加更新时间戳
newOrder.lastUpdated = new Date().toISOString();
});
}
// 更新市场数据
if (date.type === "init_character_data" && date.myMarketListings) {
updateOrders(date.myMarketListings);
} else if (date.type === "market_listings_updated" && date.endMarketListings) {
updateOrders(date.endMarketListings);
}
// 保存更新后的数据
GM_setValue('market_list', JSON.stringify(market_list));
}
function deleteOrdersBeforeDate() {
const userInput = prompt("请输入要删除之前的日期 (格式:YYYY-MM-DD)", "");
if (!userInput) return;
// 转换用户输入的日期为 Date 对象
const userDate = new Date(userInput);
if (isNaN(userDate)) {
alert("无效的日期格式,请使用 YYYY-MM-DD");
return;
}
let market_list = JSON.parse(GM_getValue('market_list', '[]'));
// 过滤出所有在用户选择日期之前的订单
const filteredMarketList = market_list.filter(order => {
const orderDate = new Date(order.lastUpdated);
return orderDate >= userDate;
});
// 更新并保存新的数据
GM_setValue('market_list', JSON.stringify(filteredMarketList));
alert("删除成功,已清理日期之前的数据。");
}
function TimeReset(hours) {
const totalMinutes = hours * 60;
const days = Math.floor(totalMinutes / (24 * 60));
const yudays = totalMinutes % (24 * 60);
const hrs = Math.floor(yudays / 60);
const minutes = Math.floor(yudays % 60);
const dtext = isZH ? "天" : "d";
const htext = isZH ? "时" : "h";
const mtext = isZH ? "分" : "m";
return `${days}${dtext} ${hrs}${htext} ${minutes}${mtext}`;
}
function updateExperienceDisplay(rateXPDayMap) {
const trElements = document.querySelectorAll(".GuildPanel_membersTable__1NwIX tbody tr");
const idleuser_list = [];
const dtext = isZH ? "天" : "d";
// 将 rateXPDayMap 转换为数组并排序
const sortedMembers = Object.entries(rateXPDayMap)
.map(([name, XPdata]) => ({ name, XPdata }))
.sort((a, b) => b.XPdata - a.XPdata);
sortedMembers.forEach(({ name, XPdata }) => {
trElements.forEach(tr => {
const nameElement = tr.querySelector(".CharacterName_name__1amXp");
const experienceElement = tr.querySelector("td:nth-child(3) > div");
const activityElement = tr.querySelector('.GuildPanel_activity__9vshh');
if (nameElement && nameElement.textContent.trim() === name) {
if (activityElement.childElementCount === 0) {
idleuser_list.push(nameElement.textContent.trim());
}
if (experienceElement) {
const newDiv = document.createElement('div');
newDiv.textContent = `${formatPrice(XPdata)}/${dtext}`;
// 计算颜色
const rank = sortedMembers.findIndex(member => member.name === name);
const hue = 120 - (rank * (120 / (sortedMembers.length - 1)));
newDiv.style.color = `hsl(${hue}, 100%, 50%)`;
experienceElement.insertAdjacentElement('afterend', newDiv);
}
return;
}
});
});
update_idleuser_tb(idleuser_list);
}
function update_idleuser_tb(idleuser_list) {
const targetElement = document.querySelector('.GuildPanel_noticeMessage__3Txji');
if (!targetElement) {
console.error('公会标语元素未找到!');
return;
}
const clonedElement = targetElement.cloneNode(true);
const namesText = idleuser_list.join(', ');
clonedElement.innerHTML = '';
clonedElement.textContent = isZH ? `闲置的成员:${namesText}` : `Idle User : ${namesText}`;
clonedElement.style.color = '#ffcc00';
// 设置复制元素的高度为原元素的25%
const originalStyle = window.getComputedStyle(targetElement);
const originalHeight = originalStyle.height;
const originalMinHeight = originalStyle.minHeight;
clonedElement.style.height = `25%`;
clonedElement.style.minHeight = `25%`; // 也设置最小高度
targetElement.parentElement.appendChild(clonedElement);
}
hookWS();
//箱子数据获取
function processAndPrintData() {
const initClientData = localStorage.getItem('initClientData');
if (!initClientData) return;
let obj;
try {
obj = JSON.parse(initClientData);
} catch (error) {
console.error('数据解析失败:', error);
return;
}
if (obj.type !== 'init_client_data') return;
const item_hrid_to_name = obj.itemDetailMap;
for (const key in item_hrid_to_name) {
if (item_hrid_to_name[key] && typeof item_hrid_to_name[key] === 'object' && item_hrid_to_name[key].name) {
item_hrid_to_name[key] = item_hrid_to_name[key].name;
}
}
const item_name_to_hrid = Object.fromEntries(
Object.entries(item_hrid_to_name).map(([key, value]) => [value, key])
);
const hrid2name = item_hrid_to_name;
let formattedShopData = {};
// 处理商店数据
for (let [key, details] of Object.entries(obj.shopItemDetailMap)) {
const { itemHrid, costs } = details;
const itemName = hrid2name[itemHrid] || formatItemName(itemHrid.split('/').pop());
costs.forEach(cost => {
const costItemName = hrid2name[cost.itemHrid] || formatItemName(cost.itemHrid.split('/').pop());
if (costItemName === "Coin") return;
const costCount = cost.count;
if (!formattedShopData[costItemName]) {
formattedShopData[costItemName] = { items: {}, 最挣钱: '', BID单价: 0 };
}
// 计算每种代币购买每个物品的收益
let bidValue = getSpecialItemPrice(itemName,"bid") || 0;
let profit = bidValue / costCount;
formattedShopData[costItemName].items[itemName] = {
花费: costCount
};
// 更新最赚钱的物品信息
if (profit > formattedShopData[costItemName].BID单价) {
formattedShopData[costItemName].最挣钱 = itemName;
formattedShopData[costItemName].BID单价 = profit;
specialItemPrices[costItemName].ask = profit;
specialItemPrices[costItemName].bid = profit;
}
});
}
const mostProfitableItems = Object.values(formattedShopData).map(item => item.最挣钱).filter(Boolean);
//console.log(mostProfitableItems)
// 处理箱子掉落物数据
for (let iteration = 0; iteration < 4; iteration++) {
for (let [key, items] of Object.entries(obj.openableLootDropMap)) {
const boxName = hrid2name[key] || formatItemName(key.split('/').pop());
if (!formattedChestDropData[boxName]) {
formattedChestDropData[boxName] = { item: {} };
}
let TotalAsk = 0;
let TotalBid = 0;
let awa = 0;
items.forEach(item => {
const { itemHrid, dropRate, minCount, maxCount } = item;
const itemName = hrid2name[itemHrid] || formatItemName(itemHrid.split('/').pop());
const expectedYield = ((minCount + maxCount) / 2) * dropRate;
let bidPrice = -1;
let askPrice = -1;
let priceColor = '';
if (specialItemPrices[itemName] && specialItemPrices[itemName].ask) {
askPrice = parseFloat(specialItemPrices[itemName].ask);
bidPrice = parseFloat(specialItemPrices[itemName].bid);
priceColor = '';
} else if (marketData?.market?.[itemName]) {
bidPrice = marketData.market[itemName].bid;
askPrice = marketData.market[itemName].ask;
} else if (MWITools_marketAPI_json?.market?.[itemName]) {
bidPrice = MWITools_marketAPI_json.market[itemName].bid;
askPrice = MWITools_marketAPI_json.market[itemName].ask;
} else {
console.log(`${itemName} 的价格未找到`);
}
if (formattedChestDropData[boxName].item[itemName] && iteration === 0) {
// 如果物品已存在,更新期望掉落和相关价格
const existingItem = formattedChestDropData[boxName].item[itemName];
existingItem.期望掉落 += expectedYield;
} else if (iteration === 0) {
formattedChestDropData[boxName].item[itemName] = {
期望掉落: expectedYield,
};
}
// 判断 itemName 是否在最挣钱物品列表中
if (mostProfitableItems.includes(itemName)) {
priceColor = '#FFb3E6';
} else if (askPrice === -1 && bidPrice === -1) {
priceColor = 'yellow';
} else if (askPrice === -1) {
askPrice = bidPrice;
priceColor = '#D95961';
} else if (bidPrice === -1) {
priceColor = '#2FC4A7';
}
const existingItem = formattedChestDropData[boxName].item[itemName];
existingItem.出售单价 = askPrice;
existingItem.收购单价 = bidPrice;
existingItem.出售总价 = (existingItem.出售单价 * existingItem.期望掉落).toFixed(2);
existingItem.收购总价 = (existingItem.收购单价 * existingItem.期望掉落).toFixed(2);
existingItem.Color = priceColor;
// 累计总价
TotalAsk += (askPrice * expectedYield);
TotalBid += (bidPrice * expectedYield);
});
formattedChestDropData[boxName] = {
...formattedChestDropData[boxName],
期望产出Ask: TotalAsk.toFixed(2),
期望产出Bid: TotalBid.toFixed(2),
};
if (!specialItemPrices[boxName]) {
specialItemPrices[boxName] = {}
}
specialItemPrices[boxName].ask = formattedChestDropData[boxName].期望产出Ask;
specialItemPrices[boxName].bid = formattedChestDropData[boxName].期望产出Bid;
}
}
for (let itemName in specialItemPrices) {
if (specialItemPrices.hasOwnProperty(itemName)) {
marketData.market[itemName] = {
ask: specialItemPrices[itemName].ask,
bid: specialItemPrices[itemName].bid
};
}
}
localStorage.setItem('MWITools_marketAPI_json', JSON.stringify(marketData));
let edibleTools = JSON.parse(localStorage.getItem('Edible_Tools')) || {};
edibleTools.Chest_Drop_Data = formattedChestDropData;
localStorage.setItem('Edible_Tools', JSON.stringify(edibleTools));
// 打印结果
//console.log("特殊物品价格表:",specialItemPrices)
//console.log("箱子掉落物列表:", formattedChestDropData);
//console.log("地牢商店列表:", formattedShopData);
}
function ShowChestPrice() {
const modalContainer = document.querySelector(".Modal_modalContainer__3B80m");
if (!modalContainer) return; // 如果不存在 Modal_modalContainer__3B80m 元素,则直接返回
const chestNameElem = document.querySelector(chestNameSelector);
if (!chestNameElem) return;
const chestName = getItemNameFromElement(chestNameElem);
const items = document.querySelectorAll(itemSelector);
const dropListContainer = document.querySelector('.ItemDictionary_openToLoot__1krnv');
if (!dropListContainer) return; // 检查 dropListContainer 是否存在
const edibleTools = JSON.parse(localStorage.getItem('Edible_Tools'))
const formattedChestDropData = edibleTools.Chest_Drop_Data;
items.forEach(item => {
const itemName = getItemNameFromElement(item.querySelector(iconSelector));
if (!itemName) return; // 检查 itemName 是否存在
const itemData = formattedChestDropData[chestName].item[itemName];
if (!itemData) return; // 检查 itemData 是否存在
const itemColor = itemData.Color;
const itemNameElem = item.querySelector('.Item_name__2C42x');
if (itemNameElem && itemColor) {
itemNameElem.style.color = itemColor;
}
});
const askPrice = formattedChestDropData[chestName]["期望产出Ask"];
const bidPrice = formattedChestDropData[chestName]["期望产出Bid"];
if (askPrice && bidPrice) {
const previousResults = dropListContainer.querySelectorAll('.resultDiv');
previousResults.forEach(result => result.remove());
const createPriceOutput = (label, price) => {
const priceOutput = document.createElement('div');
priceOutput.className = 'resultDiv';
priceOutput.textContent = `${label}: ${formatPrice(price)}`;
priceOutput.style.color = 'gold';
priceOutput.style.fontSize = '14px';
priceOutput.style.fontWeight = '400';
priceOutput.style.paddingTop = '10px';
return priceOutput;
};
const minPriceOutput = createPriceOutput('期望产出 (最低买入价计算)', askPrice);
const maxPriceOutput = createPriceOutput('期望产出 (最高收购价计算)', bidPrice);
dropListContainer.appendChild(minPriceOutput);
dropListContainer.appendChild(maxPriceOutput);
}
}
function processCombatConsumables(obj) {
battlePlayerFood = {};
battlePlayerLoot = {};
obj.players.forEach(player => {
battlePlayerFood[player.character.name] = {};
battlePlayerLoot[player.character.name] = {};
let minTimeInDays = Infinity;
let minItemName = "";
player.combatConsumables.forEach(consumable => {
const itemname = item_hrid_to_name[consumable.itemHrid];
const timePerUnit = itemname.includes('Coffee') ? 5 : 1;
const totalTimeInMinutes = consumable.count * timePerUnit;
const totalTimeInDays = totalTimeInMinutes / (24 * 60);
if (totalTimeInDays < minTimeInDays) {
minTimeInDays = totalTimeInDays;
minItemName = itemname;
}
battlePlayerFood[player.character.name][itemname] = {
"数量": consumable.count,
"剩余时间": totalTimeInDays.toFixed(1) + '天',
"颜色": "Black"
};
});
Object.keys(battlePlayerFood[player.character.name]).forEach(item => {
if (item === minItemName) {
battlePlayerFood[player.character.name][item]["颜色"] = "red";
}
});
battlePlayerFood[player.character.name]['剩余时间'] = {
"数量": minTimeInDays < 1 ? (minTimeInDays * 24).toFixed(1) + '小时' : minTimeInDays.toFixed(1) + '天',
"颜色": "Black"
};
Object.values(player.totalLootMap).forEach(Loot => {
const itemname = item_hrid_to_name[Loot.itemHrid];
battlePlayerLoot[player.character.name][itemname] = {
"数量":Loot.count,
"ID":Loot.itemHrid
}
});
});
}
function addBattlePlayerFoodButton() {
//出警按钮父元素路径
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")
var referenceTab = tabsContainer ? tabsContainer.children[1] : null;
if (!tabsContainer || !referenceTab) {
return;
}
if (tabsContainer.querySelector('.Button_battlePlayerFood__custom')) {
console.log('按钮已存在');
return;
}
var battlePlayerFoodButton = document.createElement('div');
battlePlayerFoodButton.className = referenceTab.className + ' Button_battlePlayerFood__custom';
battlePlayerFoodButton.setAttribute('script_translatedfrom', 'New Action');
battlePlayerFoodButton.textContent = '出警';
battlePlayerFoodButton.addEventListener('click', function() {
let dataHtml = '<div style="display: flex; flex-wrap: nowrap;">';
for (let player in battlePlayerFood) {
dataHtml +=
`<div style="flex: 1 0 auto; min-width: 100px; margin: 10px; padding: 10px; border: 1px solid black;">
<h3>${player}</h3>`;
let consumables = battlePlayerFood[player];
for (let item in consumables) {
dataHtml += `<p style="color:${consumables[item].颜色};">${item}: ${consumables[item].数量}</p>`;
}
dataHtml += '</div>';
}
dataHtml += '</div>';
let popup = document.createElement('div');
popup.style.position = 'fixed';
popup.style.top = '50%';
popup.style.left = '50%';
popup.style.transform = 'translate(-50%, -50%)';
popup.style.backgroundColor = 'white';
popup.style.border = '1px solid black';
popup.style.padding = '10px';
popup.style.zIndex = '10000';
popup.style.maxWidth = '75%';
popup.style.overflowX = 'auto';
popup.style.whiteSpace = 'nowrap';
popup.innerHTML = dataHtml;
let closeButton = document.createElement('button');
closeButton.textContent = '关闭';
closeButton.style.display = 'block';
closeButton.style.margin = '20px auto 0 auto';
closeButton.onclick = function() {
document.body.removeChild(popup);
};
popup.appendChild(closeButton);
document.body.appendChild(popup);
});
var lastTab = tabsContainer.children[tabsContainer.children.length - 1];
tabsContainer.insertBefore(battlePlayerFoodButton, lastTab.nextSibling);
var style = document.createElement('style');
style.innerHTML = `
.Button_battlePlayerFood__custom {
background-color: #546ddb;
color: white;
}
`;
document.head.appendChild(style);
}
function addBattlePlayerLootButton() {
// 获取按钮父元素路径
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");
var referenceTab = tabsContainer ? tabsContainer.children[1] : null;
if (!tabsContainer || !referenceTab) {
return;
}
// 如果按钮已经存在,直接返回
if (tabsContainer.querySelector('.Button_battlePlayerLoot__custom')) {
console.log('分赃按钮已存在');
return;
}
// 创建按钮
var battlePlayerLootButton = document.createElement('div');
battlePlayerLootButton.className = referenceTab.className + ' Button_battlePlayerLoot__custom';
battlePlayerLootButton.setAttribute('script_translatedfrom', 'New Action');
battlePlayerLootButton.textContent = '分赃'; // 按钮文字
// 按钮点击事件
battlePlayerLootButton.addEventListener('click', function() {
let dataHtml = '<div style="display: flex; flex-wrap: nowrap;">';
const minPrice = 10000;
// 获取所有玩家的总计价格
let playerPrices = [];
for (let player in battlePlayerLoot) {
let totalPrice = 0;
let lootItems = battlePlayerLoot[player];
for (let item in lootItems) {
let bidPrice = getSpecialItemPrice(item,"bid") || 0;
totalPrice += bidPrice * lootItems[item].数量;
}
playerPrices.push({ player, totalPrice });
}
// 找到眉笔
let minTotalPricePlayer = null;
if (playerPrices.length > 1) {
minTotalPricePlayer = playerPrices.reduce((min, current) =>
current.totalPrice < min.totalPrice ? current : min
).player;
}
// 生成 HTML
for (let player in battlePlayerLoot) {
let totalPrice = 0;
dataHtml += `<div style="flex: 1 0 auto; min-width: 100px; margin: 10px; padding: 10px; border: 1px solid black;">`;
dataHtml += `<h3>${player}</h3>`; // 玩家名字
// 计算总价格
let lootItems = battlePlayerLoot[player];
for (let item in lootItems) {
let bidPrice = getSpecialItemPrice(item,"bid") || 0;
totalPrice += bidPrice * lootItems[item].数量;
}
// 显示总计价格
if (totalPrice > 0) {
let color = '#4CAF50'; // 默认绿色
if (player === minTotalPricePlayer) {
color = '#FF0000'; // 眉笔红色
}
dataHtml += `<p style="color: ${color}; font-weight: bold;">总计价格: ${formatPrice(totalPrice)}</p>`;
}
// 显示高价值物品
for (let item in lootItems) {
let bidPrice = getSpecialItemPrice(item,"bid") || 0;
if (bidPrice > minPrice) {
// 创建图标
let svgIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svgIcon.setAttribute('width', '20');
svgIcon.setAttribute('height', '20');
svgIcon.style.marginRight = '5px';
svgIcon.style.verticalAlign = 'middle';
let useElement = document.createElementNS('http://www.w3.org/2000/svg', 'use');
useElement.setAttribute('href', `${item_icon_url}#${lootItems[item].ID.split('/').pop()}`);
svgIcon.appendChild(useElement);
// 显示物品图标和数量
dataHtml += `<p>${svgIcon.outerHTML} ${item}: ${lootItems[item].数量}</p>`;
}
}
dataHtml += '</div>';
}
dataHtml += '</div>';
// 创建弹窗
let popup = document.createElement('div');
popup.style.position = 'fixed';
popup.style.top = '50%';
popup.style.left = '50%';
popup.style.transform = 'translate(-50%, -50%)';
popup.style.backgroundColor = 'white';
popup.style.border = '1px solid black';
popup.style.padding = '10px';
popup.style.zIndex = '10000';
popup.style.maxWidth = '75%';
popup.style.overflowX = 'auto';
popup.style.whiteSpace = 'nowrap';
popup.innerHTML = dataHtml;
// 添加关闭按钮
let closeButton = document.createElement('button');
closeButton.textContent = '关闭';
closeButton.style.display = 'block';
closeButton.style.margin = '20px auto 0 auto';
closeButton.onclick = function() {
document.body.removeChild(popup);
};
popup.appendChild(closeButton);
document.body.appendChild(popup);
});
// 将按钮插入到最后一个标签后面
var lastTab = tabsContainer.children[tabsContainer.children.length - 1];
tabsContainer.insertBefore(battlePlayerLootButton, lastTab.nextSibling);
// 添加按钮样式
var style = document.createElement('style');
style.innerHTML = `
.Button_battlePlayerLoot__custom {
background-color: #db5454;
color: white;
}
`;
document.head.appendChild(style);
}
//菜单
GM_registerMenuCommand('打印所有箱子掉落物', function() {
console.log('箱子掉落物列表:', formattedChestDropData);
});
function createWindowBase() {
let windowDiv = document.createElement('div');
windowDiv.className = 'visualization-window';
windowDiv.style.position = 'fixed';
windowDiv.style.top = '50%';
windowDiv.style.left = '50%';
windowDiv.style.transform = 'translate(-50%, -50%)';
windowDiv.style.height = '60%';
windowDiv.style.backgroundColor = 'white';
windowDiv.style.border = '1px solid #000';
windowDiv.style.zIndex = '10000';
windowDiv.style.padding = '10px';
windowDiv.style.boxSizing = 'border-box';
windowDiv.style.display = 'flex';
windowDiv.style.flexDirection = 'column';
return windowDiv;
}
function createVisualizationWindow(chestData) {
let oldWindow = document.querySelector('.visualization-window');
if (oldWindow) {
oldWindow.remove();
}
let windowDiv = createWindowBase();
let title = document.createElement('h1');
title.innerText = '开箱记录';
title.style.textAlign = 'center';
windowDiv.appendChild(title);
let contentDiv = document.createElement('div');
contentDiv.style.flex = '1';
contentDiv.style.overflowY = 'auto';
// 添加箱子列表
for (let chestName in chestData) {
let chest = chestData[chestName];
// 外部容器(带边框)
let chestBox = document.createElement('div');
chestBox.style.display = 'flex';
chestBox.style.alignItems = 'center';
chestBox.style.border = '1px solid #000';
chestBox.style.borderRadius = '5px';
chestBox.style.marginBottom = '10px';
chestBox.style.padding = '5px';
chestBox.style.cursor = 'pointer';
chestBox.style.backgroundColor = '#f9f9f9';
chestBox.onmouseenter = () => (chestBox.style.backgroundColor = '#e0e0e0');
chestBox.onmouseleave = () => (chestBox.style.backgroundColor = '#f9f9f9');
// 点击事件绑定到 chestBox
chestBox.onclick = () => showChestDetails(chestName, chest);
let svgIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svgIcon.setAttribute('width', '24');
svgIcon.setAttribute('height', '24');
svgIcon.style.marginRight = '10px';
let useElement = document.createElementNS('http://www.w3.org/2000/svg', 'use');
useElement.setAttribute('href', `${item_icon_url}#${item_name_to_hrid[chestName].split('/').pop()}`);
svgIcon.appendChild(useElement);
let chestText = document.createElement('span');
chestText.style.flex = '1';
chestText.style.textAlign = 'left';
chestText.innerText = `${chestName}: ${chest['总计开箱数量']}`;
chestBox.appendChild(svgIcon);
chestBox.appendChild(chestText);
contentDiv.appendChild(chestBox);
}
windowDiv.appendChild(contentDiv);
let footerDiv = document.createElement('div');
footerDiv.style.display = 'flex';
footerDiv.style.justifyContent = 'space-between';
footerDiv.style.marginTop = '10px';
// 关闭按钮
let closeButton = document.createElement('button');
closeButton.innerText = '关闭';
closeButton.onclick = () => document.body.removeChild(windowDiv);
footerDiv.appendChild(closeButton);
// 删除数据按钮
let deleteButton = document.createElement('button');
deleteButton.innerText = '删除数据';
deleteButton.onclick = () => {
if (confirm('确定要删除所有开箱数据吗?')) {
deleteOpenChestData(chestData);
document.body.removeChild(windowDiv);
}
};
footerDiv.appendChild(deleteButton);
windowDiv.appendChild(footerDiv);
document.body.appendChild(windowDiv);
}
function deleteOpenChestData() {
let edibleToolsData = JSON.parse(localStorage.getItem('Edible_Tools'));
if (edibleToolsData && edibleToolsData.Chest_Open_Data) {
edibleToolsData.Chest_Open_Data = {};
localStorage.setItem('Edible_Tools', JSON.stringify(edibleToolsData));
}
}
// 显示箱子详细信息
function showChestDetails(chestName, chestData) {
let oldWindow = document.querySelector('.visualization-window');
if (oldWindow) {
oldWindow.remove();
}
let detailsWindow = createWindowBase();
let title = document.createElement('h1');
title.innerText = chestName;
detailsWindow.appendChild(title);
let contentDiv = document.createElement('div');
contentDiv.style.flex = '1';
contentDiv.style.overflowY = 'auto';
// 显示总计信息
let totalStats = document.createElement('p');
totalStats.innerText = `总计开箱数量: ${chestData['总计开箱数量']}\n总计Ask: ${formatPrice(chestData['总计开箱Ask'])}\n总计Bid: ${formatPrice(chestData['总计开箱Bid'])}`;
contentDiv.appendChild(totalStats);
// 添加物品信息
for (let itemName in chestData['获得物品']) {
let item = chestData['获得物品'][itemName];
let itemBox = document.createElement('div');
itemBox.style.display = 'flex';
itemBox.style.alignItems = 'center';
itemBox.style.border = '1px solid #000';
itemBox.style.borderRadius = '5px';
itemBox.style.marginBottom = '5px';
itemBox.style.padding = '5px';
// 添加图标
let svgIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svgIcon.setAttribute('width', '24');
svgIcon.setAttribute('height', '24');
svgIcon.style.marginRight = '10px';
let useElement = document.createElementNS('http://www.w3.org/2000/svg', 'use');
useElement.setAttribute('href', `${item_icon_url}#${item_name_to_hrid[itemName].split('/').pop()}`);
svgIcon.appendChild(useElement);
// 添加物品名称和数量
let itemText = document.createElement('span');
itemText.innerText = `${itemName}: ${formatPrice(item['数量'])}`;
itemText.style.flex = '1';
itemBox.appendChild(svgIcon);
itemBox.appendChild(itemText);
contentDiv.appendChild(itemBox);
}
detailsWindow.appendChild(contentDiv);
let footerDiv = document.createElement('div');
footerDiv.style.display = 'flex';
footerDiv.style.justifyContent = 'space-between';
footerDiv.style.marginTop = '10px';
// 返回按钮
let backButton = document.createElement('button');
backButton.innerText = '返回';
backButton.onclick = () => {
detailsWindow.remove();
createVisualizationWindow(JSON.parse(localStorage.getItem('Edible_Tools')).Chest_Open_Data);
};
footerDiv.appendChild(backButton);
// 关闭按钮
let closeButton = document.createElement('button');
closeButton.innerText = '关闭';
closeButton.onclick = () => detailsWindow.remove();
footerDiv.appendChild(closeButton);
detailsWindow.appendChild(footerDiv);
document.body.appendChild(detailsWindow);
}
GM_registerMenuCommand('打印全部开箱记录', function() {
const edibleTools = JSON.parse(localStorage.getItem('Edible_Tools')) || {};
const openChestData = edibleTools.Chest_Open_Data || {};
createVisualizationWindow(openChestData);
});
GM_registerMenuCommand('打印掉落物列表', function() {
let dataHtml = '<div style="display: flex; flex-wrap: nowrap;">';
const minPrice = 10000;
for (let player in battlePlayerLoot) {
let totalPrice = 0;
dataHtml += `<div style="flex: 1 0 auto; min-width: 100px; margin: 10px; padding: 10px; border: 1px solid black;">`;
dataHtml += `<h3>${player}</h3>`;
let lootItems = battlePlayerLoot[player];
for (let item in lootItems) {
let bidPrice = getSpecialItemPrice(item,"bid") || 0;
totalPrice += bidPrice*lootItems[item].数量
if (bidPrice > minPrice) {
dataHtml += `<p>${item}: ${lootItems[item].数量}</p>`;
}
}
if (totalPrice > 0) {
dataHtml += `<p>总计价格: ${formatPrice(totalPrice)}</p>`;
}
dataHtml += '</div>';
}
dataHtml += '</div>';
let popup = document.createElement('div');
popup.style.position = 'fixed';
popup.style.top = '50%';
popup.style.left = '50%';
popup.style.transform = 'translate(-50%, -50%)';
popup.style.backgroundColor = 'white';
popup.style.border = '1px solid black';
popup.style.padding = '10px';
popup.style.zIndex = '10000';
popup.style.maxWidth = '75%';
popup.style.overflowX = 'auto';
popup.style.whiteSpace = 'nowrap';
popup.innerHTML = dataHtml;
let closeButton = document.createElement('button');
closeButton.textContent = '关闭';
closeButton.style.display = 'block';
closeButton.style.margin = '20px auto 0 auto';
closeButton.onclick = function() {
document.body.removeChild(popup);
};
popup.appendChild(closeButton);
document.body.appendChild(popup);
});
function formatToChinesetime(timestamp) {
const date = new Date(timestamp);
const beijingOffset = 8 * 60;
date.setMinutes(date.getMinutes() + date.getTimezoneOffset() + beijingOffset);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
return `${year}/${month}/${day} ${hours}:${minutes}`;
}
function openSettings() {
const tran_market_list = {
"/market_listing_status/filled": "已完成",
"/market_listing_status/active": "进行中",
"/market_listing_status/cancelled": "取消",
"/market_listing_status/expired":"超时",
};
const market_List_Data = JSON.parse(GM_getValue('market_list', '[]'));
const hrid2name = item_hrid_to_name;
// 格式化数据
market_List_Data.forEach(item => {
item.itemName = hrid2name[item.itemHrid] || item.itemHrid;
if (item.enhancementLevel > 0) {
item.itemName = `${item.itemName} +${item.enhancementLevel}`;
}
item.status = tran_market_list[item.status] || item.status;
if (item.lastUpdated) {
item.format_lastUpdated = formatToChinesetime(item.lastUpdated);
}
});
const settingsContainer = document.createElement('div');
settingsContainer.style.position = 'fixed';
settingsContainer.style.top = '0';
settingsContainer.style.left = '0';
settingsContainer.style.width = '100%';
settingsContainer.style.height = '100%';
settingsContainer.style.backgroundColor = 'rgba(0, 0, 0, 0.7)';
settingsContainer.style.zIndex = '9999';
settingsContainer.style.display = 'flex';
settingsContainer.style.flexDirection = 'column';
// 页面内容
const Edible_Tools_HTML = `
<div style="flex: 1; overflow-y: auto; background-color: #fff; padding: 20px;">
<header style="background-color: #4CAF50; color: white; padding: 10px 20px; text-align: center;">
<h1>银河奶牛数据库</h1>
</header>
<div style="display: flex; flex: 1;">
<div style="width: 200px; background-color: #f4f4f4; padding: 10px;">
<button id="showMarketDataBtn" style="width: 100%; padding: 10px; margin: 5px 0; background-color: #ddd; border: none;">市场数据</button>
<button id="showOpenChestDataBtn" style="width: 100%; padding: 10px; margin: 5px 0; background-color: #ddd; border: none;">开箱数据</button>
<button id="showEnhancementDataBtn" style="width: 100%; padding: 10px; margin: 5px 0; background-color: #ddd; border: none;">强化数据</button>
</div>
<div style="flex: 1; padding: 20px; overflow-y: auto; display: block;" id="showMarketDataPage">
<h2 style="text-align: center;">市场数据</h2>
<table class="marketList-table" style="width: 100%; border-collapse: collapse;">
<thead>
<tr>
<th data-sort="id">订单ID</th>
<th data-sort="characterID">角色ID</th>
<th data-sort="status">状态</th>
<th data-sort="isSell">类型</th>
<th data-sort="itemName">物品</th>
<th data-sort="orderQuantity">数量</th>
<th data-sort="filledQuantity">已交易数量</th>
<th data-sort="price">单价</th>
<th data-sort="total">贸易额</th>
<th data-sort="format_lastUpdated">更新时间</th>
<th>操作</th>
</tr>
</thead>
<tbody id="marketDataTableBody">
<!-- 数据表会在这里插入 -->
</tbody>
</table>
</div>
<div style="flex: 1; padding: 20px; overflow-y: auto; display: none;" id="OpenChestDataPage">
<h2 style="text-align: center;">开箱数据(咕?)</h2>
</div>
<div style="flex: 1; padding: 20px; overflow-y: auto; display: none;" id="EnhancementDataPage">
<h2 style="text-align: center;">强化数据(咕咕~)</h2>
</div>
</div>
</div>
<button id="closeSettingsBtn" style="position: absolute; top: 10px; right: 10px; padding: 10px 20px; background-color: red; color: white; border: none;">关闭</button>
`;
settingsContainer.innerHTML = Edible_Tools_HTML;
document.body.appendChild(settingsContainer);
const marketDataPage = document.getElementById('showMarketDataPage');
const OpenChestDataPage = document.getElementById('OpenChestDataPage');
const EnhancementDataPage = document.getElementById('EnhancementDataPage');
function showMarketData() {
marketDataPage.style.display = 'block';
OpenChestDataPage.style.display = 'none';
EnhancementDataPage.style.display = 'none';
const tableBody = document.getElementById('marketDataTableBody');
tableBody.innerHTML = market_List_Data.map((row, index) => {
// 创建图标
let svgIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svgIcon.setAttribute('width', '20');
svgIcon.setAttribute('height', '20');
svgIcon.style.marginRight = '10px';
svgIcon.style.verticalAlign = 'middle';
let useElement = document.createElementNS('http://www.w3.org/2000/svg', 'use');
useElement.setAttribute('href', `${item_icon_url}#${row.itemHrid.split('/').pop()}`);
svgIcon.appendChild(useElement);
let itemNameWithIcon = `${svgIcon.outerHTML}${row.itemName}`;
return `
<tr data-index="${index}">
<td>${row.id}</td>
<td>${row.characterID}</td>
<td>${row.status}</td>
<td>${row.isSell ? '出售' : '收购'}</td>
<td>${itemNameWithIcon}</td>
<td>${(row.orderQuantity).toLocaleString()}</td>
<td>${(row.filledQuantity).toLocaleString()}</td>
<td>${(row.price).toLocaleString()}</td>
<td>${(row.price * row.filledQuantity).toLocaleString()}</td>
<td>${row.format_lastUpdated}</td>
<td><button class="delete-btn">删除</button></td>
</tr>
`;
}).join('');
}
function ShowOpenChestData() {
marketDataPage.style.display = 'none';
OpenChestDataPage.style.display = 'block';
EnhancementDataPage.style.display = 'none';
}
function ShowEnhancementData() {
marketDataPage.style.display = 'none';
OpenChestDataPage.style.display = 'none';
EnhancementDataPage.style.display = 'block';
}
showMarketData();
// 删除单行
function attachDeleteListeners() {
document.querySelectorAll('.delete-btn').forEach(button => {
button.addEventListener('click', (event) => {
const row = event.target.closest('tr');
const index = row.getAttribute('data-index');
market_List_Data.splice(index, 1);
GM_setValue('market_list', JSON.stringify(market_List_Data));// 更新存储的数据
showMarketData();// 重新渲染表格
attachDeleteListeners();// 重新绑定删除按钮事件
});
});
}
attachDeleteListeners();// 初始绑定删除按钮事件
// 排序功能
let sortOrder = { field: null, direction: 1 };// 1 是升序,-1 是降序
function sortTable(column) {
const field = column.getAttribute('data-sort');
const direction = sortOrder.field === field && sortOrder.direction === 1 ? -1 : 1;// 切换排序方向
market_List_Data.sort((a, b) => {
if (field === 'total') {
return (a.price * a.filledQuantity - b.price * b.filledQuantity) * direction;
}
if (typeof a[field] === 'string') {
return (a[field].localeCompare(b[field])) * direction;
}
return (a[field] - b[field]) * direction;
});
// 更新排序状态
document.querySelectorAll('th').forEach(th => {
th.classList.remove('sort-asc', 'sort-desc');
});
column.classList.add(direction === 1 ? 'sort-asc' : 'sort-desc');
sortOrder = { field, direction };
showMarketData();
attachDeleteListeners();
}
// 给每个表头添加点击事件监听器
document.querySelectorAll('th').forEach(th => {
th.addEventListener('click', () => {
sortTable(th);
});
});
// 切换数据库页面
document.getElementById('showMarketDataBtn').addEventListener('click', showMarketData);
document.getElementById('showOpenChestDataBtn').addEventListener('click', ShowOpenChestData);
document.getElementById('showEnhancementDataBtn').addEventListener('click', ShowEnhancementData);
// 关闭按钮
document.getElementById('closeSettingsBtn').addEventListener('click', () => {
document.body.removeChild(settingsContainer);
});
// 表格样式
const style = document.createElement('style');
style.innerHTML = `
.marketList-table {
width: 100%;
border-collapse: collapse;
}
.marketList-table, .marketList-table th, .marketList-table td {
border: 1px solid #ddd;
}
.marketList-table th, .marketList-table td {
padding: 10px;
text-align: center;
}
.marketList-table th {
background-color: #f2f2f2;
cursor: pointer;
}
.marketList-table th.sort-asc::after {
content: ' ▲';
}
.marketList-table th.sort-desc::after {
content: ' ▼';
}
`;
document.head.appendChild(style);
}
GM_registerMenuCommand('删除过时市场数据', deleteOrdersBeforeDate);
})();