Add-on to KJay's Loot Drop overlay
当前为
// ==UserScript==
// @name Milky Way Idle - Loot Drops Value Add-on
// @namespace https://milkywayidle.com/
// @version 2.2
// @description Add-on to KJay's Loot Drop overlay
// @author notawhale
// @license MIT
// @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 fixedValueChests = new Set([
'small treasure chest', 'medium treasure chest', 'large treasure chest',
'chimerical chest', 'sinister chest', 'enchanted chest', 'pirate chest'
]);
let marketData = {};
let dollarThreshold = null;
let dollarTagEnabled = false;
const viewModeState = {}; // Tracks view mode per player
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) || normalized === 'coin') return 1;
const entry = marketData[capitalizeEachWord(name)];
return entry?.ask || 0;
}
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';
// Clean up
header.querySelectorAll('span[data-value-injected]').forEach(el => el.remove());
const rows = Array.from(section.querySelectorAll('.ldt-loot-item-entry'));
let total = 0;
const itemData = rows.map((row) => {
const nameEl = row.querySelector('.ldt-item-name');
const countEl = row.querySelector('.ldt-item-count');
const name = nameEl?.textContent.trim() || '';
const count = parseInt(countEl?.textContent.replace(/\D/g, '') || '0', 10);
const unitValue = getUnitValue(name);
const itemValue = unitValue * count;
total += itemValue;
row.style.border = '';
if (dollarTagEnabled && dollarThreshold && unitValue >= dollarThreshold) {
row.style.border = '3px solid rgba(0, 255, 0, 0.5)';
}
return { row, name, count, value: itemValue, nameEl, countEl };
});
if (total > 0) {
const span = document.createElement('span');
span.style.color = 'gold';
span.style.fontSize = '0.9em';
span.style.fontWeight = 'normal';
span.dataset.valueInjected = 'true';
span.style.cursor = 'pointer';
const container = document.getElementById('milt-loot-drops-display');
const timerEl = container?.querySelector('.ldt-timer');
const timeMatch = timerEl?.textContent?.match(/(\d+):(\d+):(\d+)/);
let displayText = ` (${formatCoins(total)})`;
if (!viewModeState[playerName]) {
viewModeState[playerName] = 'total';
}
if (viewModeState[playerName] === 'hour' && timeMatch) {
const [_, hh, mm, ss] = timeMatch.map(Number);
const hoursElapsed = hh + mm / 60 + ss / 3600;
if (hoursElapsed >= 0.01) {
const avg = total / hoursElapsed;
displayText = ` (${formatCoins(avg)}/hr)`;
span.style.color = 'lightgreen';
}
}
span.textContent = displayText;
span.onclick = () => {
viewModeState[playerName] = (viewModeState[playerName] === 'total') ? 'hour' : 'total';
};
header.appendChild(span);
}
// Sorting
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);
});
const list = section.querySelector('.ldt-loot-list');
if (!list) return;
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();
}
function setupControls() {
if (document.getElementById('value-threshold-input')) return;
const sortBtn = document.querySelector('#milt-loot-drops-display-sortbtn');
if (!sortBtn) return;
const input = document.createElement('input');
input.type = 'number';
input.id = 'value-threshold-input';
input.placeholder = 'Value ≥';
input.style.marginLeft = '8px';
input.style.width = '80px';
input.style.fontSize = '12px';
const toggle = document.createElement('button');
toggle.textContent = '💲 Off';
toggle.style.marginLeft = '6px';
toggle.style.fontSize = '12px';
toggle.style.padding = '2px 6px';
toggle.style.cursor = 'pointer';
toggle.onclick = () => {
dollarTagEnabled = !dollarTagEnabled;
toggle.textContent = dollarTagEnabled ? '💲 On' : '💲 Off';
};
input.onchange = () => {
const val = parseInt(input.value, 10);
dollarThreshold = isNaN(val) ? null : val;
};
sortBtn.parentElement.appendChild(input);
sortBtn.parentElement.appendChild(toggle);
}
async function main() {
await loadMarketData();
setInterval(() => {
const overlay = document.getElementById('milt-loot-drops-display');
if (!overlay || overlay.classList.contains('is-hidden')) return;
overrideSortButton();
setupControls();
injectValuesAndSort();
}, 1000);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', main);
} else {
main();
}
})();