您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Shows caught and uncaught mouse breeds for every location
// ==UserScript== // @name MouseHunt - Location Catch Stats // @author Tran Situ (tsitu) // @namespace https://greasyfork.org/en/users/232363-tsitu // @version 1.5 // @description Shows caught and uncaught mouse breeds for every location // @match http://www.mousehuntgame.com/* // @match https://www.mousehuntgame.com/* // ==/UserScript== (function () { // Inject link into UI const target = document.querySelector(".mousehuntHud-gameInfo"); if (target) { const link = document.createElement("a"); link.innerText = "[Location Info]"; link.addEventListener("click", function () { const existing = document.querySelector("#tsitu-location-stats"); if (existing) existing.remove(); else render(); return false; // Prevent default link clicked behavior }); target.prepend(link); } /** * Logic to generate the popup */ function render() { const existing = document.querySelector("#tsitu-location-stats"); if (existing) existing.remove(); let selfTotal = 0; let sumTotal = 0; const detailDiv = document.createElement("div"); detailDiv.style.textAlign = "left"; // Get cached overall data const overallRaw = localStorage.getItem("tsitu-location-overall-stats"); if (overallRaw) { const overall = JSON.parse(overallRaw); // Cached detailed stats const statsObj = JSON.parse(localStorage.getItem("tsitu-location-detailed-stats")) || {}; // Previously opened details const prevOpen = JSON.parse(localStorage.getItem("tsitu-location-opened"))["open"] || {}; const completeArr = []; const incompleteArr = []; for (let loc of Object.keys(overall)) { const data = overall[loc]; if (data.caught === data.total) { completeArr.push(loc); } else { incompleteArr.push(loc); } } // Create 'Incomplete' <details> element const incompleteDetail = document.createElement("details"); incompleteDetail.style.fontSize = "16px"; incompleteDetail.open = prevOpen.indexOf("Incomplete") >= 0; const incompleteSummary = document.createElement("summary"); incompleteSummary.innerText = "Incomplete"; incompleteDetail.appendChild(incompleteSummary); // Create 'Expand/Collapse All' checkbox for 'Incomplete' const incompleteLabel = document.createElement("label"); incompleteLabel.className = "tsitu-incomplete-box-label"; incompleteLabel.htmlFor = "tsitu-incomplete-box"; incompleteLabel.innerText = "Expand/Collapse All"; incompleteLabel.style.fontSize = "12px"; const incompleteBox = document.createElement("input"); incompleteBox.id = "tsitu-incomplete-box"; incompleteBox.name = "tsitu-incomplete-box"; incompleteBox.type = "checkbox"; incompleteBox.checked = localStorage.getItem("tsitu-location-expand-incomplete") == "1"; incompleteBox.addEventListener("click", function () { if (document.querySelector("#tsitu-incomplete-box").checked) { document.querySelectorAll(".tsitu-incomplete-detail").forEach(el => { el.open = true; }); localStorage.setItem("tsitu-location-expand-incomplete", 1); } else { document.querySelectorAll(".tsitu-incomplete-detail").forEach(el => { el.open = false; }); localStorage.setItem("tsitu-location-expand-incomplete", 0); } cacheOpenedDetails(); }); // Insert 'Incomplete' checkbox incompleteSummary.insertAdjacentElement( "afterend", document.createElement("br") ); incompleteSummary.insertAdjacentElement( "afterend", document.createElement("br") ); incompleteSummary.insertAdjacentElement("afterend", incompleteLabel); incompleteSummary.insertAdjacentElement("afterend", incompleteBox); incompleteSummary.insertAdjacentElement( "afterend", document.createElement("br") ); // Generate incomplete location details for (let loc of incompleteArr.sort()) { generateDetail(loc, "incomplete"); } // Create 'Complete' <details> element const completeDetail = document.createElement("details"); completeDetail.style.fontSize = "16px"; completeDetail.open = prevOpen.indexOf("Complete") >= 0; const completeSummary = document.createElement("summary"); completeSummary.innerText = "Complete"; completeDetail.appendChild(completeSummary); // Create 'Expand/Collapse All' checkbox for 'Complete' const completeLabel = document.createElement("label"); completeLabel.className = "tsitu-complete-box-label"; completeLabel.htmlFor = "tsitu-complete-box"; completeLabel.innerText = "Expand/Collapse All"; completeLabel.style.fontSize = "12px"; const completeBox = document.createElement("input"); completeBox.id = "tsitu-complete-box"; completeBox.type = "checkbox"; completeBox.checked = localStorage.getItem("tsitu-location-expand-complete") == "1"; completeBox.addEventListener("click", function () { if (document.querySelector("#tsitu-complete-box").checked) { document.querySelectorAll(".tsitu-complete-detail").forEach(el => { el.open = true; }); localStorage.setItem("tsitu-location-expand-complete", 1); } else { document.querySelectorAll(".tsitu-complete-detail").forEach(el => { el.open = false; }); localStorage.setItem("tsitu-location-expand-complete", 0); } cacheOpenedDetails(); }); // Insert 'Complete' checkbox completeSummary.insertAdjacentElement( "afterend", document.createElement("br") ); completeSummary.insertAdjacentElement( "afterend", document.createElement("br") ); completeSummary.insertAdjacentElement("afterend", completeLabel); completeSummary.insertAdjacentElement("afterend", completeBox); completeSummary.insertAdjacentElement( "afterend", document.createElement("br") ); // Generate completed location details for (let loc of completeArr.sort()) { generateDetail(loc, "complete"); } /** * Generate inner <details> elements for main "Incomplete" and "Complete" * @param {string} loc Location name * @param {string} type "incomplete" or "complete" */ function generateDetail(loc, type) { const data = overall[loc]; selfTotal += data.caught; sumTotal += data.total; const detail = document.createElement("details"); detail.className = type === "complete" ? "tsitu-complete-detail" : "tsitu-incomplete-detail"; detail.style.fontSize = "14px"; detail.style.marginBottom = "5px"; // Check if previously opened if (prevOpen.indexOf(loc) >= 0) { detail.open = true; } const summary = document.createElement("summary"); summary.innerText = `${loc} (${data.caught} of ${data.total})`; const refreshButton = document.createElement("button"); refreshButton.innerText = "Update"; refreshButton.addEventListener("click", function () { requestLocation(data.type, loc); }); const updateSpan = document.createElement("span"); updateSpan.style.fontStyle = "italic"; updateSpan.style.fontSize = "12px"; updateSpan.innerText = `Updated: ${ statsObj[loc] ? new Date(parseInt(statsObj[loc]["date"])).toLocaleString() : "N/A" }`; // Build body text const innerSpan = document.createElement("span"); let statsText = "\n\n"; if (statsObj[loc]) { let missText = "Not yet caught here: \n"; const missArr = statsObj[loc]["missing"]; if (missArr.length === 0) { missText = "All breeds available in this location\nhave been caught here.\n"; } else { missArr.forEach(el => { missText += `- ${el}\n`; }); } let caughtText = ""; const hideCaught = localStorage.getItem("tsitu-location-hideCaught") == "1"; if (!hideCaught) { const caughtArr = statsObj[loc]["caught"]; if (caughtArr.length > 0) { caughtText = "Breeds caught here: \n"; caughtArr.forEach(el => { caughtText += `- ${el[0]} (${el[1]})\n`; }); caughtText += "\n"; } } statsText += `${missText}\n${caughtText}`; } innerSpan.innerText += statsText; innerSpan.appendChild(refreshButton); innerSpan.appendChild(document.createElement("br")); innerSpan.appendChild(document.createElement("br")); detail.appendChild(summary); detail.appendChild(document.createElement("br")); detail.appendChild(updateSpan); detail.appendChild(innerSpan); // Append to the appropriate main <details> type === "complete" ? completeDetail.appendChild(detail) : incompleteDetail.appendChild(detail); } // Append elements to detailDiv detailDiv.appendChild(incompleteDetail); detailDiv.appendChild(document.createElement("br")); detailDiv.appendChild(completeDetail); detailDiv.appendChild(document.createElement("br")); } // Create and style the main <div> const mainDiv = document.createElement("div"); mainDiv.id = "tsitu-location-stats"; mainDiv.style.backgroundColor = "#F5F5F5"; mainDiv.style.position = "absolute"; mainDiv.style.zIndex = "9999"; mainDiv.style.left = "35%"; mainDiv.style.top = "25px"; mainDiv.style.border = "solid 3px #696969"; mainDiv.style.borderRadius = "20px"; mainDiv.style.padding = "10px"; mainDiv.style.textAlign = "center"; // Top div styling (close button, title, drag instructions) const topDiv = document.createElement("div"); const titleSpan = document.createElement("span"); titleSpan.style.fontWeight = "bold"; titleSpan.style.fontSize = "18px"; titleSpan.style.textDecoration = "underline"; titleSpan.style.paddingLeft = "20px"; titleSpan.innerText = "Location Catch Stats"; const dragSpan = document.createElement("span"); dragSpan.innerText = "(Drag title to reposition this popup)"; const closeButton = document.createElement("button"); closeButton.style.float = "right"; closeButton.style.fontSize = "8px"; closeButton.innerText = "x"; closeButton.addEventListener("click", function () { document.body.removeChild(mainDiv); }); topDiv.appendChild(closeButton); topDiv.appendChild(titleSpan); topDiv.appendChild(document.createElement("br")); topDiv.appendChild(dragSpan); const overallUpdateSpan = document.createElement("span"); const cachedDate = localStorage.getItem("tsitu-location-overall-updated"); overallUpdateSpan.innerText = `Last updated (overall data): ${ cachedDate ? new Date(parseInt(cachedDate)).toLocaleString() : "N/A" }`; const updateOverallButton = document.createElement("button"); updateOverallButton.innerText = "Update overall completion data"; updateOverallButton.addEventListener("click", function () { cacheOpenedDetails(); updateOverallData(); }); // Checkbox to hide text for caught mice const hideCaughtLabel = document.createElement("label"); hideCaughtLabel.className = "tsitu-hideCaught-box-label"; hideCaughtLabel.htmlFor = "tsitu-hideCaught-box"; hideCaughtLabel.innerText = "Hide caught mice"; hideCaughtLabel.style.fontSize = "12px"; const hideCaughtBox = document.createElement("input"); hideCaughtBox.id = "tsitu-hideCaught-box"; hideCaughtBox.name = "tsitu-hideCaught-box"; hideCaughtBox.type = "checkbox"; hideCaughtBox.checked = localStorage.getItem("tsitu-location-hideCaught") == "1"; hideCaughtBox.addEventListener("click", function () { if (document.querySelector("#tsitu-hideCaught-box").checked) { localStorage.setItem("tsitu-location-hideCaught", 1); } else { localStorage.setItem("tsitu-location-hideCaught", 0); } cacheOpenedDetails(); render(); }); // TODO: sumTotal only includes unlocked locations for each user const totalSpan = document.createElement("span"); totalSpan.style.fontSize = "16px"; totalSpan.innerText = `Total: ${selfTotal} / ${sumTotal}`; // Append everything to mainDiv and document mainDiv.appendChild(topDiv); mainDiv.appendChild(document.createElement("br")); mainDiv.appendChild(overallUpdateSpan); mainDiv.appendChild(document.createElement("br")); mainDiv.appendChild(updateOverallButton); mainDiv.appendChild(document.createElement("br")); mainDiv.appendChild(document.createElement("br")); if (sumTotal > 0) { mainDiv.appendChild(totalSpan); mainDiv.appendChild(document.createElement("br")); mainDiv.appendChild(document.createElement("br")); } mainDiv.appendChild(hideCaughtBox); mainDiv.appendChild(hideCaughtLabel); mainDiv.appendChild(document.createElement("br")); mainDiv.appendChild(document.createElement("br")); mainDiv.appendChild(detailDiv); document.body.appendChild(mainDiv); dragElement(mainDiv, titleSpan); // Reposition popup based on previous dragged location const posTop = localStorage.getItem("location-stat-pos-top"); const posLeft = localStorage.getItem("location-stat-pos-left"); if (posTop && posLeft) { const intTop = parseInt(posTop); if (intTop > 0 && intTop < window.innerHeight - 150) { mainDiv.style.top = posTop; } const intLeft = parseInt(posLeft); if (intLeft > 0 && intLeft < window.innerWidth - 150) { mainDiv.style.left = posLeft; } } // Reposition current location <details> to the top let currentDetail; let currentLoc = user.environment_name || "N/A"; if (currentLoc === "Twisted Garden") { currentLoc = "Living Garden"; } else if (currentLoc === "Cursed City") { currentLoc = "Lost City"; } else if (currentLoc === "Sand Crypts") { currentLoc = "Sand Dunes"; } document.querySelectorAll("#tsitu-location-stats summary").forEach(el => { if (el.textContent.indexOf(currentLoc) >= 0) { currentDetail = el.parentElement; el.parentElement.remove(); } }); if (currentDetail) { currentDetail.className = "tsitu-current-detail"; currentDetail.style.textAlign = "left"; currentDetail.open = true; detailDiv.insertAdjacentElement("beforebegin", currentDetail); detailDiv.insertAdjacentElement( "beforebegin", document.createElement("br") ); } } /** * Cache location names for <details> with open = true */ function cacheOpenedDetails() { const obj = {}; const arr = []; document .querySelectorAll( "#tsitu-location-stats details:not(.tsitu-current-detail)" ) .forEach(el => { if (el.open) { const summary = el.querySelector("summary").textContent; if (summary !== "Complete" && summary !== "Incomplete") { const loc = summary.split(" (")[0]; arr.push(loc); } else { arr.push(summary); } } }); obj["open"] = arr; localStorage.setItem("tsitu-location-opened", JSON.stringify(obj)); } /** * Fetch overall/total completion data from getstat.php */ function updateOverallData() { const xhr = new XMLHttpRequest(); xhr.open( "POST", `https://www.mousehuntgame.com/managers/ajax/pages/page.php?page_class=HunterProfile&page_arguments%5Btab%5D=mice&page_arguments%5Bsub_tab%5D=location&uh=${user.unique_hash}` ); xhr.onload = function () { const response = JSON.parse(xhr.responseText); const locations = response.page.tabs.mice.subtabs[1].mouse_list.categories; if (locations) { const masterObj = {}; locations.forEach(el => { const obj = {}; obj["caught"] = el.caught; obj["total"] = el.total; obj["type"] = el.type; masterObj[el.name] = obj; }); localStorage.setItem( "tsitu-location-overall-stats", JSON.stringify(masterObj) ); localStorage.setItem("tsitu-location-overall-updated", Date.now()); render(); } }; xhr.onerror = function () { console.error(xhr.statusText); }; xhr.send(); } /** * Fetch individual location data from getstat.php * @param {number} category Location category string * @param {string} locationName */ function requestLocation(category, locationName) { const xhr = new XMLHttpRequest(); xhr.open( "POST", `https://www.mousehuntgame.com/managers/ajax/mice/mouse_list.php?action=get_environment&category=${category}&user_id=${user.user_id}&display_mode=stats&view=ViewMouseListEnvironments&uh=${user.unique_hash}`, true ); xhr.onload = function () { const response = JSON.parse(xhr.responseText); const stats = response.mouse_list_category.subgroups[0].mice; if (stats) { const missedArr = []; const caughtArr = []; stats.forEach(el => { const caught = typeof el.num_catches === "string" ? parseInt(el.num_catches.replace(/,/g, "")) : el.num_catches; if (caught === 0) { missedArr.push(el.name); } else if (caught > 0) { caughtArr.push([el.name, caught]); } }); const obj = {}; obj["missing"] = missedArr; obj["caught"] = caughtArr; obj["date"] = Date.now(); const cacheRaw = localStorage.getItem("tsitu-location-detailed-stats"); if (cacheRaw) { const cache = JSON.parse(cacheRaw); cache[locationName] = obj; localStorage.setItem( "tsitu-location-detailed-stats", JSON.stringify(cache) ); } else { const cache = {}; cache[locationName] = obj; localStorage.setItem( "tsitu-location-detailed-stats", JSON.stringify(cache) ); } cacheOpenedDetails(); render(); } }; xhr.onerror = function () { console.error(xhr.statusText); }; xhr.send(); } /** * Element dragging functionality * @param {HTMLElement} el Element that actually moves * @param {HTMLElement} target Element to drag in order to move 'el' */ function dragElement(el, target) { var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; if (document.getElementById(target.id + "header")) { document.getElementById(target.id + "header").onmousedown = dragMouseDown; } else { target.onmousedown = dragMouseDown; } function dragMouseDown(e) { e = e || window.event; pos3 = e.clientX; pos4 = e.clientY; document.onmouseup = closeDragElement; document.onmousemove = elementDrag; } function elementDrag(e) { e = e || window.event; pos1 = pos3 - e.clientX; pos2 = pos4 - e.clientY; pos3 = e.clientX; pos4 = e.clientY; el.style.top = el.offsetTop - pos2 + "px"; el.style.left = el.offsetLeft - pos1 + "px"; } function closeDragElement() { document.onmouseup = null; document.onmousemove = null; localStorage.setItem("location-stat-pos-top", el.style.top); localStorage.setItem("location-stat-pos-left", el.style.left); } } })();