add-on to Loot Drops Overlay
当前为
// ==UserScript==
// @name Milky Way Idle - Loot Drops Value Add-on
// @namespace https://milkywayidle.com/
// @version 1.1
// @description add-on to Loot Drops Overlay
// @author notawhale
// @license MIT License
// @match https://www.milkywayidle.com/*
// @grant none
// ==/UserScript==
(function () {
'use strict';
const CUSTOM_SORT_KEY = 'lootDropsCustomSortPref';
const SORT_MODES = ['name', 'value', 'quantity'];
const SORT_LABELS = {
name: 'Name',
value: 'Value',
quantity: 'Quantity'
};
const ENABLE_LOG = true;
let marketData = null;
const fixedValueChests = new Set([
'small treasure chest',
'medium treasure chest',
'large treasure chest',
'chimerical chest',
'sinister chest',
'enchanted chest',
'pirate chest'
]);
function formatCoins(value) {
return `${Math.round(value).toLocaleString()} coin`;
}
function capitalizeEachWord(str) {
return str
.split(' ')
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
.join(' ');
}
async function loadMarketData() {
try {
const res = await fetch(
'https://raw.githubusercontent.com/holychikenz/MWIApi/main/medianmarket.json'
);
const json = await res.json();
marketData = json.market || {};
console.log('[ValueAdd] Market data loaded.');
} catch (e) {
console.error('[ValueAdd] Failed to load market data:', e);
}
}
function getUnitValue(name) {
const normalized = name.trim().toLowerCase();
if (fixedValueChests.has(normalized)) return 1;
if (normalized === 'coin') return 1;
const entry = marketData[capitalizeEachWord(name)];
return entry?.ask || 0;
}
function calculateItemValue(name, count) {
return count * getUnitValue(name);
}
function calculateTotalValue(section, playerName = 'Player') {
const itemRows = section.querySelectorAll('.ldt-loot-item-entry');
let total = 0;
if (ENABLE_LOG) console.group(`[${playerName}] Value Breakdown`);
itemRows.forEach((row) => {
const nameEl = row.querySelector('.ldt-item-name');
const countEl = row.querySelector('.ldt-item-count');
if (!nameEl || !countEl) return;
const name = nameEl.textContent.trim();
let rawCount = countEl.dataset.count;
if (!rawCount) {
const parsed = parseInt(countEl.textContent.replace(/[^\d]/g, ''), 10);
rawCount = isNaN(parsed) ? '0' : String(parsed);
countEl.dataset.count = rawCount;
}
const count = parseInt(rawCount, 10);
const unit = getUnitValue(name);
const itemTotal = count * unit;
total += itemTotal;
if (ENABLE_LOG) {
console.log(
`• ${name}: count=${count}, unit=${formatCoins(unit)}, total=${formatCoins(itemTotal)}`
);
}
});
if (ENABLE_LOG) {
console.log(`= Total: ${formatCoins(total)}`);
console.groupEnd();
}
return total;
}
function injectValuesAndSort() {
const sortPref = localStorage.getItem(CUSTOM_SORT_KEY) || 'name';
const playerSections = document.querySelectorAll('.ldt-player-stats-section');
playerSections.forEach((section) => {
const header = section.querySelector('.ldt-player-name-header');
const playerName = header?.textContent?.split('(')[0]?.trim() || 'Unknown';
const existingSpan = header.querySelector('span[data-value-injected]');
if (existingSpan) existingSpan.remove();
const totalValue = calculateTotalValue(section, playerName);
if (totalValue > 0) {
const span = document.createElement('span');
span.style.color = 'gold';
span.style.fontWeight = 'normal';
span.style.fontSize = '0.9em';
span.textContent = ` (${formatCoins(totalValue)})`;
span.dataset.valueInjected = 'true';
header.appendChild(span);
}
const list = section.querySelector('.ldt-loot-list');
if (!list) return;
const items = Array.from(list.querySelectorAll('.ldt-loot-item-entry'));
const itemData = items.map((row) => {
const nameEl = row.querySelector('.ldt-item-name');
const countEl = row.querySelector('.ldt-item-count');
const name = nameEl?.textContent.trim() || '';
let rawCount = countEl.dataset.count;
if (!rawCount) {
const parsed = parseInt(countEl.textContent.replace(/[^\d]/g, ''), 10);
rawCount = isNaN(parsed) ? '0' : String(parsed);
countEl.dataset.count = rawCount;
}
const count = parseInt(rawCount, 10);
const value = calculateItemValue(name, count);
return { row, name, count, value, nameEl, countEl };
});
itemData.sort((a, b) => {
if (sortPref === 'value') {
return b.value - a.value || b.count - a.count || a.name.localeCompare(b.name);
}
if (sortPref === 'quantity') {
return b.count - a.count || a.name.localeCompare(b.name);
}
return a.name.localeCompare(b.name);
});
itemData.forEach(({ countEl, count }) => {
countEl.textContent = `× ${count}`;
});
list.innerHTML = '';
itemData.forEach(({ row }) => list.appendChild(row));
});
}
function overrideSortButton() {
const btn = document.querySelector('#milt-loot-drops-display-sortbtn');
if (!btn || btn.dataset.customOverrideInjected) return;
btn.dataset.customOverrideInjected = 'true';
const updateLabel = () => {
const current = localStorage.getItem(CUSTOM_SORT_KEY) || 'name';
btn.textContent = `Sort: ${SORT_LABELS[current]}`;
};
btn.addEventListener('click', (e) => {
e.stopPropagation();
const current = localStorage.getItem(CUSTOM_SORT_KEY) || 'name';
const next =
SORT_MODES[(SORT_MODES.indexOf(current) + 1) % SORT_MODES.length];
localStorage.setItem(CUSTOM_SORT_KEY, next);
updateLabel();
injectValuesAndSort();
});
updateLabel();
}
async function main() {
await loadMarketData();
setInterval(() => {
const overlay = document.getElementById('milt-loot-drops-display');
if (!overlay || overlay.classList.contains('is-hidden')) return;
overrideSortButton();
injectValuesAndSort();
}, 1000);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', main);
} else {
main();
}
})();