// ==UserScript==
// @name Milkyway Idle - Loot Tracker
// @namespace https://milkywayidle.com/
// @version 1.1
// @description Tracks loot with overlay, 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;
const previousLoot = {};
const sessionLoot = {};
let overlayReady = false;
let isMinimized = localStorage.getItem("lootOverlayMinimized") === "true";
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 = `
`;
document.body.appendChild(panel);
document.head.insertAdjacentHTML('beforeend', `
`);
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 = () => {
const lootSummary = Object.entries(sessionLoot).map(([itemHrid, count]) =>
`${itemHrid.replace("/items/","").replace(/_/g," ")},${count}`
).join("\n");
navigator.clipboard.writeText(lootSummary);
};
document.getElementById("lootClearBtn").onclick = () => {
for (let k in sessionLoot) delete sessionLoot[k];
updateOverlay();
};
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 updateOverlay(newItems = []) {
const container = document.getElementById("lootTotals");
if (!container) return;
container.innerHTML = Object.entries(sessionLoot).map(([itemHrid, count]) => {
const name = itemHrid.replace("/items/", "").replace(/_/g, " ");
const highlight = newItems.includes(itemHrid) ? 'loot-highlight' : '';
return `• ${name} × ${count}
`;
}).join("");
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", createOverlay);
} else {
setTimeout(createOverlay, 0);
}
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;
const lootMap = data.players?.[0]?.totalLootMap;
if (!lootMap) return;
const newItems = [];
for (const key in lootMap) {
const item = lootMap[key];
const delta = item.count - (previousLoot[key]?.count || 0);
if (delta > 0) {
sessionLoot[item.itemHrid] = (sessionLoot[item.itemHrid] || 0) + delta;
newItems.push(item.itemHrid);
}
previousLoot[key] = { ...item };
}
updateOverlay(newItems);
} catch {}
});
return ws;
};
window.WebSocket.prototype = originalWS.prototype;
})();