您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Tracks loot with overlay, group combat loot tabs, visual feedback, CSV export, clear log, persistent layout
当前为
// ==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 // ==/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 = ` <div id="lootHeader" style="display:flex;justify-content:space-between;align-items:center;padding:6px 10px;background:rgba(20,20,20,0.85);border-bottom:1px solid #333;border-radius:8px 8px 0 0;cursor:move;"> <span style="font-weight:bold;">📦 Current Loot</span> <div> <button id="lootExportBtn" data-tooltip="Export loot as CSV" style="background:none;border:none;color:#aaa;cursor:pointer;margin-right:6px;">CSV</button> <button id="lootClearBtn" data-tooltip="Clear all loot history" style="background:none;border:none;color:#aaa;cursor:pointer;margin-right:6px;">⟳</button> <button id="lootMinBtn" data-tooltip="Minimize/Expand" style="background:none;border:none;color:#aaa;cursor:pointer;">${ isMinimized ? "+" : "−" }</button> </div> </div> <div id="lootTabs" style="display:flex;padding:4px 10px;gap:6px;border-bottom:1px solid #333;background:rgba(24,24,24,0.8);"></div> <div id="lootTotals" style="padding:10px;display:${ isMinimized ? "none" : "block" };max-height:400px;overflow-y:auto;"></div> `; 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 += `<div style="color:white">• ${name} × ${count}</div>`; } 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(); } })();