您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Shows golem visit numbers without having to bell your golem or have an idle one. Also adds a small tooltip in the aura to inform you of the hours left until max aura and the number of golems / hats needed for it.
// ==UserScript== // @name MH - Golem Visit Stats // @version 2.1.0 // @description Shows golem visit numbers without having to bell your golem or have an idle one. Also adds a small tooltip in the aura to inform you of the hours left until max aura and the number of golems / hats needed for it. // @author hannadDev // @namespace https://greasyfork.org/en/users/1238393-hannaddev // @match https://www.mousehuntgame.com/* // @icon https://www.mousehuntgame.com/images/items/stats/large/680f6a68612ca9181a90b5719b20ef78.png // @require https://cdn.jsdelivr.net/npm/[email protected]/scripts/utils.js // @require https://cdn.jsdelivr.net/npm/[email protected]/scripts/statics.js // @require https://cdn.jsdelivr.net/npm/[email protected]/mousehunt-utils.js // @license MIT // ==/UserScript== (function () { "use strict"; const stylesheetUrl = "https://cdn.jsdelivr.net/npm/[email protected]/stylesheets/main.css"; const dataUrl = "https://raw.githubusercontent.com/hannadDev/mh-assets/main/data/golem-visit-stats-data.json"; hd_utils.addStyleElement(stylesheetUrl); let data = { eventYear: "0", eventEndTimestamp: 0, shutdownPeriodTimestamp: 0 }; // #region Variables let isDebug = false; const localStorageKey = `mh-golem-visit-stats`; let storedData = {}; // #endregion // #region Observers const observer = new MutationObserver(function (mutations) { if (isDebug) { console.log("Mutated"); for (const mutation of mutations) { console.log({ mutation }); console.log(mutation.target); } } // Only save if something was added. if (mutations.some(v => v.type === "childList" && v.addedNodes.length > 0 && v.target.className !== "journaldate")) { saveEntries(); } }); function activateMutationObserver() { let observerTarget = document.querySelector(`#journalContainer .content`); if (observerTarget !== null && observerTarget !== undefined) { observer.observe(observerTarget, { childList: true, subtree: true }); } } const xhrObserver = XMLHttpRequest.prototype.open; XMLHttpRequest.prototype.open = function () { this.addEventListener("load", function () { if (this.responseURL === `https://www.mousehuntgame.com/managers/ajax/turns/activeturn.php`) { if (isDebug) { console.log("Horn detected"); } saveEntries(); } else if (this.responseURL === `https://www.mousehuntgame.com/managers/ajax/pages/page.php`) { if (isDebug) { console.log("Page load detected"); } activateMutationObserver(); } }) xhrObserver.apply(this, arguments); } // #endregion // #region Golem Stats Methods function getGolemStats(year = data.eventYear) { cleanUpEntries(); let golemStats = {}; let isCached = false; let userQuests = null; if (user.quests.QuestIceFortress !== undefined) { userQuests = user.quests.QuestIceFortress; } if (user.quests.QuestCinnamonTreeGrove !== undefined) { userQuests = user.quests.QuestCinnamonTreeGrove; } if (user.quests.QuestGolemWorkshop !== undefined) { userQuests = user.quests.QuestGolemWorkshop; } let total = { Golems: 0, Hats: 0, Scarves: 0 }; if (year === data.eventYear && userQuests !== null) { for (const k1 in userQuests.destinations) { for (const k2 in userQuests.destinations[k1].environments) { if (userQuests.destinations[k1].environments[k2].num_golem_visits !== null) { golemStats[userQuests.destinations[k1].environments[k2].name] = { Golems: userQuests.destinations[k1].environments[k2].num_golem_visits }; } } } // Cache data if year is current year if (year === data.eventYear) { setYearStats(golemStats, data.eventYear); } } else { isCached = true; golemStats = (storedData[year] !== undefined) ? storedData[year]["stats"] ?? [] : []; } for (const k in golemStats) { total.Golems += golemStats[k].Golems; if (storedData[year] !== undefined && storedData[year]["scrapedStats"] !== undefined && storedData[year]["scrapedStats"][k] !== undefined) { golemStats[k].Hats = storedData[year]["scrapedStats"][k].Hats; golemStats[k].Scarves = storedData[year]["scrapedStats"][k].Scarves; total.Hats += storedData[year]["scrapedStats"][k].Hats; total.Scarves += storedData[year]["scrapedStats"][k].Scarves; } else { golemStats[k].Hats = 0; golemStats[k].Scarves = 0; } } if (isDebug) { console.log("Golem Stats:"); console.log(golemStats); console.log("Total:"); console.log(total); } if (golemStats !== "") { createGolemStatsPopup(golemStats, total, isCached, year); } } function showButton() { const target = document.querySelector(".mousehuntHud-gameInfo"); if (target) { const link = document.createElement("a"); link.id = "golem-stats-button"; link.innerText = "[Golem Stats]"; link.addEventListener("click", function () { getGolemStats(); }); target.prepend(link); } } function createGolemStatsPopup(golemStats, totalStats, isCached, year) { document.querySelectorAll("#golem-stats-popup-div").forEach(el => el.remove()); const golemStatsPopup = document.createElement("div"); golemStatsPopup.id = ("golem-stats-popup-div"); golemStatsPopup.classList.add("hd-popup"); // Golem Stats Division const golemStatsDiv = document.createElement("div"); // Title const title = document.createElement("h2"); title.innerText = `Golem Visit Stats - ${year}` title.classList.add("hd-bold"); golemStatsDiv.appendChild(title); // Subtitle if (isCached) { const subtitle = document.createElement("h4"); if (year === data.eventYear) { subtitle.innerText = "Visit event area for latest stats"; } else { subtitle.innerText = "Showing previous year's cached data"; } golemStatsDiv.appendChild(subtitle); } const spacing = document.createElement("br"); golemStatsDiv.appendChild(spacing); // Table for golem stats const golemStatsTable = document.createElement("table"); golemStatsTable.id = "golem-stats-table" golemStatsTable.classList.add("hd-table"); const headings = ["Locations", "Golems", "Hats", "Scarves"]; // Create headings for (let i = 0; i < headings.length; ++i) { const headingElement = document.createElement("th"); headingElement.id = `golem-stats-${headings[i].toLowerCase()}-heading`; headingElement.innerText = headings[i]; headingElement.classList.add("hd-table-heading"); golemStatsTable.appendChild(headingElement); } // Table Body const tableBody = document.createElement("tbody"); var j = 0 for (const location in golemStats) { var tableRow = document.createElement("tr"); tableRow.id = "golem-stats-table-row-" + j for (let i = 0; i < headings.length; ++i) { const tdElement = document.createElement("td"); tdElement.innerText = i === 0 ? location : golemStats[location][headings[i]]; tdElement.classList.add("hd-table-td"); tableRow.appendChild(tdElement); } tableBody.appendChild(tableRow); j++; } // Total Stats var totalStatsRow = document.createElement("tr"); totalStatsRow.id = "golem-stats-table-row-total"; totalStatsRow.classList.add("hd-table-footer-tr"); for (let i = 0; i < headings.length; ++i) { const tdElement = document.createElement("td"); tdElement.innerText = i === 0 ? "Total" : totalStats[headings[i]]; tdElement.classList.add("hd-table-td", "hd-bold"); totalStatsRow.appendChild(tdElement); } tableBody.appendChild(totalStatsRow); // Final append golemStatsTable.appendChild(tableBody) golemStatsDiv.appendChild(golemStatsTable); golemStatsPopup.appendChild(golemStatsDiv); // Years links const linksDiv = document.createElement("div"); linksDiv.classList.add("hd-m-2"); const years = Object.keys(storedData); const indexOfFlags = years.indexOf("Flags"); if (indexOfFlags >= 0) { years.splice(indexOfFlags, 1); } for (const y of years) { let yearLink; if (y === year) { yearLink = document.createElement("span"); } else { yearLink = document.createElement("a"); yearLink.onclick = function () { getGolemStats(y); } } yearLink.id = `${y}-link`; yearLink.innerText = `${y}`; yearLink.classList.add("hd-m-2"); linksDiv.appendChild(yearLink); } // Append golemStatsPopup.appendChild(linksDiv); // Close button const closeButton = document.createElement("button"); closeButton.id = "close-button"; closeButton.textContent = "Close"; closeButton.classList.add("hd-button"); closeButton.onclick = function () { document.body.removeChild(golemStatsPopup); } // Append golemStatsPopup.appendChild(closeButton); // Final Append document.body.appendChild(golemStatsPopup); hd_utils.dragElement(golemStatsPopup, golemStatsDiv); } // #endregion // #region LocalStorage Methods function getStoredData() { const savedData = localStorage.getItem(localStorageKey); if (savedData !== null) { return JSON.parse(savedData); } return {}; } function setData(stats) { localStorage.setItem(localStorageKey, JSON.stringify(stats)); } function setYearStats(stats, year) { if (storedData[year] === undefined) { storedData[year] = {}; } storedData[year]["stats"] = stats; setData(storedData); } function setYearLogs(logEntries, scrapedStats, year) { if (storedData[year] === undefined) { storedData[year] = {}; } storedData[year]["logEntries"] = logEntries; storedData[year]["scrapedStats"] = scrapedStats; setData(storedData); } // #endregion // #region Aura Methods function calculateAura() { if (document.querySelector(".MiniEventFestiveAura") != null && document.querySelector(".MiniEventFestiveAura").classList.contains("active")) { const festiveAuraTooltipElement = document.querySelector(".MiniEventFestiveAura .trapImageView-tooltip-trapAura"); // #region Current Aura const currentAuraTextSplit = festiveAuraTooltipElement.innerText.split("expires on:"); const currentAuraDateSplit = currentAuraTextSplit[currentAuraTextSplit.length - 1].trim().split(" "); if (isDebug) { console.log("Current Aura Date Split"); console.log(currentAuraDateSplit); } const timeSplit = currentAuraDateSplit[4].split(":"); let hours = timeSplit[0]; const minutes = timeSplit[1].slice(0, timeSplit[1].length - 2); let period = timeSplit[1].slice(timeSplit[1].length - 2, timeSplit[1].length); if (period === "pm" && hours != "12") { hours = 12 + parseInt(hours); } const parsedDate = new Date(`${currentAuraDateSplit[2]}/${hd_statics.monthMap.get(currentAuraDateSplit[0])}/${currentAuraDateSplit[1].replace(",", "")} ${hours}:${minutes}`); const parsedDateTimestamp = parsedDate.getTime(); if (isDebug) { console.log("Time Split:"); console.log(timeSplit); console.log(`Hours= ${hours}`); console.log(`Minutes= ${minutes}`); console.log(`Time Period= ${period}`); console.log(`ParsedDate= ${parsedDate}`); console.log(`ParsedDate Timestamp= ${parsedDateTimestamp}`); } // #endregion // #region End Date of Aura let endAuraTextSplit = festiveAuraTooltipElement.innerText.split("to:")[1].split("Your")[0]; endAuraTextSplit = endAuraTextSplit.replace("(", ""); endAuraTextSplit = endAuraTextSplit.replace(")", ""); let endAuraDateTextSplit = endAuraTextSplit.trim().split(" "); const endTimeSplit = endAuraDateTextSplit[4].split(":"); let endHours = endTimeSplit[0]; const endMinutes = endTimeSplit[1].slice(0, endTimeSplit[1].length - 2); let endPeriod = endTimeSplit[1].slice(endTimeSplit[1].length - 2, endTimeSplit[1].length); if (endPeriod === "pm" && endHours != "12") { endHours = 12 + parseInt(endHours); } const parsedEndDate = new Date(`${endAuraDateTextSplit[2]}/${hd_statics.monthMap.get(endAuraDateTextSplit[0])}/${endAuraDateTextSplit[1].replace(",", "")} ${endHours}:${endMinutes}`).getTime(); if (isDebug) { console.log(`EndAuraTextSplit= ${endAuraTextSplit}`); console.log(`EndAuraDateTextSplit`); console.log(endAuraDateTextSplit); console.log(`ParsedEndDate= ${parsedEndDate}`); } // #endregion // #region Golem Stats const hoursDifference = (parsedEndDate - parsedDateTimestamp) / 1000 / 60 / 60; if (isDebug) { console.log(`HoursDifference= ${hoursDifference}`); } // #endregion // #region Max Hunts Stats const currentTimestamp = Date.now(); const maxHunts = (data.eventEndTimestamp - currentTimestamp) / 1000 / 60 / 60 * 5; const maxShutdownHunts = (data.shutdownPeriodTimestamp - currentTimestamp) / 1000 / 60 / 60 * 5; if (isDebug) { console.log(`eventEndTimestamp= ${data.eventEndTimestamp}`); console.log(`shutdownPeriodTimestamp= ${data.shutdownPeriodTimestamp}`); console.log(`currentTimestamp= ${currentTimestamp}`); console.log(`maxHunts= ${maxHunts}`); console.log(`maxShutdownHunts= ${maxShutdownHunts}`); } // #endregion // #region Adding tooltip UI if (document.getElementById("max-aura-stats-tooltip")) { document.getElementById("max-aura-stats-tooltip").remove(); } const maxAuraStatsDiv = document.createElement("div"); maxAuraStatsDiv.id = "max-aura-stats-tooltip"; maxAuraStatsDiv.appendChild(document.createElement("br")); if (hoursDifference > 0) { const hoursRemainingText = document.createTextNode(`Hours needed for max aura: ${hoursDifference.toFixed(2)}`); maxAuraStatsDiv.appendChild(hoursRemainingText); maxAuraStatsDiv.appendChild(document.createElement("br")); const golemsNeededText = document.createTextNode(`${Math.ceil(hoursDifference / 5)} golems in ${Math.ceil(Math.ceil(hoursDifference / 5) / 3) * 25} hunts`); maxAuraStatsDiv.appendChild(golemsNeededText); maxAuraStatsDiv.appendChild(document.createElement("br")); const hatsNeededText = document.createTextNode(`${Math.ceil(hoursDifference / 10)} hats in ${Math.ceil(Math.ceil(hoursDifference / 10) / 3) * 25} hunts`); maxAuraStatsDiv.appendChild(hatsNeededText); if (maxHunts > 0 || maxShutdownHunts > 0) { maxAuraStatsDiv.appendChild(document.createElement("br")); } if (maxShutdownHunts > 0) { maxAuraStatsDiv.appendChild(document.createElement("br")); const maxShutdownHuntsText = document.createTextNode(`Larry gets ${Math.floor(maxShutdownHunts)} hunts until shutdown.`); maxAuraStatsDiv.appendChild(maxShutdownHuntsText); } if (maxHunts > 0) { maxAuraStatsDiv.appendChild(document.createElement("br")); const maxHuntsText = document.createTextNode(`Until GWH ends, Larry gets ${Math.floor(maxHunts)} hunts, can you?`); maxAuraStatsDiv.appendChild(maxHuntsText); } } else { const maxAuraText = document.createTextNode("Max aura time reached 🐋 Time to shore🎉"); maxAuraStatsDiv.appendChild(maxAuraText); } festiveAuraTooltipElement.appendChild(maxAuraStatsDiv); // #endregion } } // #endregion // #region Journal Scraping Methods function saveEntries() { const ownJournal = document.querySelector(`#journalEntries${user.user_id}`); if (!ownJournal) { if (isDebug) { console.log(`Other hunters' profile detected`); } return; } const entries = document.querySelectorAll(".entry"); let savedEntries = {}; if (storedData[data.eventYear] != undefined && storedData[data.eventYear]["logEntries"] != undefined) { savedEntries = storedData[data.eventYear]["logEntries"]; } let savedScrapedStats = {}; if (storedData[data.eventYear] != undefined && storedData[data.eventYear]["scrapedStats"] != undefined) { savedScrapedStats = storedData[data.eventYear]["scrapedStats"]; } let addedNewEntries = false; for (const entry of entries) { const entryId = entry.dataset.entryId if (!entryId) return; if (savedEntries[entryId]) { if (isDebug) console.log(`Entry ${entryId} already stored`); } else { if (isDebug) console.log(`Stored new entry ${entryId}`); if (entry.className.search(/(sendGolem)/) !== -1) { let entryInfo = extractInfoFromEntry(entry); savedEntries[entry.dataset.entryId] = entryInfo; if (savedScrapedStats[entryInfo.LocationName] !== undefined) { if (entryInfo.Hat) savedScrapedStats[entryInfo.LocationName].Hats++; if (entryInfo.Scarf) savedScrapedStats[entryInfo.LocationName].Scarves++; } else { savedScrapedStats[entryInfo.LocationName] = { Hats: entryInfo.Hat ? 1 : 0, Scarves: entryInfo.Scarf ? 1 : 0 } } addedNewEntries = true; } } } if (addedNewEntries) { setYearLogs(savedEntries, savedScrapedStats, data.eventYear); } } function cleanUpEntries() { if (!storedData.Flags) { storedData.Flags = []; } if (storedData.CleanedUpEntries20241211) { storedData.Flags.push("CleanedUpEntries20241211"); delete storedData.CleanedUpEntries20241211; return; } if (storedData.Flags.includes("CleanedUpEntries20241211")) { return; } let savedEntries = {}; if (storedData["2024"] != undefined && storedData["2024"]["logEntries"] != undefined) { savedEntries = storedData["2024"]["logEntries"]; } for (const entryId in savedEntries) { if (savedEntries[entryId].LocationName.indexOf("the ") === 0) { savedEntries[entryId].LocationName = savedEntries[entryId].LocationName.replace("the ", ""); } } let savedScrapedStats = {}; if (storedData["2024"] != undefined && storedData["2024"]["scrapedStats"] != undefined) { savedScrapedStats = storedData["2024"]["scrapedStats"]; } for (const locationName in savedScrapedStats) { if (locationName.indexOf("the ") === 0) { const trimmedLocationName = locationName.replace("the ", ""); if (!savedScrapedStats[trimmedLocationName]) { savedScrapedStats[trimmedLocationName] = { "Hats": 0, "Scarves": 0 }; } savedScrapedStats[trimmedLocationName]["Hats"] += savedScrapedStats[locationName]["Hats"]; savedScrapedStats[trimmedLocationName]["Scarves"] += savedScrapedStats[locationName]["Scarves"]; delete savedScrapedStats[locationName]; } } storedData.Flags.push("CleanedUpEntries20241211"); setYearLogs(savedEntries, savedScrapedStats, data.eventYear); } function extractInfoFromEntry(entry) { const info = { LocationName: "", Hat: false, Scarf: false }; const journalText = entry.querySelector(".journaltext").innerText; info.Hat = journalText.includes("Hat"); info.Scarf = journalText.includes("Scarf"); const split1 = journalText.split("It lumbered towards "); if (split1[1] === undefined) return null; split1[1] = split1[1].replace("the ", ""); const split2 = split1[1].split(" and will be back"); info.LocationName = split2[0]; return info; } // #endregion function Initialize() { if (isDebug) console.log(`Initializing.`); storedData = getStoredData(); showButton(); calculateAura(); activateMutationObserver(); // #region Listeners onPageChange({ change: () => { calculateAura(); } }); onRequest(() => { calculateAura(); }, "managers/ajax/turns/activeturn.php"); //#endregion } hd_utils.readJson(dataUrl, (obj) => { data = obj; Initialize(); }); })();