// ==UserScript== // @name MouseHunt - Marketplace UI Tweaks // @author Tran Situ (tsitu) // @namespace https://greasyfork.org/en/users/232363-tsitu // @version 1.0 // @description Adds useful features and tweaks to the Marketplace rework // @match http://www.mousehuntgame.com/* // @match https://www.mousehuntgame.com/* // @downloadURL none // ==/UserScript== (function() { /** * [ Notes ] * innerText has poor retrieval perf, use textContent * (http://perfectionkills.com/the-poor-misunderstood-innerText/) * Is there a better way to center scrollRow vertically within table? * (https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView) */ MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; // Initialize 'Browse' tab item caching if (localStorage.getItem("marketplace-browse-cache-tsitu") === null) { const cacheObj = { Cheese: 0, "Baskets & Kits": 0, Charms: 0, Crafting: 0, Special: 0, Collectibles: 0, Weapons: 0, Skins: 0 }; localStorage.setItem( "marketplace-browse-cache-tsitu", JSON.stringify(cacheObj) ); } // Only observe changes to the #overlayPopup element const observerTarget = document.querySelector("#overlayPopup"); const observer = new MutationObserver(function() { // Check if the Marketplace interface is open if (document.querySelector(".marketplaceView")) { // Disconnect and reconnect later to prevent mutation loop observer.disconnect(); const browseTab = document.querySelector("[data-tab=browse].active"); const backButton = document.querySelector("a.marketplaceView-breadcrumb"); if (browseTab && !backButton) { /* Browse tab logic (active Browse tab + inactive 'Back' button) */ const sidebar = document.querySelector( ".marketplaceView-browse-sidebar" ); const itemType = sidebar.querySelector( ".marketplaceView-browse-sidebar-link.active" ); const groupName = itemType ? `Browse Tab - ${itemType.textContent}` : "Browse Tab"; console.group(groupName); console.time("[Total]"); // Feature: Make item images 40x40 px document.querySelectorAll(".marketplaceView-itemImage").forEach(el => { el.style.width = "40px"; el.style.height = "40px"; el.style.backgroundSize = "100%"; }); let totalValueSum = 0; /** * Abbreviates large number values up to 1 decimal point * k = 1,000 and m = 1,000,000 * @param {number} num Integer to abbreviate * @return {string} */ function abbrev(num) { if (num <= 999) { return "" + num; } else if (num >= 1000 && num <= 999999) { let pre = Math.floor(num / 1000); let post = Math.round((num % 1000) / 100); if (post === 10) { post = 0; pre += 1; } return `${pre}.${post}k`; } else if (num >= 1000000) { let pre = Math.floor(num / 1000000); let post = Math.round((num % 1000000) / 100000); if (post === 10) { post = 0; pre += 1; } return `${pre}.${post}m`; } } const rows = document.querySelectorAll("tr[data-item-id]"); if (rows.length > 0) { const avgPriceHeader = document.querySelector( "th.marketplaceView-table-averagePrice" ); const valueHeader = document.createElement("th"); valueHeader.className = "marketplaceView-table-value marketplaceView-table-numeric"; valueHeader.onclick = "hg.views.MarketplaceView.setSortOrder(this); return false;"; valueHeader.innerText = "Value"; if ( avgPriceHeader && !document.querySelector(".marketplaceView-table-value") ) { // Add 'Value' column header avgPriceHeader.insertAdjacentElement("afterend", valueHeader); console.time("Row Logic"); rows.forEach(row => { // Add click handlers to the 's that open up an item page row.querySelectorAll("a").forEach(el => { const aText = el.onclick; if (aText) { if (aText.toString().indexOf("showItem") >= 0) { el.addEventListener("click", function() { // Parse current item name and type for caching const name = row.querySelector( ".marketplaceView-table-name" ); if (name && itemType) { // Retrieve and overwrite localStorage const lsText = localStorage.getItem( "marketplace-browse-cache-tsitu" ); if (lsText) { const lsObj = JSON.parse(lsText); lsObj[itemType.textContent] = name.textContent; localStorage.setItem( "marketplace-browse-cache-tsitu", JSON.stringify(lsObj) ); } } }); } } }); // Parse owned quantity let ownedNum = 0; const ownedText = row.querySelector( ".marketplaceView-table-quantity" ).textContent; if (ownedText !== "-") { ownedNum = parseInt(ownedText.split(",").join("")); } // Parse average prices let priceNum = 0; const priceText = row.querySelector(".marketplaceView-goldValue"); if (priceText.children.length > 0) { priceNum = parseInt( priceText.children[0].title .split(" ")[0] .split(",") .join("") ); } const multValue = ownedNum * priceNum; if (multValue > 0) { totalValueSum += multValue; row.title = `${multValue.toLocaleString()} Gold`; } let outputText = abbrev(multValue); if (priceNum === 0) { // Avg. Price currently unavailable, but value isn't necessarily 0 outputText = "N/A"; } const valueColumn = document.createElement("td"); valueColumn.innerText = outputText; valueColumn.className = "marketplaceView-table-numeric value-column-tsitu"; // Feature: Insert 'Own' x 'Avg. Price' = 'Value' column data row .querySelector(".marketplaceView-table-averagePrice") .insertAdjacentElement("afterend", valueColumn); }); console.timeEnd("Row Logic"); } } // Add info to the sidebar if (sidebar && !document.querySelector(".marketplace-sidebar-tsitu")) { // Container div const div = document.createElement("div"); div.className = "marketplace-sidebar-tsitu"; div.style.margin = "20px"; // Highlighted text const span1 = document.createElement("span"); span1.style.backgroundColor = "#D6EBA1"; span1.innerText = "Highlighted row"; // Other text const span2 = document.createElement("span"); span2.innerText = " indicates the most recently viewed item"; // Inject into DOM div.appendChild(span1); div.appendChild(span2); sidebar.appendChild(div); } // Feature: Add with sum of values on current tab const filterDiv = document.querySelector( ".marketplaceView-browse-filterContainer" ); if ( filterDiv && !document.querySelector(".marketplace-total-value-tsitu") ) { const span = document.createElement("span"); span.className = "marketplace-total-value-tsitu"; span.innerText = `(Total estimated value on this tab: ${abbrev( totalValueSum )} or ${totalValueSum.toLocaleString()} Gold)`; span.style.fontSize = "12px"; span.style.fontWeight = "normal"; const emptySpace = document.createElement("span"); emptySpace.innerHTML = "\u00A0\u00A0\u00A0"; // Inject into DOM filterDiv.insertAdjacentElement("beforebegin", emptySpace); filterDiv.insertAdjacentElement("beforebegin", span); } // Feature: Check cache for most recently clicked item and scroll to it const lsText = localStorage.getItem("marketplace-browse-cache-tsitu"); if (lsText) { const lsObj = JSON.parse(lsText); const itemType = sidebar.querySelector( ".marketplaceView-browse-sidebar-link.active" ); if (itemType) { const name = lsObj[itemType.textContent]; if (name && name !== 0) { /** * Return row element that matches existing cached item name * @param {string} name Cached item name * @return {HTMLElement|false} that should be highlighted and scrolled to */ function findElement(name) { for (let el of document.querySelectorAll("tr[data-item-id]")) { const aTags = el.querySelectorAll("a"); if (aTags.length === 5) { if (name === aTags[2].textContent) { return el; } } } return false; } // Calculate index for nth-child const targetEl = findElement(name); let nthChildValue = 0; for (let i = 0; i < rows.length; i++) { const el = rows[i]; if (el === targetEl) { nthChildValue = i + 2; break; } } // tr:nth-child value (min = 2) const recentRow = document.querySelector( `.marketplaceView-table tr:nth-child(${nthChildValue})` ); if (recentRow) { recentRow.style.backgroundColor = "#D6EBA1"; // Try scrolling up to 4 rows down let scrollRow = recentRow; for (let i = 4; i > 0; i--) { const row = document.querySelector( `.marketplaceView-table tr:nth-child(${nthChildValue + i})` ); if (row) { scrollRow = row; break; } } console.time("Render + Scroll"); scrollRow.scrollIntoView({ // Seems to wait for XHR & render - slow initially but gets moderately faster behavior: "auto", block: "nearest", inline: "nearest" }); console.timeEnd("Render + Scroll"); } } } } console.timeEnd("[Total]"); console.groupEnd(groupName); } else if (backButton) { /* Listing logic (active 'Back' button) */ // 'Quick Purchase' rows only // Passes on Buy/Sell screens, but not on 'View More' b/c only NodeList(2) const goldTable = document.querySelectorAll( ".marketplaceView-roundedTable" )[2]; if (goldTable) { const goldValues = goldTable.querySelectorAll( "td .marketplaceView-goldValue" ); if (goldValues.length > 0) { goldValues.forEach(el => { const rawVal = el.textContent; if (typeof rawVal === "string") { // Feature: Inject tariff info into DOM const value = parseInt(rawVal.split(",").join("")); // Floating point imprecision leads to .999's and other glitches // Math.floor(value / 1.1) is insufficient const preTax = Math.floor((value / 1.1).toFixed(3)); const tax = value - preTax; const titleString = `\nOriginal: ${preTax.toLocaleString()} Gold\nTariff: ${tax.toLocaleString()} Gold`; // Check for repeated appends if (el.title.indexOf("Tariff") < 0) el.title += titleString; // Add a reversible onclick with same info as title if (!el.onclick) { function initHandler() { el.className = "tsitu-null"; el.innerText = el.title; el.onclick = revertHandler; } function revertHandler() { el.className = "marketplaceView-goldValue"; el.innerText = el.title.split("Gold")[0].trim(); el.onclick = initHandler; } el.onclick = initHandler; } } }); } } } // Re-observe after mutation-inducing logic observer.observe(observerTarget, { childList: true, subtree: true }); } }); // Initial observe observer.observe(observerTarget, { childList: true, subtree: true }); })();