您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Various UI mods for Hordes.io.
当前为
// ==UserScript== // @name Hordes UI Mod // @version 1.0.2 // @description Various UI mods for Hordes.io. // @author Sakaiyo & Chandog#6373 & Cullen // @match https://hordes.io/play // @grant GM_addStyle // @namespace https://greasyfork.org/users/160017 // ==/UserScript== GM_addStyle(`/* Custom css for settings page, duplicates preexisting settings pane grid */ .uimod-settings { display: grid; grid-template-columns: 2fr 3fr; grid-gap: 8px; align-items: center; max-height: 390px; margin: 0 20px; overflow-y: auto; } /* Custom chat context menu, invisible by default */ .js-chat-context-menu { display: none; } .js-chat-context-menu .name { color: white; padding: 2px 4px; } /* Allow names in chat to be clicked (textf1 = BL, textf0 = VG) */ #chat .name, .textwhisper .textf1, .textwhisper .textf0 { pointer-events: all !important; } /* Custom chat filter colors */ .js-chat-gm { color: #a6dcd5; } /* Class that hides chat lines */ .js-line-hidden, .js-line-blocked { display: none; } /* Custom chat tabs */ .uimod-chat-tabs { position: fixed; margin-top: -22px; left: 5px; pointer-events: all; color: #5b858e; font-size: 12px; font-weight: bold; } .uimod-chat-tabs > div { cursor: pointer; background-color: rgba(0, 0, 0, 0.4); border-top-right-radius: 4px; border-top-left-radius: 4px; display: inline-block; border: 1px black solid; border-bottom: 0; margin-right: 2px; padding: 3px 5px; } .uimod-chat-tabs > div:not(.js-selected-tab):hover { border-color: #aaa; } .uimod-chat-tabs > .js-selected-tab { color: #fff; } /* Chat tab custom config */ .uimod-chat-tab-config { position: absolute; z-index: 9999999; background-color: rgba(0, 0, 0, 0.6); color: white; border-radius: 3px; text-align: center; padding: 8px 12px 8px 6px; width: 175px; font-size: 14px; border: 1px solid black; display: none; } .uimod-chat-tab-config-grid { grid-template-columns: 35% 65%; display: grid; grid-gap: 6px; align-items: center; } .uimod-chat-tab-config h1 { font-size: 16px; margin-top: 0; } .uimod-chat-tab-config .btn, .uimod-chat-tab-config input { font-size: 12px; } /* Allows windows to be moved */ .window { position: relative; } /* All purpose hidden class */ .js-hidden { display: none; } /* Friends list CSS, similar to settings but supports 4 columns */ .uimod-friends { display: grid; grid-template-columns: 2fr 1.1fr 1.5fr 0.33fr 3fr; grid-gap: 8px; align-items: center; max-height: 390px; margin: 0 20px; overflow-y: auto; } .js-map-btns { position: absolute; top: 46px; right: 12px; z-index: 999; width: 100px; height: 100px; text-align: right; display: none; pointer-events: all; } .js-map-btns:hover { display: block; } .js-map-btns button { border-radius: 10px; font-size: 18px; padding: 0 5px; background: rgba(0, 0, 0, 0.4); border: 0; color: white; font-weight: bold; cursor: pointer; } /* On hover of map, show opacity controls */ .js-map:hover .js-map-btns { display: block; } .js-chat-resize { resize: both; overflow: auto; } .js-map { /* This makes sure scroll bars don't appear when resizing the map */ overflow: hidden; } .js-map-resize:hover { resize: both; overflow: auto; direction: rtl; } /* Allows last clicked window to appear above all other windows */ .js-is-top { z-index: 9998 !important; } .panel.context:not(.commandlist) { z-index: 9999 !important; } /* The item icon being dragged in the inventory */ .container.svelte-120o2pb { z-index: 9999 !important; } .container.uimod-xpmeter-1 { z-index: 6; } .window.uimod-xpmeter-2 { padding: 5px; height: 100%; display: grid; grid-template-rows: 30px 1fr; grid-gap: 4px; transform-origin: inherit; min-width: fit-content; } .titleframe.uimod-xpmeter-2 { line-height: 1em; display: flex; align-items: center; position: relative; letter-spacing: 0.5px; } .titleicon.uimod-xpmeter-2 { margin: 3px; } .title.uimod-xpmeter-2 { width: 100%; padding-left: 4px; font-weight: bold; } .slot.uimod-xpmeter-2 { min-height: 0; } .wrapper.uimod-xpmeter-1 { width: 200px; } .bar.uimod-xpmeter-3 { background-color: rgba(45, 66, 71, 0.7); border-radius: 1.5px; position: relative; color: #DAE8EA; overflow: hidden; text-shadow: 1px 1px 2px #10131d; white-space: nowrap; text-transform: capitalize; font-weight: bold; } .buttons.uimod-xpmeter-1 { line-height: 1; font-size: 13px; } .left.uimod-xpmeter-3 { padding-left: 4px; position: relative; z-index: 1; } .right.uimod-xpmeter-3 { position: absolute; right: 7px; z-index: 1; } /* This file is for CSS mods that don't fit in any other individual mod folder */ /* Transparent chat bg color */ .frame.svelte-1vrlsr3 { background: rgba(0, 0, 0, 0.4); } /* Our mod's chat message color */ .textuimod { color: #00dd33; } /* The browser resize icon */ *::-webkit-resizer { background: linear-gradient(to right, rgba(51, 77, 80, 0), rgba(203, 202, 165, 0.5)); border-radius: 8px; box-shadow: 0 1px 1px black; } *::-moz-resizer { background: linear-gradient(to right, rgba(51, 77, 80, 0), rgba(203, 202, 165, 0.5)); border-radius: 8px; box-shadow: 0 1px 1px black; } /* Our custom window, closely mirrors main settings window */ .uimod-custom-window { position: absolute; top: 100px; left: 50%; transform: translate(-50%, 0); min-width: 350px; max-width: 600px; width: 90%; height: 80%; min-height: 350px; max-height: 500px; z-index: 9; padding: 0px 10px 5px; } `); (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){ "use strict"; var _mods = _interopRequireDefault(require("./mods")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } // Add new DOM, load our stored state, wire it up, then continuously rerun specific methods whenever UI changes function initialize() { // If the Hordes.io tab isn't active for long enough, it reloads the entire page, clearing this mod // We check for that and reinitialize the mod if that happens const $layout = document.querySelector('.layout'); if ($layout.classList.contains('uimod-initd')) { return; } $layout.classList.add('uimod-initd'); const rerunning = { // MutationObserver running whenever .layout changes onDomChange: [], // Mutation observer running whenever #chat changes onChatChange: [], // `click` Event listener running on document.body onPageClick: [] }; // Run all our mods const registerOnDomChange = callback => rerunning.onDomChange.push(callback); const registerOnChatChange = callback => rerunning.onChatChange.push(callback); const registerOnPageClick = callback => rerunning.onPageClick.push(callback); _mods.default.forEach(mod => { mod.run({ registerOnDomChange, registerOnChatChange, registerOnPageClick }); }); // Continuously re-run specific mods methods that need to be executed on UI change const rerunObserver = new MutationObserver(() => { // If new window appears, e.g. even if window is closed and reopened, we need to rewire it // Fun fact: Some windows always exist in the DOM, even when hidden, e.g. Inventory // But some windows only exist in the DOM when open, e.g. Interaction rerunning.onDomChange.forEach(callback => callback()); }); rerunObserver.observe(document.querySelector('.layout > .container'), { attributes: false, childList: true }); // Rerun only on chat messages changing const chatRerunObserver = new MutationObserver(() => { rerunning.onChatChange.forEach(callback => callback()); }); chatRerunObserver.observe(document.querySelector('#chat'), { attributes: false, childList: true }); // Event listeners for document.body might be kept when the game reloads, so don't reinitialize them if (!document.body.classList.contains('js-uimod-initd')) { document.body.classList.add('js-uimod-initd'); rerunning.onPageClick.forEach(callback => document.body.addEventListener('click', callback)); } } // Initialize mods once UI DOM has loaded const pageObserver = new MutationObserver(() => { const isUiLoaded = !!document.querySelector('.layout'); if (isUiLoaded) { initialize(); } }); pageObserver.observe(document.body, { attributes: true, childList: true }); },{"./mods":12}],2:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var chat = _interopRequireWildcard(require("../../utils/chat")); var _version = require("../../utils/version"); var _state = require("../../utils/state"); function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; } function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } function modStart() { chat.addChatMessage(`Hordes UI Mod v${_version.VERSION} is now running.`); (0, _state.loadState)(); } var _default = { name: '[REQUIRED] UI Mod Startup', description: 'Do not remove this! This handles some initial mod loading, including loading saved state.', run: modStart }; exports.default = _default; },{"../../utils/chat":21,"../../utils/state":24,"../../utils/version":25}],3:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _state = require("../../utils/state"); var _misc = require("../../utils/misc"); var player = _interopRequireWildcard(require("../../utils/player")); function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; } function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } function blockedPlayerSettings() { const state = (0, _state.getState)(); const $settings = document.querySelector('.divide:not(.js-settings-initd)'); if (!$settings) { return; } $settings.classList.add('js-settings-initd'); const $settingsChoiceList = $settings.querySelector('.choice').parentNode; $settingsChoiceList.appendChild((0, _misc.makeElement)({ element: 'div', class: 'choice js-blocked-players', content: 'Blocked players' })); // Upon click, we display our custom settings window UI document.querySelector('.js-blocked-players').addEventListener('click', () => { let blockedPlayersHTML = ''; Object.keys(state.blockList).sort().forEach(blockedName => { blockedPlayersHTML += ` <div data-player-name="${blockedName}">${blockedName}</div> <div class="btn orange js-unblock-player" data-player-name="${blockedName}">Unblock player</div> `; }); const customSettingsHTML = ` <h3 class="textprimary">Blocked players</h3> <div class="settings uimod-settings">${blockedPlayersHTML}</div> <p></p> <div class="btn purp js-close-custom-settings">Close</div> `; const $customSettings = (0, _misc.makeElement)({ element: 'div', class: 'menu panel-black js-custom-settings uimod-custom-window', content: customSettingsHTML }); document.body.appendChild($customSettings); // Wire up all the unblock buttons Array.from(document.querySelectorAll('.js-unblock-player')).forEach($button => { $button.addEventListener('click', clickEvent => { const name = clickEvent.target.getAttribute('data-player-name'); player.unblockPlayer(name); // Remove the blocked player from the list Array.from(document.querySelectorAll(`.js-custom-settings [data-player-name="${name}"]`)).forEach($element => { $element.parentNode.removeChild($element); }); }); }); // And the close button for our custom UI document.querySelector('.js-close-custom-settings').addEventListener('click', () => { const $customSettingsWindow = document.querySelector('.js-custom-settings'); $customSettingsWindow.parentNode.removeChild($customSettingsWindow); }); }); } var _default = { name: 'Blocked Players List', description: 'Allows you to view and remove blocked players from the Settings window', run: ({ registerOnDomChange }) => { blockedPlayerSettings(); // If the settings window becomes visible/invisible, we want to update it registerOnDomChange(blockedPlayerSettings); } }; exports.default = _default; },{"../../utils/misc":22,"../../utils/player":23,"../../utils/state":24}],4:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.showChatContextMenu = showChatContextMenu; var _state = require("../../utils/state"); // Makes chat context menu visible and appear under the mouse function showChatContextMenu(name, mousePos) { const state = (0, _state.getState)(); // Right before we show the context menu, we want to handle showing/hiding Friend/Unfriend const $contextMenu = document.querySelector('.js-chat-context-menu'); $contextMenu.querySelector('[name="friend"]').classList.toggle('js-hidden', !!state.friendsList[name]); $contextMenu.querySelector('[name="unfriend"]').classList.toggle('js-hidden', !state.friendsList[name]); $contextMenu.querySelector('.js-name').textContent = name; $contextMenu.setAttribute('style', `display: block; left: ${mousePos.x}px; top: ${mousePos.y}px;`); } },{"../../utils/state":24}],5:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _state = require("../../utils/state"); var _misc = require("../../utils/misc"); var helpers = _interopRequireWildcard(require("./helpers")); var chat = _interopRequireWildcard(require("../../utils/chat")); var player = _interopRequireWildcard(require("../../utils/player")); function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; } function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } // This creates the initial chat context menu DOM (which starts as hidden) function createChatContextMenu() { const tempState = (0, _state.getTempState)(); if (document.querySelector('.js-chat-context-menu')) { return; } let contextMenuHTML = ` <div class="js-name">...</div> <div class="choice" name="party">Party invite</div> <div class="choice" name="whisper">Whisper</div> <div class="choice" name="friend">Friend</div> <div class="choice" name="unfriend">Unfriend</div> <div class="choice" name="copy">Copy name</div> <div class="choice" name="block">Block</div> `; document.body.appendChild((0, _misc.makeElement)({ element: 'div', class: 'panel context border grey js-chat-context-menu', content: contextMenuHTML })); const $chatContextMenu = document.querySelector('.js-chat-context-menu'); $chatContextMenu.querySelector('[name="party"]').addEventListener('click', () => { chat.partyPlayer(tempState.chatName); }); $chatContextMenu.querySelector('[name="whisper"]').addEventListener('click', () => { chat.whisperPlayer(tempState.chatName); }); $chatContextMenu.querySelector('[name="friend"]').addEventListener('click', () => { player.friendPlayer(tempState.chatName); }); $chatContextMenu.querySelector('[name="unfriend"]').addEventListener('click', () => { player.unfriendPlayer(tempState.chatName); }); $chatContextMenu.querySelector('[name="copy"]').addEventListener('click', () => { navigator.clipboard.writeText(tempState.chatName); }); $chatContextMenu.querySelector('[name="block"]').addEventListener('click', () => { player.blockPlayer(tempState.chatName); }); } // This opens a context menu when you click a user's name in chat function chatContextMenu() { const tempState = (0, _state.getTempState)(); const addContextMenu = ($name, name) => { $name.classList.add('js-is-context-menu-initd'); // Add name to element so we can target it in CSS when filtering chat for block list $name.setAttribute('data-chat-name', name); const showContextMenu = clickEvent => { // TODO: Is there a way to pass the name to showChatContextMenumethod, instead of storing in tempState? tempState.chatName = name; helpers.showChatContextMenu(name, { x: clickEvent.pageX, y: clickEvent.pageY }); }; $name.addEventListener('click', showContextMenu); // Left click $name.addEventListener('contextmenu', showContextMenu); // Right click works too }; Array.from(document.querySelectorAll('#chat .name:not(.js-is-context-menu-initd)')).forEach($name => { addContextMenu($name, $name.textContent); }); // `textf0` is the VG faction, `textf1` is the BL faction - we want to support both with our whisper context menu Array.from(document.querySelectorAll('.textwhisper .textf1:not(.js-is-context-menu-initd), .textwhisper .textf0:not(.js-is-context-menu-initd)')).forEach($whisperName => { // $whisperName's textContent is "to [name]" or "from [name]", so we cut off the first word let name = $whisperName.textContent.split(' '); name.shift(); // Remove the first word name = name.join(' '); addContextMenu($whisperName, name); }); } // Close chat context menu if clicking outside of it function closeChatContextMenu(clickEvent) { const $target = clickEvent.target; // If clicking on name or directly on context menu, don't close it // Still closes if clicking on context menu item if ($target.classList.contains('js-is-context-menu-initd') || $target.classList.contains('js-chat-context-menu')) { return; } const $contextMenu = document.querySelector('.js-chat-context-menu'); $contextMenu.style.display = 'none'; } var _default = { name: 'Chat Context Menu', description: 'Displays a menu when you click on a player, allowing you to whisper/party/friend/block them', run: ({ registerOnPageClick, registerOnChatChange }) => { createChatContextMenu(); chatContextMenu(); // When we click anywhere on the page outside of our chat context menu, we want to close the menu registerOnPageClick(closeChatContextMenu); // Register event listeners for each name when a new chat message appears registerOnChatChange(chatContextMenu); } }; exports.default = _default; },{"../../utils/chat":21,"../../utils/misc":22,"../../utils/player":23,"../../utils/state":24,"./helpers":4}],6:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var chat = _interopRequireWildcard(require("../../utils/chat")); var _state = require("../../utils/state"); var _misc = require("../../utils/misc"); function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; } function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } // Creates DOM elements for custom chat filters function newChatFilters() { const state = (0, _state.getState)(); const $channelselect = document.querySelector('.channelselect'); if (!document.querySelector(`.js-chat-gm`)) { const $gm = (0, _misc.makeElement)({ element: 'small', class: `btn border black js-chat-gm ${state.chat.GM ? '' : 'textgrey'}`, content: 'GM' }); $channelselect.appendChild($gm); } } // Wire up new chat buttons to toggle in state+ui function newChatFilterButtons() { const state = (0, _state.getState)(); const $chatGM = document.querySelector(`.js-chat-gm`); $chatGM.addEventListener('click', () => { chat.setGMChatVisibility(!state.chat.GM); }); } var _default = { name: 'Chat filters', description: 'Enables custom chat filters: GM chat', run: ({ registerOnChatChange }) => { newChatFilters(); newChatFilterButtons(); // Whenever chat changes, we want to filter it registerOnChatChange(chat.filterAllChat); } }; exports.default = _default; },{"../../utils/chat":21,"../../utils/misc":22,"../../utils/state":24}],7:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.showChatTabConfigWindow = showChatTabConfigWindow; exports.addChatTab = addChatTab; exports.selectChatTab = selectChatTab; exports.getCurrentChatFilters = getCurrentChatFilters; var chat = _interopRequireWildcard(require("../../utils/chat")); var _state = require("../../utils/state"); var _misc = require("../../utils/misc"); function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; } function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } const DEFAULT_CHAT_TAB_NAME = 'Untitled'; // Gets current chat filters as represented in the UI // filter being true means it's invisible(filtered) in chat // filter being false means it's visible(unfiltered) in chat function getCurrentChatFilters() { const state = (0, _state.getState)(); // Saved by the official game client const gameFilters = JSON.parse(localStorage.getItem('filteredChannels')); return { global: gameFilters.includes('global'), faction: gameFilters.includes('faction'), party: gameFilters.includes('party'), clan: gameFilters.includes('clan'), pvp: gameFilters.includes('pvp'), inv: gameFilters.includes('inv'), GM: !state.chat.GM // state.chat.GM is whether or not GM chat is shown - we want whether or not GM chat should be hidden }; } // Shows the chat tab config window for a specific tab, displayed in a specific position function showChatTabConfigWindow(tabId, pos) { const state = (0, _state.getState)(); const tempState = getTempState(); const $chatTabConfig = document.querySelector('.js-chat-tab-config'); const chatTab = state.chatTabs.find(tab => tab.id === tabId); // Update position and name in chat tab config $chatTabConfig.style.left = `${pos.x}px`; $chatTabConfig.style.top = `${pos.y}px`; $chatTabConfig.querySelector('.js-chat-tab-name').value = chatTab.name; // Store tabId in state, to be used by the Remove/Add buttons in config window tempState.editedChatTabId = tabId; // Hide remove button if only one chat tab left - can't remove last one // Show it if more than one chat tab left const chatTabCount = Object.keys(state.chatTabs).length; const $removeChatTabBtn = $chatTabConfig.querySelector('.js-remove-chat-tab'); $removeChatTabBtn.style.display = chatTabCount < 2 ? 'none' : 'block'; // Show chat tab config $chatTabConfig.style.display = 'block'; } // Adds chat tab to DOM, sets it as selected // If argument chatTab is provided, will use that name+id // If no argument is provided, will create new tab name/id and add it to state // isInittingTab is optional boolean, if `true`, will _not_ set added tab as selected. Used when initializing all chat tabs on load // Returns newly added tabId function addChatTab(chatTab, isInittingTab) { const state = (0, _state.getState)(); let tabName = DEFAULT_CHAT_TAB_NAME; let tabId = (0, _misc.uuid)(); if (chatTab) { tabName = chatTab.name; tabId = chatTab.id; } else { // If no chat tab was provided, create it in state state.chatTabs.push({ name: tabName, id: tabId, filters: getCurrentChatFilters() }); (0, _state.saveState)(); } const $tabs = document.querySelector('.js-chat-tabs'); const $tab = (0, _misc.makeElement)({ element: 'div', content: tabName }); $tab.setAttribute('data-tab-id', tabId); // Add chat tab to DOM $tabs.appendChild($tab); // Wire chat tab up to open config on right click $tab.addEventListener('contextmenu', clickEvent => { const mousePos = { x: clickEvent.pageX, y: clickEvent.pageY }; showChatTabConfigWindow(tabId, mousePos); }); // And select chat tab on left click $tab.addEventListener('click', () => { selectChatTab(tabId); }); if (!isInittingTab) { // Select the newly added chat tab selectChatTab(tabId); } // Returning tabId to all adding new tab to pass tab ID to `showChatTabConfigWindow` return tabId; } // Selects chat tab [on click], updating client chat filters and custom chat filters function selectChatTab(tabId) { const state = (0, _state.getState)(); // Remove selected class from everything, then add selected class to clicked tab Array.from(document.querySelectorAll('[data-tab-id]')).forEach($tab => { $tab.classList.remove('js-selected-tab'); }); const $tab = document.querySelector(`[data-tab-id="${tabId}"]`); $tab.classList.add('js-selected-tab'); const tabFilters = state.chatTabs.find(tab => tab.id === tabId).filters; // Simulating clicks on the filters to turn them on/off const $filterButtons = Array.from(document.querySelectorAll('.channelselect small')); Object.keys(tabFilters).forEach(filter => { const $filterButton = $filterButtons.find($btn => $btn.textContent === filter); const isCurrentlyFiltered = $filterButton.classList.contains('textgrey'); // If is currently filtered but filter for this tab is turned off, click it to turn filter off if (isCurrentlyFiltered && !tabFilters[filter]) { $filterButton.click(); } // If it is not currently filtered but filter for this tab is turned on, click it to turn filter on if (!isCurrentlyFiltered && tabFilters[filter]) { $filterButton.click(); } }); // Update state for our custom chat filters to match the tab's configuration, then filter chat for it const isGMChatVisible = !tabFilters.GM; chat.setGMChatVisibility(isGMChatVisible); // Update the selected tab in state state.selectedChatTabId = tabId; (0, _state.saveState)(); } },{"../../utils/chat":21,"../../utils/misc":22,"../../utils/state":24}],8:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var helpers = _interopRequireWildcard(require("./helpers")); var _state = require("../../utils/state"); var _misc = require("../../utils/misc"); function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; } function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } // Creates DOM elements and wires them up for custom chat tabs and chat tab config // Note: Should be done after creating new custom chat filters function customChatTabs() { const state = (0, _state.getState)(); const tempState = (0, _state.getTempState)(); // Create the chat tab configuration DOM const $chatTabConfigurator = (0, _misc.makeElement)({ element: 'div', class: 'uimod-chat-tab-config js-chat-tab-config', content: ` <h1>Chat Tab Config</h1> <div class="uimod-chat-tab-config-grid"> <div>Name</div><input type="text" class="js-chat-tab-name" value="untitled"></input> <div class="btn orange js-remove-chat-tab">Remove</div><div class="btn blue js-save-chat-tab">Ok</div> </div> ` }); document.body.append($chatTabConfigurator); // Wire it up document.querySelector('.js-remove-chat-tab').addEventListener('click', () => { // Remove the chat tab from state const editedChatTab = state.chatTabs.find(tab => tab.id === tempState.editedChatTabId); const editedChatTabIndex = state.chatTabs.indexOf(editedChatTab); state.chatTabs.splice(editedChatTabIndex, 1); // Remove the chat tab from DOM const $chatTab = document.querySelector(`[data-tab-id="${tempState.editedChatTabId}"]`); $chatTab.parentNode.removeChild($chatTab); // If we just removed the currently selected chat tab if (tempState.editedChatTabId === state.selectedChatTabId) { // Select the chat tab to the left of the removed one const nextChatTabIndex = editedChatTabIndex === 0 ? 0 : editedChatTabIndex - 1; helpers.selectChatTab(state.chatTabs[nextChatTabIndex].id); } // Close chat tab config document.querySelector('.js-chat-tab-config').style.display = 'none'; }); document.querySelector('.js-save-chat-tab').addEventListener('click', () => { // Set new chat tab name in DOM const $chatTab = document.querySelector(`[data-tab-id="${state.selectedChatTabId}"]`); const newName = document.querySelector('.js-chat-tab-name').value; $chatTab.textContent = newName; // Set new chat tab name in state // `selectedChatTab` is a reference on `state.chatTabs`, so updating it above still updates it in the state - we want to save that const selectedChatTab = state.chatTabs.find(tab => tab.id === state.selectedChatTabId); selectedChatTab.name = newName; (0, _state.saveState)(); // Close chat tab config document.querySelector('.js-chat-tab-config').style.display = 'none'; }); // Create the initial chat tabs HTML const $chat = document.querySelector('#chat'); const $chatTabs = (0, _misc.makeElement)({ element: 'div', class: 'uimod-chat-tabs js-chat-tabs', content: '<div class="js-chat-tab-add">+</div>' }); // Add them to the DOM $chat.parentNode.insertBefore($chatTabs, $chat); // Add all our chat tabs from state state.chatTabs.forEach(chatTab => { const isInittingTab = true; helpers.addChatTab(chatTab, isInittingTab); }); // Wire up the add chat tab button document.querySelector('.js-chat-tab-add').addEventListener('click', clickEvent => { const chatTabId = helpers.addChatTab(); const mousePos = { x: clickEvent.pageX, y: clickEvent.pageY }; helpers.showChatTabConfigWindow(chatTabId, mousePos); }); // If initial chat tab doesn't exist, create it based off current filter settings if (!Object.keys(state.chatTabs).length) { const tabId = (0, _misc.uuid)(); const chatTab = { name: 'Main', id: tabId, filters: helpers.getCurrentChatFilters() }; state.chatTabs.push(chatTab); (0, _state.saveState)(); helpers.addChatTab(chatTab); } // Wire up click event handlers onto the filters to update the selected chat tab's filters in state document.querySelector('.channelselect').addEventListener('click', clickEvent => { const $elementMouseIsOver = document.elementFromPoint(clickEvent.clientX, clickEvent.clientY); // We only want to change the filters if the user manually clicks the filter button // If they clicked a chat tab and we programatically set filters, we don't want to update // the current tab's filter state if (!$elementMouseIsOver.classList.contains('btn')) { return; } const selectedChatTab = state.chatTabs.find(tab => tab.id === state.selectedChatTabId); selectedChatTab.filters = helpers.getCurrentChatFilters(); (0, _state.saveState)(); }); // Select the currently selected tab in state on mod initialization if (state.selectedChatTabId) { helpers.selectChatTab(state.selectedChatTabId); } } var _default = { name: 'Chat tabs', description: 'Enables support for multiple chat tabs', run: customChatTabs }; exports.default = _default; },{"../../utils/misc":22,"../../utils/state":24,"./helpers":7}],9:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.dragElement = dragElement; // Credit: https://stackoverflow.com/a/14234618 (Has been slightly modified) // $draggedElement is the item that will be dragged. // $dragTrigger is the element that must be held down to drag $draggedElement function dragElement($draggedElement, $dragTrigger) { let offset = [0, 0]; let isDown = false; $dragTrigger.addEventListener('mousedown', function (e) { isDown = true; offset = [$draggedElement.offsetLeft - e.clientX, $draggedElement.offsetTop - e.clientY]; }, true); document.addEventListener('mouseup', function () { isDown = false; }, true); document.addEventListener('mousemove', function (e) { event.preventDefault(); if (isDown) { $draggedElement.style.left = e.clientX + offset[0] + 'px'; $draggedElement.style.top = e.clientY + offset[1] + 'px'; } }, true); } },{}],10:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var helpers = _interopRequireWildcard(require("./helpers")); var _state = require("../../utils/state"); function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; } function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } // Drag all windows by their header function draggableUIWindows() { Array.from(document.querySelectorAll('.window:not(.js-can-move)')).forEach($window => { $window.classList.add('js-can-move'); helpers.dragElement($window, $window.querySelector('.titleframe')); }); } // Save dragged UI windows position to state function saveDraggedUIWindows() { const state = (0, _state.getState)(); Array.from(document.querySelectorAll('.window:not(.js-window-is-saving)')).forEach($window => { $window.classList.add('js-window-is-saving'); const $draggableTarget = $window.querySelector('.titleframe'); const windowName = $draggableTarget.querySelector('[name="title"]').textContent; $draggableTarget.addEventListener('mouseup', () => { state.windowsPos[windowName] = $window.getAttribute('style'); (0, _state.saveState)(); }); }); } // Loads draggable UI windows position from state function loadDraggedUIWindowsPositions() { const state = (0, _state.getState)(); Array.from(document.querySelectorAll('.window:not(.js-has-loaded-pos)')).forEach($window => { $window.classList.add('js-has-loaded-pos'); const windowName = $window.querySelector('[name="title"]').textContent; const pos = state.windowsPos[windowName]; if (pos) { $window.setAttribute('style', pos); } }); } var _default = { name: 'Draggable Windows', description: 'Allows you to drag windows in the UI', run: ({ registerOnDomChange }) => { draggableUIWindows(); saveDraggedUIWindows(); loadDraggedUIWindowsPositions(); // As windows open, we want to make them draggable registerOnDomChange(saveDraggedUIWindows); registerOnDomChange(draggableUIWindows); registerOnDomChange(loadDraggedUIWindowsPositions); } }; exports.default = _default; },{"../../utils/state":24,"./helpers":9}],11:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _state = require("../../utils/state"); var player = _interopRequireWildcard(require("../../utils/player")); var _misc = require("../../utils/misc"); function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; } function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } // The F icon and the UI that appears when you click it function customFriendsList() { const state = (0, _state.getState)(); var friendsIconElement = (0, _misc.makeElement)({ element: 'div', class: 'btn border black js-friends-list-icon', content: 'F' }); // Add the icon to the right of Elixir icon const $elixirIcon = document.querySelector('#sysgem'); $elixirIcon.parentNode.insertBefore(friendsIconElement, $elixirIcon.nextSibling); // Create the friends list UI document.querySelector('.js-friends-list-icon').addEventListener('click', () => { if (document.querySelector('.js-friends-list')) { // Don't open the friends list twice. return; } let friendsListHTML = ''; Object.keys(state.friendsList).sort().forEach(friendName => { friendsListHTML += ` <div data-player-name="${friendName}">${friendName}</div> <div class="btn blue js-whisper-player" data-player-name="${friendName}">Whisper</div> <div class="btn blue js-party-player" data-player-name="${friendName}">Party invite</div> <div class="btn orange js-unfriend-player" data-player-name="${friendName}">X</div> <input type="text" class="js-friend-note" data-player-name="${friendName}" value="${state.friendNotes[friendName] || ''}"></input> `; }); const customFriendsWindowHTML = ` <h3 class="textprimary">Friends list</h3> <div class="uimod-friends">${friendsListHTML}</div> <p></p> <div class="btn purp js-close-custom-friends-list">Close</div> `; const $customFriendsList = (0, _misc.makeElement)({ element: 'div', class: 'menu panel-black js-friends-list uimod-custom-window', content: customFriendsWindowHTML }); document.body.appendChild($customFriendsList); // Wire up the buttons Array.from(document.querySelectorAll('.js-whisper-player')).forEach($button => { $button.addEventListener('click', clickEvent => { const name = clickEvent.target.getAttribute('data-player-name'); player.whisperPlayer(name); }); }); Array.from(document.querySelectorAll('.js-party-player')).forEach($button => { $button.addEventListener('click', clickEvent => { const name = clickEvent.target.getAttribute('data-player-name'); player.partyPlayer(name); }); }); Array.from(document.querySelectorAll('.js-unfriend-player')).forEach($button => { $button.addEventListener('click', clickEvent => { const name = clickEvent.target.getAttribute('data-player-name'); player.unfriendPlayer(name); // Remove the blocked player from the list Array.from(document.querySelectorAll(`.js-friends-list [data-player-name="${name}"]`)).forEach($element => { $element.parentNode.removeChild($element); }); }); }); Array.from(document.querySelectorAll('.js-friend-note')).forEach($element => { $element.addEventListener('change', clickEvent => { const name = clickEvent.target.getAttribute('data-player-name'); state.friendNotes[name] = clickEvent.target.value; }); }); // The close button for our custom UI document.querySelector('.js-close-custom-friends-list').addEventListener('click', () => { const $friendsListWindow = document.querySelector('.js-friends-list'); $friendsListWindow.parentNode.removeChild($friendsListWindow); }); }); } var _default = { name: 'Friends list', description: 'Allows access to your friends list from the top right F icon', run: customFriendsList }; exports.default = _default; },{"../../utils/misc":22,"../../utils/player":23,"../../utils/state":24}],12:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _modStart = _interopRequireDefault(require("./_modStart")); var _blockedPlayerSettings = _interopRequireDefault(require("./blockedPlayerSettings")); var _chatContextMenu = _interopRequireDefault(require("./chatContextMenu")); var _chatFilters = _interopRequireDefault(require("./chatFilters")); var _chatTabs = _interopRequireDefault(require("./chatTabs")); var _draggableUI = _interopRequireDefault(require("./draggableUI")); var _friendsList = _interopRequireDefault(require("./friendsList")); var _mapControls = _interopRequireDefault(require("./mapControls")); var _resizableChat = _interopRequireDefault(require("./resizableChat")); var _resizableMap = _interopRequireDefault(require("./resizableMap")); var _selectedWindowIsTop = _interopRequireDefault(require("./selectedWindowIsTop")); var _xpMeter = _interopRequireDefault(require("./xpMeter")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } // TODO: Get /cssMods/styles.scss somehow, maybe create index.js with imported css file? Maybe separate default styles.scss outside of mods? // The array here dictates the order of which mods are executed, from top to bottom var _default = [_modStart.default, _resizableMap.default, _mapControls.default, _friendsList.default, _blockedPlayerSettings.default, _resizableChat.default, _chatFilters.default, _chatContextMenu.default, _chatTabs.default, _draggableUI.default, _selectedWindowIsTop.default, _xpMeter.default]; exports.default = _default; },{"./_modStart":2,"./blockedPlayerSettings":3,"./chatContextMenu":5,"./chatFilters":6,"./chatTabs":8,"./draggableUI":10,"./friendsList":11,"./mapControls":14,"./resizableChat":15,"./resizableMap":17,"./selectedWindowIsTop":18,"./xpMeter":20}],13:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.updateMapOpacity = updateMapOpacity; var _state = require("../../utils/state"); // On load, update map opacity to match state // We modify the opacity of the canvas and the background color alpha of the parent container // We do this to allow our opacity buttons to be visible on hover with 100% opacity // (A surprisingly difficult enough task to require this implementation) function updateMapOpacity() { const state = (0, _state.getState)(); const $map = document.querySelector('.container canvas'); const $mapContainer = document.querySelector('.js-map'); $map.style.opacity = String(state.mapOpacity / 100); const mapContainerBgColor = window.getComputedStyle($mapContainer, null).getPropertyValue('background-color'); // Credit for this regexp + This opacity+rgba dual implementation: https://stackoverflow.com/questions/16065998/replacing-changing-alpha-in-rgba-javascript let opacity = state.mapOpacity / 100; // This is a slightly lazy browser workaround to fix a bug. // If the opacity is `1`, and it sets `rgba` to `1`, then the browser changes the // rgba to rgb, dropping the alpha. We could account for that and add the `alpha` back in // later, but setting the max opacity to very close to 1 makes sure the issue never crops up. // Fun fact: 0.99 retains the alpha, but setting this to 0.999 still causes the browser to drop the alpha. Rude. if (opacity === 1) { opacity = 0.99; } const newBgColor = mapContainerBgColor.replace(/[\d\.]+\)$/g, `${opacity})`); $mapContainer.style['background-color'] = newBgColor; // Update the button opacity const $addBtn = document.querySelector('.js-map-opacity-add'); const $minusBtn = document.querySelector('.js-map-opacity-minus'); // Hide plus button if the opacity is max if (state.mapOpacity === 100) { $addBtn.style.visibility = 'hidden'; } else { $addBtn.style.visibility = 'visible'; } // Hide minus button if the opacity is lowest if (state.mapOpacity === 0) { $minusBtn.style.visibility = 'hidden'; } else { $minusBtn.style.visibility = 'visible'; } } },{"../../utils/state":24}],14:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _state = require("../../utils/state"); var helpers = _interopRequireWildcard(require("./helpers")); var _misc = require("../../utils/misc"); function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; } function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } function mapControls() { const state = (0, _state.getState)(); const $map = document.querySelector('.container canvas'); if (!$map.parentNode.classList.contains('js-map')) { $map.parentNode.classList.add('js-map'); } const $mapContainer = document.querySelector('.js-map'); const $mapButtons = (0, _misc.makeElement)({ element: 'div', class: 'js-map-btns', content: ` <button class="js-map-opacity-add">+</button> <button class="js-map-opacity-minus">-</button> <button class="js-map-reset">r</button> ` }); // Add it right before the map container div $map.parentNode.insertBefore($mapButtons, $map); helpers.updateMapOpacity(); const $addBtn = document.querySelector('.js-map-opacity-add'); const $minusBtn = document.querySelector('.js-map-opacity-minus'); const $resetBtn = document.querySelector('.js-map-reset'); // Hide the buttons if map opacity is maxed/minimum if (state.mapOpacity === 100) { $addBtn.style.visibility = 'hidden'; } if (state.mapOpacity === 0) { $minusBtn.style.visibility = 'hidden'; } // Wire it up $addBtn.addEventListener('click', clickEvent => { // Update opacity state.mapOpacity += 10; (0, _state.saveState)(); helpers.updateMapOpacity(); }); $minusBtn.addEventListener('click', clickEvent => { // Update opacity state.mapOpacity -= 10; (0, _state.saveState)(); helpers.updateMapOpacity(); }); $resetBtn.addEventListener('click', clickEvent => { state.mapOpacity = 70; state.mapWidth = '174px'; state.mapHeight = '174px'; (0, _state.saveState)(); helpers.updateMapOpacity(); $mapContainer.style.width = state.mapWidth; $mapContainer.style.height = state.mapHeight; }); helpers.updateMapOpacity(); } var _default = { name: 'Map controls', description: 'Enables hovering over the minimap to show buttons that let you increase or decrease the opacity of the map, or reset the size+transparency of it', run: mapControls }; exports.default = _default; },{"../../utils/misc":22,"../../utils/state":24,"./helpers":13}],15:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _state = require("../../utils/state"); function resizableChat() { const state = (0, _state.getState)(); // Add the appropriate classes const $chatContainer = document.querySelector('#chat').parentNode; $chatContainer.classList.add('js-chat-resize'); // Load initial chat and map size if (state.chatWidth && state.chatHeight) { $chatContainer.style.width = state.chatWidth; $chatContainer.style.height = state.chatHeight; } // Save chat size on resize const resizeObserverChat = new ResizeObserver(() => { const chatWidthStr = window.getComputedStyle($chatContainer, null).getPropertyValue('width'); const chatHeightStr = window.getComputedStyle($chatContainer, null).getPropertyValue('height'); state.chatWidth = chatWidthStr; state.chatHeight = chatHeightStr; (0, _state.saveState)(); }); resizeObserverChat.observe($chatContainer); } var _default = { name: 'Resizable chat', description: 'Allows you to resize chat by clicking and dragging from the bottom right of chat', run: resizableChat }; exports.default = _default; },{"../../utils/state":24}],16:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.onMapResize = onMapResize; exports.triggerMapResize = triggerMapResize; var _state = require("../../utils/state"); // When the map container resizes, we want to update the canvas width/height and the state function onMapResize(e) { if (!document.querySelector('.layout')) { return; } const state = (0, _state.getState)(); const tempState = (0, _state.getTempState)(); const $map = document.querySelector('.container canvas').parentNode; const $canvas = $map.querySelector('canvas'); // Get real values of map height/width, excluding padding/margin/etc const mapWidthStr = window.getComputedStyle($map, null).getPropertyValue('width'); const mapHeightStr = window.getComputedStyle($map, null).getPropertyValue('height'); const mapWidth = Number(mapWidthStr.slice(0, -2)); const mapHeight = Number(mapHeightStr.slice(0, -2)); // If height/width are 0 or unset, don't resize canvas if (!mapWidth || !mapHeight) { return; } if ($canvas.width !== mapWidth) { $canvas.width = mapWidth; } if ($canvas.height !== mapHeight) { $canvas.height = mapHeight; } // If we're clicking map, i.e. manually resizing, then save state // Don't save state when minimizing/maximizing map via [M] if (tempState.clickingMap) { state.mapWidth = mapWidthStr; state.mapHeight = mapHeightStr; (0, _state.saveState)(); } else { const isMaximized = mapWidth > tempState.lastMapWidth && mapHeight > tempState.lastMapHeight; if (!isMaximized) { $map.style.width = state.mapWidth; $map.style.height = state.mapHeight; } } // Store last map width/height in temp state, so we know if we've minimized or maximized tempState.lastMapWidth = mapWidth; tempState.lastMapHeight = mapHeight; } // We need to observe canvas resizes to tell when the user presses M to open the big map // At that point, we resize the map to match the canvas function triggerMapResize() { if (!document.querySelector('.layout')) { return; } const $map = document.querySelector('.container canvas').parentNode; const $canvas = $map.querySelector('canvas'); // Get real values of map height/width, excluding padding/margin/etc const mapWidthStr = window.getComputedStyle($map, null).getPropertyValue('width'); const mapHeightStr = window.getComputedStyle($map, null).getPropertyValue('height'); const mapWidth = Number(mapWidthStr.slice(0, -2)); const mapHeight = Number(mapHeightStr.slice(0, -2)); // If height/width are 0 or unset, we don't care about resizing yet if (!mapWidth || !mapHeight) { return; } if ($canvas.width !== mapWidth) { $map.style.width = `${$canvas.width}px`; } if ($canvas.height !== mapHeight) { $map.style.height = `${$canvas.height}px`; } } },{"../../utils/state":24}],17:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _state = require("../../utils/state"); var helpers = _interopRequireWildcard(require("./helpers")); var _misc = require("../../utils/misc"); function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; } function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } function resizableMap() { const state = (0, _state.getState)(); const tempState = (0, _state.getTempState)(); const $map = document.querySelector('.container canvas').parentNode; const $canvas = $map.querySelector('canvas'); $map.classList.add('js-map-resize'); // Track whether we're clicking (resizing) map or not // Used to detect if resize changes are manually done, or from minimizing/maximizing map (with [M]) $map.addEventListener('mousedown', () => { tempState.clickingMap = true; }); // Sometimes the mouseup event may be registered outside of the map - we account for this document.body.addEventListener('mouseup', () => { tempState.clickingMap = false; }); if (state.mapWidth && state.mapHeight) { $map.style.width = state.mapWidth; $map.style.height = state.mapHeight; helpers.onMapResize(); // Update canvas size on initial load of saved map size } // On resize of map, resize canvas to match // Debouncing allows map to be visible while resizing const debouncedMapResize = (0, _misc.debounce)(helpers.onMapResize, 1); const resizeObserverMap = new ResizeObserver(debouncedMapResize); helpers.onMapResize(); resizeObserverMap.observe($map); // We debounce the canvas resize, so it doesn't resize every single // pixel you move when resizing the DOM. If this were to happen, // resizing would constantly be interrupted. You'd have to resize a tiny bit, // lift left click, left click again to resize a tiny bit more, etc. // Resizing is smooth when we debounce this canvas. const debouncedTriggerResize = (0, _misc.debounce)(helpers.triggerMapResize, 50); const resizeObserverCanvas = new ResizeObserver(debouncedTriggerResize); resizeObserverCanvas.observe($canvas); } var _default = { name: 'Resizable map', description: 'Allows you to resize the map by clicking and dragging from the bottom left', run: resizableMap }; exports.default = _default; },{"../../utils/misc":22,"../../utils/state":24,"./helpers":16}],18:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; // The last clicked UI window displays above all other UI windows // This is useful when, for example, your inventory is near the market window, // and you want the window and the tooltips to display above the market window. function selectedWindowIsTop() { Array.from(document.querySelectorAll('.window:not(.js-is-top-initd)')).forEach($window => { $window.classList.add('js-is-top-initd'); $window.addEventListener('mousedown', () => { // First, make the other is-top window not is-top const $otherWindowContainer = document.querySelector('.js-is-top'); if ($otherWindowContainer) { $otherWindowContainer.classList.remove('js-is-top'); } // Then, make our window's container (the z-index container) is-top $window.parentNode.classList.add('js-is-top'); }); }); } var _default = { name: 'Make Selected Window Top', description: 'The UI window you click will always be displayed over other UI windows', run: ({ registerOnDomChange }) => { selectedWindowIsTop(); // As windows are opened, we want to enable them to become the top window when they're clicked registerOnDomChange(selectedWindowIsTop); } }; exports.default = _default; },{}],19:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getCurrentCharacterLvl = getCurrentCharacterLvl; exports.getCurrentXp = getCurrentXp; exports.getNextLevelXp = getNextLevelXp; exports.resetXpMeterState = resetXpMeterState; exports.toggleXpMeterVisibility = toggleXpMeterVisibility; exports.msToString = msToString; var _state = require("../../utils/state"); function getCurrentCharacterLvl() { return Number(document.querySelector('#ufplayer .bgmana > .left').textContent.split('Lv. ')[1]); } function getCurrentXp() { return Number(document.querySelector('#expbar .progressBar > .left').textContent.split('/')[0].trim()); } function getNextLevelXp() { return Number(document.querySelector('#expbar .progressBar > .left').textContent.split('/')[1].trim()); } // user invoked reset of xp meter stats function resetXpMeterState() { const state = (0, _state.getState)(); state.xpMeterState.xpGains = []; // array of xp deltas every second state.xpMeterState.averageXp = 0; state.xpMeterState.gainedXp = 0; (0, _state.saveState)(); document.querySelector('.js-xp-time').textContent = '-:-:-'; } // toggle the xp meter function toggleXpMeterVisibility() { const xpMeterContainer = document.querySelector('.js-xpmeter'); xpMeterContainer.style.display = xpMeterContainer.style.display === 'none' ? 'block' : 'none'; } function msToString(ms) { const pad = value => value < 10 ? `0${value}` : value; const hours = pad(Math.floor(ms / (1000 * 60 * 60) % 60)); const minutes = pad(Math.floor(ms / (1000 * 60) % 60)); const seconds = pad(Math.floor(ms / 1000 % 60)); return `${hours}:${minutes}:${seconds}`; } },{"../../utils/state":24}],20:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _state = require("../../utils/state"); var helpers = _interopRequireWildcard(require("./helpers")); var _misc = require("../../utils/misc"); function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; } function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } // Adds XP Meter DOM icon and window, starts continuous interval to get current xp over time function xpMeter() { const state = (0, _state.getState)(); const tempState = (0, _state.getTempState)(); const $layoutContainer = document.querySelector('body > div.layout > div.container:nth-child(1)'); const $dpsMeterToggleElement = document.querySelector('#systrophy'); const $xpMeterToggleElement = (0, _misc.makeElement)({ element: 'div', class: 'js-sysxp js-xpmeter-icon btn border black', content: 'XP' }); const xpMeterHTMLString = ` <div class="l-corner-lr container uimod-xpmeter-1 js-xpmeter" style="display: none"> <div class="window panel-black uimod-xpmeter-2"> <div class="titleframe uimod-xpmeter-2"> <img src="/assets/ui/icons/trophy.svg?v=3282286" class="titleicon svgicon uimod-xpmeter-2"> <div class="textprimary title uimod-xpmeter-2"> <div name="title">Experience / XP</div> </div> <img src="/assets/ui/icons/cross.svg?v=3282286" class="js-xpmeter-close-icon btn black svgicon"> </div> <div class="slot uimod-xpmeter-2" style=""> <div class="wrapper uimod-xpmeter-1"> <div class="bar uimod-xpmeter-3" style="z-index: 0;"> <div class="progressBar bgc1 uimod-xpmeter-3" style="width: 100%; font-size: 1em;"> <span class="left uimod-xpmeter-3">XP per minute:</span> <span class="right uimod-xpmeter-3 js-xpm">-</span> </div> <div class="progressBar bgc1 uimod-xpmeter-3" style="width: 100%; font-size: 1em;"> <span class="left uimod-xpmeter-3">XP per hour:</span> <span class="right uimod-xpmeter-3 js-xph">-</span> </div> <div class="progressBar bgc1 uimod-xpmeter-3" style="width: 100%; font-size: 1em;"> <span class="left uimod-xpmeter-3">XP Gained:</span> <span class="right uimod-xpmeter-3 js-xpg">-</span> </div> <div class="progressBar bgc1 uimod-xpmeter-3" style="width: 100%; font-size: 1em;"> <span class="left uimod-xpmeter-3">XP Left:</span> <span class="right uimod-xpmeter-3 js-xpl">-</span> </div> <div class="progressBar bgc1 uimod-xpmeter-3" style="width: 100%; font-size: 1em;"> <span class="left uimod-xpmeter-3">Session Time: </span> <span class="right uimod-xpmeter-3 js-xp-s-time">-</span> </div> <div class="progressBar bgc1 uimod-xpmeter-3" style="width: 100%; font-size: 1em;"> <span class="left uimod-xpmeter-3">Time to lvl: </span> <span class="right uimod-xpmeter-3 js-xp-time">-</span> </div> </div> </div> <div class="grid buttons marg-top uimod-xpmeter-1 js-xpmeter-reset-button"> <div class="btn grey">Reset</div> </div> </div> </div> </div> `; $dpsMeterToggleElement.parentNode.insertBefore($xpMeterToggleElement, $dpsMeterToggleElement.nextSibling); const $xpMeterElement = (0, _misc.makeElement)({ element: 'div', content: xpMeterHTMLString.trim() }); $layoutContainer.appendChild($xpMeterElement.firstChild); // Wire up icon and xpmeter window document.querySelector('.js-sysxp').addEventListener('click', helpers.toggleXpMeterVisibility); document.querySelector('.js-xpmeter-close-icon').addEventListener('click', helpers.toggleXpMeterVisibility); document.querySelector('.js-xpmeter-reset-button').addEventListener('click', helpers.resetXpMeterState); state.xpMeterState.currentXp = helpers.getCurrentXp(); state.xpMeterState.currentLvl = helpers.getCurrentCharacterLvl(); (0, _state.saveState)(); if (tempState.xpMeterInterval) clearInterval(tempState.xpMeterInterval); // every second we run the operations for xp meter, update xps, calc delta, etc // TODO Cleanup: This interval may not be cleaned up if the UI mod reinitializes, // e.g. user is away from tab for a while then comes back // Should confirm if this is an issue, and try to fix it if possible. tempState.xpMeterInterval = setInterval(() => { if (!document.querySelector('#expbar')) { return; } const currentXp = helpers.getCurrentXp(); const nextLvlXp = helpers.getNextLevelXp(); const currentLvl = helpers.getCurrentCharacterLvl(); state.xpMeterState.gainedXp += currentXp - state.xpMeterState.currentXp; state.xpMeterState.xpGains.push(currentXp - state.xpMeterState.currentXp); // array of xp deltas every second state.xpMeterState.currentXp = currentXp; state.xpMeterState.averageXp = state.xpMeterState.xpGains.reduce((a, b) => a + b) / state.xpMeterState.xpGains.length; (0, _state.saveState)(); if (document.querySelector('.js-xpmeter')) { document.querySelector('.js-xpm').textContent = parseInt((state.xpMeterState.averageXp * 60).toFixed(0)).toLocaleString(); document.querySelector('.js-xph').textContent = parseInt((state.xpMeterState.averageXp * 60 * 60).toFixed(0)).toLocaleString(); document.querySelector('.js-xpg').textContent = state.xpMeterState.gainedXp.toLocaleString(); document.querySelector('.js-xpl').textContent = (nextLvlXp - currentXp).toLocaleString(); document.querySelector('.js-xp-s-time').textContent = helpers.msToString(state.xpMeterState.xpGains.length * 1000); // need a positive integer for averageXp to calc time left if (state.xpMeterState.averageXp > 0) document.querySelector('.js-xp-time').textContent = helpers.msToString((nextLvlXp - currentXp) / state.xpMeterState.averageXp * 1000); } if (state.xpMeterState.currentLvl < currentLvl) { helpers.resetXpMeterState(); state.xpMeterState.currentLvl = currentLvl; (0, _state.saveState)(); } }, 1000); } var _default = { name: 'XP Meter', description: "Tracks your XP/minute and displays how much XP you're getting and lets you know how long until you level up", run: xpMeter }; exports.default = _default; },{"../../utils/misc":22,"../../utils/state":24,"./helpers":19}],21:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.setGMChatVisibility = setGMChatVisibility; exports.filterAllChat = filterAllChat; exports.whisperPlayer = whisperPlayer; exports.partyPlayer = partyPlayer; exports.addChatMessage = addChatMessage; var _state = require("./state"); var _misc = require("./misc"); // Updates state.chat.GM and the DOM to make text white/grey depending on if gm chat is visible/filtered // Then filters chat and saves updated chat state function setGMChatVisibility(isGMChatVisible) { const state = (0, _state.getState)(); const $chatGM = document.querySelector(`.js-chat-gm`); state.chat.GM = isGMChatVisible; $chatGM.classList.toggle('textgrey', !state.chat.GM); filterAllChat(); (0, _state.saveState)(); } // Filters all chat based on custom filters function filterAllChat() { const state = (0, _state.getState)(); // Blocked user filter Object.keys(state.blockList).forEach(blockedName => { // Get the `.name` elements from the blocked user, if we haven't already hidden their messages const $blockedChatNames = Array.from(document.querySelectorAll(`[data-chat-name="${blockedName}"]:not(.js-line-blocked)`)); // Hide each of their messages $blockedChatNames.forEach($name => { // Add the class name to $name so we can track whether it's been hidden in our CSS selector $blockedChatNames $name.classList.add('js-line-blocked'); const $line = $name.parentNode.parentNode.parentNode; // Add the class name to $line so we can visibly hide the entire chat line $line.classList.add('js-line-blocked'); }); }); // Custom channel filter Object.keys(state.chat).forEach(channel => { Array.from(document.querySelectorAll(`.text${channel}.content`)).forEach($textItem => { const $line = $textItem.parentNode.parentNode; $line.classList.toggle('js-line-hidden', !state.chat[channel]); }); }); } function enterTextIntoChat(text) { // Open chat input const enterEvent = new KeyboardEvent('keydown', { bubbles: true, cancelable: true, keyCode: 13 }); document.body.dispatchEvent(enterEvent); // Place text into chat const $input = document.querySelector('#chatinput input'); $input.value = text; // Get chat input to recognize slash commands and change the channel // by triggering the `input` event. // (Did some debugging to figure out the channel only changes when the // svelte `input` event listener exists.) const inputEvent = new KeyboardEvent('input', { bubbles: true, cancelable: true }); $input.dispatchEvent(inputEvent); } function submitChat() { const $input = document.querySelector('#chatinput input'); const kbEvent = new KeyboardEvent('keydown', { bubbles: true, cancelable: true, keyCode: 13 }); $input.dispatchEvent(kbEvent); } // Automated chat command helpers // (We've been OK'd to do these by the dev - all automation like this should receive approval from the dev) function whisperPlayer(playerName) { enterTextIntoChat(`/${playerName} `); } function partyPlayer(playerName) { enterTextIntoChat(`/partyinvite ${playerName}`); submitChat(); } // Pushes message to chat // TODO: The margins for the message are off slightly compared to other messages - why? function addChatMessage(text) { const newMessageHTML = ` <div class="linewrap svelte-1vrlsr3"> <span class="time svelte-1vrlsr3">00.00</span> <span class="textuimod content svelte-1vrlsr3"> <span class="capitalize channel svelte-1vrlsr3">UIMod</span> </span> <span class="svelte-1vrlsr3">${text}</span> </div> `; const element = (0, _misc.makeElement)({ element: 'article', class: 'line svelte-1vrlsr3', content: newMessageHTML }); document.querySelector('#chat').appendChild(element); } },{"./misc":22,"./state":24}],22:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.makeElement = makeElement; exports.debounce = debounce; exports.uuid = uuid; // Nicer impl to create elements in one method call function makeElement(args) { const $node = document.createElement(args.element); if (args.class) { $node.className = args.class; } if (args.content) { $node.innerHTML = args.content; } if (args.src) { $node.src = args.src; } return $node; } // Credit: David Walsh function debounce(func, wait, immediate) { var timeout; return function () { var context = this, args = arguments; var later = function () { timeout = null; if (!immediate) func.apply(context, args); }; var callNow = immediate && !timeout; clearTimeout(timeout); timeout = setTimeout(later, wait); if (callNow) func.apply(context, args); }; } // Credit: https://gist.github.com/jcxplorer/823878 // Generate random UUID string function uuid() { var uuid = '', i, random; for (i = 0; i < 32; i++) { random = Math.random() * 16 | 0; if (i == 8 || i == 12 || i == 16 || i == 20) { uuid += '-'; } uuid += (i == 12 ? 4 : i == 16 ? random & 3 | 8 : random).toString(16); } return uuid; } },{}],23:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.friendPlayer = friendPlayer; exports.unfriendPlayer = unfriendPlayer; exports.blockPlayer = blockPlayer; exports.unblockPlayer = unblockPlayer; var _state = require("./state"); var chat = _interopRequireWildcard(require("./chat")); function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; } function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } function friendPlayer(playerName) { const state = (0, _state.getState)(); if (state.friendsList[playerName]) { return; } state.friendsList[playerName] = true; chat.addChatMessage(`${playerName} has been added to your friends list.`); (0, _state.saveState)(); } function unfriendPlayer(playerName) { const state = (0, _state.getState)(); if (!state.friendsList[playerName]) { return; } delete state.friendsList[playerName]; delete state.friendNotes[playerName]; chat.addChatMessage(`${playerName} is no longer on your friends list.`); (0, _state.saveState)(); } // Adds player to block list, to be filtered out of chat function blockPlayer(playerName) { const state = (0, _state.getState)(); if (state.blockList[playerName]) { return; } state.blockList[playerName] = true; chat.filterAllChat(); chat.addChatMessage(`${playerName} has been blocked.`); (0, _state.saveState)(); } // Removes player from block list and makes their messages visible again function unblockPlayer(playerName) { const state = (0, _state.getState)(); delete state.blockList[playerName]; chat.addChatMessage(`${playerName} has been unblocked.`); (0, _state.saveState)(); // Make messages visible again const $chatNames = Array.from(document.querySelectorAll(`.js-line-blocked[data-chat-name="${playerName}"]`)); $chatNames.forEach($name => { $name.classList.remove('js-line-blocked'); const $line = $name.parentNode.parentNode.parentNode; $line.classList.remove('js-line-blocked'); }); } },{"./chat":21,"./state":24}],24:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getState = getState; exports.getTempState = getTempState; exports.saveState = saveState; exports.loadState = loadState; var _version = require("./version"); const STORAGE_STATE_KEY = 'hordesio-uimodsakaiyo-state'; // Manually saved in local state const state = { breakingVersion: _version.BREAKING_VERSION, chat: { GM: true }, windowsPos: {}, blockList: {}, friendsList: {}, mapOpacity: 70, // e.g. 70 = opacity: 0.7 friendNotes: {}, chatTabs: [], xpMeterState: { currentXp: 0, xpGains: [], // array of xp deltas every second averageXp: 0, gainedXp: 0, currentLvl: 0 } }; // tempState is saved only between page refreshes. const tempState = { // The last name clicked in chat chatName: null, lastMapWidth: 0, lastMapHeight: 0, xpMeterInterval: null // tracks the interval for fetching xp data }; function getState() { return state; } function getTempState() { return tempState; } function saveState() { localStorage.setItem(STORAGE_STATE_KEY, JSON.stringify(state)); } function loadState() { const storedStateJson = localStorage.getItem(STORAGE_STATE_KEY); if (storedStateJson) { const storedState = JSON.parse(storedStateJson); if (storedState.breakingVersion !== _version.BREAKING_VERSION) { localStorage.setItem(STORAGE_STATE_KEY, JSON.stringify(state)); return; } for (let [key, value] of Object.entries(storedState)) { state[key] = value; } } } },{"./version":25}],25:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.VERSION = exports.BREAKING_VERSION = void 0; // If this version is different from the user's stored state, // e.g. they have upgraded the version of this script and there are breaking changes, // then their stored state will be deleted. const BREAKING_VERSION = 1; // Used for initialization message in chat, and userscript version exports.BREAKING_VERSION = BREAKING_VERSION; const VERSION = '1.0.2'; exports.VERSION = VERSION; },{}]},{},[1]);