// ==UserScript==
// @name Milkyway Idle - Current Loot Tracker
// @namespace https://milkywayidle.com/
// @version 1.2
// @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 = {};
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 = `
`;
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] = {};
}
// 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;
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, " ");
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 => {
addTab(player);
if (player.name === activePlayer) {
updateLootDisplay(activePlayer);
}
});
} catch (e) {
console.error("Error processing WebSocket message:", e);
}
});
return ws;
};
window.WebSocket.prototype = originalWS.prototype;
})();