// ==UserScript== // @name Milkyway Idle - Current Loot Tracker // @namespace https://milkywayidle.com/ // @version 1.5 // @description Tracks loot with overlay, group combat loot tabs, visual feedback, CSV export, clear log, persistent layout // @match https://www.milkywayidle.com/* // @grant none // @run-at document-start // @license MIT // @downloadURL none // ==/UserScript== (function () { const playerLootData = {}; const previousLootCounts = {}; let myPlayerName = null; let activePlayer = null; let selfTabSelected = false; let isMinimized = localStorage.getItem("lootOverlayMinimized") === "true"; let overlayReady = false; function detectPlayerName() { const nameDiv = document.querySelector( ".CharacterName_name__1amXp[data-name]" ); if (nameDiv) { myPlayerName = nameDiv.dataset.name; } else { setTimeout(detectPlayerName, 500); } } function createOverlay() { if (overlayReady || document.getElementById("lootOverlay")) return; overlayReady = true; const panel = document.createElement("div"); panel.id = "lootOverlay"; panel.style.position = "fixed"; panel.style.top = localStorage.getItem("lootOverlayTop") || "100px"; panel.style.left = localStorage.getItem("lootOverlayLeft") || "20px"; panel.style.width = "260px"; panel.style.background = "rgba(30, 30, 30, 0.95)"; panel.style.color = "#fff"; panel.style.fontFamily = "monospace"; panel.style.fontSize = "13px"; panel.style.border = "1px solid #555"; panel.style.borderRadius = "8px"; panel.style.zIndex = 99999; panel.style.userSelect = "none"; panel.style.boxShadow = "0 4px 10px rgba(0,0,0,0.4)"; panel.innerHTML = `
📦 Current Loot
`; document.body.appendChild(panel); const style = document.createElement("style"); style.textContent = ` #lootOverlay button:hover::after { content: attr(data-tooltip); position: absolute; left: 50%; top: 100%; transform: translateX(-50%); background: #222; color: #fff; padding: 4px 8px; font-size: 11px; border-radius: 4px; white-space: nowrap; opacity: 0.9; pointer-events: none; z-index: 100000; margin-top: 4px; } #lootTabs button { background: none; border: 1px solid #444; color: #aaa; padding: 2px 6px; font-family: monospace; cursor: pointer; border-radius: 4px; font-size: 12px; } #lootTabs button.active { background: #4caf50; color: #fff; border-color: #4caf50; } `; document.head.appendChild(style); document.getElementById("lootMinBtn").onclick = () => { isMinimized = !isMinimized; document.getElementById("lootTotals").style.display = isMinimized ? "none" : "block"; document.getElementById("lootMinBtn").textContent = isMinimized ? "+" : "−"; localStorage.setItem("lootOverlayMinimized", isMinimized); }; document.getElementById("lootExportBtn").onclick = function () { if (!activePlayer || !playerLootData[activePlayer]) return; const csvContent = Object.entries(playerLootData[activePlayer]) .map(([itemHrid, count]) => { const itemName = itemHrid.replace("/items/", "").replace(/_/g, " "); return `"${itemName}",${count}`; }) .join("\n"); navigator.clipboard.writeText(csvContent); }; document.getElementById("lootClearBtn").onclick = function () { for (const player in playerLootData) { playerLootData[player] = {}; previousLootCounts[player] = {}; } document.getElementById("lootTabs").innerHTML = ""; document.getElementById("lootTotals").innerHTML = ""; activePlayer = null; selfTabSelected = false; }; let dragging = false, offsetX, offsetY; document.getElementById("lootHeader").onmousedown = (e) => { if (e.target.tagName === "BUTTON") return; dragging = true; offsetX = e.clientX - panel.offsetLeft; offsetY = e.clientY - panel.offsetTop; }; document.onmousemove = (e) => { if (dragging) { panel.style.left = `${e.clientX - offsetX}px`; panel.style.top = `${e.clientY - offsetY}px`; } }; document.onmouseup = () => { if (dragging) { localStorage.setItem("lootOverlayTop", panel.style.top); localStorage.setItem("lootOverlayLeft", panel.style.left); } dragging = false; }; } function updateLootDisplay(playerName) { const container = document.getElementById("lootTotals"); if (!container || !playerLootData[playerName]) return; if (!previousLootCounts[playerName]) previousLootCounts[playerName] = {}; const sortedItems = Object.entries(playerLootData[playerName]).sort( (a, b) => b[1] - a[1] || a[0].localeCompare(b[0]) ); let html = ""; for (const [itemHrid, count] of sortedItems) { const name = itemHrid.replace("/items/", "").replace(/_/g, " "); html += `
• ${name} × ${count}
`; } container.innerHTML = html; container.style.display = "block"; } function switchTab(playerName) { activePlayer = playerName; document.querySelectorAll("#lootTabs button").forEach((btn) => { btn.classList.toggle("active", btn.textContent === playerName); }); updateLootDisplay(playerName); } function addTab(player) { const playerName = player.name; const lootMap = player.totalLootMap || {}; const container = document.getElementById("lootTabs"); if (!container.querySelector(`button[data-name="${playerName}"]`)) { const btn = document.createElement("button"); btn.textContent = playerName; btn.dataset.name = playerName; btn.onclick = () => switchTab(playerName); container.appendChild(btn); } if (!playerLootData[playerName]) { playerLootData[playerName] = {}; } for (const key in lootMap) { const item = lootMap[key]; const itemHrid = item.itemHrid; const count = item.count; playerLootData[playerName][itemHrid] = count; } if (playerName === myPlayerName && !selfTabSelected) { selfTabSelected = true; switchTab(playerName); } } function interceptWebSocket() { const OriginalWebSocket = window.WebSocket; window.WebSocket = class extends OriginalWebSocket { constructor(...args) { super(...args); this.addEventListener("message", (event) => { try { const data = JSON.parse(event.data); if (data.type !== "new_battle") return; (data.players || []).forEach((player) => { addTab(player); if (player.name === activePlayer) { updateLootDisplay(activePlayer); } }); } catch (err) {} }); } }; } if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", () => { createOverlay(); detectPlayerName(); interceptWebSocket(); }); } else { createOverlay(); detectPlayerName(); interceptWebSocket(); } })();