🐭️ MouseHunt Utils Beta Testing

Testing version

当前为 2023-04-14 提交的版本,查看 最新版本

此脚本不应直接安装,它是供其他脚本使用的外部库。如果您需要使用该库,请在脚本元属性加入:// @require https://update.cn-greasyfork.org/scripts/464008/1175785/%F0%9F%90%AD%EF%B8%8F%20MouseHunt%20Utils%20Beta%20Testing.js

  1. // ==UserScript==
  2. // @name 🐭️ MouseHunt Utils Beta Testing
  3. // @author bradp
  4. // @version 1.5.5
  5. // @description Testing version
  6. // @license MIT
  7. // @namespace bradp
  8. // @match https://www.mousehuntgame.com/*
  9. // @icon https://i.mouse.rip/mouse.png
  10. // @grant none
  11. // ==/UserScript==
  12.  
  13. /* eslint-disable no-unused-vars */
  14.  
  15. /**
  16. * Add styles to the page.
  17. *
  18. * @param {string} styles The styles to add.
  19. * @param {string} identifier The identifier to use for the style element.
  20. * @param {boolean} once Only add the styles once for the identifier.
  21. */
  22. const addStyles = (styles, identifier = 'mh-utils-custom-styles', once = false) => {
  23. // Check to see if the existing element exists.
  24. const existingStyles = document.getElementById(identifier);
  25.  
  26. // If so, append our new styles to the existing element.
  27. if (existingStyles) {
  28. if (once) {
  29. return;
  30. }
  31.  
  32. existingStyles.innerHTML += styles;
  33. return;
  34. }
  35.  
  36. // Otherwise, create a new element and append it to the head.
  37. const style = document.createElement('style');
  38. style.id = identifier;
  39. style.innerHTML = styles;
  40. document.head.appendChild(style);
  41. };
  42.  
  43. /**
  44. * Do something when ajax requests are completed.
  45. *
  46. * @param {Function} callback The callback to call when an ajax request is completed.
  47. * @param {string} url The url to match. If not provided, all ajax requests will be matched.
  48. * @param {boolean} skipSuccess Skip the success check.
  49. */
  50. const onAjaxRequest = (callback, url = null, skipSuccess = false) => {
  51. const req = XMLHttpRequest.prototype.open;
  52. XMLHttpRequest.prototype.open = function () {
  53. this.addEventListener('load', function () {
  54. if (this.responseText) {
  55. let response = {};
  56. try {
  57. response = JSON.parse(this.responseText);
  58. } catch (e) {
  59. return;
  60. }
  61.  
  62. if (response.success || skipSuccess) {
  63. if (! url) {
  64. callback(response);
  65. return;
  66. }
  67.  
  68. if (this.responseURL.indexOf(url) !== -1) {
  69. callback(response);
  70. }
  71. }
  72. }
  73. });
  74. req.apply(this, arguments);
  75. };
  76. };
  77.  
  78. /**
  79. * Run the callbacks depending on visibility.
  80. *
  81. * @param {Object} settings Settings object.
  82. * @param {Node} parentNode The parent node.
  83. * @param {Object} callbacks The callbacks to run.
  84. *
  85. * @return {Object} The settings.
  86. */
  87. const runCallbacks = (settings, parentNode, callbacks) => {
  88. console.log('runCallbacks', settings, parentNode, callbacks);
  89. // Loop through the keys on our settings object.
  90. Object.keys(settings).forEach((key) => {
  91. console.log(key);
  92. // If the parentNode that's passed in contains the selector for the key.
  93. if (parentNode.classList.contains(settings[ key ].selector)) {
  94. // Set as visible.
  95. settings[ key ].isVisible = true;
  96.  
  97. // If there is a show callback, run it.
  98. if (callbacks[ key ] && callbacks[ key ].show) {
  99. callbacks[ key ].show();
  100. }
  101. } else if (settings[ key ].isVisible) {
  102. // Mark as not visible.
  103. settings[ key ].isVisible = false;
  104.  
  105. // If there is a hide callback, run it.
  106. if (callbacks[ key ] && callbacks[ key ].hide) {
  107. callbacks[ key ].hide();
  108. }
  109. }
  110. });
  111.  
  112. return settings;
  113. };
  114.  
  115. /**
  116. * Do something when the overlay is shown or hidden.
  117. *
  118. * @param {Object} callbacks
  119. * @param {Function} callbacks.show The callback to call when the overlay is shown.
  120. * @param {Function} callbacks.hide The callback to call when the overlay is hidden.
  121. * @param {Function} callbacks.change The callback to call when the overlay is changed.
  122. */
  123. const onOverlayChange = (callbacks) => {
  124. // Track the different overlay states.
  125. let overlayData = {
  126. map: {
  127. isVisible: false,
  128. selector: 'treasureMapPopup'
  129. },
  130. item: {
  131. isVisible: false,
  132. selector: 'itemViewPopup'
  133. },
  134. mouse: {
  135. isVisible: false,
  136. selector: 'mouseViewPopup'
  137. },
  138. image: {
  139. isVisible: false,
  140. selector: 'largerImage'
  141. },
  142. convertible: {
  143. isVisible: false,
  144. selector: 'convertibleOpenViewPopup'
  145. },
  146. adventureBook: {
  147. isVisible: false,
  148. selector: 'adventureBookPopup'
  149. },
  150. marketplace: {
  151. isVisible: false,
  152. selector: 'marketplaceViewPopup'
  153. },
  154. gifts: {
  155. isVisible: false,
  156. selector: 'giftSelectorViewPopup'
  157. },
  158. support: {
  159. isVisible: false,
  160. selector: 'supportPageContactUsForm'
  161. },
  162. premiumShop: {
  163. isVisible: false,
  164. selector: 'MHCheckout'
  165. }
  166. };
  167.  
  168. // Observe the overlayPopup element for changes.
  169. const observer = new MutationObserver(() => {
  170. if (callbacks.change) {
  171. callbacks.change();
  172. }
  173.  
  174. // Grab the overlayPopup element and make sure it has classes on it.
  175. const overlayType = document.getElementById('overlayPopup');
  176. if (overlayType && overlayType.classList.length <= 0) {
  177. return;
  178. }
  179.  
  180. // Grab the overlayBg and check if it is visible or not.
  181. const overlayBg = document.getElementById('overlayBg');
  182. if (overlayBg && overlayBg.classList.length > 0) {
  183. // If there's a show callback, run it.
  184. if (callbacks.show) {
  185. callbacks.show();
  186. }
  187. } else if (callbacks.hide) {
  188. // If there's a hide callback, run it.
  189. callbacks.hide();
  190. }
  191.  
  192. // Run all the specific callbacks.
  193. overlayData = runCallbacks(overlayData, overlayType, callbacks);
  194. });
  195.  
  196. // Observe the overlayPopup element for changes.
  197. const observeTarget = document.getElementById('overlayPopup');
  198. if (observeTarget) {
  199. observer.observe(observeTarget, {
  200. attributes: true,
  201. attributeFilter: ['class']
  202. });
  203. }
  204. };
  205.  
  206. /**
  207. * Do something when the page or tab changes.
  208. *
  209. * @param {Object} callbacks
  210. * @param {Function} callbacks.show The callback to call when the page is navigated to.
  211. * @param {Function} callbacks.hide The callback to call when the page is navigated away from.
  212. * @param {Function} callbacks.change The callback to call when the page is changed.
  213. */
  214. const onPageChange = (callbacks) => {
  215. // Track our page tab states.
  216. let tabData = {
  217. blueprint: { isVisible: null, selector: 'showBlueprint' },
  218. tem: { isVisible: false, selector: 'showTrapEffectiveness' },
  219. trap: { isVisible: false, selector: 'editTrap' },
  220. camp: { isVisible: false, selector: 'PageCamp' },
  221. travel: { isVisible: false, selector: 'PageTravel' },
  222. inventory: { isVisible: false, selector: 'PageInventory' },
  223. shop: { isVisible: false, selector: 'PageShops' },
  224. mice: { isVisible: false, selector: 'PageAdversaries' },
  225. friends: { isVisible: false, selector: 'PageFriends' },
  226. sendSupplies: { isVisible: false, selector: 'PageSupplyTransfer' },
  227. team: { isVisible: false, selector: 'PageTeam' },
  228. tournament: { isVisible: false, selector: 'PageTournament' },
  229. news: { isVisible: false, selector: 'PageNews' },
  230. scoreboards: { isVisible: false, selector: 'PageScoreboards' },
  231. discord: { isVisible: false, selector: 'PageJoinDiscord' },
  232. preferences: { isVisible: false, selector: 'PagePreferences' },
  233. };
  234.  
  235. // Observe the mousehuntContainer element for changes.
  236. const observer = new MutationObserver(() => {
  237. // If there's a change callback, run it.
  238. if (callbacks.change) {
  239. callbacks.change();
  240. }
  241.  
  242. // Grab the container element and make sure it has classes on it.
  243. const mhContainer = document.getElementById('mousehuntContainer');
  244. if (mhContainer && mhContainer.classList.length > 0) {
  245. // Run the callbacks.
  246. tabData = runCallbacks(tabData, mhContainer, callbacks);
  247. }
  248. });
  249.  
  250. // Observe the mousehuntContainer element for changes.
  251. const observeTarget = document.getElementById('mousehuntContainer');
  252. if (observeTarget) {
  253. observer.observe(observeTarget, {
  254. attributes: true,
  255. attributeFilter: ['class']
  256. });
  257. }
  258. };
  259.  
  260. /**
  261. * Do something when the trap tab is changed.
  262. *
  263. * @param {Object} callbacks
  264. */
  265. const onTrapChange = (callbacks) => {
  266. // Track our trap states.
  267. let trapData = {
  268. bait: {
  269. isVisible: false,
  270. selector: 'bait'
  271. },
  272. base: {
  273. isVisible: false,
  274. selector: 'base'
  275. },
  276. weapon: {
  277. isVisible: false,
  278. selector: 'weapon'
  279. },
  280. charm: {
  281. isVisible: false,
  282. selector: 'trinket'
  283. },
  284. skin: {
  285. isVisible: false,
  286. selector: 'skin'
  287. }
  288. };
  289.  
  290. // Observe the trapTabContainer element for changes.
  291. const observer = new MutationObserver(() => {
  292. // Fire the change callback.
  293. if (callbacks.change) {
  294. callbacks.change();
  295. }
  296.  
  297. // If we're not viewing a blueprint tab, bail.
  298. const mhContainer = document.getElementById('mousehuntContainer');
  299. if (mhContainer.classList.length <= 0 || ! mhContainer.classList.contains('showBlueprint')) {
  300. return;
  301. }
  302.  
  303. // If we don't have the container, bail.
  304. const trapContainerParent = document.querySelector('.campPage-trap-blueprintContainer');
  305. if (! trapContainerParent || ! trapContainerParent.children || ! trapContainerParent.children.length > 0) {
  306. return;
  307. }
  308.  
  309. // If we're not in the itembrowser, bail.
  310. const trapContainer = trapContainerParent.children[ 0 ];
  311. if (! trapContainer || trapContainer.classList.length <= 0 || ! trapContainer.classList.contains('campPage-trap-itemBrowser')) {
  312. return;
  313. }
  314.  
  315. // Run the callbacks.
  316. trapData = runCallbacks(trapData, trapContainer, callbacks);
  317. });
  318.  
  319. // Grab the campPage-trap-blueprintContainer element and make sure it has children on it.
  320. const observeTargetParent = document.querySelector('.campPage-trap-blueprintContainer');
  321. if (! observeTargetParent || ! observeTargetParent.children || ! observeTargetParent.children.length > 0) {
  322. return;
  323. }
  324.  
  325. // Observe the first child of the campPage-trap-blueprintContainer element for changes.
  326. const observeTarget = observeTargetParent.children[ 0 ];
  327. if (observeTarget) {
  328. observer.observe(observeTarget, {
  329. attributes: true,
  330. attributeFilter: ['class']
  331. });
  332. }
  333. };
  334.  
  335. /**
  336. * Get the current page slug.
  337. *
  338. * @return {string} The page slug.
  339. */
  340. const getCurrentPage = () => {
  341. return hg.utils.PageUtil.getCurrentPage().toLowerCase(); // eslint-disable-line no-undef
  342. };
  343.  
  344. /**
  345. * Get the current page tab, defaulting to the current page if no tab is found.
  346. *
  347. * @return {string} The page tab.
  348. */
  349. const getCurrentTab = () => {
  350. const urlParams = new URLSearchParams(window.location.search);
  351. const tab = urlParams.get('tab');
  352. if (! tab) {
  353. return getCurrentPage();
  354. }
  355.  
  356. return tab.toLowerCase();
  357. };
  358.  
  359. /**
  360. * Get the current page sub tab, defaulting to the current tab if no sub tab is found.
  361. *
  362. * @return {string} The page tab.
  363. */
  364. const getCurrentSubTab = () => {
  365. const urlParams = new URLSearchParams(window.location.search);
  366. const tab = urlParams.get('sub_tab');
  367. if (! tab) {
  368. return getCurrentPage();
  369. }
  370.  
  371. return tab.toLowerCase();
  372. };
  373.  
  374. /**
  375. * Check if the overlay is visible.
  376. *
  377. * @return {boolean} True if the overlay is visible, false otherwise.
  378. */
  379. const isOverlayVisible = () => {
  380. // Check if the jsDialog function exists.
  381. if (jsDialog && typeof jsDialog === 'function' && jsDialog().isVisible && typeof jsDialog().isVisible === 'function') { // eslint-disable-line no-undef
  382. return jsDialog().isVisible(); // eslint-disable-line no-undef
  383. }
  384.  
  385. return false;
  386. };
  387.  
  388. /**
  389. * Get the current overlay.
  390. *
  391. * @return {string} The current overlay.
  392. */
  393. const getCurrentOverlay = () => {
  394. const overlay = document.getElementById('overlayPopup');
  395. if (overlay && overlay.classList.length <= 0) {
  396. return null;
  397. }
  398.  
  399. let overlayType = overlay.classList.value;
  400. overlayType = overlayType.replace('jsDialogFixed', '');
  401. overlayType = overlayType.replace('default', '');
  402. overlayType = overlayType.replace('wide', '');
  403. overlayType = overlayType.replace('ajax', '');
  404. overlayType = overlayType.replace('overlay', '');
  405.  
  406. // Replace some overlay types with more readable names.
  407. overlayType = overlayType.replace('treasureMapPopup', 'map');
  408. overlayType = overlayType.replace('itemViewPopup', 'item');
  409. overlayType = overlayType.replace('mouseViewPopup', 'mouse');
  410. overlayType = overlayType.replace('largerImage', 'image');
  411. overlayType = overlayType.replace('convertibleOpenViewPopup', 'convertible');
  412. overlayType = overlayType.replace('adventureBookPopup', 'adventureBook');
  413. overlayType = overlayType.replace('marketplaceViewPopup', 'marketplace');
  414. overlayType = overlayType.replace('giftSelectorViewPopup', 'gifts');
  415. overlayType = overlayType.replace('supportPageContactUsForm', 'support');
  416. overlayType = overlayType.replace('MHCheckout', 'premiumShop');
  417.  
  418. return overlayType.trim();
  419. };
  420.  
  421. /**
  422. * Get the current location.
  423. *
  424. * @return {string} The current location.
  425. */
  426. const getCurrentLocation = () => {
  427. return user.environment_type.toLowerCase();
  428. };
  429.  
  430. /**
  431. * Check if the user is logged in.
  432. *
  433. * @return {boolean} True if the user is logged in, false otherwise.
  434. */
  435. const isLoggedIn = () => {
  436. return user.length > 0 && 'login' !== getCurrentPage();
  437. };
  438.  
  439. /**
  440. * Get the saved settings.
  441. *
  442. * @param {string} key The key to get.
  443. * @param {boolean} defaultValue The default value.
  444. * @param {string} identifier The identifier for the settings.
  445. *
  446. * @return {Object} The saved settings.
  447. */
  448. const getSetting = (key = null, defaultValue = null, identifier = 'mh-utils-settings') => {
  449. // Grab the local storage data.
  450. const settings = JSON.parse(localStorage.getItem(identifier)) || {};
  451.  
  452. // If we didn't get a key passed in, we want all the settings.
  453. if (! key) {
  454. return settings;
  455. }
  456.  
  457. // If the setting doesn't exist, return the default value.
  458. if (Object.prototype.hasOwnProperty.call(settings, key)) {
  459. return settings[ key ];
  460. }
  461.  
  462. return defaultValue;
  463. };
  464.  
  465. /**
  466. * Save a setting.
  467. *
  468. * @param {string} key The setting key.
  469. * @param {boolean} value The setting value.
  470. * @param {string} identifier The identifier for the settings.
  471. */
  472. const saveSetting = (key, value, identifier = 'mh-utils-settings') => {
  473. // Grab all the settings, set the new one, and save them.
  474. const settings = getSetting(null, {}, identifier);
  475. settings[ key ] = value;
  476.  
  477. localStorage.setItem(identifier, JSON.stringify(settings));
  478. };
  479.  
  480. /**
  481. * Save a setting and toggle the class in the settings UI.
  482. *
  483. * @param {Node} node The setting node to animate.
  484. * @param {string} key The setting key.
  485. * @param {boolean} value The setting value.
  486. */
  487. const saveSettingAndToggleClass = (node, key, value) => {
  488. // Toggle the state of the checkbox.
  489. node.classList.toggle('active');
  490.  
  491. // Save the setting.
  492. saveSetting(key, value);
  493.  
  494. // Add the completed class & remove it in a second.
  495. node.parentNode.classList.add('completed');
  496. setTimeout(() => {
  497. node.parentNode.classList.remove('completed');
  498. }, 1000);
  499. };
  500.  
  501. /**
  502. * Make the settings tab.
  503. *
  504. * @param {string} identifier The identifier for the settings.
  505. * @param {string} name The name of the settings tab.
  506. */
  507. const addSettingsTab = (identifier = 'userscript-settings', name = 'Userscript Settings') => {
  508. addSettingsTabOnce(identifier, name);
  509. onPageChange({ preferences: { show: () => addSettingsTabOnce(identifier, name) }});
  510.  
  511. return identifier;
  512. };
  513.  
  514. /**
  515. * Make the settings tab once.
  516. *
  517. * @param {string} identifier The identifier for the settings.
  518. * @param {string} name The name of the settings tab.
  519. */
  520. const addSettingsTabOnce = (identifier = 'userscript-settings', name = 'Userscript Settings') => {
  521. if ('preferences' !== getCurrentPage()) {
  522. return;
  523. }
  524.  
  525. const existingSettings = document.querySelector(`#${identifier}`);
  526. if (existingSettings) {
  527. return;
  528. }
  529.  
  530. const tabsContainer = document.querySelector('.mousehuntHud-page-tabHeader-container');
  531. if (! tabsContainer) {
  532. return;
  533. }
  534.  
  535. const tabsContentContainer = document.querySelector('.mousehuntHud-page-tabContentContainer');
  536. if (! tabsContentContainer) {
  537. return;
  538. }
  539.  
  540. const settingsTab = document.createElement('a');
  541. settingsTab.id = identifier;
  542. settingsTab.href = '#';
  543. settingsTab.classList.add('mousehuntHud-page-tabHeader', identifier);
  544. settingsTab.setAttribute('data-tab', identifier);
  545. settingsTab.setAttribute('onclick', 'hg.utils.PageUtil.onclickPageTabHandler(this); return false;');
  546.  
  547. const settingsTabText = document.createElement('span');
  548. settingsTabText.innerText = name;
  549.  
  550. settingsTab.appendChild(settingsTabText);
  551. tabsContainer.appendChild(settingsTab);
  552.  
  553. const settingsTabContent = document.createElement('div');
  554. settingsTabContent.classList.add('mousehuntHud-page-tabContent', 'game_settings', identifier);
  555. settingsTabContent.setAttribute('data-tab', identifier);
  556.  
  557. tabsContentContainer.appendChild(settingsTabContent);
  558.  
  559. if (identifier === getCurrentTab()) {
  560. const tab = document.getElementById(identifier);
  561. if (tab) {
  562. tab.click();
  563. }
  564. }
  565. };
  566.  
  567. /**
  568. * Add a setting to the preferences page, both on page load and when the page changes.
  569. *
  570. * @param {string} name The setting name.
  571. * @param {string} key The setting key.
  572. * @param {boolean} defaultValue The default value.
  573. * @param {string} description The setting description.
  574. * @param {Object} section The section settings.
  575. * @param {string} tab The tab to add the settings to.
  576. */
  577. const addSetting = (name, key, defaultValue = true, description = '', section = {}, tab = 'userscript-settings') => {
  578. onPageChange({ preferences: { show: () => addSettingOnce(name, key, defaultValue, description, section, tab) } });
  579. addSettingOnce(name, key, defaultValue, description, section, tab);
  580. };
  581.  
  582. /**
  583. * Add a setting to the preferences page.
  584. *
  585. * @param {string} name The setting name.
  586. * @param {string} key The setting key.
  587. * @param {boolean} defaultValue The default value.
  588. * @param {string} description The setting description.
  589. * @param {Object} section The section settings.
  590. * @param {string} tab The tab to add the settings to.
  591. */
  592. const addSettingOnce = (name, key, defaultValue = true, description = '', section = {}, tab = 'userscript-settings') => {
  593. // Make sure we have the container for our settings.
  594. const container = document.querySelector(`.mousehuntHud-page-tabContent.${tab}`);
  595. if (! container) {
  596. return;
  597. }
  598.  
  599. section = {
  600. id: section.id || 'mh-utils-settings',
  601. name: section.name || 'Userscript Settings',
  602. description: section.description || '',
  603. };
  604.  
  605. // If we don't have our custom settings section, then create it.
  606. let sectionExists = document.querySelector(`#${section.id}`);
  607. if (! sectionExists) {
  608. // Make the element, add the ID and class.
  609. const title = document.createElement('div');
  610. title.id = section.id;
  611. title.classList.add('gameSettingTitle');
  612.  
  613. // Set the title of our section.
  614. title.textContent = section.name;
  615.  
  616. // Add a separator.
  617. const seperator = document.createElement('div');
  618. seperator.classList.add('separator');
  619.  
  620. // Append the separator.
  621. title.appendChild(seperator);
  622.  
  623. // Append it.
  624. container.appendChild(title);
  625.  
  626. sectionExists = document.querySelector(`#${section.id}`);
  627.  
  628. if (section.description) {
  629. const settingSubHeader = makeElement('h4', ['settings-subheader', 'mh-utils-settings-subheader'], section.description);
  630. sectionExists.insertBefore(settingSubHeader, seperator);
  631.  
  632. addStyles(`.mh-utils-settings-subheader {
  633. padding-top: 10px;
  634. padding-bottom: 10px;
  635. font-size: 10px;
  636. color: #848484;
  637. }`, 'mh-utils-settings-subheader', true);
  638. }
  639. }
  640.  
  641. // If we already have a setting visible for our key, bail.
  642. const settingExists = document.getElementById(`${section.id}-${key}`);
  643. if (!settingExists) {
  644. // Create the markup for the setting row.
  645. const settings = document.createElement('div');
  646. settings.classList.add('settingRowTable');
  647. settings.id = `${section.id}-${key}`;
  648.  
  649. const settingRow = document.createElement('div');
  650. settingRow.classList.add('settingRow');
  651.  
  652. const settingRowLabel = document.createElement('div');
  653. settingRowLabel.classList.add('settingRow-label');
  654.  
  655. const settingName = document.createElement('div');
  656. settingName.classList.add('name');
  657. settingName.innerHTML = name;
  658.  
  659. const defaultSettingText = document.createElement('div');
  660. defaultSettingText.classList.add('defaultSettingText');
  661. defaultSettingText.textContent = defaultValue ? 'Enabled' : 'Disabled';
  662.  
  663. const settingDescription = document.createElement('div');
  664. settingDescription.classList.add('description');
  665. settingDescription.innerHTML = description;
  666.  
  667. settingRowLabel.appendChild(settingName);
  668. settingRowLabel.appendChild(defaultSettingText);
  669. settingRowLabel.appendChild(settingDescription);
  670.  
  671. const settingRowAction = document.createElement('div');
  672. settingRowAction.classList.add('settingRow-action');
  673.  
  674. const settingRowInput = document.createElement('div');
  675. settingRowInput.classList.add('settingRow-action-inputContainer');
  676.  
  677. const settingRowInputCheckbox = document.createElement('div');
  678. settingRowInputCheckbox.classList.add('mousehuntSettingSlider');
  679.  
  680. // Depending on the current state of the setting, add the active class.
  681. const currentSetting = getSetting(key);
  682. let isActive = false;
  683. if (currentSetting) {
  684. settingRowInputCheckbox.classList.add('active');
  685. isActive = true;
  686. } else if (null === currentSetting && defaultValue) {
  687. settingRowInputCheckbox.classList.add('active');
  688. isActive = true;
  689. }
  690.  
  691. // Event listener for when the setting is clicked.
  692. settingRowInputCheckbox.onclick = (event) => {
  693. saveSettingAndToggleClass(event.target, key, ! isActive);
  694. };
  695.  
  696. // Add the input to the settings row.
  697. settingRowInput.appendChild(settingRowInputCheckbox);
  698. settingRowAction.appendChild(settingRowInput);
  699.  
  700. // Add the label and action to the settings row.
  701. settingRow.appendChild(settingRowLabel);
  702. settingRow.appendChild(settingRowAction);
  703.  
  704. // Add the settings row to the settings container.
  705. settings.appendChild(settingRow);
  706. sectionExists.appendChild(settings);
  707. }
  708.  
  709. addSettingRefreshReminder();
  710. onPageChange({ preferences: { show: addSettingRefreshReminder } });
  711. };
  712.  
  713. /**
  714. * Add a refresh reminder to the settings page.
  715. */
  716. const addSettingRefreshReminder = () => {
  717. addStyles(`.mh-utils-settings-refresh-message {
  718. position: fixed;
  719. bottom: 0;
  720. background-color: #d6f2d6;
  721. padding: 1em;
  722. left: 0;
  723. border-top: 1px solid black;
  724. right: 0;
  725. text-align: center;
  726. z-index: 5;
  727. font-size: 1.5em;
  728. }`, '#d6f2d6', true);
  729.  
  730. const settingsToggles = document.querySelectorAll('.mousehuntSettingSlider');
  731. if (! settingsToggles) {
  732. return;
  733. }
  734.  
  735. settingsToggles.forEach((toggle) => {
  736. if (toggle.getAttribute('data-has-refresh-reminder')) {
  737. return;
  738. }
  739.  
  740. toggle.setAttribute('data-has-refresh-reminder', true);
  741.  
  742. toggle.addEventListener('click', () => {
  743. const refreshMessage = document.querySelector('.mh-utils-settings-refresh-message');
  744. if (refreshMessage) {
  745. refreshMessage.classList.remove('hidden');
  746. }
  747. });
  748. });
  749.  
  750. const existingRefreshMessage = document.querySelector('.mh-utils-settings-refresh-message');
  751. if (existingRefreshMessage) {
  752. return;
  753. }
  754.  
  755. const tab = document.querySelector('.mousehuntHud-page-tabContent.game_settings.mh-ui-settings.active');
  756. if (tab) {
  757. makeElement('div', ['mh-utils-settings-refresh-message', 'hidden'], 'Refresh the page to apply the changes.', tab);
  758. }
  759. };
  760.  
  761. /**
  762. * POST a request to the server and return the response.
  763. *
  764. * @param {string} url The url to post to, not including the base url.
  765. * @param {Object} formData The form data to post.
  766. *
  767. * @return {Promise} The response.
  768. */
  769. const doRequest = async (url, formData = {}) => {
  770. // If we don't have the needed params, bail.
  771. if ('undefined' === typeof lastReadJournalEntryId || 'undefined' === typeof user) {
  772. return;
  773. }
  774.  
  775. // If our needed params are empty, bail.
  776. if (! lastReadJournalEntryId || ! user || ! user.unique_hash) { // eslint-disable-line no-undef
  777. return;
  778. }
  779.  
  780. // Build the form for the request.
  781. const form = new FormData();
  782. form.append('sn', 'Hitgrab');
  783. form.append('hg_is_ajax', 1);
  784. form.append('last_read_journal_entry_id', lastReadJournalEntryId ? lastReadJournalEntryId : 0); // eslint-disable-line no-undef
  785. form.append('uh', user.unique_hash ? user.unique_hash : ''); // eslint-disable-line no-undef
  786.  
  787. // Add in the passed in form data.
  788. for (const key in formData) {
  789. form.append(key, formData[ key ]);
  790. }
  791.  
  792. // Convert the form to a URL encoded string for the body.
  793. const requestBody = new URLSearchParams(form).toString();
  794.  
  795. // Send the request.
  796. const response = await fetch(
  797. callbackurl ? callbackurl + url : 'https://www.mousehuntgame.com/' + url, // eslint-disable-line no-undef
  798. {
  799. method: 'POST',
  800. body: requestBody,
  801. headers: {
  802. 'Content-Type': 'application/x-www-form-urlencoded',
  803. },
  804. }
  805. );
  806.  
  807. // Wait for the response and return it.
  808. const data = await response.json();
  809. return data;
  810. };
  811.  
  812. /**
  813. * Check if the legacy HUD is enabled.
  814. *
  815. * @return {boolean} Whether the legacy HUD is enabled.
  816. */
  817. const isLegacyHUD = () => {
  818. const hud = document.querySelector('.mousehuntHud-menu');
  819. return hud && hud.classList.contains('legacy');
  820. };
  821.  
  822. /**
  823. * Check if an item is in the inventory.
  824. *
  825. * @param {string} item The item to check for.
  826. *
  827. * @return {boolean} Whether the item is in the inventory.
  828. */
  829. const userHasItem = async (item) => {
  830. const hasItem = await getUserItems([item]);
  831. return hasItem.length > 0;
  832. };
  833.  
  834. /**
  835. * Check if an item is in the inventory.
  836. *
  837. * @param {Array} items The item to check for.
  838. *
  839. * @return {Array} The item data.
  840. */
  841. const getUserItems = async (items) => {
  842. return new Promise((resolve) => {
  843. hg.utils.UserInventory.getItems(items, (resp) => {
  844. resolve(resp);
  845. });
  846. });
  847. };
  848.  
  849. /**
  850. * Get the user's setup details.
  851. *
  852. * @return {Object} The user's setup details.
  853. */
  854. const getUserSetupDetails = () => {
  855. const userObj = user; // eslint-disable-line no-undef
  856. const setup = {
  857. type: userObj.trap_power_type_name,
  858. stats: {
  859. power: userObj.trap_power,
  860. powerBonus: userObj.trap_power_bonus,
  861. luck: userObj.trap_luck,
  862. attractionBonus: userObj.trap_attraction_bonus,
  863. cheeseEfect: userObj.trap_cheese_effect,
  864. },
  865. bait: {
  866. id: parseInt(userObj.bait_item_id),
  867. name: userObj.bait_name,
  868. quantity: parseInt(userObj.bait_quantity),
  869. power: 0,
  870. powerBonus: 0,
  871. luck: 0,
  872. attractionBonus: 0,
  873. },
  874. base: {
  875. id: parseInt(userObj.base_item_id),
  876. name: userObj.base_name,
  877. power: 0,
  878. powerBonus: 0,
  879. luck: 0,
  880. attractionBonus: 0,
  881. },
  882. charm: {
  883. id: parseInt(userObj.trinket_item_id),
  884. name: userObj.trinket_name,
  885. quantity: parseInt(userObj.trinket_quantity),
  886. power: 0,
  887. powerBonus: 0,
  888. luck: 0,
  889. attractionBonus: 0,
  890. },
  891. weapon: {
  892. id: parseInt(userObj.weapon_item_id),
  893. name: userObj.weapon_name,
  894. power: 0,
  895. powerBonus: 0,
  896. luck: 0,
  897. attractionBonus: 0,
  898. },
  899. aura: {
  900. lgs: {
  901. active: false,
  902. power: 0,
  903. powerBonus: 0,
  904. luck: 0,
  905. },
  906. lightning: {
  907. active: false,
  908. power: 0,
  909. powerBonus: 0,
  910. luck: 0,
  911. },
  912. chrome: {
  913. active: false,
  914. power: 0,
  915. powerBonus: 0,
  916. luck: 0,
  917. },
  918. slayer: {
  919. active: false,
  920. power: 0,
  921. powerBonus: 0,
  922. luck: 0,
  923. },
  924. festive: {
  925. active: false,
  926. power: 0,
  927. powerBonus: 0,
  928. luck: 0,
  929. },
  930. luckycodex: {
  931. active: false,
  932. power: 0,
  933. powerBonus: 0,
  934. luck: 0,
  935. },
  936. riftstalker: {
  937. active: false,
  938. power: 0,
  939. powerBonus: 0,
  940. luck: 0,
  941. },
  942. },
  943. location: {
  944. name: userObj.environment_name,
  945. id: userObj.environment_id,
  946. slug: userObj.environment_type,
  947. },
  948. };
  949.  
  950. if ('camp' !== getCurrentPage()) {
  951. return setup;
  952. }
  953.  
  954. const calculations = document.querySelectorAll('.campPage-trap-trapStat');
  955. if (! calculations) {
  956. return setup;
  957. }
  958.  
  959. calculations.forEach((calculation) => {
  960. if (calculation.classList.length <= 1) {
  961. return;
  962. }
  963.  
  964. const type = calculation.classList[ 1 ];
  965. const math = calculation.querySelectorAll('.math .campPage-trap-trapStat-mathRow');
  966. if (! math) {
  967. return;
  968. }
  969.  
  970. math.forEach((row) => {
  971. if (row.classList.contains('label')) {
  972. return;
  973. }
  974.  
  975. let value = row.querySelector('.campPage-trap-trapStat-mathRow-value');
  976. let name = row.querySelector('.campPage-trap-trapStat-mathRow-name');
  977.  
  978. if (! value || ! name || ! name.innerText) {
  979. return;
  980. }
  981.  
  982. name = name.innerText;
  983. value = value.innerText || '0';
  984.  
  985. let tempType = type;
  986. let isBonus = false;
  987. if (value.includes('%')) {
  988. tempType = type + 'Bonus';
  989. isBonus = true;
  990. }
  991.  
  992. // Because attraction_bonus is silly.
  993. tempType = tempType.replace('_bonusBonus', 'Bonus');
  994.  
  995. value = value.replace('%', '');
  996. value = value.replace(',', '');
  997. value = parseInt(value * 100) / 100;
  998.  
  999. if (tempType === 'attractionBonus') {
  1000. value = value / 100;
  1001. }
  1002.  
  1003. // 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
  1004. if (setup.weapon.name === name) {
  1005. setup.weapon[ tempType ] = value;
  1006. } else if (setup.base.name === name) {
  1007. setup.base[ tempType ] = value;
  1008. } else if (setup.charm.name === name) {
  1009. setup.charm[ tempType ] = value;
  1010. } else if (setup.bait.name === name) {
  1011. setup.bait[ tempType ] = value;
  1012. } else if ('Your trap has no cheese effect bonus.' === name) {
  1013. setup.cheeseEffect = 'No Effect';
  1014. } else {
  1015. let auraType = name.replace(' Aura', '');
  1016. if (! auraType) {
  1017. return;
  1018. }
  1019.  
  1020. auraType = auraType.toLowerCase();
  1021. auraType = auraType.replaceAll(' ', '_');
  1022. // remove any non alphanumeric characters
  1023. auraType = auraType.replace(/[^a-z0-9_]/gi, '');
  1024. auraType = auraType.replace('golden_luck_boost', 'lgs');
  1025. auraType = auraType.replace('2023_lucky_codex', 'luckycodex');
  1026. auraType = auraType.replace('_set_bonus_2_pieces', '');
  1027. auraType = auraType.replace('_set_bonus_3_pieces', '');
  1028.  
  1029. if (! setup.aura[ auraType ]) {
  1030. setup.aura[ auraType ] = {
  1031. active: true,
  1032. type: auraType,
  1033. power: 0,
  1034. powerBonus: 0,
  1035. luck: 0,
  1036. };
  1037. } else {
  1038. setup.aura[ auraType ].active = true;
  1039. setup.aura[ auraType ].type = auraType;
  1040. }
  1041.  
  1042. value = parseInt(value);
  1043.  
  1044. if (isBonus) {
  1045. value = value / 100;
  1046. }
  1047.  
  1048. setup.aura[ auraType ][ tempType ] = value;
  1049. }
  1050. });
  1051. });
  1052.  
  1053. return setup;
  1054. };
  1055.  
  1056. /**
  1057. * Add a submenu item to a menu.
  1058. *
  1059. * @param {Object} options The options for the submenu item.
  1060. * @param {string} options.menu The menu to add the submenu item to.
  1061. * @param {string} options.label The label for the submenu item.
  1062. * @param {string} options.icon The icon for the submenu item.
  1063. * @param {string} options.href The href for the submenu item.
  1064. * @param {string} options.class The class for the submenu item.
  1065. * @param {Function} options.callback The callback for the submenu item.
  1066. * @param {boolean} options.external Whether the submenu item is external or not.
  1067. */
  1068. const addSubmenuItem = (options) => {
  1069. // Default to sensible values.
  1070. const settings = Object.assign({}, {
  1071. menu: 'kingdom',
  1072. label: '',
  1073. icon: 'https://www.mousehuntgame.com/images/ui/hud/menu/special.png',
  1074. href: '',
  1075. class: '',
  1076. callback: null,
  1077. external: false,
  1078. }, options);
  1079.  
  1080. // Grab the menu item we want to add the submenu to.
  1081. const menuTarget = document.querySelector(`.mousehuntHud-menu .${settings.menu}`);
  1082. if (! menuTarget) {
  1083. return;
  1084. }
  1085.  
  1086. // If the menu already has a submenu, just add the item to it.
  1087. if (! menuTarget.classList.contains('hasChildren')) {
  1088. menuTarget.classList.add('hasChildren');
  1089. }
  1090.  
  1091. let submenu = menuTarget.querySelector('ul');
  1092. if (! submenu) {
  1093. submenu = document.createElement('ul');
  1094. menuTarget.appendChild(submenu);
  1095. }
  1096.  
  1097. // Create the item.
  1098. const item = document.createElement('li');
  1099. item.classList.add('custom-submenu-item');
  1100. const cleanLabel = settings.label.toLowerCase().replace(/[^a-z0-9]/g, '-');
  1101.  
  1102. const exists = document.querySelector(`#custom-submenu-item-${cleanLabel}`);
  1103. if (exists) {
  1104. return;
  1105. }
  1106.  
  1107. item.id = `custom-submenu-item-${cleanLabel}`;
  1108. item.classList.add(settings.class);
  1109.  
  1110. // Create the link.
  1111. const link = document.createElement('a');
  1112. link.href = settings.href || '#';
  1113.  
  1114. if (settings.callback) {
  1115. link.addEventListener('click', (e) => {
  1116. e.preventDefault();
  1117. settings.callback();
  1118. });
  1119. }
  1120.  
  1121. // Create the icon.
  1122. const icon = document.createElement('div');
  1123. icon.classList.add('icon');
  1124. icon.style = `background-image: url(${settings.icon});`;
  1125.  
  1126. // Create the label.
  1127. const name = document.createElement('div');
  1128. name.classList.add('name');
  1129. name.innerText = settings.label;
  1130.  
  1131. // Add the icon and label to the link.
  1132. link.appendChild(icon);
  1133. link.appendChild(name);
  1134.  
  1135. // If it's an external link, also add the icon for it.
  1136. if (settings.external) {
  1137. const externalLinkIcon = document.createElement('div');
  1138. externalLinkIcon.classList.add('external_icon');
  1139. link.appendChild(externalLinkIcon);
  1140.  
  1141. // Set the target to _blank so it opens in a new tab.
  1142. link.target = '_blank';
  1143. link.rel = 'noopener noreferrer';
  1144. }
  1145.  
  1146. // Add the link to the item.
  1147. item.appendChild(link);
  1148.  
  1149. // Add the item to the submenu.
  1150. submenu.appendChild(item);
  1151. };
  1152.  
  1153. /**
  1154. * Add the mouse.rip link to the kingdom menu.
  1155. */
  1156. const addMouseripLink = () => {
  1157. addSubmenuItem({
  1158. menu: 'kingdom',
  1159. label: 'mouse.rip',
  1160. icon: 'https://www.mousehuntgame.com/images/ui/hud/menu/prize_shoppe.png',
  1161. href: 'https://mouse.rip',
  1162. external: true,
  1163. });
  1164. };
  1165.  
  1166. /**
  1167. * Add an item to the top 'Hunters Online' menu.
  1168. *
  1169. * @param {Object} options The options for the menu item.
  1170. * @param {string} options.label The label for the menu item.
  1171. * @param {string} options.href The href for the menu item.
  1172. * @param {string} options.class The class for the menu item.
  1173. * @param {Function} options.callback The callback for the menu item.
  1174. * @param {boolean} options.external Whether the link is external or not.
  1175. */
  1176. const addItemToGameInfoBar = (options) => {
  1177. const settings = Object.assign({}, {
  1178. label: '',
  1179. href: '',
  1180. class: '',
  1181. callback: null,
  1182. external: false,
  1183. }, options);
  1184.  
  1185. const safeLabel = settings.label.replace(/[^a-z0-9]/gi, '_').toLowerCase();
  1186. const exists = document.querySelector(`#mh-custom-topmenu-${safeLabel}`);
  1187. if (exists) {
  1188. return;
  1189. }
  1190.  
  1191. addStyles(`.mousehuntHud-gameInfo .mousehuntHud-menu {
  1192. position: relative;
  1193. top: unset;
  1194. left: unset;
  1195. display: inline;
  1196. width: unset;
  1197. height: unset;
  1198. padding-top: unset;
  1199. padding-left: unset;
  1200. background: unset;
  1201. }
  1202. `, 'mh-custom-topmenu', true);
  1203.  
  1204. const menu = document.querySelector('.mousehuntHud-gameInfo');
  1205. if (! menu) {
  1206. return;
  1207. }
  1208.  
  1209. const item = document.createElement('a');
  1210. item.id = `mh-custom-topmenu-${safeLabel}`;
  1211. item.classList.add('mousehuntHud-gameInfo-item');
  1212. item.classList.add('mousehuntHud-custom-menu-item');
  1213.  
  1214. item.href = settings.href || '#';
  1215.  
  1216. const name = document.createElement('div');
  1217. name.classList.add('name');
  1218.  
  1219. if (settings.label) {
  1220. name.innerText = settings.label;
  1221. }
  1222.  
  1223. item.appendChild(name);
  1224.  
  1225. if (settings.class) {
  1226. item.classList.add(settings.class);
  1227. }
  1228.  
  1229. if (settings.href) {
  1230. item.href = settings.href;
  1231. }
  1232.  
  1233. if (settings.callback) {
  1234. item.addEventListener('click', settings.callback);
  1235. }
  1236.  
  1237. if (settings.external) {
  1238. const externalLinkIconWrapper = document.createElement('div');
  1239. externalLinkIconWrapper.classList.add('mousehuntHud-menu');
  1240.  
  1241. const externalLinkIcon = document.createElement('div');
  1242. externalLinkIcon.classList.add('external_icon');
  1243.  
  1244. externalLinkIconWrapper.appendChild(externalLinkIcon);
  1245. item.appendChild(externalLinkIconWrapper);
  1246. }
  1247.  
  1248. menu.insertBefore(item, menu.firstChild);
  1249. };
  1250.  
  1251. /**
  1252. * Build a popup.
  1253. *
  1254. * Templates:
  1255. * ajax: no close button in lower right, 'prefix' instead of title. 'suffix' for close button area.
  1256. * default: {*title*} {*content*}
  1257. * error: in red, with error icon{*title*} {*content*}
  1258. * largerImage: full width image {*title*} {*image*}
  1259. * largerImageWithClass: smaller than larger image, with caption {*title*} {*image*} {*imageCaption*} {*imageClass*} (goes on the img tag)
  1260. * loading: Just says loading
  1261. * multipleItems: {*title*} {*content*} {*items*}
  1262. * singleItemLeft: {*title*} {*content*} {*items*}
  1263. * singleItemRight: {*title*} {*content*} {*items*}
  1264. *
  1265. * @param {Object} options The popup options.
  1266. * @param {string} options.title The title of the popup.
  1267. * @param {string} options.content The content of the popup.
  1268. * @param {boolean} options.hasCloseButton Whether or not the popup has a close button.
  1269. * @param {string} options.template The template to use for the popup.
  1270. * @param {boolean} options.show Whether or not to show the popup.
  1271. * @param {string} options.className The class name to add to the popup.
  1272. */
  1273. const createPopup = (options) => {
  1274. // If we don't have jsDialog, bail.
  1275. if ('undefined' === typeof jsDialog || ! jsDialog) { // eslint-disable-line no-undef
  1276. return;
  1277. }
  1278.  
  1279. // Default to sensible values.
  1280. const settings = Object.assign({}, {
  1281. title: '',
  1282. content: '',
  1283. hasCloseButton: true,
  1284. template: 'default',
  1285. show: true,
  1286. className: '',
  1287. }, options);
  1288.  
  1289. // Initiate the popup.
  1290. const popup = new jsDialog(); // eslint-disable-line no-undef
  1291. popup.setIsModal(! settings.hasCloseButton);
  1292.  
  1293. // Set the template & add in the content.
  1294. popup.setTemplate(settings.template);
  1295. popup.addToken('{*title*}', settings.title);
  1296. popup.addToken('{*content*}', settings.content);
  1297.  
  1298. popup.setAttributes({
  1299. className: settings.className,
  1300. });
  1301.  
  1302. // If we want to show the popup, show it.
  1303. if (settings.show) {
  1304. popup.show();
  1305. }
  1306.  
  1307. return popup;
  1308. };
  1309.  
  1310. /**
  1311. * Create a popup with an image.
  1312. *
  1313. * @param {Object} options Popup options.
  1314. * @param {string} options.title The title of the popup.
  1315. * @param {string} options.image The image to show in the popup.
  1316. * @param {boolean} options.show Whether or not to show the popup.
  1317. */
  1318. const createImagePopup = (options) => {
  1319. // Default to sensible values.
  1320. const settings = Object.assign({}, {
  1321. title: '',
  1322. image: '',
  1323. show: true,
  1324. }, options);
  1325.  
  1326. // Create the popup.
  1327. const popup = createPopup({
  1328. title: settings.title,
  1329. template: 'largerImage',
  1330. show: false,
  1331. });
  1332.  
  1333. // Add the image to the popup.
  1334. popup.addToken('{*image*}', settings.image);
  1335.  
  1336. // If we want to show the popup, show it.
  1337. if (settings.show) {
  1338. popup.show();
  1339. }
  1340.  
  1341. return popup;
  1342. };
  1343.  
  1344. /**
  1345. * Show a map-popup.
  1346. *
  1347. * @param {Object} options The popup options.
  1348. * @param {string} options.title The title of the popup.
  1349. * @param {string} options.content The content of the popup.
  1350. * @param {string} options.closeClass The class to add to the close button.
  1351. * @param {string} options.closeText The text to add to the close button.
  1352. * @param {boolean} options.show Whether or not to show the popup.
  1353. */
  1354. const createMapPopup = (options) => {
  1355. // Check to make sure we can call the hg views.
  1356. if (! (hg && hg.views && hg.views.TreasureMapDialogView)) { // eslint-disable-line no-undef
  1357. return;
  1358. }
  1359.  
  1360. // Default to sensible values.
  1361. const settings = Object.assign({}, {
  1362. title: '',
  1363. content: '',
  1364. closeClass: 'acknowledge',
  1365. closeText: 'ok',
  1366. show: true,
  1367. }, options);
  1368.  
  1369. // Initiate the popup.
  1370. const dialog = new hg.views.TreasureMapDialogView(); // eslint-disable-line no-undef
  1371.  
  1372. // Set all the content and options.
  1373. dialog.setTitle(options.title);
  1374. dialog.setContent(options.content);
  1375. dialog.setCssClass(options.closeClass);
  1376. dialog.setContinueAction(options.closeText);
  1377.  
  1378. // If we want to show & we can show, show it.
  1379. if (settings.show && hg.controllers && hg.controllers.TreasureMapDialogController) { // eslint-disable-line no-undef
  1380. hg.controllers.TreasureMapController.show(); // eslint-disable-line no-undef
  1381. hg.controllers.TreasureMapController.showDialog(dialog); // eslint-disable-line no-undef
  1382. }
  1383.  
  1384. return dialog;
  1385. };
  1386.  
  1387. /**
  1388. * Create a welcome popup.
  1389. *
  1390. * @param {Object} options The popup options.
  1391. * @param {string} options.id The ID of the popup.
  1392. * @param {string} options.title The title of the popup.
  1393. * @param {string} options.content The content of the popup.
  1394. * @param {Array} options.columns The columns of the popup.
  1395. * @param {string} options.columns.title The title of the column.
  1396. * @param {string} options.columns.content The content of the column.
  1397. */
  1398. const createWelcomePopup = (options = {}) => {
  1399. if (! (options && options.id && options.title && options.content)) {
  1400. return;
  1401. }
  1402.  
  1403. if (! isLoggedIn()) {
  1404. return;
  1405. }
  1406.  
  1407. const hasSeenWelcome = getSetting('has-seen-welcome', false, options.id);
  1408. if (hasSeenWelcome) {
  1409. return;
  1410. }
  1411.  
  1412. addStyles(`#overlayPopup.mh-welcome .jsDialog.top,
  1413. #overlayPopup.mh-welcome .jsDialog.bottom,
  1414. #overlayPopup.mh-welcome .jsDialog.background {
  1415. padding: 0;
  1416. margin: 0;
  1417. background: none;
  1418. }
  1419.  
  1420. #overlayPopup.mh-welcome .jsDialogContainer .prefix,
  1421. #overlayPopup.mh-welcome .jsDialogContainer .content {
  1422. padding: 0;
  1423. }
  1424.  
  1425. #overlayPopup.mh-welcome #jsDialogClose,
  1426. #overlayPopup.mh-welcome .jsDialogContainer .suffix {
  1427. display: none;
  1428. }
  1429.  
  1430. #overlayPopup.mh-welcome .jsDialogContainer {
  1431. padding: 0 20px;
  1432. background-image: url(https://www.mousehuntgame.com/images/ui/newsposts/np_border.png);
  1433. background-repeat: repeat-y;
  1434. background-size: 100%;
  1435. }
  1436.  
  1437. #overlayPopup.mh-welcome .jsDialogContainer::before {
  1438. position: absolute;
  1439. top: -80px;
  1440. right: 0;
  1441. left: 0;
  1442. height: 100px;
  1443. content: '';
  1444. background-image: url(https://www.mousehuntgame.com/images/ui/newsposts/np_header.png);
  1445. background-repeat: no-repeat;
  1446. background-size: 100%;
  1447. }
  1448.  
  1449. #overlayPopup.mh-welcome .jsDialogContainer::after {
  1450. position: absolute;
  1451. top: 100%;
  1452. right: 0;
  1453. left: 0;
  1454. height: 126px;
  1455. content: '';
  1456. background-image: url(https://www.mousehuntgame.com/images/ui/newsposts/np_footer.png);
  1457. background-repeat: no-repeat;
  1458. background-size: 100%;
  1459. }
  1460.  
  1461. .mh-welcome .mh-title {
  1462. position: relative;
  1463. top: -90px;
  1464. display: flex;
  1465. align-items: center;
  1466. justify-content: center;
  1467. width: 412px;
  1468. height: 90px;
  1469. margin: 20px auto 0;
  1470. font-family: Georgia, serif;
  1471. font-size: 26px;
  1472. font-weight: 700;
  1473. color: #7d3b0a;
  1474. text-align: center;
  1475. text-shadow: 1px 1px 1px #e9d5a2;
  1476. background: url(https://www.mousehuntgame.com/images/ui/larry_gifts/ribbon.png?asset_cache_version=2) no-repeat;
  1477. }
  1478.  
  1479. .mh-welcome .mh-inner-wrapper {
  1480. display: flex;
  1481. padding: 5px 10px 25px;
  1482. margin-top: -90px;
  1483. }
  1484.  
  1485. .mh-welcome .text {
  1486. margin-left: 30px;
  1487. line-height: 18px;
  1488. text-align: left;
  1489. }
  1490.  
  1491. .mh-welcome .text p {
  1492. font-size: 13px;
  1493. line-height: 19px;
  1494. }
  1495.  
  1496. .mh-welcome .mh-inner-title {
  1497. padding: 10px 0;
  1498. font-size: 1.5em;
  1499. font-weight: 700;
  1500. }
  1501.  
  1502. .mh-welcome .mh-button-wrapper {
  1503. display: flex;
  1504. align-items: center;
  1505. justify-content: center;
  1506. }
  1507.  
  1508. .mh-welcome .mh-button {
  1509. padding: 10px 50px;
  1510. font-size: 1.5em;
  1511. color: #000;
  1512. background: linear-gradient(to bottom, #fff600, #f4e830);
  1513. border: 1px solid #000;
  1514. border-radius: 5px;
  1515. box-shadow: 0 0 10px 1px #d6d13b inset;
  1516. }
  1517.  
  1518. .mh-welcome .mh-intro-text {
  1519. margin: 2em 1em;
  1520. font-size: 15px;
  1521. line-height: 25px;
  1522. }
  1523.  
  1524. .mh-welcome-columns {
  1525. display: grid;
  1526. grid-template-columns: 1fr 1fr;
  1527. gap: 2em;
  1528. margin: 1em;
  1529. -ms-grid-columns: 1fr 2em 1fr;
  1530. }
  1531.  
  1532. .mh-welcome-column h2 {
  1533. margin-bottom: 1em;
  1534. font-size: 16px;
  1535. color: #7d3b0a;
  1536. border-bottom: 1px solid #cba36d;
  1537. }
  1538.  
  1539. .mh-welcome-column ul {
  1540. margin-left: 3em;
  1541. list-style: disc;
  1542. }
  1543. `, 'mh-welcome', true);
  1544.  
  1545. const markup = `<div class="mh-welcome">
  1546. <h1 class="mh-title">${options.title}</h1>
  1547. <div class="mh-inner-wrapper">
  1548. <div class="text">
  1549. <div class="mh-intro-text">
  1550. ${options.content}
  1551. </div>
  1552. <div class="mh-welcome-columns">
  1553. ${options.columns.map((column) => `<div class="mh-welcome-column">
  1554. <h2>${column.title}</h2>
  1555. ${column.content}
  1556. </div>`).join('')}
  1557. </div>
  1558. </div>
  1559. </div>
  1560. <div class="mh-button-wrapper">
  1561. <a href="#" id="mh-welcome-${options.id}-continue" class="mh-button">Continue</a>
  1562. </div>
  1563. </div>`;
  1564.  
  1565. // Initiate the popup.
  1566. const welcomePopup = createPopup({
  1567. hasCloseButton: false,
  1568. template: 'ajax',
  1569. content: markup,
  1570. show: false,
  1571. });
  1572.  
  1573. // Set more of our tokens.
  1574. welcomePopup.addToken('{*prefix*}', '');
  1575. welcomePopup.addToken('{*suffix*}', '');
  1576.  
  1577. // Set the attribute and show the popup.
  1578. welcomePopup.setAttributes({ className: `mh-welcome mh-welcome-popup-${options.id}` });
  1579.  
  1580. // If we want to show the popup, show it.
  1581. welcomePopup.show();
  1582.  
  1583. // Add the event listener to the continue button.
  1584. const continueButton = document.getElementById(`mh-welcome-${options.id}-continue`);
  1585. continueButton.addEventListener('click', () => {
  1586. saveSetting('has-seen-welcome', true, options.id);
  1587. welcomePopup.hide();
  1588. });
  1589. };
  1590.  
  1591. /**
  1592. * Create a popup with the larry's office style.
  1593. *
  1594. * @param {string} content Content to display in the popup.
  1595. */
  1596. const createLarryPopup = (content) => {
  1597. const message = {
  1598. content: { body: content },
  1599. css_class: 'larryOffice',
  1600. show_overlay: true,
  1601. is_modal: true
  1602. };
  1603.  
  1604. hg.views.MessengerView.addMessage(message);
  1605. hg.views.MessengerView.go();
  1606. };
  1607.  
  1608. /**
  1609. * Add a popup similar to the larry's gift popup.
  1610. *
  1611. * addPaperPopup({
  1612. * title: 'Whoa! A popup!',
  1613. * content: {
  1614. * title: 'This is the title of the content',
  1615. * 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;',
  1616. * image: 'https://api.mouse.rip/hunter/trap/8209591.png',
  1617. * },
  1618. * button: {
  1619. * text: 'A button',
  1620. * href: '#',
  1621. * },
  1622. * show: true,
  1623. * });
  1624. *
  1625. * @param {Object} options The popup options.
  1626. * @param {string} options.title The title of the popup.
  1627. * @param {Object} options.content The content of the popup.
  1628. * @param {string} options.content.title The title of the popup.
  1629. * @param {string} options.content.text The text of the popup.
  1630. * @param {string} options.content.image The image of the popup.
  1631. * @param {Array} options.button The button of the popup.
  1632. * @param {string} options.button.text The text of the button.
  1633. * @param {string} options.button.href The url of the button.
  1634. * @param {boolean} options.show Whether to show the popup or not.
  1635. */
  1636. const createPaperPopup = (options) => {
  1637. // If we don't have jsDialog, bail.
  1638. if ('undefined' === typeof jsDialog || ! jsDialog) { // eslint-disable-line no-undef
  1639. return;
  1640. }
  1641.  
  1642. // Add the styles for our popup.
  1643. addStyles(`#overlayPopup.mh-paper-popup-dialog-wrapper .jsDialog.top,
  1644. #overlayPopup.mh-paper-popup-dialog-wrapper .jsDialog.bottom,
  1645. #overlayPopup.mh-paper-popup-dialog-wrapper .jsDialog.background {
  1646. padding: 0;
  1647. margin: 0;
  1648. background: none;
  1649. }
  1650.  
  1651. #overlayPopup.mh-paper-popup-dialog-wrapper .jsDialogContainer .prefix,
  1652. #overlayPopup.mh-paper-popup-dialog-wrapper .jsDialogContainer .content {
  1653. padding: 0;
  1654. }
  1655.  
  1656. #overlayPopup.mh-paper-popup-dialog-wrapper #jsDialogClose,
  1657. #overlayPopup.mh-paper-popup-dialog-wrapper .jsDialogContainer .suffix {
  1658. display: none;
  1659. }
  1660.  
  1661. #overlayPopup.mh-paper-popup-dialog-wrapper .jsDialogContainer {
  1662. padding: 0 20px;
  1663. background-image: url(https://www.mousehuntgame.com/images/ui/newsposts/np_border.png);
  1664. background-repeat: repeat-y;
  1665. background-size: 100%;
  1666. }
  1667.  
  1668. #overlayPopup.mh-paper-popup-dialog-wrapper .jsDialogContainer::before {
  1669. position: absolute;
  1670. top: -80px;
  1671. right: 0;
  1672. left: 0;
  1673. height: 100px;
  1674. content: '';
  1675. background-image: url(https://www.mousehuntgame.com/images/ui/newsposts/np_header.png);
  1676. background-repeat: no-repeat;
  1677. background-size: 100%;
  1678. }
  1679.  
  1680. #overlayPopup.mh-paper-popup-dialog-wrapper .jsDialogContainer::after {
  1681. position: absolute;
  1682. top: 100%;
  1683. right: 0;
  1684. left: 0;
  1685. height: 126px;
  1686. content: '';
  1687. background-image: url(https://www.mousehuntgame.com/images/ui/newsposts/np_footer.png);
  1688. background-repeat: no-repeat;
  1689. background-size: 100%;
  1690. }
  1691.  
  1692. .mh-paper-popup-dialog-wrapper .mh-title {
  1693. position: relative;
  1694. top: -40px;
  1695. display: flex;
  1696. align-items: center;
  1697. justify-content: center;
  1698. width: 412px;
  1699. height: 99px;
  1700. margin: 20px auto 0;
  1701. font-family: Georgia, serif;
  1702. font-size: 34px;
  1703. font-weight: 700;
  1704. color: #7d3b0a;
  1705. text-align: center;
  1706. text-shadow: 1px 1px 1px #e9d5a2;
  1707. background: url(https://www.mousehuntgame.com/images/ui/larry_gifts/ribbon.png?asset_cache_version=2) no-repeat;
  1708. }
  1709.  
  1710. .mh-paper-popup-dialog-wrapper .mh-inner-wrapper {
  1711. display: flex;
  1712. padding: 5px 10px 25px;
  1713. }
  1714.  
  1715. .mh-paper-popup-dialog-wrapper .mh-inner-image-wrapper {
  1716. position: relative;
  1717. padding: 10px;
  1718. margin: 0 auto 10px;
  1719. background: #f7e3af;
  1720. border-radius: 10px;
  1721. box-shadow: 0 3px 10px #bd7d3c;
  1722. }
  1723.  
  1724. .mh-paper-popup-dialog-wrapper .mh-inner-image {
  1725. width: 200px;
  1726. height: 200px;
  1727. background-color: #f5edd7;
  1728. border-radius: 5px;
  1729. box-shadow: 0 0 100px #6c340b inset;
  1730. }
  1731.  
  1732. .mh-paper-popup-dialog-wrapper .mh-inner-text {
  1733. margin-left: 30px;
  1734. line-height: 18px;
  1735. text-align: left;
  1736. }
  1737.  
  1738. .mh-paper-popup-dialog-wrapper .mh-inner-title {
  1739. padding: 10px 0;
  1740. font-size: 1.5em;
  1741. font-weight: 700;
  1742. }
  1743.  
  1744. .mh-paper-popup-dialog-wrapper .mh-button-wrapper {
  1745. display: flex;
  1746. align-items: center;
  1747. justify-content: center;
  1748. }
  1749.  
  1750. .mh-paper-popup-dialog-wrapper .mh-button {
  1751. padding: 10px 50px;
  1752. font-size: 1.5em;
  1753. color: #000;
  1754. background: linear-gradient(to bottom, #fff600, #f4e830);
  1755. border: 1px solid #000;
  1756. border-radius: 5px;
  1757. box-shadow: 0 0 10px 1px #d6d13b inset;
  1758. }
  1759. `);
  1760.  
  1761. // Default to sensible values.
  1762. const settings = Object.assign({}, {
  1763. title: '',
  1764. content: {
  1765. title: '',
  1766. text: '',
  1767. image: '',
  1768. },
  1769. button: {
  1770. text: '',
  1771. href: '',
  1772. },
  1773. show: true,
  1774. }, options);
  1775.  
  1776. // Build the markup with our content.
  1777. const markup = `<div class="mh-paper-popup-wrapper">
  1778. <div class="mh-title">${settings.title}</div>
  1779. <div class="mh-inner-wrapper">
  1780. <div class="mh-inner-image-wrapper">
  1781. <img class="mh-inner-image" src="${settings.content.image}" />
  1782. </div>
  1783. <div class="mh-inner-text">
  1784. <div class="mh-inner-title">${settings.content.title}</div>
  1785. <p>${settings.content.text}</p>
  1786. </div>
  1787. </div>
  1788. <div class="mh-button-wrapper">
  1789. <a href="${settings.button.href}" class="mh-button">${settings.button.text}</a>
  1790. </div>
  1791. </div>`;
  1792.  
  1793. // Initiate the popup.
  1794. const popup = createPopup({
  1795. hasCloseButton: false,
  1796. template: 'ajax',
  1797. content: markup,
  1798. show: false,
  1799. });
  1800.  
  1801. // Set more of our tokens.
  1802. popup.addToken('{*prefix*}', '');
  1803. popup.addToken('{*suffix*}', '');
  1804.  
  1805. // Set the attribute and show the popup.
  1806. popup.setAttributes({ className: 'mh-paper-popup-dialog-wrapper' });
  1807.  
  1808. // If we want to show the popup, show it.
  1809. if (settings.show) {
  1810. popup.show();
  1811. }
  1812.  
  1813. return popup;
  1814. };
  1815.  
  1816. /**
  1817. * Make an element draggable. Saves the position to local storage.
  1818. *
  1819. * @param {string} dragTarget The selector for the element that should be dragged.
  1820. * @param {string} dragHandle The selector for the element that should be used to drag the element.
  1821. * @param {number} defaultX The default X position.
  1822. * @param {number} defaultY The default Y position.
  1823. * @param {string} storageKey The key to use for local storage.
  1824. * @param {boolean} savePosition Whether or not to save the position to local storage.
  1825. */
  1826. const makeElementDraggable = (dragTarget, dragHandle, defaultX = null, defaultY = null, storageKey = null, savePosition = true) => {
  1827. const modal = document.querySelector(dragTarget);
  1828. if (! modal) {
  1829. return;
  1830. }
  1831.  
  1832. const handle = document.querySelector(dragHandle);
  1833. if (! handle) {
  1834. return;
  1835. }
  1836.  
  1837. /**
  1838. * Make sure the coordinates are within the bounds of the window.
  1839. *
  1840. * @param {string} type The type of coordinate to check.
  1841. * @param {number} value The value of the coordinate.
  1842. *
  1843. * @return {number} The value of the coordinate, or the max/min value if it's out of bounds.
  1844. */
  1845. const keepWithinLimits = (type, value) => {
  1846. if ('top' === type) {
  1847. return value < -20 ? -20 : value;
  1848. }
  1849.  
  1850. if (value < (handle.offsetWidth * -1) + 20) {
  1851. return (handle.offsetWidth * -1) + 20;
  1852. }
  1853.  
  1854. if (value > document.body.clientWidth - 20) {
  1855. return document.body.clientWidth - 20;
  1856. }
  1857.  
  1858. return value;
  1859. };
  1860.  
  1861. /**
  1862. * When the mouse is clicked, add the class and event listeners.
  1863. *
  1864. * @param {Object} e The event object.
  1865. */
  1866. const onMouseDown = (e) => {
  1867. e.preventDefault();
  1868. setTimeout(() => {
  1869. // Get the current mouse position.
  1870. x1 = e.clientX;
  1871. y1 = e.clientY;
  1872.  
  1873. // Add the class to the element.
  1874. modal.classList.add('mh-is-dragging');
  1875.  
  1876. // Add the onDrag and finishDrag events.
  1877. document.onmousemove = onDrag;
  1878. document.onmouseup = finishDrag;
  1879. }, 50);
  1880. };
  1881.  
  1882. /**
  1883. * When the drag is finished, remove the dragging class and event listeners, and save the position.
  1884. */
  1885. const finishDrag = () => {
  1886. document.onmouseup = null;
  1887. document.onmousemove = null;
  1888.  
  1889. // Remove the class from the element.
  1890. modal.classList.remove('mh-is-dragging');
  1891.  
  1892. if (storageKey) {
  1893. localStorage.setItem(storageKey, JSON.stringify({ x: modal.offsetLeft, y: modal.offsetTop }));
  1894. }
  1895. };
  1896.  
  1897. /**
  1898. * When the mouse is moved, update the element's position.
  1899. *
  1900. * @param {Object} e The event object.
  1901. */
  1902. const onDrag = (e) => {
  1903. e.preventDefault();
  1904.  
  1905. // Calculate the new cursor position.
  1906. x2 = x1 - e.clientX;
  1907. y2 = y1 - e.clientY;
  1908.  
  1909. x1 = e.clientX;
  1910. y1 = e.clientY;
  1911.  
  1912. const newLeft = keepWithinLimits('left', modal.offsetLeft - x2);
  1913. const newTop = keepWithinLimits('top', modal.offsetTop - y2);
  1914.  
  1915. // Set the element's new position.
  1916. modal.style.left = `${newLeft}px`;
  1917. modal.style.top = `${newTop}px`;
  1918. };
  1919.  
  1920. // Set the default position.
  1921. let startX = defaultX || 0;
  1922. let startY = defaultY || 0;
  1923.  
  1924. // If the storageKey was passed in, get the position from local storage.
  1925. if (! storageKey) {
  1926. storageKey = `mh-draggable-${dragTarget}-${dragHandle}`;
  1927. }
  1928.  
  1929. if (savePosition) {
  1930. const storedPosition = localStorage.getItem(storageKey);
  1931. if (storedPosition) {
  1932. const position = JSON.parse(storedPosition);
  1933.  
  1934. // Make sure the position is within the bounds of the window.
  1935. startX = keepWithinLimits('left', position.x);
  1936. startY = keepWithinLimits('top', position.y);
  1937. }
  1938. }
  1939.  
  1940. // Set the element's position.
  1941. modal.style.left = `${startX}px`;
  1942. modal.style.top = `${startY}px`;
  1943.  
  1944. // Set up our variables to track the mouse position.
  1945. let x1 = 0,
  1946. y1 = 0,
  1947. x2 = 0,
  1948. y2 = 0;
  1949.  
  1950. // Add the event listener to the handle.
  1951. handle.onmousedown = onMouseDown;
  1952. };
  1953.  
  1954. /**
  1955. * Creates an element with the given tag, classname, text, and appends it to the given element.
  1956. *
  1957. * @param {string} tag The tag of the element to create.
  1958. * @param {string} classes The classes of the element to create.
  1959. * @param {string} text The text of the element to create.
  1960. * @param {HTMLElement} appendTo The element to append the created element to.
  1961. *
  1962. * @return {HTMLElement} The created element.
  1963. */
  1964. const makeElement = (tag, classes = '', text = '', appendTo = null) => {
  1965. const element = document.createElement(tag);
  1966.  
  1967. // if classes is an array, join it with a space.
  1968. if (Array.isArray(classes)) {
  1969. classes = classes.join(' ');
  1970. }
  1971.  
  1972. element.className = classes;
  1973. element.innerHTML = text;
  1974.  
  1975. if (appendTo) {
  1976. appendTo.appendChild(element);
  1977. return appendTo;
  1978. }
  1979.  
  1980. return element;
  1981. };
  1982.  
  1983. /**
  1984. * Creates a popup with two choices.
  1985. *
  1986. * createChoicePopup({
  1987. * title: 'Choose your first trap',
  1988. * choices: [
  1989. * {
  1990. * id: 'treasurer_mouse',
  1991. * name: 'Treasurer',
  1992. * image: 'https://www.mousehuntgame.com/images/mice/medium/bb55034f6691eb5e3423927e507b5ec9.jpg?cv=2',
  1993. * meta: 'Mouse',
  1994. * text: 'This is a mouse',
  1995. * button: 'Select',
  1996. * callback: () => {
  1997. * console.log('treasurer selected');
  1998. * }
  1999. * },
  2000. * {
  2001. * id: 'high_roller_mouse',
  2002. * name: 'High Roller',
  2003. * image: 'https://www.mousehuntgame.com/images/mice/medium/3f71c32f9d8da2b2727fc8fd288f7974.jpg?cv=2',
  2004. * meta: 'Mouse',
  2005. * text: 'This is a mouse',
  2006. * button: 'Select',
  2007. * callback: () => {
  2008. * console.log('high roller selected');
  2009. * }
  2010. * },
  2011. * ],
  2012. * });
  2013. *
  2014. * @param {Object} options The options for the popup.
  2015. * @param {string} options.title The title of the popup.
  2016. * @param {Array} options.choices The choices for the popup.
  2017. * @param {string} options.choices[].id The ID of the choice.
  2018. * @param {string} options.choices[].name The name of the choice.
  2019. * @param {string} options.choices[].image The image of the choice.
  2020. * @param {string} options.choices[].meta The smaller text under the name.
  2021. * @param {string} options.choices[].text The description of the choice.
  2022. * @param {string} options.choices[].button The text of the button.
  2023. * @param {string} options.choices[].action The action to take when the button is clicked.
  2024. */
  2025. const createChoicePopup = (options) => {
  2026. let choices = '';
  2027. const numChoices = options.choices.length;
  2028. let currentChoice = 0;
  2029.  
  2030. options.choices.forEach((choice) => {
  2031. choices += `<a href="#" id=${choice.id}" class="weaponContainer">
  2032. <div class="weapon">
  2033. <div class="trapImage" style="background-image: url(${choice.image});"></div>
  2034. <div class="trapDetails">
  2035. <div class="trapName">${choice.name}</div>
  2036. <div class="trapDamageType">${choice.meta}</div>
  2037. <div class="trapDescription">${choice.text}</div>
  2038. <div class="trapButton" id="${choice.id}-action">${choice.button || 'Select'}</div>
  2039. </div>
  2040. </div>
  2041. </a>`;
  2042.  
  2043. currentChoice++;
  2044. if (currentChoice < numChoices) {
  2045. choices += '<div class="spacer"></div>';
  2046. }
  2047. });
  2048.  
  2049. const content = `<div class="trapIntro">
  2050. <div id="OnboardArrow" class="larryCircle">
  2051. <div class="woodgrain">
  2052. <div class="whiteboard">${options.title}</div>
  2053. </div>
  2054. <div class="characterContainer">
  2055. <div class="character"></div>
  2056. </div>
  2057. </div>
  2058. </div>
  2059. <div>
  2060. ${choices}
  2061. </div>`;
  2062.  
  2063. hg.views.MessengerView.addMessage({
  2064. content: { body: content },
  2065. css_class: 'chooseTrap',
  2066. show_overlay: true,
  2067. is_modal: true
  2068. });
  2069. hg.views.MessengerView.go();
  2070.  
  2071. options.choices.forEach((choice) => {
  2072. const target = document.querySelector(`#${choice.id}-action`);
  2073. if (target) {
  2074. target.addEventListener('click', () => {
  2075. hg.views.MessengerView.hide();
  2076. if (choice.action) {
  2077. choice.action();
  2078. }
  2079. });
  2080. }
  2081. });
  2082. };
  2083.  
  2084. /**
  2085. * Creates a favorite button that can toggle.
  2086. *
  2087. * createFavoriteButton({
  2088. * id: 'testing_favorite',
  2089. * target: infobar,
  2090. * size: 'small',
  2091. * defaultState: false,
  2092. * });
  2093. *
  2094. * @param {Object} options The options for the button.
  2095. * @param {string} options.selector The selector for the button.
  2096. * @param {string} options.small Whether or not to use the small version of the button.
  2097. * @param {string} options.active Whether or not the button should be active by default.
  2098. * @param {string} options.onChange The function to run when the button is toggled.
  2099. * @param {string} options.onActivate The function to run when the button is activated.
  2100. * @param {string} options.onDeactivate The function to run when the button is deactivated.
  2101. */
  2102. const createFavoriteButton = async (options) => {
  2103. addStyles(`.mh-utils-custom-favorite-button {
  2104. top: 0;
  2105. right: 0;
  2106. display: inline-block;
  2107. width: 35px;
  2108. height: 35px;
  2109. vertical-align: middle;
  2110. background: url(https://www.mousehuntgame.com/images/ui/camp/trap/star_empty.png?asset_cache_version=2) 50% 50% no-repeat;
  2111. background-size: 90%;
  2112. border-radius: 50%;
  2113. }
  2114.  
  2115. .custom-favorite-button-small {
  2116. width: 20px;
  2117. height: 20px;
  2118. }
  2119.  
  2120. .custom-favorite-button:hover, .custom-favorite-button:focus {
  2121. background-color: #fff;
  2122. outline: 2px solid #ccc;
  2123. }
  2124.  
  2125. .custom-favorite-button.active {
  2126. background-image: url(https://www.mousehuntgame.com/images/ui/camp/trap/star_favorite.png?asset_cache_version=2);
  2127. }
  2128.  
  2129. .custom-favorite-button.busy {
  2130. background-image: url(https://www.mousehuntgame.com/images/ui/loaders/small_spinner.gif?asset_cache_version=2);
  2131. }
  2132. `, 'custom-favorite-button', true);
  2133.  
  2134. const {
  2135. id,
  2136. target,
  2137. size = 'small',
  2138. defaultState = false,
  2139. onChange = null,
  2140. onActivate = null,
  2141. onDeactivate = null,
  2142. } = options;
  2143.  
  2144. const star = document.createElement('a');
  2145.  
  2146. star.classList.add('custom-favorite-button');
  2147. if (size === 'small') {
  2148. star.classList.add('custom-favorite-button-small');
  2149. }
  2150.  
  2151. star.setAttribute('data-item-id', id);
  2152. star.setAttribute('href', '#');
  2153.  
  2154. star.style.display = 'inline-block';
  2155.  
  2156. const currentSetting = getSetting(id, defaultState);
  2157. if (currentSetting) {
  2158. star.classList.add('active');
  2159. } else {
  2160. star.classList.add('inactive');
  2161. }
  2162.  
  2163. star.addEventListener('click', async (e) => {
  2164. star.classList.add('busy');
  2165. e.preventDefault();
  2166. e.stopPropagation();
  2167. let state = ! star.classList.contains('active');
  2168. if (onChange !== null) {
  2169. state = await callback(state);
  2170. } else {
  2171. await new Promise((resolve) => setTimeout(resolve, 500));
  2172. saveSetting(id, ! star.classList.contains('active'));
  2173. }
  2174.  
  2175. if (state === undefined) {
  2176. star.classList.remove('busy');
  2177. return;
  2178. }
  2179.  
  2180. if (state) {
  2181. if (onActivate !== null) {
  2182. await callback(state);
  2183. }
  2184.  
  2185. star.classList.remove('inactive');
  2186. star.classList.add('active');
  2187. } else {
  2188. if (onDeactivate !== null) {
  2189. await callback(state);
  2190. }
  2191.  
  2192. star.classList.remove('active');
  2193. star.classList.add('inactive');
  2194. }
  2195.  
  2196. star.classList.remove('busy');
  2197. });
  2198.  
  2199. target.appendChild(star);
  2200. };
  2201.  
  2202. /**
  2203. * Wait for a specified amount of time.
  2204. *
  2205. * @param {number} ms The number of milliseconds to wait.
  2206. */
  2207. const wait = (ms) => {
  2208. return new Promise((resolve) => setTimeout(resolve, ms));
  2209. };
  2210.  
  2211. /**
  2212. * Log to the console.
  2213. *
  2214. * @param {string|Object} message The message to log.
  2215. * @param {Object} args The arguments to pass to the console.
  2216. */
  2217. const clog = (message, ...args) => {
  2218. // If a string is passed in, log it in line with our prefix.
  2219. if ('string' === typeof message) {
  2220. console.log(`%c[MH Utils] %c${message}`, 'color: #ff0000; font-weight: bold;', 'color: #000000;'); // eslint-disable-line no-console
  2221. console.log(...args); // eslint-disable-line no-console
  2222. } else {
  2223. // Otherwise, log it separately.
  2224. console.log('%c[MH Utils]', 'color: #ff0000; font-weight: bold;'); // eslint-disable-line no-console
  2225. console.log(message); // eslint-disable-line no-console
  2226. }
  2227. };
  2228.  
  2229. /**
  2230. * Log to the console if debug mode is enabled.
  2231. *
  2232. * @param {string|Object} message The message to log.
  2233. * @param {Object} args The arguments to pass to the console.
  2234. */
  2235. const debug = (message, ...args) => {
  2236. if (getSetting('debug-mode', false)) {
  2237. clog(message, ...args);
  2238. }
  2239. };
  2240.  
  2241. /**
  2242. * Add a setting to enable debug mode.
  2243. */
  2244. const enableDebugMode = () => {
  2245. window.mhutils = { debugModeEnabled: true, debug: getSetting('debug-mode', false) };
  2246. addSetting('Debug Mode', 'debug-mode', false, 'Enable debug mode', {}, 'game_settings');
  2247. };