// ==UserScript==
// @name 🐭️ MouseHunt - Item Links
// @description Adds a drop rate table from MHCT, links to the MouseHunt wiki, MHCT looter, and Markethunt, as well as various other features to the item view page.
// @version 2.1.0
// @license MIT
// @author bradp
// @namespace bradp
// @match https://www.mousehuntgame.com/*
// @icon https://i.mouse.rip/mh-improved/icon-64.png
// @run-at document-end
// @grant none
// @require https://cdn.jsdelivr.net/npm/script-migration@1.1.1
// ==/UserScript==
var mhui = (() => {
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var __async = (__this, __arguments, generator) => {
return new Promise((resolve, reject) => {
var fulfilled = (value) => {
try {
step(generator.next(value));
} catch (e) {
reject(e);
}
};
var rejected = (value) => {
try {
step(generator.throw(value));
} catch (e) {
reject(e);
}
};
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
step((generator = generator.apply(__this, __arguments)).next());
});
};
// src/modules/better-item-view/index.js
var better_item_view_exports = {};
__export(better_item_view_exports, {
default: () => better_item_view_default
});
// src/utils/event-registry.js
var eventsAdded = {};
var onEvent = (event, callback, remove = false) => {
if (!eventRegistry) {
return;
}
const id = `${event}-${remove.toString()}-${callback.toString()}`;
if (eventsAdded[id]) {
return;
}
eventsAdded[id] = true;
eventRegistry.addEventListener(event, callback, null, remove);
};
// src/utils/styles.js
var addModuleStyles = (styles, identifier = "mh-improved-styles", replace = false) => {
const existingStyles = document.querySelector(`#${identifier}`);
styles = Array.isArray(styles) ? styles.join("\n") : styles;
if (existingStyles) {
if (replace) {
existingStyles.innerHTML = styles;
} else {
existingStyles.innerHTML += styles;
}
return existingStyles;
}
const style = document.createElement("style");
style.id = identifier;
style.innerHTML = styles;
document.head.append(style);
return style;
};
var addStyles = (styles, module = false, identifier = "mh-improved-styles") => {
if (!module) {
throw new Error("Module ID is required for adding module styles.", module);
}
const key = `${identifier}-${module}`;
let stylesEl = addModuleStyles(styles, key, true);
onEvent(`mh-improved-settings-changed-${module}`, (enabled) => {
if (enabled) {
stylesEl = addModuleStyles(styles, key, true);
} else if (stylesEl) {
stylesEl.remove();
}
});
};
// src/utils/settings.js
var getSettingDirect = (key = null, defaultValue = null, identifier = "mousehunt-improved-settings") => {
const settings = JSON.parse(localStorage.getItem(identifier)) || {};
if (!key) {
return settings;
}
if (!key.includes(".")) {
if (settings[key] === void 0) {
return defaultValue;
}
return settings[key];
}
const groupAndKey = getGroupAndKey(key);
if (!groupAndKey.group) {
if (settings[groupAndKey.key] === void 0) {
return defaultValue;
}
return settings[groupAndKey.key];
}
const groupSettings = settings[groupAndKey.group] || {};
if (groupSettings[groupAndKey.key] === void 0) {
return defaultValue;
}
return groupSettings[groupAndKey.key];
};
var getGroupAndKey = (key) => {
const split = key.split(".");
if (split.length === 1) {
return {
group: null,
key: split[0]
};
}
if (split[0] === "location-huds-enabled") {
return {
group: "location-huds-enabled",
key: split[1]
};
}
return {
group: `${split[0]}-settings`,
key: split[1]
};
};
var getSetting = (key, defaultValue = false) => {
return getSettingDirect(key, defaultValue, "mousehunt-improved-settings");
};
// src/utils/elements.js
var makeElement = (tag, classes = "", text = "", appendTo = null) => {
const element = document.createElement(tag);
if (Array.isArray(classes)) {
classes = classes.join(" ");
}
if (classes && classes.length) {
element.className = classes;
}
element.innerHTML = text;
if (appendTo) {
appendTo.append(element);
return appendTo;
}
return element;
};
var makeLink = (text, href, encodeAsSpace = false) => {
if (encodeAsSpace) {
href = href.replaceAll("_", "%20");
}
return `<a href="${href}" target="_mouse" class="mousehuntActionButton tiny"><span>${text}</span></a>`;
};
var makeTooltip = (options) => {
if (!options.appendTo) {
return false;
}
const { appendTo, className = "", text = "" } = options;
const tooltip = makeElement("div", ["PreferencesPage__blackTooltip", "mh-improved-tooltip", className]);
makeElement("span", "PreferencesPage__blackTooltipText", text, tooltip);
appendTo.append(tooltip);
return tooltip;
};
// src/utils/db.js
var database = (databaseName) => __async(void 0, null, function* () {
return new Promise((resolve, reject) => {
const request = indexedDB.open(`mh-improved-${databaseName}`, 6);
request.onerror = (event) => {
reject(event.target.error);
};
request.onsuccess = (event) => {
resolve(event.target.result);
};
request.onupgradeneeded = (event) => {
const db = event.target.result;
if (!db.objectStoreNames.contains(databaseName)) {
db.createObjectStore(databaseName, { keyPath: "id" });
}
};
});
});
var dbGet = (databaseName, id) => __async(void 0, null, function* () {
const db = yield database(databaseName);
const transaction = db.transaction(databaseName, "readonly");
transaction.onerror = (event) => {
throw new Error(event.target.error);
};
const objectStore = transaction.objectStore(databaseName);
const request = objectStore.get(id);
return new Promise((resolve, reject) => {
request.onsuccess = () => {
resolve(request.result);
};
request.onerror = () => {
reject(request.error);
};
transaction.oncomplete = () => {
db.close();
};
});
});
var dbSet = (databaseName, data) => __async(void 0, null, function* () {
const db = yield database(databaseName);
const transaction = db.transaction(databaseName, "readwrite");
const objectStore = transaction.objectStore(databaseName);
data = {
data,
id: data.id || Date.now()
};
const request = objectStore.put(data);
return new Promise((resolve, reject) => {
request.onsuccess = () => {
resolve(request.result);
};
request.onerror = () => {
reject(request.error);
};
transaction.oncomplete = () => {
db.close();
};
});
});
// src/utils/global.js
var getGlobal = (key) => {
if (window && window.mhui) {
return window.mhui[key] || false;
}
if ("undefined" !== typeof app && app && app.mhui) {
return app.mhui[key] || false;
}
return false;
};
// src/utils/data.js
var getHeaders = () => {
return {
"Content-Type": "application/json",
"X-MH-Improved": "true",
"X-MH-Improved-Version": mhImprovedVersion || "unknown",
"X-MH-Improved-Platform": mhImprovedPlatform || "unknown"
};
};
// src/utils/events.js
var runCallbacks = (settings, parentNode, callbacks) => {
Object.keys(settings).forEach((key) => {
if (parentNode && parentNode.classList && parentNode.classList.contains(settings[key].selector)) {
settings[key].isVisible = true;
if (callbacks[key] && callbacks[key].show) {
callbacks[key].show();
}
} else if (settings[key].isVisible) {
settings[key].isVisible = false;
if (callbacks[key] && callbacks[key].hide) {
callbacks[key].hide();
}
}
});
return settings;
};
var overlayMutationObserver = null;
var overlayCallbacks = [];
var onOverlayChange = (callbacks) => {
let overlayData = {
map: {
isVisible: false,
selector: "treasureMapPopup"
},
item: {
isVisible: false,
selector: "itemViewPopup"
},
mouse: {
isVisible: false,
selector: "mouseViewPopup"
},
image: {
isVisible: false,
selector: "largerImage"
},
convertible: {
isVisible: false,
selector: "convertibleOpenViewPopup"
},
adventureBook: {
isVisible: false,
selector: "adventureBookPopup"
},
marketplace: {
isVisible: false,
selector: "marketplaceViewPopup"
},
gifts: {
isVisible: false,
selector: "giftSelectorViewPopup"
},
support: {
isVisible: false,
selector: "supportPageContactUsForm"
},
premiumShop: {
isVisible: false,
selector: "MHCheckout"
}
};
overlayCallbacks.push(callbacks);
if (overlayMutationObserver) {
return;
}
overlayMutationObserver = true;
const observer = new MutationObserver(() => {
overlayCallbacks.forEach((callback) => {
if (callback.change) {
callback.change();
}
const overlayType = document.querySelector("#overlayPopup");
if (overlayType && overlayType.classList.length <= 0) {
return;
}
const overlayBg = document.querySelector("#overlayBg");
if (overlayBg && overlayBg.classList.length > 0) {
if (callback.show) {
callback.show();
}
} else if (callback.hide) {
callback.hide();
}
overlayData = runCallbacks(overlayData, overlayType, callback);
});
});
const observeTarget = document.querySelector("#overlayPopup");
if (observeTarget) {
observer.observe(observeTarget, {
attributes: true,
attributeFilter: ["class"]
});
}
};
// src/utils/maps.js
var mapper = (key = false) => {
if (key) {
const mapperData = getGlobal("mapper");
if (!mapperData || !mapperData[key]) {
return false;
}
return mapperData[key];
}
return getGlobal("mapper");
};
var mapData = () => {
const m = mapper();
if (!m) {
return {};
}
return m.mapData;
};
var getCachedValue = (key) => __async(void 0, null, function* () {
var _a;
const value = yield dbGet("ar-cache", key);
if (!((_a = value == null ? void 0 : value.data) == null ? void 0 : _a.value)) {
return null;
}
return value.data.value;
});
var setCachedValue = (key, value) => __async(void 0, null, function* () {
yield dbSet("ar-cache", { id: key, value });
});
var getArForMouse = (id, type = "mouse") => __async(void 0, null, function* () {
let mhctJson = [];
const cacheKey = `${type}-${id}`;
const cachedAr = yield getCachedValue(cacheKey);
if (cachedAr) {
return cachedAr;
}
const isItem = "item" === type;
const mhctPath = isItem ? "mhct-item" : "mhct";
let mhctData = [];
const data = mapData() || {};
const mapType = (data == null ? void 0 : data.map_type) || "";
let url = `https://api.mouse.rip/${mhctPath}/${id}`;
if (mapType.toLowerCase().includes("halloween")) {
url = `https://api.mouse.rip/${mhctPath}/${id}-hlw_22`;
}
try {
mhctData = yield fetch(url, { headers: getHeaders() });
} catch (error) {
console.error("Error fetching MHCT data:", error);
yield new Promise((resolve) => setTimeout(resolve, 500));
try {
mhctData = yield fetch(url, { headers: getHeaders() });
} catch (errorRetry) {
console.error("Error fetching MHCT data:", errorRetry);
return [];
}
}
if (!mhctData.ok) {
return [];
}
mhctJson = yield mhctData.json();
if (!mhctJson || mhctJson.length === 0) {
return [];
}
if (isItem) {
for (const rate of mhctJson) {
rate.rate = Number.parseInt(rate.drop_pct * 100);
delete rate.drop_ct;
}
}
if (mhctJson.error) {
return [];
}
mhctJson = mhctJson.filter((rate) => {
if (rate.rate === 0) {
return false;
}
if (rate.rate === 9999) {
rate.rate = 1e4;
}
return true;
});
yield setCachedValue(cacheKey, mhctJson);
return mhctJson;
});
// src/utils/messages.js
hadAddedErrorStyles = false;
// src/modules/better-item-view/settings/index.js
var settings_default = () => __async(void 0, null, function* () {
return [
{
id: "better-item-view.show-drop-rates",
title: "Show drop rates",
default: true
},
{
id: "better-item-view.show-item-hover",
title: "Show item details on hover (in journal)",
default: true
}
];
});
// src/modules/better-item-view/styles.css
var styles_default = '.itemView-titleContainer{height:26px}.itemView-header-name{display:flex;align-items:center;justify-content:space-between}.mh-item-links{display:flex;justify-content:flex-end;margin-right:-10px}.mh-item-links a{margin-right:5px}.itemView-header-name .mh-item-links span{display:inline-block;font-size:11px;font-weight:400}.itemView-has-mhct .mouse-ar-wrapper{display:grid;grid-template-columns:150px auto 50px;place-items:center stretch;padding:5px;margin:5px 0;font-size:12px}.itemView-has-mhct .has-stages .mouse-ar-wrapper{grid-template-columns:110px 140px auto 50px}.itemView-has-mhct .mouse-ar-wrapper div{padding:0 2px}.itemView-has-mhct .mice-ar-wrapper{margin-right:10px}.mouse-ar-wrapper .stage{font-size:10px}.mouse-ar-wrapper .cheese{font-size:11px}.itemView-has-mhct .ar-header{display:flex;align-items:center;justify-content:space-between;height:26px;padding-bottom:2px;margin-top:10px;margin-bottom:10px;font-size:12px;font-weight:900;border-bottom:1px solid #ccc}.itemView-has-mhct .ar-link{font-size:9px}.itemView-has-mhct .rate{text-align:right}.itemView-has-mhct .mouse-ar-wrapper:nth-child(odd){background-color:#e7e7e7}.itemView-has-mhct .itemView-description{font-weight:500;line-height:19px}.itemView-action.crafting_item b{display:none}.itemView-action.crafting_item:before{content:"This can be used to craft other items!"}.itemViewContainer.map_piece .itemView-action-text.map_piece,.itemViewContainer.base .itemView-action-text.base,.itemViewContainer.weapon .itemView-actio-textn.weapon,.itemViewContainer.bait .itemView-action-text.bait,.itemViewContainer.trinket .itemView-action-text.trinket,.itemViewContainer.potion .itemView-action-text.potion,.itemViewContainer.readiness_item .itemView-action-text.readiness_item,.itemViewContainer.convertible .itemView-action-text.convertible,.itemViewContainer.torn_page .itemView-action-text.torn_page,.itemViewContainer.crafting_item .itemView-action-text.crafting_item,.itemViewContainer.collectible .itemView-action-text.collectible,.itemViewContainer.message_item .itemView-action-text.message_item,.itemViewContainer.bonus_loot .itemView-action-text.bonus_loot,.itemViewContainer.stat .itemView-action-text.stat,.itemViewContainer.quest .itemView-action-text.quest,.itemViewContainer.skin .itemView-action-text.skin{display:none!important}.itemViewContainer .shopCustomization .itemViewStatBlock-stat{display:flex;flex-direction:column;align-items:center}.itemViewContainer .itemViewStatBlock-stat{display:flex;flex-direction:row;align-items:center;justify-content:flex-start}.itemViewContainer .itemViewStatBlock-stat-value{flex:1;text-align:left}.itemViewContainer .itemViewStatBlock-stat.cheeseEffect{font-size:9px;text-align:center}.itemViewContainer .itemViewStatBlock.trinket .itemViewStatBlock-padding{display:flex;flex-direction:column;align-items:stretch;width:100px}.itemViewContainer .itemViewStatBlock.trinket{width:100px;font-size:13px}#overlayPopup.itemViewPopup #jsDialogClose{z-index:1}#overlayPopup.itemViewPopup .itemView-header-classification{right:25px}.itemView-actionContainer{display:flex;flex-wrap:wrap;gap:10px}.itemView-action{border-top:none}.itemViewContainer.potion .inventoryPage-item-recipeOptions li{width:365px}.itemView-character-image{width:auto;height:84px;margin-top:-15px;margin-left:-9px}.itemView-character-name{left:-11px;width:75px;font-size:15px}.itemView-padding{margin-left:70px}.itemView-thumbnail.large{margin-left:-15px}input.itemView-action-convert-quantity{width:50px}.itemViewPopup .itemViewStatBlock-padding{flex-direction:column}.itemView-character .itemView-character-image{transition:all .4s ease-out;transform-origin:bottom}.itemView-character:hover .itemView-character-image{transform:scale(1.2) rotate(-10deg) translate(5px)}.itemView-header-classification{visibility:hidden}.itemView-header-classification span{visibility:visible}.itemViewStatBlock-stat{display:flex;align-items:center}.itemView-sidebar-checklistItem:nth-child(1),.itemView-sidebar-checklistItem:nth-child(2),.itemView-sidebar-checklistItem.checked{display:block}.itemView-sidebar-checklistItem{background:url(https://www.mousehuntgame.com/images/icons/bad_idea.png) 1px 4px no-repeat;background-size:14px}.itemView-partsContainer{display:flex;flex-direction:column;align-items:stretch;padding-top:15px;padding-bottom:10px;margin-top:15px;border-top:1px solid #666}\n';
// src/modules/better-item-view/index.js
var getLinkMarkup = (name, id) => {
return makeLink("MHCT", `https://www.mhct.win/loot.php?item=${id}`, true) + makeLink("Wiki", `https://mhwiki.hitgrab.com/wiki/index.php/${name}`);
};
var addLinks = (itemId) => {
const title = document.querySelector(".itemView-header-name");
if (!title) {
return;
}
const currentLinks = document.querySelector(".mh-item-links");
if (currentLinks) {
currentLinks.remove();
}
const div = document.createElement("div");
div.classList.add("mh-item-links");
div.innerHTML = getLinkMarkup(title.innerText, itemId);
title.append(div);
const values = document.querySelector(".mouseView-values");
const desc = document.querySelector(".mouseView-descriptionContainer");
if (values && desc) {
desc.insertBefore(values, desc.firstChild);
}
};
var updateItemView = () => __async(void 0, null, function* () {
const itemView = document.querySelector(".itemViewContainer");
if (!itemView) {
return;
}
const itemId = itemView.getAttribute("data-item-id");
if (!itemId) {
return;
}
const sidebar = document.querySelector(".itemView-sidebar");
if (sidebar) {
const crafting = document.querySelector(".itemView-action.crafting_item");
if (crafting) {
sidebar.append(crafting);
}
const smashing = document.querySelector(".itemView-partsContainer");
if (smashing) {
sidebar.append(smashing);
if (smashing.getAttribute("data-has-changed-title")) {
return;
}
const smashingTitle = smashing.querySelector("b");
if (smashingTitle) {
smashingTitle.innerText = "Hunter's Hammer to get:";
smashing.setAttribute("data-has-changed-title", "true");
smashing.innerHtml = smashing.innerHTML.replace("If you smash it, you'll get:", "");
}
}
}
addLinks(itemId);
if (!getSetting("better-item-view.show-drop-rates", true)) {
return;
}
const id = Number.parseInt(itemId, 10);
const ignored = [
2473,
// Mina's gift
823,
// party charm
803,
// chrome charm
420,
// king's credits
1980,
// king's keys
585
// scrambles
];
if (ignored.includes(id)) {
return;
}
let mhctJson = yield getArForMouse(itemId, "item");
if (!mhctJson || mhctJson === void 0) {
return;
}
itemView.classList.add("mouseview-has-mhct");
const container = itemView.querySelector(".itemView-padding");
if (!container) {
return;
}
const arWrapper = makeElement("div", "ar-wrapper");
const title = makeElement("div", "ar-header");
const titleText = makeElement("div", "ar-title", "Drop Rates", title);
makeTooltip({
appendTo: titleText,
text: 'The best location and bait, according to data gathered by <a href="https://mhct.win/" target="_blank" rel="noopener noreferrer">MHCT</a>.'
});
const link = makeElement("a", "ar-link", "View on MHCT \u2192");
link.href = `https://www.mhct.win/loot.php?item=${itemId}`;
link.target = "_mhct";
title.append(link);
arWrapper.append(title);
const itemsArWrapper = makeElement("div", "item-ar-wrapper");
const hasStages = mhctJson.some((itemAr) => itemAr.stage);
if (hasStages) {
itemsArWrapper.classList.add("has-stages");
}
mhctJson = mhctJson.filter((itemAr) => Number.parseInt(itemAr.drop_pct, 10) > 0).slice(0, 10);
mhctJson.forEach((itemAr) => {
const dropPercent = Number.parseInt(itemAr.drop_pct, 10).toFixed(2);
if (dropPercent !== "0.00") {
const itemArWrapper = makeElement("div", "mouse-ar-wrapper");
makeElement("div", "location", itemAr.location, itemArWrapper);
if (hasStages) {
makeElement("div", "stage", itemAr.stage, itemArWrapper);
}
makeElement("div", "cheese", itemAr.cheese, itemArWrapper);
makeElement("div", "rate", `${dropPercent}%`, itemArWrapper);
itemsArWrapper.append(itemArWrapper);
}
});
if (mhctJson.length > 0) {
arWrapper.append(itemsArWrapper);
container.append(arWrapper);
}
});
var init = () => __async(void 0, null, function* () {
addStyles(styles_default, "better-item-view");
if (getSetting("better-item-view.show-item-hover", true)) {
}
onOverlayChange({ item: { show: updateItemView } });
});
var better_item_view_default = {
id: "better-item-view",
name: "Better Item View",
type: "better",
default: true,
description: "Updates the styles and shows drop rates, links to MHCT, and MH Wiki.",
load: init,
settings: settings_default
};
return __toCommonJS(better_item_view_exports);
})();
mhImprovedVersion = "0.0.0-userscript;"
mhImprovedPlatform = "userscript";
mhui.default.load();
migrateUserscript('Item Links', 'https://greasyfork.org/en/scripts/445920-mousehunt-item-links');