// ==UserScript== // @name Milkyway Idle - Current Loot Tracker // @namespace https://milkywayidle.com/ // @version 1.3 // @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 originalWS = window.WebSocket; let overlayReady = false; let isMinimized = localStorage.getItem("lootOverlayMinimized") === "true"; let myPlayerName = null; let activePlayer = null; let selfTabSelected = false; const playerLootData = {}; const previousLootCounts = {}; // New: Track previous counts for highlighting 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); document.head.insertAdjacentHTML( "beforeend", ` ` ); // Minimize button document.getElementById("lootMinBtn").onclick = () => { isMinimized = !isMinimized; document.getElementById("lootTotals").style.display = isMinimized ? "none" : "block"; document.getElementById("lootMinBtn").textContent = isMinimized ? "+" : "−"; localStorage.setItem("lootOverlayMinimized", isMinimized); }; // Export to CSV button 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); this.classList.add("button-active"); setTimeout(() => this.classList.remove("button-active"), 500); }; // Clear history button document.getElementById("lootClearBtn").onclick = function() { // Clear all player data for (const player in playerLootData) { playerLootData[player] = {}; } // NEW: Also clear previous counts tracking for (const player in previousLootCounts) { previousLootCounts[player] = {}; } // Clear tabs const tabsContainer = document.getElementById("lootTabs"); tabsContainer.innerHTML = ""; activePlayer = null; selfTabSelected = false; // Clear display document.getElementById("lootTotals").innerHTML = ""; // Visual feedback this.classList.add("button-active"); setTimeout(() => this.classList.remove("button-active"), 500); }; // Make panel draggable 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; // Initialize previous counts if needed if (!previousLootCounts[playerName]) { previousLootCounts[playerName] = {}; } const sortedItems = Object.entries(playerLootData[playerName]) .sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])); container.innerHTML = sortedItems .map(([itemHrid, count]) => { const name = itemHrid.replace("/items/", "").replace(/_/g, " "); const prevCount = previousLootCounts[playerName][itemHrid] || 0; const isNew = count > prevCount; previousLootCounts[playerName][itemHrid] = count; // Update for next check return `
• ${name} × ${count}
`; }) .join(""); } 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 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); } // Store the player's loot data const lootMap = player.totalLootMap || {}; playerLootData[playerName] = {}; for (const key in lootMap) { const item = lootMap[key]; playerLootData[playerName][item.itemHrid] = item.count; } // Auto-select our own tab if we haven't selected one yet if (playerName === myPlayerName && !selfTabSelected) { selfTabSelected = true; switchTab(playerName); } } // Initialize if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", () => { createOverlay(); detectPlayerName(); }); } else { createOverlay(); detectPlayerName(); } // WebSocket handling window.WebSocket = function (...args) { const ws = new originalWS(...args); ws.addEventListener("message", (event) => { try { const data = JSON.parse(event.data); if (data.type !== "new_battle") return; (data.players || []).forEach(player => { const playerName = player.name; // Initialize tracking for this player if needed if (!previousLootCounts[playerName]) { previousLootCounts[playerName] = {}; } addTab(player); if (player.name === activePlayer) { updateLootDisplay(activePlayer); } }); } catch (e) { console.error("Error processing WebSocket message:", e); } }); return ws; }; window.WebSocket.prototype = originalWS.prototype; })();