- // ==UserScript==
- // @name 🐭️ MouseHunt Utils
- // @author bradp
- // @version 1.10.5
- // @description MouseHunt Utils is a library of functions that can be used to make other MouseHunt userscripts easily.
- // @license MIT
- // @namespace bradp
- // @match https://www.mousehuntgame.com/*
- // @icon https://i.mouse.rip/mouse.png
- // @grant none
- // ==/UserScript==
-
- /* eslint-disable no-unused-vars */
-
- /**
- * Add styles to the page.
- *
- * @author bradp
- * @since 1.0.0
- *
- * @example <caption>Basic usage</caption>
- * addStyles(`.my-class {
- * color: red;
- * }`);
- *
- * @example <caption>With an identifier</caption>
- * addStyles(`.my-class {
- * display: none;
- * }`, 'my-identifier');
- *
- * @example <caption>With an identifier, but will only add the styles once</caption>
- * addStyles(`.my-other-class {
- * color: blue;
- * }`, 'my-identifier', true);
- *
- * @param {string} styles The styles to add.
- * @param {string} identifier The identifier to use for the style element.
- * @param {boolean} once Only add the styles once for the identifier.
- *
- * @return {Element} The style element.
- */
- const addStyles = (styles, identifier = 'mh-utils-custom-styles', once = false) => {
- identifier = `mh-utils-${identifier}`;
-
- // Check to see if the existing element exists.
- const existingStyles = document.getElementById(identifier);
-
- // If so, append our new styles to the existing element.
- if (existingStyles) {
- if (once) {
- return existingStyles;
- }
-
- existingStyles.innerHTML += styles;
- return existingStyles;
- }
-
- // Otherwise, create a new element and append it to the head.
- const style = document.createElement('style');
- style.id = identifier;
- style.innerHTML = styles;
- document.head.appendChild(style);
-
- return style;
- };
-
- /**
- * Do something when ajax requests are completed.
- *
- * @author bradp
- * @since 1.0.0
- *
- * @example <caption>Basic usage</caption>
- * onRequest((response) => {
- * console.log(response);
- * }, 'managers/ajax/turns/activeturn.php');
- *
- * @example <caption>Basic usage, but skip the success check</caption>
- * onRequest((response) => {
- * console.log(response);
- * }, 'managers/ajax/turns/activeturn.php', true);
- *
- * @example <caption>Basic usage, running for all ajax requests</caption>
- * onRequest((response) => {
- * console.log(response);
- * });
- *
- * @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 onRequest = (callback, url = null, 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);
- };
- };
-
- const onAjaxRequest = onRequest;
-
- /**
- * Run the callbacks depending on visibility.
- *
- * @author bradp
- * @since 1.0.0
- *
- * @ignore
- *
- * @param {Object} settings Settings object.
- * @param {Node} parentNode The parent node.
- * @param {Object} callbacks The callbacks to run.
- *
- * @return {Object} The settings.
- */
- const runCallbacks = (settings, parentNode, callbacks) => {
- // Loop through the keys on our settings object.
- Object.keys(settings).forEach((key) => {
- // If the parentNode that's passed in contains the selector for the key.
- if (parentNode && parentNode.classList && parentNode.classList.contains(settings[ key ].selector)) {
- // Set as visible.
- settings[ key ].isVisible = true;
-
- // If there is a show callback, run it.
- if (callbacks[ key ] && callbacks[ key ].show) {
- callbacks[ key ].show();
- }
- } else if (settings[ key ].isVisible) {
- // Mark as not visible.
- settings[ key ].isVisible = false;
-
- // If there is a hide callback, run it.
- if (callbacks[ key ] && callbacks[ key ].hide) {
- callbacks[ key ].hide();
- }
- }
- });
-
- return settings;
- };
-
- /**
- * Do something when the overlay is shown or hidden.
- *
- * @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 onOverlayChange = (callbacks) => {
- // Track the different overlay states.
- 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'
- }
- };
-
- // Observe the overlayPopup element for changes.
- const observer = new MutationObserver(() => {
- if (callbacks.change) {
- callbacks.change();
- }
-
- // Grab the overlayPopup element and make sure it has classes on it.
- const overlayType = document.getElementById('overlayPopup');
- if (overlayType && overlayType.classList.length <= 0) {
- return;
- }
-
- // Grab the overlayBg and check if it is visible or not.
- const overlayBg = document.getElementById('overlayBg');
- if (overlayBg && overlayBg.classList.length > 0) {
- // If there's a show callback, run it.
- if (callbacks.show) {
- callbacks.show();
- }
- } else if (callbacks.hide) {
- // If there's a hide callback, run it.
- callbacks.hide();
- }
-
- // Run all the specific callbacks.
- overlayData = runCallbacks(overlayData, overlayType, callbacks);
- });
-
- // Observe the overlayPopup element for changes.
- const observeTarget = document.getElementById('overlayPopup');
- if (observeTarget) {
- observer.observe(observeTarget, {
- attributes: true,
- attributeFilter: ['class']
- });
- }
- };
-
- /**
- * TODO: update this docblock.
- *
- * @param {*} callback
- */
- const onOverlayClose = (callback) => {
- eventRegistry.addEventListener('js_dialog_hide', callback);
- };
-
- /**
- * TODO: update this docblock.
- */
- const getDialogMapping = () => {
- return {
- treasureMapPopup: 'map',
- itemViewPopup: 'item',
- mouseViewPopup: 'mouse',
- largerImage: 'image',
- convertibleOpenViewPopup: 'convertible',
- adventureBookPopup: 'adventureBook',
- marketplaceViewPopup: 'marketplace',
- giftSelectorViewPopup: 'gifts',
- supportPageContactUsForm: 'support',
- MHCheckout: 'premiumShop',
- };
- };
-
- /**
- * TODO: update this docblock.
- *
- * @param {*} callback
- * @param {*} overlay
- * @param {*} once
- */
- const onDialogShow = (callback, overlay = null, once = false) => {
- eventRegistry.addEventListener('js_dialog_show', () => {
- if (! activejsDialog) {
- return;
- }
-
- // Get all the tokens and check the content.
- const tokens = activejsDialog.getAllTokens();
-
- // Make sure we have the 'content' key.
- // For item and mouse views, the entire event fires twice, once while loading and
- // once when the content is loaded. We only want to run this once, so we check if
- // the content is empty in a weird way.
- if (
- ! tokens ||
- ! tokens[ '{*content*}' ] ||
- ! tokens[ '{*content*}' ].value ||
- tokens[ '{*content*}' ].value === '' ||
- tokens[ '{*content*}' ].value.indexOf('data-item-type=""') > -1 || // Item view.
- tokens[ '{*content*}' ].value.indexOf('data-mouse-id=""') > -1 // Mouse view.
- ) {
- return;
- }
-
- // Grab the attributes of the dialog to determine the type.
- const atts = activejsDialog.getAttributes();
- const dialogType = atts.className
- .replace('jsDialogFixed', '')
- .replace('wide', '')
- .replace('default', '')
- .replaceAll(' ', ' ')
- .replaceAll(' ', '.')
- .trim();
-
- // Make sure this only ran once within the last 100ms for the same overlay.
- if (window.mhutils?.lastDialog?.overlay === dialogType && (Date.now() - window.mhutils.lastDialog.timestamp) < 250) {
- return;
- }
-
- const lastDialog = {
- overlay: dialogType,
- timestamp: Date.now(),
- };
-
- window.mhutils = window.mhutils ? { ...window.mhutils, ...lastDialog } : lastDialog;
-
- if (! overlay && 'function' === typeof callback) {
- return callback();
- }
-
- const dialogMapping = getDialogMapping();
-
- if ('function' === typeof callback && (overlay === dialogType || overlay === dialogMapping[ dialogType ])) {
- return callback();
- }
- }, null, once);
- };
-
- /**
- * TODO: update this docblock.
- *
- * @param {*} callback
- * @param {*} overlay
- * @param {*} once
- */
- const onDialogHide = (callback, overlay = null, once = false) => {
- eventRegistry.addEventListener('js_dialog_hide', () => {
- const dialogType = window?.mhutils?.lastDialog?.overlay || null;
- window.mhutils.lastDialog = {};
-
- if (! overlay) {
- return callback();
- }
-
- const dialogMapping = getDialogMapping();
- if (overlay === dialogType || overlay === dialogMapping[ dialogType ]) {
- return callback();
- }
- }, null, once);
- };
-
- /**
- * Do something when the page or tab changes.
- *
- * @param {Object} callbacks
- * @param {Function} callbacks.show The callback to call when the page is navigated to.
- * @param {Function} callbacks.hide The callback to call when the page is navigated away from.
- * @param {Function} callbacks.change The callback to call when the page is changed.
- */
- const onPageChange = (callbacks) => {
- // Track our page tab states.
- let tabData = {
- blueprint: { isVisible: null, selector: 'showBlueprint' },
- tem: { isVisible: false, selector: 'showTrapEffectiveness' },
- trap: { isVisible: false, selector: 'editTrap' },
- camp: { isVisible: false, selector: 'PageCamp' },
- travel: { isVisible: false, selector: 'PageTravel' },
- inventory: { isVisible: false, selector: 'PageInventory' },
- shop: { isVisible: false, selector: 'PageShops' },
- mice: { isVisible: false, selector: 'PageAdversaries' },
- friends: { isVisible: false, selector: 'PageFriends' },
- sendSupplies: { isVisible: false, selector: 'PageSupplyTransfer' },
- team: { isVisible: false, selector: 'PageTeam' },
- tournament: { isVisible: false, selector: 'PageTournament' },
- news: { isVisible: false, selector: 'PageNews' },
- scoreboards: { isVisible: false, selector: 'PageScoreboards' },
- discord: { isVisible: false, selector: 'PageJoinDiscord' },
- preferences: { isVisible: false, selector: 'PagePreferences' },
- profile: { isVisible: false, selector: 'HunterProfile' },
- };
-
- // Observe the mousehuntContainer element for changes.
- const observer = new MutationObserver(() => {
- // If there's a change callback, run it.
- if (callbacks.change) {
- callbacks.change();
- }
-
- // Grab the container element and make sure it has classes on it.
- const mhContainer = document.getElementById('mousehuntContainer');
- if (mhContainer && mhContainer.classList.length > 0) {
- // Run the callbacks.
- tabData = runCallbacks(tabData, mhContainer, callbacks);
- }
- });
-
- // Observe the mousehuntContainer element for changes.
- const observeTarget = document.getElementById('mousehuntContainer');
- if (observeTarget) {
- observer.observe(observeTarget, {
- attributes: true,
- attributeFilter: ['class']
- });
- }
- };
-
- /**
- * Do something when the trap tab is changed.
- *
- * @param {Object} callbacks
- */
- const onTrapChange = (callbacks) => {
- // Track our trap states.
- let trapData = {
- bait: {
- isVisible: false,
- selector: 'bait'
- },
- base: {
- isVisible: false,
- selector: 'base'
- },
- weapon: {
- isVisible: false,
- selector: 'weapon'
- },
- charm: {
- isVisible: false,
- selector: 'trinket'
- },
- skin: {
- isVisible: false,
- selector: 'skin'
- }
- };
-
- // Observe the trapTabContainer element for changes.
- const observer = new MutationObserver(() => {
- // Fire the change callback.
- if (callbacks.change) {
- callbacks.change();
- }
-
- // If we're not viewing a blueprint tab, bail.
- const mhContainer = document.getElementById('mousehuntContainer');
- if (mhContainer.classList.length <= 0 || ! mhContainer.classList.contains('showBlueprint')) {
- return;
- }
-
- // If we don't have the container, bail.
- const trapContainerParent = document.querySelector('.campPage-trap-blueprintContainer');
- if (! trapContainerParent || ! trapContainerParent.children || ! trapContainerParent.children.length > 0) {
- return;
- }
-
- // If we're not in the itembrowser, bail.
- const trapContainer = trapContainerParent.children[ 0 ];
- if (! trapContainer || trapContainer.classList.length <= 0 || ! trapContainer.classList.contains('campPage-trap-itemBrowser')) {
- return;
- }
-
- // Run the callbacks.
- trapData = runCallbacks(trapData, trapContainer, callbacks);
- });
-
- // Grab the campPage-trap-blueprintContainer element and make sure it has children on it.
- const observeTargetParent = document.querySelector('.campPage-trap-blueprintContainer');
- if (! observeTargetParent || ! observeTargetParent.children || ! observeTargetParent.children.length > 0) {
- return;
- }
-
- // Observe the first child of the campPage-trap-blueprintContainer element for changes.
- const observeTarget = observeTargetParent.children[ 0 ];
- if (observeTarget) {
- observer.observe(observeTarget, {
- attributes: true,
- attributeFilter: ['class']
- });
- }
- };
-
- /**
- * Add something to the event registry.
- *
- * @param {string} event The event name.
- * @param {Function} callback The callback to run when the event is fired.
- * @param {boolean} remove Whether or not to remove the event listener after it's fired.
- */
- const onEvent = (event, callback, remove = false) => {
- eventRegistry.addEventListener(event, callback, null, remove);
- };
-
- /**
- * Do something when the user travels to a location.
- *
- * @param {string} location The location traveled to.
- * @param {Object} options The options
- * @param {string} options.shouldAddReminder Whether or not to add a reminder.
- * @param {string} options.title The title of the reminder.
- * @param {string} options.text The text of the reminder.
- * @param {string} options.button The button text of the reminder.
- * @param {string} options.action The action to take when the button is clicked.
- * @param {string} options.callback The callback to run when the user is at the location.
- */
- const onTravel = (location, options) => {
- eventRegistry.addEventListener('travel_complete', () => onTravelCallback(location, options));
- };
-
- /**
- * Do something when the user travels to a location.
- * This is a callback for the onTravel function.
- *
- * @param {string} location The location traveled to.
- * @param {Object} options The options
- * @param {string} options.shouldAddReminder Whether or not to add a reminder.
- * @param {string} options.title The title of the reminder.
- * @param {string} options.text The text of the reminder.
- * @param {string} options.button The button text of the reminder.
- * @param {string} options.action The action to take when the button is clicked.
- * @param {string} options.callback The callback to run when the user is at the location.
- *
- */
- const onTravelCallback = (location, options) => {
- if (location && location !== getCurrentLocation()) {
- return;
- }
-
- if (options?.shouldAddReminder) {
- showHornMessage({
- title: options.title || '',
- text: options.text || '',
- button: options.button || 'Dismiss',
- action: options.action || null,
- });
- }
-
- if (options.callback) {
- options.callback();
- }
- };
-
- /**
- * TODO: update this docblock.
- *
- * @param {string} targetPage The target page.
- * @param {string} targetTab The target tab.
- * @param {string} targetSubtab The target subtab.
- * @param {string} forceCurrentPage The current page.
- * @param {string} forceCurrentTab The current tab.
- * @param {string} forceCurrentSubtab The current subtab.
- */
- const matchesCurrentPage = (targetPage = null, targetTab = null, targetSubtab = null, forceCurrentPage = null, forceCurrentTab = null, forceCurrentSubtab = null) => {
- if (! targetPage) {
- return false;
- }
-
- // Only targetPage is being checked.
- const currentPage = forceCurrentPage || getCurrentPage();
- if (! targetTab) {
- return currentPage === targetPage;
- }
-
- // Only targetTab is being checked.
- const currentTab = forceCurrentTab || getCurrentTab();
- if (! targetSubtab) {
- return currentPage === targetPage && currentTab === targetTab;
- }
-
- // Only targetSubtab is being checked.
- const currentSubtab = forceCurrentSubtab || getCurrentSubtab();
- if (currentSubtab === currentTab) {
- return currentPage === targetPage && currentTab === targetTab;
- }
-
- return currentPage === targetPage && currentTab === targetTab && currentSubtab === targetSubtab;
- };
-
- /*
- onNavigation(() => console.log('mouse stats by location'),
- {
- page: 'adversaries',
- tab: 'your_stats',
- subtab: 'location'
- }
- );
-
- onNavigation(() => console.log('friend request page'),
- {
- page:'friends',
- tab: 'requests'
- }
- );
-
- onNavigation(() => console.log('hunter profile, but not when refreshing the page'),
- {
- page: 'hunterprofile',
- onLoad: true
- }
- );
- */
-
- /**
- * TODO: update this docblock
- *
- * @param {Function} callback The callback to run when the user navigates to the page.
- * @param {Object} options The options
- * @param {string} options.page The page to watch for.
- * @param {string} options.tab The tab to watch for.
- * @param {string} options.subtab The subtab to watch for.
- * @param {boolean} options.onLoad Whether or not to run the callback on load.
- */
- const onNavigation = (callback, options = {}) => {
- const defaults = {
- page: false,
- tab: false,
- subtab: false,
- onLoad: false,
- };
-
- // merge the defaults with the options
- const { page, tab, subtab, onLoad } = Object.assign(defaults, options);
-
- // If we don't pass in a page, then we want to run the callback on every page.
- let bypassMatch = false;
- if (! page) {
- bypassMatch = true;
- }
-
- // We do this once on load in case we are starting on the page we want to watch for.
- if (onLoad) {
- if (bypassMatch || matchesCurrentPage(page, tab, subtab)) {
- callback();
- }
- }
-
- eventRegistry.addEventListener('set_page', (e) => {
- const tabs = e?.data?.tabs || {};
-
- const currentTab = Object.keys(tabs).find((key) => tabs[ key ].is_active_tab);
- const forceCurrentTab = currentTab?.type;
-
- if (! subtab) {
- if (matchesCurrentPage(page, tab, false, getCurrentPage(), forceCurrentTab)) {
- callback();
- }
-
- return;
- }
-
- if (currentTab?.subtabs && currentTab?.subtabs.length > 0) {
- const forceSubtab = currentTab.subtabs.find((searchTab) => searchTab.is_active_subtab).subtab_type;
-
- if (matchesCurrentPage(page, tab, subtab, getCurrentPage(), forceCurrentTab, forceSubtab)) {
- callback();
- }
- }
- });
-
- eventRegistry.addEventListener('set_tab', (e) => {
- const forceCurrentTab = e.page_arguments.tab;
- const forceCurrentSubtab = e.page_arguments.sub_tab;
-
- if (matchesCurrentPage(page, tab, subtab, getCurrentPage(), forceCurrentTab, forceCurrentSubtab)) {
- callback();
- }
- });
- };
-
- const onNavigate = onNavigation;
-
- /**
- * Get the current page slug.
- *
- * @return {string} The page slug.
- */
- const getCurrentPage = () => {
- return hg.utils.PageUtil.getCurrentPage().toLowerCase(); // eslint-disable-line no-undef
- };
-
- /**
- * Get the current page tab, defaulting to the current page if no tab is found.
- *
- * @return {string} The page tab.
- */
- const getCurrentTab = () => {
- const tab = hg.utils.PageUtil.getCurrentPageTab().toLowerCase(); // eslint-disable-line no-undef
- if (tab.length <= 0) {
- return getCurrentPage();
- }
-
- return tab;
- };
-
- /**
- * Get the current page sub tab, defaulting to the current tab if no sub tab is found.
- *
- * @return {string} The page tab.
- */
- const getCurrentSubtab = () => {
- const subtab = hg.utils.PageUtil.getCurrentPageSubTab();
- if (! subtab || subtab.length <= 0) {
- return getCurrentTab();
- }
-
- return subtab.toLowerCase();
- };
-
- // Backwards compatibility.
- const getCurrentSubTab = getCurrentSubtab;
-
- /**
- * Check if the overlay is visible.
- *
- * @return {boolean} True if the overlay is visible, false otherwise.
- */
- const isOverlayVisible = () => {
- return activejsDialog && activejsDialog.isVisible();
- };
-
- /**
- * Get the current overlay.
- *
- * @return {string} The current overlay.
- */
- const getCurrentOverlay = () => {
- const overlay = document.getElementById('overlayPopup');
- if (overlay && overlay.classList.length <= 0) {
- return null;
- }
-
- let overlayType = overlay.classList.value;
- overlayType = overlayType.replace('jsDialogFixed', '');
- overlayType = overlayType.replace('default', '');
- overlayType = overlayType.replace('wide', '');
- overlayType = overlayType.replace('ajax', '');
- overlayType = overlayType.replace('overlay', '');
-
- // Replace some overlay types with more readable names.
- overlayType = overlayType.replace('treasureMapPopup', 'map');
- overlayType = overlayType.replace('itemViewPopup', 'item');
- overlayType = overlayType.replace('mouseViewPopup', 'mouse');
- overlayType = overlayType.replace('largerImage', 'image');
- overlayType = overlayType.replace('convertibleOpenViewPopup', 'convertible');
- overlayType = overlayType.replace('adventureBookPopup', 'adventureBook');
- overlayType = overlayType.replace('marketplaceViewPopup', 'marketplace');
- overlayType = overlayType.replace('giftSelectorViewPopup', 'gifts');
- overlayType = overlayType.replace('supportPageContactUsForm', 'support');
- overlayType = overlayType.replace('MHCheckout', 'premiumShop');
-
- return overlayType.trim();
- };
-
- /**
- * Get the current location.
- *
- * @return {string} The current location.
- */
- const getCurrentLocation = () => {
- const location = user?.environment_type || '';
- return location.toLowerCase();
- };
-
- /**
- * Check if the user is logged in.
- *
- * @return {boolean} True if the user is logged in, false otherwise.
- */
- const isLoggedIn = () => {
- return user.length > 0 && 'login' !== getCurrentPage();
- };
-
- /**
- * Get the saved settings.
- *
- * @param {string} key The key to get.
- * @param {boolean} defaultValue The default value.
- * @param {string} identifier The identifier for the settings.
- *
- * @return {Object} The saved settings.
- */
- const getSetting = (key = null, defaultValue = null, identifier = 'mh-utils-settings') => {
- // Grab the local storage data.
- const settings = JSON.parse(localStorage.getItem(identifier)) || {};
-
- // If we didn't get a key passed in, we want all the settings.
- if (! key) {
- return settings;
- }
-
- // If the setting doesn't exist, return the default value.
- if (Object.prototype.hasOwnProperty.call(settings, key)) {
- return settings[ key ];
- }
-
- return defaultValue;
- };
-
- /**
- * Save a setting.
- *
- * @param {string} key The setting key.
- * @param {boolean} value The setting value.
- * @param {string} identifier The identifier for the settings.
- */
- const saveSetting = (key, value, identifier = 'mh-utils-settings') => {
- // Grab all the settings, set the new one, and save them.
- const settings = getSetting(null, {}, identifier);
- settings[ key ] = value;
-
- localStorage.setItem(identifier, JSON.stringify(settings));
- };
-
- /**
- * Save a setting and toggle the class in the settings UI.
- *
- * @ignore
- *
- * @param {Node} node The setting node to animate.
- * @param {string} key The setting key.
- * @param {boolean} value The setting value.
- */
- const saveSettingAndToggleClass = (node, key, value, identifier = 'mh-utils-settings') => {
- node.parentNode.parentNode.classList.add('busy');
-
- // Toggle the state of the checkbox.
- node.classList.toggle('active');
-
- // Save the setting.
- saveSetting(key, value, identifier);
-
- // Add the completed class & remove it in a second.
- node.parentNode.parentNode.classList.remove('busy');
- node.parentNode.parentNode.classList.add('completed');
- setTimeout(() => {
- node.parentNode.parentNode.classList.remove('completed');
- }, 1000);
-
- addSettingRefreshReminder();
- };
-
- /**
- * Make the settings tab.
- *
- * @param {string} identifier The identifier for the settings.
- * @param {string} name The name of the settings tab.
- */
- const addSettingsTab = (identifier = 'userscript-settings', name = 'Userscript Settings') => {
- addSettingsTabOnce(identifier, name);
- onPageChange({ preferences: { show: () => addSettingsTabOnce(identifier, name) } });
-
- return identifier;
- };
-
- /**
- * Make the settings tab once.
- *
- * @ignore
- *
- * @param {string} identifier The identifier for the settings.
- * @param {string} name The name of the settings tab.
- */
- const addSettingsTabOnce = (identifier = 'userscript-settings', name = 'Userscript Settings') => {
- if ('preferences' !== getCurrentPage()) {
- return;
- }
-
- const existingSettings = document.querySelector(`#${identifier}`);
- if (existingSettings) {
- return;
- }
-
- const tabsContainer = document.querySelector('.mousehuntHud-page-tabHeader-container');
- if (! tabsContainer) {
- return;
- }
-
- const tabsContentContainer = document.querySelector('.mousehuntHud-page-tabContentContainer');
- if (! tabsContentContainer) {
- return;
- }
-
- // make sure the identifier is unique and safe to use as a class.
- identifier = identifier.replace(/[^a-z0-9-_]/gi, '');
-
- const settingsTab = document.createElement('a');
- settingsTab.id = identifier;
- settingsTab.href = '#';
- settingsTab.classList.add('mousehuntHud-page-tabHeader', identifier);
- settingsTab.setAttribute('data-tab', identifier);
- settingsTab.setAttribute('onclick', 'hg.utils.PageUtil.onclickPageTabHandler(this); return false;');
-
- const settingsTabText = document.createElement('span');
- settingsTabText.innerText = name;
-
- settingsTab.appendChild(settingsTabText);
- tabsContainer.appendChild(settingsTab);
-
- const settingsTabContent = document.createElement('div');
- settingsTabContent.classList.add('mousehuntHud-page-tabContent', 'game_settings', identifier);
- settingsTabContent.setAttribute('data-tab', identifier);
-
- tabsContentContainer.appendChild(settingsTabContent);
-
- if (identifier === getCurrentTab()) {
- const tab = document.getElementById(identifier);
- if (tab) {
- tab.click();
- }
- }
- };
-
- /**
- * Add a setting to the preferences page, both on page load and when the page changes.
- *
- * @param {string} name The setting name.
- * @param {string} key The setting key.
- * @param {boolean} defaultValue The default value.
- * @param {string} description The setting description.
- * @param {Object} section The section settings.
- * @param {string} tab The tab to add the settings to.
- * @param {Object} settings The settings for the settings.
- */
- const addSetting = (name, key, defaultValue = true, description = '', section = {}, tab = 'userscript-settings', settings = null) => {
- onPageChange({ preferences: { show: () => addSettingOnce(name, key, defaultValue, description, section, tab, settings) } });
- addSettingOnce(name, key, defaultValue, description, section, tab, settings);
-
- addSettingRefreshReminder();
- onPageChange({ preferences: { show: addSettingRefreshReminder } });
- };
-
- /**
- * Add a setting to the preferences page.
- *
- * @ignore
- *
- * @param {string} name The setting name.
- * @param {string} key The setting key.
- * @param {boolean} defaultValue The default value.
- * @param {string} description The setting description.
- * @param {Object} section The section settings.
- * @param {string} tab The tab to add the settings to.
- * @param {Object} settingSettings The settings for the settings.
- */
- const addSettingOnce = (name, key, defaultValue = true, description = '', section = {}, tab = 'userscript-settings', settingSettings = null) => {
- // Make sure we have the container for our settings.
- const container = document.querySelector(`.mousehuntHud-page-tabContent.${tab}`);
- if (! container) {
- return;
- }
-
- section = {
- id: section.id || 'settings',
- name: section.name || 'Userscript Settings',
- description: section.description || '',
- };
-
- let tabId = 'mh-utils-settings';
- if (tab !== 'userscript-settings') {
- tabId = tab;
- }
-
- section.id = `${tabId}-${section.id.replace(/[^a-z0-9-_]/gi, '')}`;
-
- // If we don't have our custom settings section, then create it.
- let sectionExists = document.querySelector(`#${section.id}`);
- if (! sectionExists) {
- // Make the element, add the ID and class.
- const title = document.createElement('div');
- title.id = section.id;
- title.classList.add('PagePreferences__title');
-
- // Set the title of our section.
- const titleText = document.createElement('h3');
- titleText.classList.add('PagePreferences__titleText');
- titleText.textContent = section.name;
-
- // Append the title.
- title.appendChild(titleText);
-
- // Add a separator.
- const seperator = document.createElement('div');
- seperator.classList.add('PagePreferences__separator');
-
- // Append the separator.
- title.appendChild(seperator);
-
- // Append it.
- container.appendChild(title);
-
- sectionExists = document.querySelector(`#${section.id}`);
-
- if (section.description) {
- const settingSubHeader = makeElement('h4', ['settings-subheader', 'mh-utils-settings-subheader'], section.description);
- sectionExists.insertBefore(settingSubHeader, seperator);
-
- addStyles(`.mh-utils-settings-subheader {
- padding-top: 10px;
- padding-bottom: 10px;
- font-size: 10px;
- color: #848484;
- }`, 'mh-utils-settings-subheader', true);
- }
- }
-
- // If we already have a setting visible for our key, bail.
- const settingExists = document.getElementById(`${section.id}-${key}`);
- if (settingExists) {
- return;
- }
-
- // Create the markup for the setting row.
- const settings = document.createElement('div');
- settings.classList.add('PagePreferences__settingsList');
- settings.id = `${section.id}-${key}`;
-
- const settingRow = document.createElement('div');
- settingRow.classList.add('PagePreferences__setting');
-
- const settingRowLabel = document.createElement('div');
- settingRowLabel.classList.add('PagePreferences__settingLabel');
-
- const settingName = document.createElement('div');
- settingName.classList.add('PagePreferences__settingName');
- settingName.innerHTML = name;
-
- const defaultSettingText = document.createElement('div');
- defaultSettingText.classList.add('PagePreferences__settingDefault');
-
- if (settingSettings && (settingSettings.type === 'select' || settingSettings.type === 'multi-select')) {
- addStyles(`.PagePreferences .mousehuntHud-page-tabContent.game_settings.userscript-settings .settingRow .settingRow-action-inputContainer.select.busy:before,
- .PagePreferences .mousehuntHud-page-tabContent.game_settings.userscript-settings .settingRow .settingRow-action-inputContainer.select.completed:before,
- .PagePreferences .mousehuntHud-page-tabContent.game_settings.better-mh-settings .settingRow .settingRow-action-inputContainer.select.busy:before,
- .PagePreferences .mousehuntHud-page-tabContent.game_settings.better-mh-settings .settingRow .settingRow-action-inputContainer.select.completed:before {
- left: unset;
- right: -25px;
- top: 30px;
- }
-
- .PagePreferences .mousehuntHud-page-tabContent.game_settings .settingRow .name {
- height: unset;
- min-height: 20px;
- }
-
- .PagePreferences__settingAction.inputDropdownWrapper.busy:before,
- .PagePreferences__settingAction.inputDropdownWrapper.completed:before {
- left: unset;
- right: -40px;
- }
-
- .inputBoxContainer.multiSelect {
- max-width: 400px;
- }`, 'mh-utils-settings-select', true);
-
- defaultSettingText.textContent = defaultValue.map((item) => item.name).join(', ');
- } else {
- defaultSettingText.textContent = defaultValue ? 'Enabled' : 'Disabled';
- }
-
- defaultSettingText.textContent = `Default setting: ${defaultSettingText.textContent}`;
-
- const settingDescription = document.createElement('div');
- settingDescription.classList.add('PagePreferences__settingDescription');
- settingDescription.innerHTML = description;
-
- settingRowLabel.appendChild(settingName);
- settingRowLabel.appendChild(defaultSettingText);
- settingRowLabel.appendChild(settingDescription);
-
- const settingRowAction = document.createElement('div');
- settingRowAction.classList.add('PagePreferences__settingAction');
-
- const settingRowInput = document.createElement('div');
- settingRowInput.classList.add('settingRow-action-inputContainer');
-
- if (settingSettings && (settingSettings.type === 'select' || settingSettings.type === 'multi-select')) {
- // Create the dropdown.
- const settingRowInputDropdown = document.createElement('div');
- settingRowInputDropdown.classList.add('inputBoxContainer');
-
- if (settingSettings.type === 'multi-select') {
- settingRowInputDropdown.classList.add('multiSelect');
- settingRowInput.classList.add('multiSelect', 'select');
- }
-
- const amount = settingSettings.type === 'multi-select' ? settingSettings.number : 1;
-
- // make a multi-select dropdown.
- for (let i = 0; i < amount; i++) {
- const settingRowInputDropdownSelect = document.createElement('select');
- settingRowInputDropdownSelect.classList.add('inputBox');
-
- if (settingSettings.type === 'multi-select') {
- settingRowInputDropdownSelect.classList.add('multiSelect');
- }
-
- const currentSetting = getSetting(`${key}-${i}`, null, tab);
- let foundSelected = false;
-
- settingSettings.options.forEach((option) => {
- const settingRowInputDropdownSelectOption = document.createElement('option');
- settingRowInputDropdownSelectOption.value = option.value;
- settingRowInputDropdownSelectOption.textContent = option.name;
-
- if (currentSetting && currentSetting === option.value) {
- settingRowInputDropdownSelectOption.selected = true;
- foundSelected = true;
- } else if (! foundSelected && defaultValue && defaultValue[ i ] && defaultValue[ i ].value === option.value) {
- settingRowInputDropdownSelectOption.selected = true;
- foundSelected = true;
- }
-
- settingRowInputDropdownSelect.appendChild(settingRowInputDropdownSelectOption);
- });
-
- settingRowInputDropdown.appendChild(settingRowInputDropdownSelect);
-
- // Event listener for when the setting is clicked.
- settingRowInputDropdownSelect.onchange = (event) => {
- const parent = settingRowInputDropdownSelect.parentNode.parentNode.parentNode;
- parent.classList.add('inputDropdownWrapper');
- parent.classList.add('busy');
-
- // save the setting.
- saveSetting(`${key}-${i}`, event.target.value, tab);
-
- parent.classList.remove('busy');
- parent.classList.add('completed');
- setTimeout(() => {
- parent.classList.remove('completed');
- }, 1000);
- };
-
- settingRowInput.appendChild(settingRowInputDropdown);
- settingRowAction.appendChild(settingRowInput);
- }
- } else {
- const settingRowInputCheckbox = document.createElement('div');
- settingRowInputCheckbox.classList.add('mousehuntSettingSlider');
-
- // Depending on the current state of the setting, add the active class.
- const currentSetting = getSetting(key, null, tab);
- let isActive = false;
- if (currentSetting) {
- settingRowInputCheckbox.classList.add('active');
- isActive = true;
- } else if (null === currentSetting && defaultValue) {
- settingRowInputCheckbox.classList.add('active');
- isActive = true;
- }
-
- // Event listener for when the setting is clicked.
- settingRowInputCheckbox.onclick = (event) => {
- saveSettingAndToggleClass(event.target, key, ! isActive, tab);
- };
-
- // Add the input to the settings row.
- settingRowInput.appendChild(settingRowInputCheckbox);
- settingRowAction.appendChild(settingRowInput);
- }
-
- // Add the label and action to the settings row.
- settingRow.appendChild(settingRowLabel);
- settingRow.appendChild(settingRowAction);
-
- // Add the settings row to the settings container.
- settings.appendChild(settingRow);
- sectionExists.appendChild(settings);
- };
-
- /**
- * Add a refresh reminder to the settings page.
- *
- * @ignore
- */
- const addSettingRefreshReminder = () => {
- const existing = document.querySelector('.mh-utils-settings-refresh-message');
- if (existing) {
- return;
- }
-
- addStyles(`.mh-utils-settings-refresh-message {
- position: fixed;
- right: 0;
- bottom: 0;
- left: 0;
- z-index: 5;
- padding: 1em;
- font-size: 1.5em;
- text-align: center;
- background-color: #d6f2d6;
- border-top: 1px solid #6cc36c;
- opacity: 1;
- transition: opacity 0.5s ease-in-out;
- pointer-events: none;
- }
-
- .mh-utils-settings-refresh-message-hidden {
- opacity: 0;
- }`, 'mh-utils-settings-refresh-message', true);
-
- const settingsToggles = document.querySelectorAll('.mousehuntSettingSlider');
- if (! settingsToggles) {
- return;
- }
-
- settingsToggles.forEach((toggle) => {
- if (toggle.getAttribute('data-has-refresh-reminder')) {
- return;
- }
-
- toggle.setAttribute('data-has-refresh-reminder', true);
-
- toggle.addEventListener('click', () => {
- const refreshMessage = document.querySelector('.mh-utils-settings-refresh-message');
- if (refreshMessage) {
- refreshMessage.classList.remove('mh-utils-settings-refresh-message-hidden');
- }
-
- setTimeout(() => {
- if (refreshMessage) {
- refreshMessage.classList.add('mh-utils-settings-refresh-message-hidden');
- }
- }, 5000);
- });
- });
-
- const existingRefreshMessage = document.querySelector('.mh-utils-settings-refresh-message');
- if (! existingRefreshMessage) {
- const body = document.querySelector('body');
- if (body) {
- makeElement('div', ['mh-utils-settings-refresh-message', 'mh-utils-settings-refresh-message-hidden'], 'Refresh the page to apply your changes.', body);
- }
- }
- };
-
- /**
- * POST a request to the server and return the response.
- *
- * @async
- * @param {string} url The url to post to, not including the base url.
- * @param {Object} formData The form data to post.
- *
- * @return {Promise} The response.
- */
- const doRequest = async (url, formData = {}) => {
- // If we don't have the needed params, bail.
- if ('undefined' === typeof lastReadJournalEntryId || 'undefined' === typeof user) {
- return;
- }
-
- // If our needed params are empty, bail.
- if (! lastReadJournalEntryId || ! user || ! user.unique_hash) { // eslint-disable-line no-undef
- return;
- }
-
- // Build the form for the request.
- const form = new FormData();
- form.append('sn', 'Hitgrab');
- form.append('hg_is_ajax', 1);
- form.append('last_read_journal_entry_id', lastReadJournalEntryId ? lastReadJournalEntryId : 0); // eslint-disable-line no-undef
- form.append('uh', user.unique_hash ? user.unique_hash : ''); // eslint-disable-line no-undef
-
- // Add in the passed in form data.
- for (const key in formData) {
- form.append(key, formData[ key ]);
- }
-
- // Convert the form to a URL encoded string for the body.
- const requestBody = new URLSearchParams(form).toString();
-
- // Send the request.
- const response = await fetch(
- callbackurl ? callbackurl + url : 'https://www.mousehuntgame.com/' + url, // eslint-disable-line no-undef
- {
- method: 'POST',
- body: requestBody,
- headers: {
- 'Content-Type': 'application/x-www-form-urlencoded',
- },
- }
- );
-
- // Wait for the response and return it.
- const data = await response.json();
- return data;
- };
-
- /**
- * Check if the legacy HUD is enabled.
- *
- * @return {boolean} Whether the legacy HUD is enabled.
- */
- const isLegacyHUD = () => {
- return hg.utils.PageUtil.isLegacy();
- };
-
- /**
- * Check if an item is in the inventory.
- *
- * @async
- *
- * @param {string} item The item to check for.
- *
- * @return {boolean} Whether the item is in the inventory.
- */
- const userHasItem = async (item) => {
- const hasItem = await getUserItems([item]);
- return hasItem.length > 0;
- };
-
- /**
- * Check if an item is in the inventory.
- *
- * @async
- *
- * @param {Array} items The item to check for.
- *
- * @return {Array} The item data.
- */
- const getUserItems = async (items) => {
- return new Promise((resolve) => {
- hg.utils.UserInventory.getItems(items, (resp) => {
- resolve(resp);
- });
- });
- };
-
- /**
- * Get the user's setup details.
- *
- * @return {Object} The user's setup details.
- */
- const getUserSetupDetails = () => {
- const userObj = user; // eslint-disable-line no-undef
- const setup = {
- type: userObj.trap_power_type_name,
- stats: {
- power: userObj.trap_power,
- powerBonus: userObj.trap_power_bonus,
- luck: userObj.trap_luck,
- attractionBonus: userObj.trap_attraction_bonus,
- cheeseEfect: userObj.trap_cheese_effect,
- },
- bait: {
- id: parseInt(userObj.bait_item_id),
- name: userObj.bait_name,
- quantity: parseInt(userObj.bait_quantity),
- power: 0,
- powerBonus: 0,
- luck: 0,
- attractionBonus: 0,
- },
- base: {
- id: parseInt(userObj.base_item_id),
- name: userObj.base_name,
- power: 0,
- powerBonus: 0,
- luck: 0,
- attractionBonus: 0,
- },
- charm: {
- id: parseInt(userObj.trinket_item_id),
- name: userObj.trinket_name,
- quantity: parseInt(userObj.trinket_quantity),
- power: 0,
- powerBonus: 0,
- luck: 0,
- attractionBonus: 0,
- },
- weapon: {
- id: parseInt(userObj.weapon_item_id),
- name: userObj.weapon_name,
- power: 0,
- powerBonus: 0,
- luck: 0,
- attractionBonus: 0,
- },
- aura: {
- lgs: {
- active: false,
- power: 0,
- powerBonus: 0,
- luck: 0,
- },
- lightning: {
- active: false,
- power: 0,
- powerBonus: 0,
- luck: 0,
- },
- chrome: {
- active: false,
- power: 0,
- powerBonus: 0,
- luck: 0,
- },
- slayer: {
- active: false,
- power: 0,
- powerBonus: 0,
- luck: 0,
- },
- festive: {
- active: false,
- power: 0,
- powerBonus: 0,
- luck: 0,
- },
- luckycodex: {
- active: false,
- power: 0,
- powerBonus: 0,
- luck: 0,
- },
- riftstalker: {
- active: false,
- power: 0,
- powerBonus: 0,
- luck: 0,
- },
- },
- location: {
- name: userObj.environment_name,
- id: userObj.environment_id,
- slug: userObj.environment_type,
- },
- };
-
- if ('camp' !== getCurrentPage()) {
- return setup;
- }
-
- const calculations = document.querySelectorAll('.campPage-trap-trapStat');
- if (! calculations) {
- return setup;
- }
-
- calculations.forEach((calculation) => {
- if (calculation.classList.length <= 1) {
- return;
- }
-
- const type = calculation.classList[ 1 ];
- const math = calculation.querySelectorAll('.math .campPage-trap-trapStat-mathRow');
- if (! math) {
- return;
- }
-
- math.forEach((row) => {
- if (row.classList.contains('label')) {
- return;
- }
-
- let value = row.querySelector('.campPage-trap-trapStat-mathRow-value');
- let name = row.querySelector('.campPage-trap-trapStat-mathRow-name');
-
- if (! value || ! name || ! name.innerText) {
- return;
- }
-
- name = name.innerText;
- value = value.innerText || '0';
-
- let tempType = type;
- let isBonus = false;
- if (value.includes('%')) {
- tempType = type + 'Bonus';
- isBonus = true;
- }
-
- // Because attraction_bonus is silly.
- tempType = tempType.replace('_bonusBonus', 'Bonus');
-
- value = value.replace('%', '');
- value = value.replace(',', '');
- value = parseInt(value * 100) / 100;
-
- if (tempType === 'attractionBonus') {
- value = value / 100;
- }
-
- // Check if the name matches either setup.weapon.name, setup.base.name, setup.charm.name, setup.bait.name and if so, update the setup object with the value
- if (setup.weapon.name === name) {
- setup.weapon[ tempType ] = value;
- } else if (setup.base.name === name) {
- setup.base[ tempType ] = value;
- } else if (setup.charm.name === name) {
- setup.charm[ tempType ] = value;
- } else if (setup.bait.name === name) {
- setup.bait[ tempType ] = value;
- } else if ('Your trap has no cheese effect bonus.' === name) {
- setup.cheeseEffect = 'No Effect';
- } else {
- let auraType = name.replace(' Aura', '');
- if (! auraType) {
- return;
- }
-
- auraType = auraType.toLowerCase();
- auraType = auraType.replaceAll(' ', '_');
- // remove any non alphanumeric characters
- auraType = auraType.replace(/[^a-z0-9_]/gi, '');
- auraType = auraType.replace('golden_luck_boost', 'lgs');
- auraType = auraType.replace('2023_lucky_codex', 'luckycodex');
- auraType = auraType.replace('_set_bonus_2_pieces', '');
- auraType = auraType.replace('_set_bonus_3_pieces', '');
-
- if (! setup.aura[ auraType ]) {
- setup.aura[ auraType ] = {
- active: true,
- type: auraType,
- power: 0,
- powerBonus: 0,
- luck: 0,
- };
- } else {
- setup.aura[ auraType ].active = true;
- setup.aura[ auraType ].type = auraType;
- }
-
- value = parseInt(value);
-
- if (isBonus) {
- value = value / 100;
- }
-
- setup.aura[ auraType ][ tempType ] = value;
- }
- });
- });
-
- return setup;
- };
-
- /**
- * Add a submenu item to a menu.
- *
- * @param {Object} options The options for the submenu item.
- * @param {string} options.menu The menu to add the submenu item to.
- * @param {string} options.label The label for the submenu item.
- * @param {string} options.icon The icon for the submenu item.
- * @param {string} options.href The href for the submenu item.
- * @param {string} options.class The class for the submenu item.
- * @param {Function} options.callback The callback for the submenu item.
- * @param {boolean} options.external Whether the submenu item is external or not.
- */
- const addSubmenuItem = (options) => {
- // Default to sensible values.
- const settings = Object.assign({}, {
- menu: 'kingdom',
- label: '',
- icon: 'https://www.mousehuntgame.com/images/ui/hud/menu/special.png',
- href: '',
- class: '',
- callback: null,
- external: false,
- }, options);
-
- // Grab the menu item we want to add the submenu to.
- const menuTarget = document.querySelector(`.mousehuntHud-menu .${settings.menu}`);
- if (! menuTarget) {
- return;
- }
-
- // If the menu already has a submenu, just add the item to it.
- if (! menuTarget.classList.contains('hasChildren')) {
- menuTarget.classList.add('hasChildren');
- }
-
- let hasSubmenu = true;
- let submenu = menuTarget.querySelector('ul');
- if (! submenu) {
- hasSubmenu = false;
- submenu = document.createElement('ul');
- }
-
- // Create the item.
- const item = document.createElement('li');
- item.classList.add('custom-submenu-item');
- const cleanLabel = settings.label.toLowerCase().replace(/[^a-z0-9]/g, '-');
-
- const exists = document.querySelector(`#custom-submenu-item-${cleanLabel}`);
- if (exists) {
- return;
- }
-
- item.id = `custom-submenu-item-${cleanLabel}`;
- if (settings.class) {
- item.classList.add(settings.class);
- }
-
- // Create the link.
- const link = document.createElement('a');
- link.href = settings.href || '#';
-
- if (settings.callback) {
- link.addEventListener('click', (e) => {
- e.preventDefault();
- settings.callback();
- });
- }
-
- // Create the icon.
- const icon = document.createElement('div');
- icon.classList.add('icon');
- icon.style = `background-image: url(${settings.icon});`;
-
- // Create the label.
- const name = document.createElement('div');
- name.classList.add('name');
- name.innerText = settings.label;
-
- // Add the icon and label to the link.
- link.appendChild(icon);
- link.appendChild(name);
-
- // If it's an external link, also add the icon for it.
- if (settings.external) {
- const externalLinkIcon = document.createElement('div');
- externalLinkIcon.classList.add('external_icon');
- link.appendChild(externalLinkIcon);
-
- // Set the target to _blank so it opens in a new tab.
- link.target = '_blank';
- link.rel = 'noopener noreferrer';
- }
-
- // Add the link to the item.
- item.appendChild(link);
-
- // Add the item to the submenu.
- submenu.appendChild(item);
-
- if (! hasSubmenu) {
- menuTarget.appendChild(submenu);
- }
- };
-
- /**
- * Add the mouse.rip link to the kingdom menu.
- *
- * @ignore
- */
- const addMouseripLink = () => {
- addSubmenuItem({
- menu: 'kingdom',
- label: 'mouse.rip',
- icon: 'https://www.mousehuntgame.com/images/ui/hud/menu/prize_shoppe.png',
- href: 'https://mouse.rip',
- external: true,
- });
- };
-
- /**
- * Add an item to the top 'Hunters Online' menu.
- *
- * @param {Object} options The options for the menu item.
- * @param {string} options.label The label for the menu item.
- * @param {string} options.href The href for the menu item.
- * @param {string} options.class The class for the menu item.
- * @param {Function} options.callback The callback for the menu item.
- * @param {boolean} options.external Whether the link is external or not.
- */
- const addItemToGameInfoBar = (options) => {
- const settings = Object.assign({}, {
- label: '',
- href: '',
- class: '',
- callback: null,
- external: false,
- }, options);
-
- const safeLabel = settings.label.replace(/[^a-z0-9]/gi, '_').toLowerCase();
- const exists = document.querySelector(`#mh-custom-topmenu-${safeLabel}`);
- if (exists) {
- return;
- }
-
- addStyles(`.mousehuntHud-gameInfo .mousehuntHud-menu {
- position: relative;
- top: unset;
- left: unset;
- display: inline;
- width: unset;
- height: unset;
- padding-top: unset;
- padding-left: unset;
- background: unset;
- }
- `, 'mh-custom-topmenu', true);
-
- const menu = document.querySelector('.mousehuntHud-gameInfo');
- if (! menu) {
- return;
- }
-
- const item = document.createElement('a');
- item.id = `mh-custom-topmenu-${safeLabel}`;
- item.classList.add('mousehuntHud-gameInfo-item');
- item.classList.add('mousehuntHud-custom-menu-item');
-
- item.href = settings.href || '#';
-
- const name = document.createElement('div');
- name.classList.add('name');
-
- if (settings.label) {
- name.innerText = settings.label;
- }
-
- item.appendChild(name);
-
- if (settings.class) {
- item.classList.add(settings.class);
- }
-
- if (settings.href) {
- item.href = settings.href;
- }
-
- if (settings.callback) {
- item.addEventListener('click', settings.callback);
- }
-
- if (settings.external) {
- const externalLinkIconWrapper = document.createElement('div');
- externalLinkIconWrapper.classList.add('mousehuntHud-menu');
-
- const externalLinkIcon = document.createElement('div');
- externalLinkIcon.classList.add('external_icon');
-
- externalLinkIconWrapper.appendChild(externalLinkIcon);
- item.appendChild(externalLinkIconWrapper);
- }
-
- menu.insertBefore(item, menu.firstChild);
- };
-
- /**
- * Build a popup.
- *
- * Templates:
- * ajax: no close button in lower right, 'prefix' instead of title. 'suffix' for close button area.
- * default: {*title*} {*content*}
- * error: in red, with error icon{*title*} {*content*}
- * largerImage: full width image {*title*} {*image*}
- * largerImageWithClass: smaller than larger image, with caption {*title*} {*image*} {*imageCaption*} {*imageClass*} (goes on the img tag)
- * loading: Just says loading
- * multipleItems: {*title*} {*content*} {*items*}
- * singleItemLeft: {*title*} {*content*} {*items*}
- * singleItemRight: {*title*} {*content*} {*items*}
- *
- * @param {Object} options The popup options.
- * @param {string} options.title The title of the popup.
- * @param {string} options.content The content of the popup.
- * @param {boolean} options.hasCloseButton Whether or not the popup has a close button.
- * @param {string} options.template The template to use for the popup.
- * @param {boolean} options.show Whether or not to show the popup.
- * @param {string} options.className The class name to add to the popup.
- */
- const createPopup = (options) => {
- // If we don't have jsDialog, bail.
- if ('undefined' === typeof jsDialog || ! jsDialog) { // eslint-disable-line no-undef
- return;
- }
-
- // Default to sensible values.
- const settings = Object.assign({}, {
- title: '',
- content: '',
- hasCloseButton: true,
- template: 'default',
- show: true,
- className: '',
- }, options);
-
- // Initiate the popup.
- const popup = new jsDialog(); // eslint-disable-line no-undef
- popup.setIsModal(! settings.hasCloseButton);
-
- // Set the template & add in the content.
- popup.setTemplate(settings.template);
- popup.addToken('{*title*}', settings.title);
- popup.addToken('{*content*}', settings.content);
-
- popup.setAttributes({
- className: settings.className,
- });
-
- // If we want to show the popup, show it.
- if (settings.show) {
- popup.show();
- }
-
- return popup;
- };
-
- /**
- * Create a popup with an image.
- *
- * @param {Object} options Popup options.
- * @param {string} options.title The title of the popup.
- * @param {string} options.image The image to show in the popup.
- * @param {boolean} options.show Whether or not to show the popup.
- */
- const createImagePopup = (options) => {
- // Default to sensible values.
- const settings = Object.assign({}, {
- title: '',
- image: '',
- show: true,
- }, options);
-
- // Create the popup.
- const popup = createPopup({
- title: settings.title,
- template: 'largerImage',
- show: false,
- });
-
- // Add the image to the popup.
- popup.addToken('{*image*}', settings.image);
-
- // If we want to show the popup, show it.
- if (settings.show) {
- popup.show();
- }
-
- return popup;
- };
-
- /**
- * Show a map-popup.
- *
- * @param {Object} options The popup options.
- * @param {string} options.title The title of the popup.
- * @param {string} options.content The content of the popup.
- * @param {string} options.closeClass The class to add to the close button.
- * @param {string} options.closeText The text to add to the close button.
- * @param {boolean} options.show Whether or not to show the popup.
- */
- const createMapPopup = (options) => {
- // Check to make sure we can call the hg views.
- if (! (hg && hg.views && hg.views.TreasureMapDialogView)) { // eslint-disable-line no-undef
- return;
- }
-
- // Default to sensible values.
- const settings = Object.assign({}, {
- title: '',
- content: '',
- closeClass: 'acknowledge',
- closeText: 'ok',
- show: true,
- }, options);
-
- // Initiate the popup.
- const dialog = new hg.views.TreasureMapDialogView(); // eslint-disable-line no-undef
-
- // Set all the content and options.
- dialog.setTitle(options.title);
- dialog.setContent(options.content);
- dialog.setCssClass(options.closeClass);
- dialog.setContinueAction(options.closeText);
-
- // If we want to show & we can show, show it.
- if (settings.show && hg.controllers && hg.controllers.TreasureMapDialogController) { // eslint-disable-line no-undef
- hg.controllers.TreasureMapController.show(); // eslint-disable-line no-undef
- hg.controllers.TreasureMapController.showDialog(dialog); // eslint-disable-line no-undef
- }
-
- return dialog;
- };
-
- /**
- * Create a welcome popup.
- *
- * @param {Object} options The popup options.
- * @param {string} options.id The ID of the popup.
- * @param {string} options.title The title of the popup.
- * @param {string} options.content The content of the popup.
- * @param {Array} options.columns The columns of the popup.
- * @param {string} options.columns.title The title of the column.
- * @param {string} options.columns.content The content of the column.
- */
- const createWelcomePopup = (options = {}) => {
- if (! (options && options.id && options.title && options.content)) {
- return;
- }
-
- if (! isLoggedIn()) {
- return;
- }
-
- const hasSeenWelcome = getSetting('has-seen-welcome', false, options.id);
- if (hasSeenWelcome) {
- return;
- }
-
- addStyles(`#overlayPopup.mh-welcome .jsDialog.top,
- #overlayPopup.mh-welcome .jsDialog.bottom,
- #overlayPopup.mh-welcome .jsDialog.background {
- padding: 0;
- margin: 0;
- background: none;
- }
-
- #overlayPopup.mh-welcome .jsDialogContainer .prefix,
- #overlayPopup.mh-welcome .jsDialogContainer .content {
- padding: 0;
- }
-
- #overlayPopup.mh-welcome #jsDialogClose,
- #overlayPopup.mh-welcome .jsDialogContainer .suffix {
- display: none;
- }
-
- #overlayPopup.mh-welcome .jsDialogContainer {
- padding: 0 20px;
- background-image: url(https://www.mousehuntgame.com/images/ui/newsposts/np_border.png);
- background-repeat: repeat-y;
- background-size: 100%;
- }
-
- #overlayPopup.mh-welcome .jsDialogContainer::before {
- position: absolute;
- top: -80px;
- right: 0;
- left: 0;
- height: 100px;
- content: '';
- background-image: url(https://www.mousehuntgame.com/images/ui/newsposts/np_header.png);
- background-repeat: no-repeat;
- background-size: 100%;
- }
-
- #overlayPopup.mh-welcome .jsDialogContainer::after {
- position: absolute;
- top: 100%;
- right: 0;
- left: 0;
- height: 126px;
- content: '';
- background-image: url(https://www.mousehuntgame.com/images/ui/newsposts/np_footer.png);
- background-repeat: no-repeat;
- background-size: 100%;
- }
-
- .mh-welcome .mh-title {
- position: relative;
- top: -90px;
- display: flex;
- align-items: center;
- justify-content: center;
- width: 412px;
- height: 90px;
- margin: 20px auto 0;
- font-family: Georgia, serif;
- font-size: 26px;
- font-weight: 700;
- color: #7d3b0a;
- text-align: center;
- text-shadow: 1px 1px 1px #e9d5a2;
- background: url(https://www.mousehuntgame.com/images/ui/larry_gifts/ribbon.png?asset_cache_version=2) no-repeat;
- }
-
- .mh-welcome .mh-inner-wrapper {
- display: flex;
- padding: 5px 10px 25px;
- margin-top: -90px;
- }
-
- .mh-welcome .text {
- margin-left: 30px;
- line-height: 18px;
- text-align: left;
- }
-
- .mh-welcome .text p {
- font-size: 13px;
- line-height: 19px;
- }
-
- .mh-welcome .mh-inner-title {
- padding: 10px 0;
- font-size: 1.5em;
- font-weight: 700;
- }
-
- .mh-welcome .mh-button-wrapper {
- display: flex;
- align-items: center;
- justify-content: center;
- }
-
- .mh-welcome .mh-button {
- padding: 10px 50px;
- font-size: 1.5em;
- color: #000;
- background: linear-gradient(to bottom, #fff600, #f4e830);
- border: 1px solid #000;
- border-radius: 5px;
- box-shadow: 0 0 10px 1px #d6d13b inset;
- }
-
- .mh-welcome .mh-intro-text {
- margin: 2em 1em;
- font-size: 15px;
- line-height: 25px;
- }
-
- .mh-welcome-columns {
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 2em;
- margin: 1em;
- -ms-grid-columns: 1fr 2em 1fr;
- }
-
- .mh-welcome-column h2 {
- margin-bottom: 1em;
- font-size: 16px;
- color: #7d3b0a;
- border-bottom: 1px solid #cba36d;
- }
-
- .mh-welcome-column ul {
- margin-left: 3em;
- list-style: disc;
- }
- `, 'mh-welcome', true);
-
- const markup = `<div class="mh-welcome">
- <h1 class="mh-title">${options.title}</h1>
- <div class="mh-inner-wrapper">
- <div class="text">
- <div class="mh-intro-text">
- ${options.content}
- </div>
- <div class="mh-welcome-columns">
- ${options.columns.map((column) => `<div class="mh-welcome-column">
- <h2>${column.title}</h2>
- ${column.content}
- </div>`).join('')}
- </div>
- </div>
- </div>
- <div class="mh-button-wrapper">
- <a href="#" id="mh-welcome-${options.id}-continue" class="mh-button">Continue</a>
- </div>
- </div>`;
-
- // Initiate the popup.
- const welcomePopup = createPopup({
- hasCloseButton: false,
- template: 'ajax',
- content: markup,
- show: false,
- });
-
- // Set more of our tokens.
- welcomePopup.addToken('{*prefix*}', '');
- welcomePopup.addToken('{*suffix*}', '');
-
- // Set the attribute and show the popup.
- welcomePopup.setAttributes({ className: `mh-welcome mh-welcome-popup-${options.id}` });
-
- // If we want to show the popup, show it.
- welcomePopup.show();
-
- // Add the event listener to the continue button.
- const continueButton = document.getElementById(`mh-welcome-${options.id}-continue`);
- continueButton.addEventListener('click', () => {
- saveSetting('has-seen-welcome', true, options.id);
- welcomePopup.hide();
- });
- };
-
- /**
- * Create a popup with the larry's office style.
- *
- * @param {string} content Content to display in the popup.
- * @param {Array} classes Classes to add to the popup.
- */
- const createLarryPopup = (content, classes = []) => {
- const message = {
- content: { body: content },
- css_class: ['larryOffice', ...classes].join(' '),
- show_overlay: true,
- is_modal: true
- };
-
- hg.views.MessengerView.addMessage(message);
- hg.views.MessengerView.go();
- };
-
- /**
- * Add a popup similar to the larry's gift popup.
- *
- * createPaperPopup({
- * title: 'Whoa! A popup!',
- * content: {
- * title: 'This is the title of the content',
- * text: 'This is some text for the content Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quid ergo hoc loco intellegit honestum? Dicimus aliquem hilare vivere; Cui Tubuli nomen odio non est? Duo Reges: constructio interrete. Sed venio ad inconstantiae crimen, ne saepius dicas me aberrare; Aliena dixit in physicis nec ea ipsa, quae tibi probarentur;',
- * image: 'https://api.mouse.rip/hunter/trap/8209591.png',
- * },
- * button: {
- * text: 'A button',
- * href: '#',
- * },
- * show: true,
- * });
- *
- * @param {Object} options The popup options.
- * @param {string} options.title The title of the popup.
- * @param {Object} options.content The content of the popup.
- * @param {string} options.content.title The title of the popup.
- * @param {string} options.content.text The text of the popup.
- * @param {string} options.content.image The image of the popup.
- * @param {Array} options.button The button of the popup.
- * @param {string} options.button.text The text of the button.
- * @param {string} options.button.href The url of the button.
- * @param {boolean} options.show Whether to show the popup or not.
- */
- const createPaperPopup = (options) => {
- // If we don't have jsDialog, bail.
- if ('undefined' === typeof jsDialog || ! jsDialog) { // eslint-disable-line no-undef
- return;
- }
-
- // Add the styles for our popup.
- addStyles(`#overlayPopup.mh-paper-popup-dialog-wrapper .jsDialog.top,
- #overlayPopup.mh-paper-popup-dialog-wrapper .jsDialog.bottom,
- #overlayPopup.mh-paper-popup-dialog-wrapper .jsDialog.background {
- padding: 0;
- margin: 0;
- background: none;
- }
-
- #overlayPopup.mh-paper-popup-dialog-wrapper .jsDialogContainer .prefix,
- #overlayPopup.mh-paper-popup-dialog-wrapper .jsDialogContainer .content {
- padding: 0;
- }
-
- #overlayPopup.mh-paper-popup-dialog-wrapper #jsDialogClose,
- #overlayPopup.mh-paper-popup-dialog-wrapper .jsDialogContainer .suffix {
- display: none;
- }
-
- #overlayPopup.mh-paper-popup-dialog-wrapper .jsDialogContainer {
- padding: 0 20px;
- background-image: url(https://www.mousehuntgame.com/images/ui/newsposts/np_border.png);
- background-repeat: repeat-y;
- background-size: 100%;
- }
-
- #overlayPopup.mh-paper-popup-dialog-wrapper .jsDialogContainer::before {
- position: absolute;
- top: -80px;
- right: 0;
- left: 0;
- height: 100px;
- content: '';
- background-image: url(https://www.mousehuntgame.com/images/ui/newsposts/np_header.png);
- background-repeat: no-repeat;
- background-size: 100%;
- }
-
- #overlayPopup.mh-paper-popup-dialog-wrapper .jsDialogContainer::after {
- position: absolute;
- top: 100%;
- right: 0;
- left: 0;
- height: 126px;
- content: '';
- background-image: url(https://www.mousehuntgame.com/images/ui/newsposts/np_footer.png);
- background-repeat: no-repeat;
- background-size: 100%;
- }
-
- .mh-paper-popup-dialog-wrapper .mh-title {
- position: relative;
- top: -40px;
- display: flex;
- align-items: center;
- justify-content: center;
- width: 412px;
- height: 99px;
- margin: 20px auto 0;
- font-family: Georgia, serif;
- font-size: 34px;
- font-weight: 700;
- color: #7d3b0a;
- text-align: center;
- text-shadow: 1px 1px 1px #e9d5a2;
- background: url(https://www.mousehuntgame.com/images/ui/larry_gifts/ribbon.png?asset_cache_version=2) no-repeat;
- }
-
- .mh-paper-popup-dialog-wrapper .mh-inner-wrapper {
- display: flex;
- padding: 5px 10px 25px;
- }
-
- .mh-paper-popup-dialog-wrapper .mh-inner-image-wrapper {
- position: relative;
- padding: 10px;
- margin: 0 auto 10px;
- background: #f7e3af;
- border-radius: 10px;
- box-shadow: 0 3px 10px #bd7d3c;
- }
-
- .mh-paper-popup-dialog-wrapper .mh-inner-image {
- width: 200px;
- height: 200px;
- background-color: #f5edd7;
- border-radius: 5px;
- box-shadow: 0 0 100px #6c340b inset;
- }
-
- .mh-paper-popup-dialog-wrapper .mh-inner-text {
- margin-left: 30px;
- line-height: 18px;
- text-align: left;
- }
-
- .mh-paper-popup-dialog-wrapper .mh-inner-title {
- padding: 10px 0;
- font-size: 1.5em;
- font-weight: 700;
- }
-
- .mh-paper-popup-dialog-wrapper .mh-button-wrapper {
- display: flex;
- align-items: center;
- justify-content: center;
- }
-
- .mh-paper-popup-dialog-wrapper .mh-button {
- padding: 10px 50px;
- font-size: 1.5em;
- color: #000;
- background: linear-gradient(to bottom, #fff600, #f4e830);
- border: 1px solid #000;
- border-radius: 5px;
- box-shadow: 0 0 10px 1px #d6d13b inset;
- }
- `);
-
- // Default to sensible values.
- const settings = Object.assign({}, {
- title: '',
- content: {
- title: '',
- text: '',
- image: '',
- },
- button: {
- text: '',
- href: '',
- },
- show: true,
- className: '',
- }, options);
-
- // Build the markup with our content.
- const markup = `<div class="mh-paper-popup-wrapper">
- <div class="mh-title">${settings.title}</div>
- <div class="mh-inner-wrapper">
- <div class="mh-inner-image-wrapper">
- <img class="mh-inner-image" src="${settings.content.image}" />
- </div>
- <div class="mh-inner-text">
- <div class="mh-inner-title">${settings.content.title}</div>
- <p>${settings.content.text}</p>
- </div>
- </div>
- <div class="mh-button-wrapper">
- <a href="${settings.button.href}" class="mh-button">${settings.button.text}</a>
- </div>
- </div>`;
-
- // Initiate the popup.
- const popup = createPopup({
- hasCloseButton: false,
- template: 'ajax',
- content: markup,
- show: false,
- });
-
- // Set more of our tokens.
- popup.addToken('{*prefix*}', '');
- popup.addToken('{*suffix*}', '');
-
- // Set the attribute and show the popup.
- popup.setAttributes({ className: `mh-paper-popup-dialog-wrapper ${settings.className}` });
-
- // If we want to show the popup, show it.
- if (settings.show) {
- popup.show();
- }
-
- return popup;
- };
-
- /**
- * Show a message in the horn dialog.
- *
- * Type can be one of these: bait_empty unknown_error bait_disarmed recent_turn recent_linked_turn puzzle
- *
- * @param {Object} options Options for the message.
- * @param {string} options.title Title of the message. Keep it under 50 characters.
- * @param {string} options.text Text of the message. Keep it under 90 characters.
- * @param {string} options.button Text of the button.
- * @param {Function} options.action Callback for the button.
- * @param {number} options.dismiss Time to dismiss the message.
- * @param {string} options.type Type of the message.
- * @param {string} options.classname Classname of the message.
- */
- const showHornMessage = (options) => {
- const huntersHornView = document.querySelector('.huntersHornView__messageContainer');
- if (! huntersHornView) {
- return;
- }
-
- const settings = {
- title: options.title || 'Hunters Horn',
- text: options.text || 'This is a message from the Hunters Horn',
- button: options.button || 'OK',
- action: options.action || (() => { }),
- dismiss: options.dismiss || null,
- type: options.type || 'recent_linked_turn',
- classname: options.classname || '',
- image: options.image || null,
- imageLink: options.imageLink || null,
- imageCallback: options.imageCallback || null,
- };
-
- // do the other effects
- const backdrop = document.querySelector('.huntersHornView__backdrop');
- if (backdrop) {
- backdrop.classList.add('huntersHornView__backdrop--active');
- }
-
- const gameInfo = document.querySelector('.mousehuntHud-gameInfo');
- if (gameInfo) {
- gameInfo.classList.add('blur');
- }
-
- const messageWrapper = makeElement('div', ['huntersHornView__message huntersHornView__message--active', settings.classname]);
- const message = makeElement('div', ['huntersHornMessageView', `huntersHornMessageView--${settings.type}`]);
- makeElement('div', 'huntersHornMessageView__title', settings.title, message);
- const content = makeElement('div', 'huntersHornMessageView__content');
- if (settings.image) {
- const imgWrapper = makeElement('div', 'huntersHornMessageView__friend');
- const img = makeElement('a', 'huntersHornMessageView__friendProfilePic');
- if (settings.imageLink) {
- img.href = settings.imageLink;
- } else if (settings.imageCallback) {
- img.addEventListener('click', settings.imageCallback);
- } else {
- img.href = '#';
- }
-
- img.style.backgroundImage = `url(${settings.image})`;
-
- imgWrapper.appendChild(img);
- content.appendChild(imgWrapper);
- }
- makeElement('div', 'huntersHornMessageView__text', settings.text, content);
- const buttonSpacer = makeElement('div', 'huntersHornMessageView__buttonSpacer');
- const button = makeElement('button', 'huntersHornMessageView__action');
- const buttonLabel = makeElement('div', 'huntersHornMessageView__actionLabel');
- makeElement('span', 'huntersHornMessageView__actionText', settings.button, buttonLabel);
-
- button.appendChild(buttonLabel);
-
- button.addEventListener('click', () => {
- if (settings.action) {
- settings.action();
- }
-
- messageWrapper.innerHTML = '';
- backdrop.classList.remove('huntersHornView__backdrop--active');
- gameInfo.classList.remove('blur');
- });
-
- buttonSpacer.appendChild(button);
- content.appendChild(buttonSpacer);
-
- message.appendChild(content);
-
- if (settings.dismiss) {
- const countdown = makeElement('button', ['huntersHornMessageView__countdown']);
- makeElement('div', 'huntersHornMessageView__countdownButtonImage', '', countdown);
-
- const svgMarkup = `<svg class="huntersHornMessageView__countdownSVG">
- <circle r="46%" cx="50%" cy="50%" class="huntersHornMessageView__countdownCircleTrack"></circle>
- <circle r="46%" cx="50%" cy="50%" class="huntersHornMessageView__countdownCircle" style="animation-duration: ${settings.dismiss}ms;"></circle>
- </svg>`;
- countdown.innerHTML += svgMarkup;
- message.appendChild(countdown);
- }
-
- messageWrapper.appendChild(message);
-
- // remove any existing messages
- const existingMessages = huntersHornView.querySelector('.huntersHornView__message');
- if (existingMessages) {
- existingMessages.remove();
- }
-
- huntersHornView.appendChild(messageWrapper);
-
- if (settings.dismiss) {
- setTimeout(() => {
- const countdown = messageWrapper.querySelector('.huntersHornMessageView__countdown');
- if (countdown) {
- countdown.classList.add('huntersHornMessageView__countdown--complete');
- }
- messageWrapper.innerHTML = '';
- backdrop.classList.remove('huntersHornView__backdrop--active');
- gameInfo.classList.remove('blur');
- }, settings.dismiss);
- }
- };
-
- const toggleHornDom = (verb = 'remove') => {
- const els = [
- {
- selector: '.huntersHornView__horn',
- class: 'huntersHornView__horn--active',
- },
- {
- selector: '.huntersHornView__backdrop',
- class: 'huntersHornView__backdrop--active',
- },
- {
- selector: '.huntersHornView__message',
- class: 'huntersHornView__message--active',
- },
- {
- selector: '.mousehuntHud-environmentName',
- class: 'blur'
- },
- {
- selector: '.mousehuntHud-gameInfo',
- class: 'blur'
- },
- {
- selector: '.huntersHornView__horn',
- class: 'huntersHornView__horn--hide'
- },
- {
- selector: '.huntersHornView__backdrop',
- class: 'huntersHornView__backdrop--active'
- },
- {
- selector: '.huntersHornView__message',
- class: 'huntersHornView__message--active'
- },
- ];
-
- els.forEach((el) => {
- const dom = document.querySelector(el.selector);
- if (dom) {
- dom.classList[ verb ](el.class);
- }
- }
- );
-
- return document.querySelector('.huntersHornView__message');
- };
-
- /**
- * TODO: document this
- *
- * @param {*} message
- */
- const showHuntersHornMessage = (message) => {
- const defaultValues = {
- callback: null,
- countdown: null,
- actionText: null,
- };
-
- message = Object.assign(defaultValues, message);
-
- // if the callback was passed in, we need to wrap it in a function that will dismiss the message
- if (message.callback) {
- const originalCallback = message.callback;
- message.callback = () => {
- originalCallback();
- dismissHuntersHornMessage();
- };
- } else {
- message.callback = dismissHuntersHornMessage;
- }
-
- const messageDom = toggleHornDom('add');
- const messageView = new hg.views.HuntersHornMessageView(message);
- messageDom.innerHTML = '';
- messageDom.appendChild(messageView.render()[ 0 ]);
- };
-
- /**
- * TODO: document this
- */
- const dismissHuntersHornMessage = () => {
- toggleHornDom('remove');
- };
-
- /**
- * Make an element draggable. Saves the position to local storage.
- *
- * @param {string} dragTarget The selector for the element that should be dragged.
- * @param {string} dragHandle The selector for the element that should be used to drag the element.
- * @param {number} defaultX The default X position.
- * @param {number} defaultY The default Y position.
- * @param {string} storageKey The key to use for local storage.
- * @param {boolean} savePosition Whether or not to save the position to local storage.
- */
- const makeElementDraggable = (dragTarget, dragHandle, defaultX = null, defaultY = null, storageKey = null, savePosition = true) => {
- const modal = document.querySelector(dragTarget);
- if (! modal) {
- return;
- }
-
- const handle = document.querySelector(dragHandle);
- if (! handle) {
- return;
- }
-
- /**
- * Make sure the coordinates are within the bounds of the window.
- *
- * @param {string} type The type of coordinate to check.
- * @param {number} value The value of the coordinate.
- *
- * @return {number} The value of the coordinate, or the max/min value if it's out of bounds.
- */
- const keepWithinLimits = (type, value) => {
- if ('top' === type) {
- return value < -20 ? -20 : value;
- }
-
- if (value < (handle.offsetWidth * -1) + 20) {
- return (handle.offsetWidth * -1) + 20;
- }
-
- if (value > document.body.clientWidth - 20) {
- return document.body.clientWidth - 20;
- }
-
- return value;
- };
-
- /**
- * When the mouse is clicked, add the class and event listeners.
- *
- * @param {Object} e The event object.
- */
- const onMouseDown = (e) => {
- e.preventDefault();
- setTimeout(() => {
- // Get the current mouse position.
- x1 = e.clientX;
- y1 = e.clientY;
-
- // Add the class to the element.
- modal.classList.add('mh-is-dragging');
-
- // Add the onDrag and finishDrag events.
- document.onmousemove = onDrag;
- document.onmouseup = finishDrag;
- }, 50);
- };
-
- /**
- * When the drag is finished, remove the dragging class and event listeners, and save the position.
- */
- const finishDrag = () => {
- document.onmouseup = null;
- document.onmousemove = null;
-
- // Remove the class from the element.
- modal.classList.remove('mh-is-dragging');
-
- if (storageKey) {
- localStorage.setItem(storageKey, JSON.stringify({ x: modal.offsetLeft, y: modal.offsetTop }));
- }
- };
-
- /**
- * When the mouse is moved, update the element's position.
- *
- * @param {Object} e The event object.
- */
- const onDrag = (e) => {
- e.preventDefault();
-
- // Calculate the new cursor position.
- x2 = x1 - e.clientX;
- y2 = y1 - e.clientY;
-
- x1 = e.clientX;
- y1 = e.clientY;
-
- const newLeft = keepWithinLimits('left', modal.offsetLeft - x2);
- const newTop = keepWithinLimits('top', modal.offsetTop - y2);
-
- // Set the element's new position.
- modal.style.left = `${newLeft}px`;
- modal.style.top = `${newTop}px`;
- };
-
- // Set the default position.
- let startX = defaultX || 0;
- let startY = defaultY || 0;
-
- // If the storageKey was passed in, get the position from local storage.
- if (! storageKey) {
- storageKey = `mh-draggable-${dragTarget}-${dragHandle}`;
- }
-
- if (savePosition) {
- const storedPosition = localStorage.getItem(storageKey);
- if (storedPosition) {
- const position = JSON.parse(storedPosition);
-
- // Make sure the position is within the bounds of the window.
- startX = keepWithinLimits('left', position.x);
- startY = keepWithinLimits('top', position.y);
- }
- }
-
- // Set the element's position.
- modal.style.left = `${startX}px`;
- modal.style.top = `${startY}px`;
-
- // Set up our variables to track the mouse position.
- let x1 = 0,
- y1 = 0,
- x2 = 0,
- y2 = 0;
-
- // Add the event listener to the handle.
- handle.onmousedown = onMouseDown;
- };
-
- const makeDraggableModal = (opts) => {
- const {
- id,
- title,
- content,
- defaultX,
- defaultY,
- storageKey,
- savePosition,
- } = opts;
-
- // set the defaults for the options
- opts = Object.assign({
- id: 'mh-utils-modal',
- title: '',
- content: '',
- defaultX: null,
- defaultY: null,
- storageKey: 'mh-utils-modal',
- savePosition: true,
- }, opts);
-
- // Remove the existing modal.
- const existing = document.getElementById(`mh-utils-modal-${id}`);
- if (existing) {
- existing.remove();
- }
-
- // Create the modal.
- const modalWrapper = makeElement('div', 'mh-utils-modal-wrapper');
- modalWrapper.id = `mh-utils-modal-${id}`;
-
- const modal = makeElement('div', 'mh-utils-modal');
- const header = makeElement('div', 'mh-utils-modal-header');
- makeElement('h1', 'mh-utils-modal-title', title, header);
-
- // Create a close button icon.
- const closeIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
- closeIcon.classList.add('mh-utils-modal-close');
- closeIcon.setAttribute('viewBox', '0 0 24 24');
- closeIcon.setAttribute('width', '18');
- closeIcon.setAttribute('height', '18');
- closeIcon.setAttribute('fill', 'none');
- closeIcon.setAttribute('stroke', 'currentColor');
- closeIcon.setAttribute('stroke-width', '1.5');
-
- // Create the path.
- const closePath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
- closePath.setAttribute('d', 'M18 6L6 18M6 6l12 12');
- closeIcon.appendChild(closePath);
-
- // Close the modal when the icon is clicked.
- closeIcon.addEventListener('click', () => {
- modalWrapper.remove();
- });
-
- // Append the button.
- header.appendChild(closeIcon);
-
- // Add the header to the modal.
- modal.appendChild(header);
-
- // Make the mouse stats table.
- const mouseBody =
- document.createElement('div');
- mouseBody.classList.add('mh-utils-modal-body');
-
- modal.appendChild(content);
-
- // Add the modal to the wrapper.
- modalWrapper.appendChild(modal);
-
- // Add the wrapper to the body.
- document.body.appendChild(modalWrapper);
-
- // Make the modal draggable.
- makeElementDraggable(
- `mh-utils-modal-${id}`,
- 'mh-utils-modal',
- 'mh-utils-modal-header',
- defaultX,
- defaultY,
- storageKey,
- savePosition
- );
- };
-
- /**
- * Creates an element with the given tag, classname, text, and appends it to the given element.
- *
- * @param {string} tag The tag of the element to create.
- * @param {string} classes The classes of the element to create.
- * @param {string} text The text of the element to create.
- * @param {HTMLElement} appendTo The element to append the created element to.
- *
- * @return {HTMLElement} The created element.
- */
- const makeElement = (tag, classes = '', text = '', appendTo = null) => {
- const element = document.createElement(tag);
-
- // if classes is an array, join it with a space.
- if (Array.isArray(classes)) {
- classes = classes.join(' ');
- }
-
- element.className = classes;
- element.innerHTML = text;
-
- if (appendTo) {
- appendTo.appendChild(element);
- return appendTo;
- }
-
- return element;
- };
-
- /**
- * Return an anchor element with the given text and href.
- *
- * @param {string} text Text to use for link.
- * @param {string} href URL to link to.
- * @param {boolean} tiny Use the tiny button style.
- * @param {Array} extraClasses Extra classes to add to the link.
- * @param {boolean} encodeAsSpace Encode spaces as %20 instead of _.
- *
- * @return {string} HTML for link.
- */
- const makeButton = (text, href, tiny = true, extraClasses = [], encodeAsSpace = false) => {
- href = href.replace(/\s/g, '_');
-
- if (encodeAsSpace) {
- href = href.replace(/_/g, '%20');
- } else {
- href = href.replace(/\s/g, '_');
- }
-
- href = href.replace(/\$/g, '_');
-
- return `<a href="${href}" class="mousehuntActionButton ${tiny ? 'tiny' : ''} ${extraClasses.join(' ')}"><span>${text}</span></a>`;
- };
-
- /**
- * Creates a popup with two choices.
- *
- * createChoicePopup({
- * title: 'Choose your first trap',
- * choices: [
- * {
- * id: 'treasurer_mouse',
- * name: 'Treasurer',
- * image: 'https://www.mousehuntgame.com/images/mice/medium/bb55034f6691eb5e3423927e507b5ec9.jpg?cv=2',
- * meta: 'Mouse',
- * text: 'This is a mouse',
- * button: 'Select',
- * callback: () => {
- * console.log('treasurer selected');
- * }
- * },
- * {
- * id: 'high_roller_mouse',
- * name: 'High Roller',
- * image: 'https://www.mousehuntgame.com/images/mice/medium/3f71c32f9d8da2b2727fc8fd288f7974.jpg?cv=2',
- * meta: 'Mouse',
- * text: 'This is a mouse',
- * button: 'Select',
- * callback: () => {
- * console.log('high roller selected');
- * }
- * },
- * ],
- * });
- *
- * @param {Object} options The options for the popup.
- * @param {string} options.title The title of the popup.
- * @param {Array} options.choices The choices for the popup.
- * @param {string} options.choices[].id The ID of the choice.
- * @param {string} options.choices[].name The name of the choice.
- * @param {string} options.choices[].image The image of the choice.
- * @param {string} options.choices[].meta The smaller text under the name.
- * @param {string} options.choices[].text The description of the choice.
- * @param {string} options.choices[].button The text of the button.
- * @param {string} options.choices[].action The action to take when the button is clicked.
- */
- const createChoicePopup = (options) => {
- let choices = '';
- const numChoices = options.choices.length;
- let currentChoice = 0;
-
- options.choices.forEach((choice) => {
- choices += `<a href="#" id=${choice.id}" class="weaponContainer">
- <div class="weapon">
- <div class="trapImage" style="background-image: url(${choice.image});"></div>
- <div class="trapDetails">
- <div class="trapName">${choice.name}</div>
- <div class="trapDamageType">${choice.meta}</div>
- <div class="trapDescription">${choice.text}</div>
- <div class="trapButton" id="${choice.id}-action">${choice.button || 'Select'}</div>
- </div>
- </div>
- </a>`;
-
- currentChoice++;
- if (currentChoice < numChoices) {
- choices += '<div class="spacer"></div>';
- }
- });
-
- const content = `<div class="trapIntro">
- <div id="OnboardArrow" class="larryCircle">
- <div class="woodgrain">
- <div class="whiteboard">${options.title}</div>
- </div>
- <div class="characterContainer">
- <div class="character"></div>
- </div>
- </div>
- </div>
- <div>
- ${choices}
- </div>`;
-
- hg.views.MessengerView.addMessage({
- content: { body: content },
- css_class: 'chooseTrap',
- show_overlay: true,
- is_modal: true
- });
- hg.views.MessengerView.go();
-
- options.choices.forEach((choice) => {
- const target = document.querySelector(`#${choice.id}-action`);
- if (target) {
- target.addEventListener('click', () => {
- hg.views.MessengerView.hide();
- if (choice.action) {
- choice.action();
- }
- });
- }
- });
- };
-
- /**
- * Creates a favorite button that can toggle.
- *
- * @async
- *
- * @example <caption>Creating a favorite button</caption>
- * createFavoriteButton({
- * id: 'testing_favorite',
- * target: infobar,
- * size: 'small',
- * defaultState: false,
- * });
- *
- * @param {Object} options The options for the button.
- * @param {string} options.selector The selector for the button.
- * @param {string} options.size Whether or not to use the small version of the button.
- * @param {string} options.active Whether or not the button should be active by default.
- * @param {string} options.onChange The function to run when the button is toggled.
- * @param {string} options.onActivate The function to run when the button is activated.
- * @param {string} options.onDeactivate The function to run when the button is deactivated.
- */
- const createFavoriteButton = async (options) => {
- addStyles(`.custom-favorite-button {
- top: 0;
- right: 0;
- display: inline-block;
- width: 35px;
- height: 35px;
- vertical-align: middle;
- background: url(https://www.mousehuntgame.com/images/ui/camp/trap/star_empty.png?asset_cache_version=2) 50% 50% no-repeat;
- background-size: 90%;
- border-radius: 50%;
- }
-
- .custom-favorite-button-small {
- width: 20px;
- height: 20px;
- }
-
- .custom-favorite-button:hover {
- background-color: #fff;
- outline: 2px solid #ccc;
- background-image: url(https://www.mousehuntgame.com/images/ui/camp/trap/star_favorite.png?asset_cache_version=2);
- }
-
- .custom-favorite-button.active {
- background-image: url(https://www.mousehuntgame.com/images/ui/camp/trap/star_favorite.png?asset_cache_version=2);
- }
-
- .custom-favorite-button.busy {
- background-image: url(https://www.mousehuntgame.com/images/ui/loaders/small_spinner.gif?asset_cache_version=2);
- }
- `, 'custom-favorite-button', true);
-
- const {
- id = null,
- target = null,
- size = 'small',
- state = false,
- isSetting = true,
- defaultState = false,
- onChange = null,
- onActivate = null,
- onDeactivate = null,
- } = options;
-
- const star = document.createElement('a');
-
- star.classList.add('custom-favorite-button');
- if (size === 'small') {
- star.classList.add('custom-favorite-button-small');
- }
-
- star.setAttribute('data-item-id', id);
- star.setAttribute('href', '#');
-
- star.style.display = 'inline-block';
-
- let currentSetting = false;
- if (isSetting) {
- currentSetting = getSetting(id, defaultState);
- } else {
- currentSetting = state;
- }
-
- if (currentSetting) {
- star.classList.add('active');
- } else {
- star.classList.add('inactive');
- }
-
- star.addEventListener('click', async (e) => {
- star.classList.add('busy');
- e.preventDefault();
- e.stopPropagation();
- const currentStar = e.target;
- const currentState = ! currentStar.classList.contains('active');
-
- if (onChange !== null) {
- await onChange(currentState);
- } else if (isSetting) {
- saveSetting(id, currentState);
- }
-
- currentStar.classList.remove('inactive');
- currentStar.classList.remove('active');
-
- if (currentState) {
- currentStar.classList.add('active');
- if (onActivate !== null) {
- await onActivate(currentState);
- }
- } else {
- currentStar.classList.add('inactive');
- if (onDeactivate !== null) {
- await onDeactivate(currentState);
- }
- }
-
- currentStar.classList.remove('busy');
- });
-
- if (target) {
- target.appendChild(star);
- }
-
- return star;
- };
-
- /**
- * Wait for a specified amount of time.
- *
- * @param {number} ms The number of milliseconds to wait.
- */
- const wait = (ms) => {
- return new Promise((resolve) => setTimeout(resolve, ms));
- };
-
- /**
- * Log to the console.
- *
- * @param {string|Object} message The message to log.
- * @param {Object} args The arguments to pass to the console.
- */
- const clog = (message, ...args) => {
- // If a string is passed in, log it in line with our prefix.
- if ('string' === typeof message) {
- console.log(`%c[MH Utils] %c${message}`, 'color: #ff0000; font-weight: bold;', 'color: #000000;'); // eslint-disable-line no-console
- console.log(...args); // eslint-disable-line no-console
- } else {
- // Otherwise, log it separately.
- console.log('%c[MH Utils]', 'color: #ff0000; font-weight: bold;'); // eslint-disable-line no-console
- console.log(message); // eslint-disable-line no-console
- }
- };
-
- /**
- * Log to the console if debug mode is enabled.
- *
- * @param {string|Object} message The message to log.
- * @param {Object} args The arguments to pass to the console.
- */
- const debug = (message, ...args) => {
- if (getSetting('debug-mode', false)) {
- clog(message, ...args);
- }
- };
-
- /**
- * Add a setting to enable debug mode.
- */
- const enableDebugMode = () => {
- const debugSettings = {
- debugModeEnabled: true,
- debug: getSetting('debug-mode', false)
- };
-
- window.mhutils = window.mhutils ? { ...window.mhutils, ...debugSettings } : debugSettings;
-
- addSetting('Debug Mode', 'debug-mode', false, 'Enable debug mode', {}, 'game_settings');
- };
-
- /**
- * Helper to run a callback when loaded, on ajax request, on overlay close, and on travel.
- *
- * @param {Function} action The callback to run.
- */
- const run = async (action) => {
- action();
- onAjaxRequest(action);
- onOverlayClose(action);
- onTravel(null, { callback: action });
- };
-
- /**
- * Check if dark mode is enabled.
- *
- * @return {boolean} True if dark mode is enabled, false otherwise.
- */
- const isDarkMode = () => {
- return !! getComputedStyle(document.documentElement).getPropertyValue('--mhdm-white');
- };
-
- /**
- * Adds classes to the body to enable styling based on the location or if dark mode is enabled.
- */
- const addBodyClasses = () => {
- const addLocationBodyClass = () => {
- const addClass = () => {
- const location = getCurrentLocation();
- document.body.classList.add(`mh-location-${location}`);
- };
-
- addClass();
- onTravel(null, { callback: addClass });
- };
-
- const addDarkModeBodyClass = () => {
- if (isDarkMode()) {
- document.body.classList.add('mh-dark-mode');
- }
- };
-
- addLocationBodyClass();
- addDarkModeBodyClass();
- };
-
- /**
- * Wait for the app to initialize, then add classes to the body.
- */
- setTimeout(() => {
- addBodyClasses();
- eventRegistry.addEventListener('app_init', addBodyClasses);
- }, 250);