🐭️ MouseHunt - Minluck & Catch Rate Estimate

View the minluck and catch rate estimate, right on the camp page.

当前为 2023-06-26 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         🐭️ MouseHunt - Minluck & Catch Rate Estimate
// @version      1.43.0
// @description  View the minluck and catch rate estimate, right on the camp page.
// @license      MIT
// @author       bradp
// @namespace    bradp
// @match        https://www.mousehuntgame.com/*
// @icon         https://brrad.com/mouse.png
// @require      https://cdn.jsdelivr.net/npm/[email protected]/minlucks.js
// @grant        none
// @run-at       document-end
// ==/UserScript==

(function () {
	'use strict';

	const allMiceInfo = window.minlucks;

	/**
	 * Add styles to the page.
	 *
	 * @param {string} styles The styles to add.
	 */
	const addStyles = (styles) => {
		const existingStyles = document.getElementById('mh-mouseplace-custom-styles');

		if (existingStyles) {
			existingStyles.innerHTML += styles;
		} else {
			const style = document.createElement('style');
			style.id = 'mh-mouseplace-custom-styles';

			style.innerHTML = styles;
			document.head.appendChild(style);
		}
	};

	/**
	 * Get the current page slug.
	 *
	 * @return {string} The page slug.
	 */
	const getCurrentPage = () => {
		const container = document.getElementById('mousehuntContainer');
		if (! container || container.classList.length <= 0) {
			return null;
		}

		return container.classList[ 0 ].replace('Page', '').toLowerCase();
	};

	/**
	 * Do something when ajax requests are completed.
	 *
	 * @param {Function} callback    The callback to call when an ajax request is completed.
	 * @param {string}   url         The url to match. If not provided, all ajax requests will be matched.
	 * @param {boolean}  skipSuccess Skip the success check.
	 */
	const onAjaxRequest = (callback, url = false, skipSuccess = false) => {
		const req = XMLHttpRequest.prototype.open;
		XMLHttpRequest.prototype.open = function () {
			this.addEventListener('load', function () {
				if (this.responseText) {
					let response = {};
					try {
						response = JSON.parse(this.responseText);
					} catch (e) {
						return;
					}

					if (response.success || skipSuccess) {
						if (! url) {
							callback(response);
							return;
						}

						if (this.responseURL.indexOf(url) !== -1) {
							callback(response);
						}
					}
				}
			});
			req.apply(this, arguments);
		};
	};

	/**
	 * Do something when the page or tab changes.
	 *
	 * @param {Object}   callbacks
	 * @param {Function} callbacks.show   The callback to call when the overlay is shown.
	 * @param {Function} callbacks.hide   The callback to call when the overlay is hidden.
	 * @param {Function} callbacks.change The callback to call when the overlay is changed.
	 */
	const onPageChange = (callbacks) => {
		const observer = new MutationObserver(() => {
			if (callbacks.change) {
				callbacks.change();
			}
		});

		const observeTarget = document.getElementById('mousehuntContainer');
		if (observeTarget) {
			observer.observe(observeTarget, {
				attributes: true,
				attributeFilter: ['class']
			});
		}
	};

	const allType = [
		'Arcane',
		'Draconic',
		'Forgotten',
		'Hydro',
		'Parental',
		'Physical',
		'Shadow',
		'Tactical',
		'Law',
		'Rift'
	];

	const updateMinLucks = async () => {
		if ('camp' !== getCurrentPage()) {
			return;
		}

		const effectiveness = await getMiceEffectivness();

		const miceNames = Object.values(effectiveness)
			.map(({ mice }) => mice).flat()
			.map(({ name }) => name).flat();

		renderList(miceNames);
	};

	function renderList(list) {
		const minWrap = document.getElementById('minluck-list');
		if (minWrap) {
			minWrap.remove();
		}
		const mintable = document.getElementById('minluck-list-table');
		if (mintable) {
			mintable.remove();
		}

		const powerEl = document.querySelector('.campPage-trap-trapStat.power');
		const luckEl = document.querySelector('.campPage-trap-trapStat.luck');
		const powerTypeEl = document.querySelector('.campPage-trap-trapStat.power');

		if (! powerEl || ! luckEl || ! powerTypeEl) {
			return;
		}

		const luck = luckEl.innerText.match(/\d/g).join('');
		const power = powerEl.innerText.match(/\d/g).join('');
		const powerType = powerTypeEl.innerText.match(/[A-Za-z]/g).join('');

		const minluckList = document.createElement('div');
		minluckList.id = 'minluck-list';
		minluckList.className = 'campPage-trap-trapEffectiveness';

		const miceheader = document.createElement('th');
		miceheader.innerText = 'Mouse';
		miceheader.classList = 'mousename-header';

		const table = document.createElement('table');
		table.id = 'minluck-list-table';
		table.appendChild(miceheader);

		const minluckheader = document.createElement('th');
		minluckheader.innerText = 'Minluck';
		table.appendChild(minluckheader);

		const crheader = document.createElement('th');
		crheader.innerText = 'CRE';
		table.appendChild(crheader);

		const totalStats = { good: 0, bad: 0, okay: 0, total: 0 };

		for (let i = 0; i < list.length; i++) {
			const mouseNameConverted = list[ i ];
			const powerIndex = allType.indexOf(powerType);
			const micePower = allMiceInfo[ mouseNameConverted ].power;
			const miceEff = allMiceInfo[ mouseNameConverted ].effs[ powerIndex ];
			const minluckString = replaceInfinity(micePower, miceEff);
			const catchRateString = convertToCR(power, luck, micePower, miceEff);

			const row = document.createElement('tr');
			row.className = 'minlucklist-minluck-row';

			const mouseName = document.createElement('td');
			mouseName.innerText = mouseNameConverted;
			mouseName.className = 'minlucklist-minluck-name';
			row.appendChild(mouseName);

			const minLuck = document.createElement('td');
			minLuck.className = 'minlucklist-minluck-data' + (luck >= minluckString ? ' minlucklist-minluck-data-good' : '');
			minLuck.innerText = minluckString;
			row.appendChild(minLuck);

			const catchRate = document.createElement('td');
			catchRate.innerText = catchRateString;
			if (catchRateString === '100.00%') {
				totalStats.good += 1;
				catchRate.className = 'minlucklist-minluck-data minlucklist-minluck-data-good';
			} else if ((parseInt(catchRateString)) <= 60) {
				totalStats.bad += 1;
				catchRate.className = 'minlucklist-minluck-data minlucklist-minluck-data-bad';
			} else {
				catchRate.className = 'minlucklist-minluck-data';
			}
			totalStats.total += 1;
			row.appendChild(catchRate);

			table.appendChild(row);
		}

		minluckList.appendChild(table);

		const statsContainer = document.querySelector('.campPage-trap-statsContainer');
		if (statsContainer) {
			statsContainer.appendChild(minluckList);
		}

		const trap = document.getElementsByClassName('trapImageView-layerWrapper')[ 0 ];

		trap.classList.remove('minluck-indicator-all-good');
		trap.classList.remove('minluck-indicator-bad');
		trap.classList.remove('minluck-indicator-good');
		trap.classList.remove('minluck-indicator-none');

		if (totalStats.good === totalStats.total) {
			trap.classList.add('minluck-indicator-all-good');
		} else if (totalStats.bad === totalStats.total) {
			trap.classList.add('minluck-indicator-bad');
		} else if (totalStats.good > totalStats.total / 2) {
			trap.classList.add('minluck-indicator-good');
		} else {
			trap.classList.add('minluck-indicator-none');
		}

		const rowData = table.getElementsByTagName('tr');

		for (let i = 0; i < rowData.length - 1; i++) {
			for (let j = 0; j < rowData.length - (i + 1); j++) {
				if (Number(rowData.item(j).getElementsByTagName('td').item(1).innerHTML.replace(/[^0-9.]+/g, '')) < Number(rowData.item(j + 1).getElementsByTagName('td').item(1).innerHTML.replace(/[^0-9.]+/g, ''))) {
					table.insertBefore(rowData.item(j + 1), rowData.item(j));
				}
			}
		}
	}

	const getUserHash = () => {
		// eslint-disable-next-line no-undef
		if (typeof unsafeWindow !== 'undefined' && unsafeWindow.user.unique_hash) {
			// eslint-disable-next-line no-undef
			return unsafeWindow.user.unique_hash;
		}

		// if window.user exists, return the hash
		if (window.user && window.user.unique_hash) {
			return window.user.unique_hash;
		}
	};

	const getMiceEffectivness = async () => {
		const url = 'https://www.mousehuntgame.com/managers/ajax/users/getmiceeffectiveness.php';
		const formData = 'sn=Hitgrab&hg_is_ajax=1&uh=' + getUserHash();

		// post the data to the url
		const response = await fetch(url, {
			method: 'POST',
			headers: {
				'Content-Type': 'application/x-www-form-urlencoded',
			},
			body: formData,
		});

		const responseText = await response.text();
		const data = JSON.parse(responseText);

		if (! data.effectiveness) {
			return;
		}

		return data.effectiveness;
	};

	function replaceInfinity(mousePower, eff) {
		// Can't evalute infinity symbol, so was replaced with 9999 as minluck instead
		const infinitySym = String.fromCharCode(0x221E);
		eff = eff / 100;

		if (eff === 0) {
			return infinitySym;
		}

		const minluck = Math.ceil(Math.ceil(Math.sqrt(mousePower / 2)) / Math.min(eff, 1.4));
		if (minluck >= 9999) {
			return infinitySym;
		}

		if (2 * Math.pow(Math.floor(Math.min(1.4, eff) * minluck), 2) >= mousePower) {
			return minluck;
		}

		return minluck + 1;
	}

	function convertToCR(power, luck, mPower, mEff) {
		mEff = mEff / 100;
		// eslint-disable-next-line no-mixed-operators
		let result = Math.min(1, (power * mEff + 2 * Math.pow(Math.floor(luck * Math.min(mEff, 1.4)), 2)) / (mPower + power * mEff));
		result = (result * 100).toFixed(2) + '%';
		return result;
	}

	const makeMinLuckButton = () => {
		const button = document.getElementById('minluck-button');
		if (! button) {
			const minluckButton = document.createElement('a');
			minluckButton.id = 'minluck-button';
			minluckButton.classList.add('campPage-trap-trapEffectiveness');
			minluckButton.textContent = '🐭️ Minluck & Catch Rate Estimate';

			const statsContainer = document.querySelector('.campPage-trap-statsContainer');
			if (statsContainer) {
				statsContainer.appendChild(minluckButton);
			}
		}
	};

	addStyles(`
	#minluck-button {
		margin-top: 10px;
	}

	#minluck-list {
		padding: 10px;
		margin-bottom: 10px;
		margin-top: -2px;
		border-top-left-radius: 0;
		border-top-right-radius: 0;
	}

	#minluck-list table {
		margin: 0 10px 10px 10px;
		width: 100%;
	}

	#minluck-list table th {
		text-align: center;
		font-weight: bold;
	}

	#minluck-list table th.mousename-header {
		text-align: left;
	}

	#minluck-list table:first-child {
		text-align: left;
	}

	.minlucklist-minluck-name {
		min-width: 85px;
		white-space: nowrap;
		overflow: hidden;
		max-width: 150px;
		text-overflow: ellipsis;
	}

	.minlucklist-minluck-data {
		text-align: center;
		min-width: 70px;
	}

	.minlucklist-minluck-data-good {
		color: #138f13;
	}

	.minlucklist-minluck-data-bad {
		color: #bb4646;
	}

	.minluck-indicator-all-good {
		border-top: 5px solid blue;
	}

	.minluck-indicator-good {
		border-top: 5px solid #2f82ec;
	}

	.minluck-indicator-bad {
		border-top: 5px solid #990000;
	}

	.minluck-indicator-all-good,
	.minluck-indicator-good,
	.minluck-indicator-bad {
		border-top: none;
	}`);

	onAjaxRequest(updateMinLucks, '/managers/ajax/users/changetrap.php');
	onPageChange({ change: updateMinLucks });

	makeMinLuckButton();
	updateMinLucks();

	setTimeout(() => {
		makeMinLuckButton();
		updateMinLucks();
	}, 750);
}());