· 6 years ago · Jan 08, 2020, 11:44 AM
1// ==UserScript==
2// @name Hordes UI Mod
3// @version 1.2.5
4// @description Various UI mods for Hordes.io.
5// @author Sakaiyo & Chandog#6373 & Cullen
6// @match https://hordes.io/play
7// @grant GM_addStyle
8// @namespace https://greasyfork.org/users/160017
9// ==/UserScript==
10
11GM_addStyle(`/* Custom chat context menu, invisible by default */
12.js-chat-context-menu {
13 display: none; }
14
15.js-chat-context-menu .name {
16 color: white;
17 padding: 2px 4px; }
18
19/* Allow names in chat to be clicked (textf1 = BL, textf0 = VG) */
20#chat .name,
21.textwhisper .textf1,
22.textwhisper .textf0 {
23 pointer-events: all !important; }
24/* Custom chat filter colors */
25.js-chat-gm {
26 color: #a6dcd5; }
27
28/* Class that hides chat lines */
29.js-line-hidden,
30.js-line-blocked {
31 display: none; }
32/* Custom chat tabs */
33.uimod-chat-tabs {
34 position: fixed;
35 margin-top: -22px;
36 left: 5px;
37 pointer-events: all;
38 color: #5b858e;
39 font-size: 12px;
40 font-weight: bold; }
41
42.uimod-chat-tabs > div {
43 cursor: pointer;
44 background-color: rgba(0, 0, 0, 0.4);
45 border-top-right-radius: 4px;
46 border-top-left-radius: 4px;
47 display: inline-block;
48 border: 1px black solid;
49 border-bottom: 0;
50 margin-right: 2px;
51 padding: 3px 5px; }
52
53.uimod-chat-tabs > div:not(.js-selected-tab):hover {
54 border-color: #aaa; }
55
56.uimod-chat-tabs > .js-selected-tab {
57 color: #fff; }
58
59/* Chat tab custom config */
60.uimod-chat-tab-config {
61 position: absolute;
62 z-index: 9999999;
63 background-color: rgba(0, 0, 0, 0.6);
64 color: white;
65 border-radius: 3px;
66 text-align: center;
67 padding: 8px 12px 8px 6px;
68 width: 175px;
69 font-size: 14px;
70 border: 1px solid black;
71 display: none; }
72
73.uimod-chat-tab-config-grid {
74 grid-template-columns: 35% 65%;
75 display: grid;
76 grid-gap: 6px;
77 align-items: center; }
78
79.uimod-chat-tab-config h1 {
80 font-size: 16px;
81 margin-top: 0; }
82
83.uimod-chat-tab-config .btn,
84.uimod-chat-tab-config input {
85 font-size: 12px; }
86/* Lazy way to get tables to display side by side, given they share their container with various other elements */
87.uimod-clan-lastseen-table {
88 float: right;
89 width: 25%;
90 /* Make the last seen table look like its part of the main clan members table */
91 position: relative;
92 right: 1px;
93 border-top-left-radius: 0;
94 border-bottom-left-radius: 0; }
95 .uimod-clan-lastseen-table tr.js-offline-member {
96 opacity: 0.5; }
97
98.uimod-clan-members-table {
99 float: left;
100 width: 75%; }
101/* Custom css for settings page, duplicates preexisting settings pane grid */
102.uimod-settings {
103 display: grid;
104 grid-template-columns: 2fr 3fr;
105 grid-gap: 8px;
106 align-items: center;
107 max-height: 390px;
108 margin: 0 20px;
109 overflow-y: auto; }
110/* Allows windows and frames to be moved */
111.window,
112.partyframes,
113#ufplayer,
114#uftarget,
115#skillbar,
116.js-map {
117 position: relative; }
118
119/* Retaining the default party frame with so we can override the "style" property */
120.partyframes {
121 width: 200px; }
122
123/* All purpose hidden class */
124.js-hidden {
125 display: none; }
126/* Friends list CSS, similar to settings but supports 4 columns */
127.uimod-friends {
128 display: grid;
129 grid-template-columns: 2fr 1.1fr 1.5fr 0.33fr 3fr;
130 grid-gap: 8px;
131 align-items: center;
132 max-height: 390px;
133 margin: 0 20px;
134 overflow-y: auto; }
135
136/* Helps imitate normal UI window */
137.uimod-friends-list-helper.titleframe {
138 line-height: 1em;
139 display: flex;
140 align-items: start;
141 position: relative;
142 letter-spacing: 0.5px;
143 margin-top: 8px; }
144
145.uimod-friends-list-helper.titleicon {
146 margin: 3px; }
147
148.uimod-friends-list-helper.title {
149 width: 100%;
150 padding-left: 4px;
151 font-weight: bold; }
152
153.uimod-friends-intro {
154 width: 100%;
155 margin: 4px 0 14px;
156 text-align: center;
157 border-bottom: 2px solid #999;
158 padding-bottom: 6px;
159 font-weight: bold;
160 user-select: none; }
161.uimod-locked-slot {
162 pointer-events: all;
163 z-index: 10;
164 background: rgba(255, 0, 0, 0.4);
165 position: absolute;
166 width: 46px;
167 height: 46px; }
168.js-map-btns {
169 position: absolute;
170 top: 8px;
171 right: 8px;
172 z-index: 999;
173 width: 100px;
174 height: 100px;
175 text-align: right;
176 display: none;
177 pointer-events: all; }
178
179.js-map-btns:hover {
180 display: block; }
181
182.js-map-btns button {
183 border-radius: 10px;
184 font-size: 18px;
185 padding: 0 5px;
186 background: rgba(0, 0, 0, 0.4);
187 border: 0;
188 color: white;
189 font-weight: bold;
190 cursor: pointer; }
191
192/* On hover of map, show opacity controls */
193.js-map:hover .js-map-btns {
194 display: block; }
195/* Mirror styles of other merchant inputs */
196.uidom-merchant-input {
197 margin: 4px 0;
198 align-self: center; }
199
200/* Add 225px column for new filters input */
201.uidom-merchant-with-filters .search {
202 grid-template-columns: 120px auto 50px auto 50px 225px 1fr auto auto; }
203.js-chat-resize {
204 resize: both;
205 overflow: auto; }
206.js-map {
207 /* This makes sure scroll bars don't appear when resizing the map */
208 overflow: hidden; }
209
210.js-map-resize:hover {
211 resize: both;
212 overflow: auto;
213 direction: rtl; }
214/* Allows last clicked window to appear above all other windows */
215.js-is-top {
216 z-index: 9998 !important; }
217
218.panel.context:not(.commandlist) {
219 z-index: 9999 !important; }
220
221/* The item icon being dragged in the inventory */
222.container.svelte-120o2pb {
223 z-index: 9999 !important; }
224.js-cooldown-num {
225 position: absolute;
226 bottom: 10px;
227 left: 0;
228 width: 40px;
229 text-align: center;
230 font-weight: bold;
231 color: white;
232 pointer-events: none; }
233.container.uimod-xpmeter-1 {
234 z-index: 6; }
235
236.window.uimod-xpmeter-2 {
237 padding: 5px;
238 height: 100%;
239 display: grid;
240 grid-template-rows: 30px 1fr;
241 grid-gap: 4px;
242 transform-origin: inherit;
243 min-width: fit-content; }
244
245.titleframe.uimod-xpmeter-2 {
246 line-height: 1em;
247 display: flex;
248 align-items: center;
249 position: relative;
250 letter-spacing: 0.5px; }
251
252.titleicon.uimod-xpmeter-2 {
253 margin: 3px; }
254
255.title.uimod-xpmeter-2 {
256 width: 100%;
257 padding-left: 4px;
258 font-weight: bold; }
259
260.slot.uimod-xpmeter-2 {
261 min-height: 0; }
262
263.wrapper.uimod-xpmeter-1 {
264 width: 200px; }
265
266.bar.uimod-xpmeter-3 {
267 background-color: rgba(45, 66, 71, 0.7);
268 border-radius: 1.5px;
269 position: relative;
270 color: #DAE8EA;
271 overflow: hidden;
272 text-shadow: 1px 1px 2px #10131d;
273 white-space: nowrap;
274 text-transform: capitalize;
275 font-weight: bold; }
276
277.buttons.uimod-xpmeter-1 {
278 line-height: 1;
279 font-size: 13px; }
280
281.left.uimod-xpmeter-3 {
282 padding-left: 4px;
283 position: relative;
284 z-index: 1; }
285
286.right.uimod-xpmeter-3 {
287 position: absolute;
288 right: 7px;
289 z-index: 1; }
290/* This file is for CSS mods that don't fit in any other individual mod folder */
291/* Transparent chat bg color */
292.frame.svelte-1vrlsr3 {
293 background: rgba(0, 0, 0, 0.4); }
294
295/* Our mod's chat message color */
296.textuimod {
297 color: #00dd33; }
298
299/* The browser resize icon */
300*::-webkit-resizer {
301 background: linear-gradient(to right, rgba(51, 77, 80, 0), rgba(203, 202, 165, 0.5));
302 border-radius: 8px;
303 box-shadow: 0 1px 1px black; }
304
305*::-moz-resizer {
306 background: linear-gradient(to right, rgba(51, 77, 80, 0), rgba(203, 202, 165, 0.5));
307 border-radius: 8px;
308 box-shadow: 0 1px 1px black; }
309
310/* Our custom window, closely mirrors main settings window */
311.uimod-custom-window {
312 position: absolute;
313 top: 100px;
314 left: 50%;
315 transform: translate(-50%, 0);
316 min-width: 350px;
317 max-width: 600px;
318 width: 90%;
319 height: 80%;
320 min-height: 350px;
321 max-height: 500px;
322 z-index: 9;
323 padding: 0px 10px 5px; }
324`);
325
326(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){
327"use strict";
328
329var _mods = _interopRequireDefault(require("./mods"));
330
331function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
332
333function initialize() {
334 // If the Hordes.io tab isn't active for long enough, it reloads the entire page, clearing this mod
335 // We check for that and reinitialize the mod if that happens
336 const $layout = document.querySelector('.layout');
337
338 if ($layout.classList.contains('uimod-initd')) {
339 return;
340 }
341
342 $layout.classList.add('uimod-initd');
343 const rerunning = {
344 // MutationObserver running whenever .layout changes
345 onDomChange: [],
346 // Mutation observer running whenever #chat changes
347 onChatChange: [],
348 // `click` Event listener running on document.body
349 onLeftClick: [],
350 // `contextmenu` Event listener running on document.body
351 onRightClick: []
352 }; // Run all our mods
353
354 const registerOnDomChange = callback => rerunning.onDomChange.push(callback);
355
356 const registerOnChatChange = callback => rerunning.onChatChange.push(callback);
357
358 const registerOnLeftClick = callback => rerunning.onLeftClick.push(callback);
359
360 const registerOnRightClick = callback => rerunning.onRightClick.push(callback);
361
362 _mods.default.forEach(mod => {
363 mod.run({
364 registerOnDomChange,
365 registerOnChatChange,
366 registerOnLeftClick,
367 registerOnRightClick
368 });
369 }); // Continuously re-run specific mods methods that need to be executed on UI change
370
371
372 const rerunObserver = new MutationObserver(mutations => {
373 // If new window appears, e.g. even if window is closed and reopened, we need to rewire it
374 // Fun fact: Some windows always exist in the DOM, even when hidden, e.g. Inventory
375 // But some windows only exist in the DOM when open, e.g. Interaction
376 rerunning.onDomChange.forEach(callback => callback(mutations));
377 });
378 Array.from(document.querySelectorAll('.layout > .container, .actionbarcontainer, .partyframes, .targetframes')).forEach($container => {
379 rerunObserver.observe($container, {
380 attributes: false,
381 childList: true
382 });
383 }); // Rerun only on chat messages changing
384
385 const chatRerunObserver = new MutationObserver(mutations => {
386 rerunning.onChatChange.forEach(callback => callback(mutations));
387 });
388 chatRerunObserver.observe(document.querySelector('#chat'), {
389 attributes: false,
390 childList: true
391 }); // Event listeners for document.body might be kept when the game reloads, so don't reinitialize them
392
393 if (!document.body.classList.contains('js-uimod-initd')) {
394 document.body.classList.add('js-uimod-initd');
395 rerunning.onLeftClick.forEach(callback => document.body.addEventListener('click', callback));
396 rerunning.onRightClick.forEach(callback => document.body.addEventListener('contextmenu', callback));
397 }
398} // Initialize mods once UI DOM has loaded
399// Rerunning updates on every call to initialize
400
401
402const pageObserver = new MutationObserver(() => {
403 const isUiLoaded = !!document.querySelector('.layout');
404
405 if (isUiLoaded) {
406 initialize();
407 }
408});
409pageObserver.observe(document.body, {
410 attributes: true,
411 childList: true
412});
413
414},{"./mods":17}],2:[function(require,module,exports){
415"use strict";
416
417Object.defineProperty(exports, "__esModule", {
418 value: true
419});
420exports.default = void 0;
421
422var _state = require("../../utils/state");
423
424// Note: For a split second after these event handlers are added,
425// They may not actually be listening.
426// e.g. Refresh page with inventory open, immediately control+right click item
427// to copy its stats. It won't work because `keydown` didn't register the keydown event yet
428// Doesn't look like there's anything we can do about it, just something to keep in mind.
429function keyPressTracker() {
430 const tempState = (0, _state.getTempState)();
431 window.addEventListener('keydown', keyEvent => {
432 if (keyEvent.key === 'Control') {
433 tempState.keyModifiers.control = true;
434 } else if (keyEvent.key === 'Alt') {
435 tempState.keyModifiers.alt = true;
436 } else if (keyEvent.key === 'Shift') {
437 // Shouldn't set keyModifiers.shift if we're programatically doing it while getting tooltip content
438 // tempState.gettingTooltipContentShiftPress should only be `true` if user already isn't pressing shift
439 // See game.js `getTooltipContent` for more details
440 if (tempState.gettingTooltipContentShiftPress) {
441 return;
442 }
443
444 tempState.keyModifiers.shift = true;
445 }
446 });
447 window.addEventListener('keyup', keyEvent => {
448 if (keyEvent.key === 'Control') {
449 tempState.keyModifiers.control = false;
450 } else if (keyEvent.key === 'Alt') {
451 tempState.keyModifiers.alt = false;
452 } else if (keyEvent.key === 'Shift') {
453 tempState.keyModifiers.shift = false;
454 }
455 }); // If page ever regains focus, e.g. tabbing back in after tabbing out, make sure we reset our modifiers.
456 // This prevents things like holding control, leaving the tab without releasing it, then coming back in and
457 // the game will think you are still holding it, even if you're not.
458
459 window.addEventListener('focus', () => {
460 tempState.keyModifiers.control = false;
461 tempState.keyModifiers.alt = false;
462 tempState.keyModifiers.shift = false;
463 });
464}
465
466var _default = {
467 name: '[REQUIRED] Key press tracker',
468 description: 'Identifies when you are pressing Ctrl/etc key modifiers, which is used by some other mods',
469 run: keyPressTracker
470};
471exports.default = _default;
472
473},{"../../utils/state":37}],3:[function(require,module,exports){
474"use strict";
475
476Object.defineProperty(exports, "__esModule", {
477 value: true
478});
479exports.default = void 0;
480
481var chat = _interopRequireWildcard(require("../../utils/chat"));
482
483var _version = require("../../utils/version");
484
485var _state = require("../../utils/state");
486
487function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }
488
489function _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; }
490
491function modStart() {
492 chat.addChatMessage(`Hordes UI Mod v${_version.VERSION} is now running.`);
493 (0, _state.loadState)();
494}
495
496var _default = {
497 name: '[REQUIRED] UI Mod Startup',
498 description: 'Do not remove this! This displays a welcome message, loads saved state, and includes misc styles.',
499 run: modStart
500};
501exports.default = _default;
502
503},{"../../utils/chat":33,"../../utils/state":37,"../../utils/version":39}],4:[function(require,module,exports){
504"use strict";
505
506Object.defineProperty(exports, "__esModule", {
507 value: true
508});
509exports.showChatContextMenu = showChatContextMenu;
510
511var _state = require("../../utils/state");
512
513// Makes chat context menu visible and appear under the mouse
514function showChatContextMenu(name, mousePos) {
515 const state = (0, _state.getState)(); // Right before we show the context menu, we want to handle showing/hiding Friend/Unfriend
516
517 const $contextMenu = document.querySelector('.js-chat-context-menu');
518 $contextMenu.querySelector('[name="friend"]').classList.toggle('js-hidden', !!state.friendsList[name]);
519 $contextMenu.querySelector('[name="unfriend"]').classList.toggle('js-hidden', !state.friendsList[name]);
520 $contextMenu.querySelector('.js-name').textContent = name;
521 $contextMenu.setAttribute('style', `display: block; left: ${mousePos.x}px; top: ${mousePos.y}px;`);
522}
523
524},{"../../utils/state":37}],5:[function(require,module,exports){
525"use strict";
526
527Object.defineProperty(exports, "__esModule", {
528 value: true
529});
530exports.default = void 0;
531
532var _state = require("../../utils/state");
533
534var _misc = require("../../utils/misc");
535
536var helpers = _interopRequireWildcard(require("./helpers"));
537
538var chat = _interopRequireWildcard(require("../../utils/chat"));
539
540var player = _interopRequireWildcard(require("../../utils/player"));
541
542function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }
543
544function _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; }
545
546// This creates the initial chat context menu DOM (which starts as hidden)
547function createChatContextMenu() {
548 const tempState = (0, _state.getTempState)();
549
550 if (document.querySelector('.js-chat-context-menu')) {
551 return;
552 }
553
554 let contextMenuHTML = `
555 <div class="js-name">...</div>
556 <div class="choice" name="party">Party invite</div>
557 <div class="choice" name="whisper">Whisper</div>
558 <div class="choice" name="friend">Friend</div>
559 <div class="choice" name="unfriend">Unfriend</div>
560 <div class="choice" name="copy">Copy name</div>
561 <div class="choice" name="block">Block</div>
562 `;
563 document.body.appendChild((0, _misc.makeElement)({
564 element: 'div',
565 class: 'panel context border grey js-chat-context-menu',
566 content: contextMenuHTML
567 }));
568 const $chatContextMenu = document.querySelector('.js-chat-context-menu');
569 $chatContextMenu.querySelector('[name="party"]').addEventListener('click', () => {
570 chat.partyPlayer(tempState.chatName);
571 });
572 $chatContextMenu.querySelector('[name="whisper"]').addEventListener('click', () => {
573 chat.whisperPlayer(tempState.chatName);
574 });
575 $chatContextMenu.querySelector('[name="friend"]').addEventListener('click', () => {
576 player.friendPlayer(tempState.chatName);
577 });
578 $chatContextMenu.querySelector('[name="unfriend"]').addEventListener('click', () => {
579 player.unfriendPlayer(tempState.chatName);
580 });
581 $chatContextMenu.querySelector('[name="copy"]').addEventListener('click', () => {
582 navigator.clipboard.writeText(tempState.chatName);
583 });
584 $chatContextMenu.querySelector('[name="block"]').addEventListener('click', () => {
585 player.blockPlayer(tempState.chatName);
586 });
587} // This opens a context menu when you click a user's name in chat
588
589
590function chatContextMenu() {
591 const tempState = (0, _state.getTempState)();
592
593 const addContextMenu = ($name, name) => {
594 $name.classList.add('js-is-context-menu-initd'); // Add name to element so we can target it in CSS, e.g. when filtering chat for block list
595
596 $name.setAttribute('data-chat-name', name);
597
598 const showContextMenu = clickEvent => {
599 // TODO: Is there a way to pass the name to showChatContextMenumethod, instead of storing in tempState?
600 tempState.chatName = name;
601 helpers.showChatContextMenu(name, {
602 x: clickEvent.pageX,
603 y: clickEvent.pageY
604 });
605 };
606
607 $name.addEventListener('click', showContextMenu); // Left click
608
609 $name.addEventListener('contextmenu', showContextMenu); // Right click works too
610 };
611
612 Array.from(document.querySelectorAll('#chat .name:not(.js-is-context-menu-initd)')).forEach($name => {
613 addContextMenu($name, $name.textContent);
614 }); // `textf0` is the VG faction, `textf1` is the BL faction - we want to support both with our whisper context menu
615
616 Array.from(document.querySelectorAll('.textwhisper .textf1:not(.js-is-context-menu-initd), .textwhisper .textf0:not(.js-is-context-menu-initd)')).forEach($whisperName => {
617 // $whisperName's textContent is "to [name]" or "from [name]", so we cut off the first word
618 let name = $whisperName.textContent.split(' ');
619 name.shift(); // Remove the first word
620
621 name = name.join(' ');
622 addContextMenu($whisperName, name);
623 });
624} // Close chat context menu if clicking outside of it
625
626
627function closeChatContextMenu(clickEvent) {
628 const $target = clickEvent.target; // If clicking on name or directly on context menu, don't close it
629 // Still closes if clicking on context menu item
630
631 if ($target.classList.contains('js-is-context-menu-initd') || $target.classList.contains('js-chat-context-menu')) {
632 return;
633 }
634
635 const $contextMenu = document.querySelector('.js-chat-context-menu');
636 $contextMenu.style.display = 'none';
637}
638
639var _default = {
640 name: 'Chat Context Menu',
641 description: 'Displays a menu when you click on a player, allowing you to whisper/party/friend/block them',
642 run: ({
643 registerOnLeftClick,
644 registerOnChatChange
645 }) => {
646 createChatContextMenu();
647 chatContextMenu(); // When we click anywhere on the page outside of our chat context menu, we want to close the menu
648
649 registerOnLeftClick(closeChatContextMenu); // Register event listeners for each name when a new chat message appears
650
651 registerOnChatChange(chatContextMenu);
652 }
653};
654exports.default = _default;
655
656},{"../../utils/chat":33,"../../utils/misc":35,"../../utils/player":36,"../../utils/state":37,"./helpers":4}],6:[function(require,module,exports){
657"use strict";
658
659Object.defineProperty(exports, "__esModule", {
660 value: true
661});
662exports.default = void 0;
663
664var chat = _interopRequireWildcard(require("../../utils/chat"));
665
666var _state = require("../../utils/state");
667
668var _misc = require("../../utils/misc");
669
670function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }
671
672function _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; }
673
674// Creates DOM elements for custom chat filters
675function newChatFilters() {
676 const state = (0, _state.getState)();
677 const $channelselect = document.querySelector('.channelselect');
678
679 if (!document.querySelector(`.js-chat-gm`)) {
680 const $gm = (0, _misc.makeElement)({
681 element: 'small',
682 class: `btn border black js-chat-gm ${state.chat.GM ? '' : 'textgrey'}`,
683 content: 'GM'
684 });
685 $channelselect.appendChild($gm);
686 }
687} // Wire up new chat buttons to toggle in state+ui
688
689
690function newChatFilterButtons() {
691 const state = (0, _state.getState)();
692 const $chatGM = document.querySelector(`.js-chat-gm`);
693 $chatGM.addEventListener('click', () => {
694 chat.setGMChatVisibility(!state.chat.GM);
695 });
696}
697
698var _default = {
699 name: 'Chat filters',
700 description: 'Enables custom chat filters: GM chat',
701 run: ({
702 registerOnChatChange
703 }) => {
704 newChatFilters();
705 newChatFilterButtons(); // Whenever chat changes, we want to filter it
706
707 registerOnChatChange(chat.filterAllChat);
708 }
709};
710exports.default = _default;
711
712},{"../../utils/chat":33,"../../utils/misc":35,"../../utils/state":37}],7:[function(require,module,exports){
713"use strict";
714
715Object.defineProperty(exports, "__esModule", {
716 value: true
717});
718exports.showChatTabConfigWindow = showChatTabConfigWindow;
719exports.addChatTab = addChatTab;
720exports.selectChatTab = selectChatTab;
721exports.getCurrentChatFilters = getCurrentChatFilters;
722
723var chat = _interopRequireWildcard(require("../../utils/chat"));
724
725var _state = require("../../utils/state");
726
727var _misc = require("../../utils/misc");
728
729function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }
730
731function _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; }
732
733const DEFAULT_CHAT_TAB_NAME = 'Untitled'; // Gets current chat filters as represented in the UI
734// filter being true means it's invisible(filtered) in chat
735// filter being false means it's visible(unfiltered) in chat
736
737function getCurrentChatFilters() {
738 const state = (0, _state.getState)(); // Saved by the official game client
739
740 const gameFilters = JSON.parse(localStorage.getItem('filteredChannels'));
741 return {
742 global: gameFilters.includes('global'),
743 faction: gameFilters.includes('faction'),
744 party: gameFilters.includes('party'),
745 clan: gameFilters.includes('clan'),
746 pvp: gameFilters.includes('pvp'),
747 inv: gameFilters.includes('inv'),
748 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
749
750 };
751} // Shows the chat tab config window for a specific tab, displayed in a specific position
752
753
754function showChatTabConfigWindow(tabId, pos) {
755 const state = (0, _state.getState)();
756 const tempState = (0, _state.getTempState)();
757 const $chatTabConfig = document.querySelector('.js-chat-tab-config');
758 const chatTab = state.chatTabs.find(tab => tab.id === tabId); // Update position and name in chat tab config
759
760 $chatTabConfig.style.left = `${pos.x}px`;
761 $chatTabConfig.style.top = `${pos.y}px`;
762 $chatTabConfig.querySelector('.js-chat-tab-name').value = chatTab.name; // Store tabId in state, to be used by the Remove/Add buttons in config window
763
764 tempState.editedChatTabId = tabId; // Hide remove button if only one chat tab left - can't remove last one
765 // Show it if more than one chat tab left
766
767 const chatTabCount = Object.keys(state.chatTabs).length;
768 const $removeChatTabBtn = $chatTabConfig.querySelector('.js-remove-chat-tab');
769 $removeChatTabBtn.style.display = chatTabCount < 2 ? 'none' : 'block'; // Show chat tab config
770
771 $chatTabConfig.style.display = 'block';
772} // Adds chat tab to DOM, sets it as selected
773// If argument chatTab is provided, will use that name+id
774// If no argument is provided, will create new tab name/id and add it to state
775// isInittingTab is optional boolean, if `true`, will _not_ set added tab as selected. Used when initializing all chat tabs on load
776// Returns newly added tabId
777
778
779function addChatTab(chatTab, isInittingTab) {
780 const state = (0, _state.getState)();
781 let tabName = DEFAULT_CHAT_TAB_NAME;
782 let tabId = (0, _misc.uuid)();
783
784 if (chatTab) {
785 tabName = chatTab.name;
786 tabId = chatTab.id;
787 } else {
788 // If no chat tab was provided, create it in state
789 state.chatTabs.push({
790 name: tabName,
791 id: tabId,
792 filters: getCurrentChatFilters()
793 });
794 (0, _state.saveState)();
795 }
796
797 const $tabs = document.querySelector('.js-chat-tabs');
798 const $tab = (0, _misc.makeElement)({
799 element: 'div',
800 content: tabName
801 });
802 $tab.setAttribute('data-tab-id', tabId); // Add chat tab to DOM
803
804 $tabs.appendChild($tab); // Wire chat tab up to open config on right click
805
806 $tab.addEventListener('contextmenu', clickEvent => {
807 const mousePos = {
808 x: clickEvent.pageX,
809 y: clickEvent.pageY
810 };
811 showChatTabConfigWindow(tabId, mousePos);
812 }); // And select chat tab on left click
813
814 $tab.addEventListener('click', () => {
815 selectChatTab(tabId);
816 });
817
818 if (!isInittingTab) {
819 // Select the newly added chat tab
820 selectChatTab(tabId);
821 } // Returning tabId to all adding new tab to pass tab ID to `showChatTabConfigWindow`
822
823
824 return tabId;
825} // Selects chat tab [on click], updating client chat filters and custom chat filters
826
827
828function selectChatTab(tabId) {
829 const state = (0, _state.getState)(); // Remove selected class from everything, then add selected class to clicked tab
830
831 Array.from(document.querySelectorAll('[data-tab-id]')).forEach($tab => {
832 $tab.classList.remove('js-selected-tab');
833 });
834 const $tab = document.querySelector(`[data-tab-id="${tabId}"]`);
835 $tab.classList.add('js-selected-tab');
836 const tabFilters = state.chatTabs.find(tab => tab.id === tabId).filters; // Simulating clicks on the filters to turn them on/off
837
838 const $filterButtons = Array.from(document.querySelectorAll('.channelselect small'));
839 Object.keys(tabFilters).forEach(filter => {
840 const $filterButton = $filterButtons.find($btn => $btn.textContent === filter);
841 const isCurrentlyFiltered = $filterButton.classList.contains('textgrey'); // If is currently filtered but filter for this tab is turned off, click it to turn filter off
842
843 if (isCurrentlyFiltered && !tabFilters[filter]) {
844 $filterButton.click();
845 } // If it is not currently filtered but filter for this tab is turned on, click it to turn filter on
846
847
848 if (!isCurrentlyFiltered && tabFilters[filter]) {
849 $filterButton.click();
850 }
851 }); // Update state for our custom chat filters to match the tab's configuration, then filter chat for it
852
853 const isGMChatVisible = !tabFilters.GM;
854 chat.setGMChatVisibility(isGMChatVisible); // Update the selected tab in state
855
856 state.selectedChatTabId = tabId;
857 (0, _state.saveState)();
858}
859
860},{"../../utils/chat":33,"../../utils/misc":35,"../../utils/state":37}],8:[function(require,module,exports){
861"use strict";
862
863Object.defineProperty(exports, "__esModule", {
864 value: true
865});
866exports.default = void 0;
867
868var helpers = _interopRequireWildcard(require("./helpers"));
869
870var _state = require("../../utils/state");
871
872var _misc = require("../../utils/misc");
873
874function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }
875
876function _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; }
877
878// Creates DOM elements and wires them up for custom chat tabs and chat tab config
879// Note: Should be done after creating new custom chat filters
880function customChatTabs() {
881 const state = (0, _state.getState)();
882 const tempState = (0, _state.getTempState)(); // Create the chat tab configuration DOM
883
884 const $chatTabConfigurator = (0, _misc.makeElement)({
885 element: 'div',
886 class: 'uimod-chat-tab-config js-chat-tab-config',
887 content: `
888 <h1>Chat Tab Config</h1>
889 <div class="uimod-chat-tab-config-grid">
890 <div>Name</div><input type="text" class="js-chat-tab-name" value="untitled"></input>
891 <div class="btn orange js-remove-chat-tab">Remove</div><div class="btn blue js-save-chat-tab">Ok</div>
892 </div>
893 `
894 });
895 document.body.append($chatTabConfigurator); // Wire it up
896
897 document.querySelector('.js-remove-chat-tab').addEventListener('click', () => {
898 // Remove the chat tab from state
899 const editedChatTab = state.chatTabs.find(tab => tab.id === tempState.editedChatTabId);
900 const editedChatTabIndex = state.chatTabs.indexOf(editedChatTab);
901 state.chatTabs.splice(editedChatTabIndex, 1); // Remove the chat tab from DOM
902
903 const $chatTab = document.querySelector(`[data-tab-id="${tempState.editedChatTabId}"]`);
904 $chatTab.parentNode.removeChild($chatTab); // If we just removed the currently selected chat tab
905
906 if (tempState.editedChatTabId === state.selectedChatTabId) {
907 // Select the chat tab to the left of the removed one
908 const nextChatTabIndex = editedChatTabIndex === 0 ? 0 : editedChatTabIndex - 1;
909 helpers.selectChatTab(state.chatTabs[nextChatTabIndex].id);
910 } // Close chat tab config
911
912
913 document.querySelector('.js-chat-tab-config').style.display = 'none';
914 });
915 document.querySelector('.js-save-chat-tab').addEventListener('click', () => {
916 // Set new chat tab name in DOM
917 const $chatTab = document.querySelector(`[data-tab-id="${state.selectedChatTabId}"]`);
918 const newName = document.querySelector('.js-chat-tab-name').value;
919 $chatTab.textContent = newName; // Set new chat tab name in state
920 // `selectedChatTab` is a reference on `state.chatTabs`, so updating it above still updates it in the state - we want to save that
921
922 const selectedChatTab = state.chatTabs.find(tab => tab.id === state.selectedChatTabId);
923 selectedChatTab.name = newName;
924 (0, _state.saveState)(); // Close chat tab config
925
926 document.querySelector('.js-chat-tab-config').style.display = 'none';
927 }); // Create the initial chat tabs HTML
928
929 const $chat = document.querySelector('#chat');
930 const $chatTabs = (0, _misc.makeElement)({
931 element: 'div',
932 class: 'uimod-chat-tabs js-chat-tabs',
933 content: '<div class="js-chat-tab-add">+</div>'
934 }); // Add them to the DOM
935
936 $chat.parentNode.insertBefore($chatTabs, $chat); // Add all our chat tabs from state
937
938 state.chatTabs.forEach(chatTab => {
939 const isInittingTab = true;
940 helpers.addChatTab(chatTab, isInittingTab);
941 }); // Wire up the add chat tab button
942
943 document.querySelector('.js-chat-tab-add').addEventListener('click', clickEvent => {
944 const chatTabId = helpers.addChatTab();
945 const mousePos = {
946 x: clickEvent.pageX,
947 y: clickEvent.pageY
948 };
949 helpers.showChatTabConfigWindow(chatTabId, mousePos);
950 }); // If initial chat tab doesn't exist, create it based off current filter settings
951
952 if (!Object.keys(state.chatTabs).length) {
953 const tabId = (0, _misc.uuid)();
954 const chatTab = {
955 name: 'Main',
956 id: tabId,
957 filters: helpers.getCurrentChatFilters()
958 };
959 state.chatTabs.push(chatTab);
960 (0, _state.saveState)();
961 helpers.addChatTab(chatTab);
962 } // Wire up click event handlers onto the filters to update the selected chat tab's filters in state
963
964
965 document.querySelector('.channelselect').addEventListener('click', clickEvent => {
966 const $elementMouseIsOver = document.elementFromPoint(clickEvent.clientX, clickEvent.clientY); // We only want to change the filters if the user manually clicks the filter button
967 // If they clicked a chat tab and we programatically set filters, we don't want to update
968 // the current tab's filter state
969
970 if (!$elementMouseIsOver.classList.contains('btn')) {
971 return;
972 }
973
974 const selectedChatTab = state.chatTabs.find(tab => tab.id === state.selectedChatTabId);
975 selectedChatTab.filters = helpers.getCurrentChatFilters();
976 (0, _state.saveState)();
977 }); // Select the currently selected tab in state on mod initialization
978
979 if (state.selectedChatTabId) {
980 helpers.selectChatTab(state.selectedChatTabId);
981 }
982}
983
984var _default = {
985 name: 'Chat tabs',
986 description: 'Enables support for multiple chat tabs',
987 run: customChatTabs
988};
989exports.default = _default;
990
991},{"../../utils/misc":35,"../../utils/state":37,"./helpers":7}],9:[function(require,module,exports){
992"use strict";
993
994Object.defineProperty(exports, "__esModule", {
995 value: true
996});
997exports.handleClanWindowChange = handleClanWindowChange;
998
999var _misc = require("../../utils/misc");
1000
1001var _state = require("../../utils/state");
1002
1003function _lastSeenFromTimestamp(ts) {
1004 if (!ts) return 'Never';
1005 const nowTs = Date.now();
1006 const seconds = (nowTs - ts) / 1000; // Divide by 1000 because Date.now returns milliseconds
1007
1008 const minutes = seconds / 60;
1009 const hours = minutes / 60;
1010 const days = hours / 24;
1011 const weeks = days / 7;
1012 const months = weeks / 30;
1013 const years = months / 12;
1014
1015 const getPluralizedText = (num, word) => {
1016 num = Math.round(num);
1017 return num === 1 ? `${num} ${word}` : `${num} ${word}s`;
1018 };
1019
1020 if (seconds < 60) return `${getPluralizedText(seconds, 'second')} ago`;
1021 if (minutes < 60) return `${getPluralizedText(minutes, 'minute')} ago`;
1022 if (hours < 24) return `${getPluralizedText(hours, 'hour')} ago`;
1023 if (days < 7) return `${getPluralizedText(days, 'day')} ago`;
1024 if (days < 30) return `${getPluralizedText(weeks, 'week')} ago`;
1025 if (months < 12) return `${getPluralizedText(months, 'month')} ago`;
1026 return `${getPluralizedText(years, 'year')} ago`;
1027}
1028
1029function _handleClanMemberTableChange() {
1030 const state = (0, _state.getState)();
1031 const $clanLastSeenTable = document.querySelector('.js-clan-lastseen-table');
1032 const $clanMemberTable = document.querySelector('.js-clan-members-table-initd'); // Update+Save current online users last seen time
1033
1034 const currentTimestamp = Date.now();
1035 const $memberNames = Array.from($clanMemberTable.querySelectorAll('tr .name'));
1036 const latestMemberNames = [];
1037 $memberNames.map($name => {
1038 const isOnline = !$name.parentNode.parentNode.classList.contains('offline');
1039 const name = $name.textContent.trim();
1040
1041 if (isOnline) {
1042 // Update current timestamp of online members
1043 state.clanLastActiveMembers[name] = currentTimestamp;
1044 } else if (!state.clanLastActiveMembers.hasOwnProperty(name)) {
1045 // If not existing in state, add them so that we can check update their last seen time when they type in chat (See `refreshLastSeenClanMember`)
1046 state.clanLastActiveMembers[name] = null;
1047 }
1048
1049 latestMemberNames.push(name);
1050 }); // Remove clan members that've left the clan from state, so their last seen time is no longer tracked when they type in chat
1051
1052 const removedMembers = Object.keys(state.clanLastActiveMembers).filter(nameInState => !latestMemberNames.includes(nameInState));
1053 removedMembers.forEach(removedName => delete state.clanLastActiveMembers[removedName]);
1054 (0, _state.saveState)(); // Update changed last seen times in DOM
1055
1056 const $names = Array.from($clanMemberTable.querySelectorAll('tr .name'));
1057 const $lastSeenRows = Array.from($clanLastSeenTable.querySelectorAll('.js-clan-lastseen-row')); // If necessary, update the quantity of rows in our custom table
1058
1059 const $tableBody = $clanLastSeenTable.querySelector('tbody');
1060
1061 if ($names.length !== $lastSeenRows.length) {
1062 const $newRow = (0, _misc.makeElement)({
1063 element: 'tr',
1064 class: 'striped js-clan-lastseen-row',
1065 content: '<td></td>'
1066 });
1067
1068 if ($names.length > $lastSeenRows.length) {
1069 // Add last seen rows to match names length
1070 const rowsToAddCount = $names.length - $lastSeenRows.length;
1071
1072 for (var i = 0; i < rowsToAddCount; i++) {
1073 $tableBody.appendChild($newRow.cloneNode(true));
1074 }
1075 } else {
1076 // Remove last seen rows to match names length
1077 const rowsToRemoveCount = $lastSeenRows.length - $names.length;
1078
1079 for (var i = 0; i < rowsToRemoveCount; i++) {
1080 $tableBody.querySelector('tr').remove();
1081 }
1082 }
1083 } // Update last seen rows with appropriate last seen time
1084
1085
1086 const $tableRows = Array.from($tableBody.querySelectorAll('td'));
1087 $names.forEach(($name, index) => {
1088 const name = $name.textContent.trim();
1089 const isOnline = state.clanLastActiveMembers[name] === currentTimestamp;
1090 const lastSeenStr = isOnline ? 'Now' : _lastSeenFromTimestamp(state.clanLastActiveMembers[name]);
1091 const $tableRow = $tableRows[index];
1092 const rowLastSeenStr = $tableRow.textContent;
1093 const isLastSeenChanged = rowLastSeenStr !== lastSeenStr;
1094 if (isLastSeenChanged) $tableRow.textContent = lastSeenStr; // Mirroring the 50% opacity that the normal clan member table has on offline members
1095
1096 const lineClassList = $tableRow.parentNode.classList;
1097 const displayingRowAsOffline = lineClassList.contains('js-offline-member');
1098
1099 if (!isOnline && !displayingRowAsOffline) {
1100 lineClassList.add('js-offline-member');
1101 } else if (isOnline && displayingRowAsOffline) {
1102 lineClassList.remove('js-offline-member');
1103 }
1104 });
1105}
1106
1107function handleClanWindowChange() {
1108 const state = (0, _state.getState)();
1109 const tempState = (0, _state.getTempState)();
1110 const $clanWindow = document.querySelector('.window .clanView'); // Table takes a moment to be created after clanView window is opened
1111
1112 const $clanMemberTable = $clanWindow.querySelector('table:not(.js-clan-lastseen-initd)');
1113 if (!$clanMemberTable) return; // If not in Members tab (e.g. Applications tab), don't initialize Last seen
1114 // Check if we're in Members tab by seeing if there are 2 columns or not
1115 // (This allows us to support multiple languages, as opposed to checking for "Applications")
1116
1117 const isMembersTab = Array.from($clanMemberTable.querySelectorAll('thead th')).length === 2;
1118 const $lastSeenTable = $clanWindow.querySelector('.js-clan-lastseen-table');
1119
1120 if (!isMembersTab) {
1121 // Hide last seen table if it's visible
1122 if ($lastSeenTable) $lastSeenTable.style.display = 'none';
1123 return;
1124 } else if ($lastSeenTable) {
1125 // Unhide it when we are on Members table
1126 $lastSeenTable.setAttribute('style', '');
1127 } // Initialize the table column if we haven't already
1128 // The clan member table loses its class when the tab is changed, so we check
1129
1130
1131 if (!$clanMemberTable.classList.contains('js-clan-members-table-initd')) {
1132 $clanMemberTable.classList.add('js-clan-members-table-initd', 'uimod-clan-members-table'); // Last seen table may already exist if we're switching from Applications tab back to Members tab
1133
1134 if ($lastSeenTable) return; // If last seen table hasn't been created, create it.
1135 // We add a new table next to the preexisting table.
1136 // We don't just add a new column because Svelte changes the columns and rows around
1137 // a lot, pretty randomly. This leads to our right-most column occasionally bugging out
1138 // and ending up as the left-most column.
1139 // Using our own table lets us control everything about it without Svelte interfering.
1140
1141 $clanMemberTable.parentNode.appendChild((0, _misc.makeElement)({
1142 element: 'table',
1143 class: 'marg-top panel-black js-clan-lastseen-table uimod-clan-lastseen-table',
1144 content: `
1145 <thead>
1146 <tr class="textprimary">
1147 <th>Last seen</th>
1148 </tr>
1149 </thead>
1150 <tbody>
1151 <tr class="striped js-clan-lastseen-row">
1152 <td></td>
1153 </tr>
1154 </tbody>
1155 `
1156 })); // Reset last active members state if clan has changed
1157
1158 const clanName = $clanWindow.querySelector('.textcenter h1').textContent;
1159
1160 if (clanName !== state.currentClanName) {
1161 state.currentClanName = clanName.trim();
1162 state.clanLastActiveMembers = {};
1163 (0, _state.saveState)();
1164 }
1165 }
1166
1167 if (!tempState.clanTableObserver) {
1168 _handleClanMemberTableChange();
1169
1170 tempState.clanTableObserver = new MutationObserver(_handleClanMemberTableChange);
1171 tempState.clanTableObserver.observe($clanMemberTable, {
1172 attributes: true,
1173 childList: true,
1174 subtree: true
1175 });
1176 }
1177}
1178
1179},{"../../utils/misc":35,"../../utils/state":37}],10:[function(require,module,exports){
1180"use strict";
1181
1182Object.defineProperty(exports, "__esModule", {
1183 value: true
1184});
1185exports.default = void 0;
1186
1187var _ui = require("../../utils/ui");
1188
1189var _state = require("../../utils/state");
1190
1191var _helpers = require("./helpers");
1192
1193// When clan window is open, initialize the mutation observer to add Last seen and track last seen in state
1194function clanActivityTracker() {
1195 const tempState = (0, _state.getTempState)();
1196 const $clanWindow = document.querySelector('.window .clanView'); // If the window is no longer visible, update the state to denote the window has closed and kill the observer
1197
1198 if (!$clanWindow) {
1199 if ((0, _ui.isWindowOpen)(_ui.WindowNames.clan)) {
1200 if (tempState.clanWindowObserver) {
1201 tempState.clanWindowObserver.disconnect();
1202 delete tempState.clanWindowObserver;
1203 }
1204
1205 if (tempState.clanTableObserver) {
1206 tempState.clanTableObserver.disconnect();
1207 delete tempState.clanTableObserver;
1208 }
1209
1210 (0, _ui.setWindowClosed)(_ui.WindowNames.clan);
1211 }
1212 } else if (!tempState.clanWindowObserver) {
1213 (0, _ui.setWindowOpen)(_ui.WindowNames.clan);
1214 (0, _helpers.handleClanWindowChange)();
1215 tempState.clanWindowObserver = new MutationObserver(_helpers.handleClanWindowChange);
1216 tempState.clanWindowObserver.observe($clanWindow, {
1217 attributes: true,
1218 childList: true
1219 });
1220 }
1221} // Update last seen for clan members when they type in chat
1222
1223
1224function refreshLastSeenClanMember(mutations) {
1225 const state = (0, _state.getState)();
1226 let updatedState = false;
1227 const $newChatLines = mutations.map(mutation => Array.from(mutation.addedNodes)).flat();
1228 $newChatLines.forEach($chatLine => {
1229 const $name = $chatLine.querySelector('.name');
1230 if (!$name) return;
1231 const name = $name.textContent.trim(); // If not clan member, don't update state
1232
1233 if (!state.clanLastActiveMembers.hasOwnProperty(name)) return;
1234 updatedState = true;
1235 state.clanLastActiveMembers[name] = Date.now();
1236 });
1237 if (updatedState) (0, _state.saveState)();
1238}
1239
1240var _default = {
1241 name: 'Clan activity tracker',
1242 description: 'Updates clan member table with a Last seen column',
1243 run: ({
1244 registerOnDomChange,
1245 registerOnChatChange
1246 }) => {
1247 clanActivityTracker(); // Run it initially once in case clan is already open on mod load
1248
1249 registerOnDomChange(clanActivityTracker); // Run it on dom change for whenever the clan window is opened/closed
1250
1251 registerOnChatChange(refreshLastSeenClanMember); // Run it on chat change so whenever a clan member chats, their last seen is updated
1252 }
1253};
1254exports.default = _default;
1255
1256},{"../../utils/state":37,"../../utils/ui":38,"./helpers":9}],11:[function(require,module,exports){
1257"use strict";
1258
1259Object.defineProperty(exports, "__esModule", {
1260 value: true
1261});
1262exports.default = void 0;
1263
1264var _misc = require("../../utils/misc");
1265
1266var _ui = require("../../utils/ui");
1267
1268function customSettings() {
1269 const $settings = document.querySelector('.divide:not(.js-settings-initd)');
1270
1271 if (!$settings) {
1272 return;
1273 }
1274
1275 $settings.classList.add('js-settings-initd');
1276 const $settingsChoiceList = $settings.querySelector('.choice').parentNode;
1277 $settingsChoiceList.appendChild((0, _misc.makeElement)({
1278 element: 'div',
1279 class: 'choice js-blocked-players',
1280 content: 'Blocked players'
1281 }));
1282 $settingsChoiceList.appendChild((0, _misc.makeElement)({
1283 element: 'div',
1284 class: 'choice js-reset-ui-pos',
1285 content: 'Reset UI Positions'
1286 })); // Upon click, we display our custom settings window UI
1287
1288 document.querySelector('.js-blocked-players').addEventListener('click', _ui.createBlockList); // Reset positions immediately upon click
1289
1290 document.querySelector('.js-reset-ui-pos').addEventListener('click', _ui.resetUiPositions); // If it was open when the game last closed keep it open
1291
1292 if ((0, _ui.isWindowOpen)(_ui.WindowNames.blockList)) {
1293 (0, _ui.createBlockList)();
1294 }
1295}
1296
1297var _default = {
1298 name: 'Custom settings',
1299 description: 'Allows you to view and remove blocked players from the Settings window. Also adds Reset UI Position to settings',
1300 run: ({
1301 registerOnDomChange
1302 }) => {
1303 customSettings(); // If the settings window becomes visible/invisible, we want to update it
1304
1305 registerOnDomChange(customSettings);
1306 }
1307};
1308exports.default = _default;
1309
1310},{"../../utils/misc":35,"../../utils/ui":38}],12:[function(require,module,exports){
1311"use strict";
1312
1313Object.defineProperty(exports, "__esModule", {
1314 value: true
1315});
1316exports.deposit = deposit;
1317
1318var _game = require("../../utils/game");
1319
1320var _ui = require("../../utils/ui");
1321
1322function deposit() {
1323 const $stash = (0, _game.getWindow)(_ui.WindowNames.stash); // Select normal deposit button
1324
1325 $stash.querySelector('.slot .grey.gold:not(.js-deposit-all)').dispatchEvent(new Event('click'));
1326 const $currencyInput = $stash.querySelector('input.formatted'); // Input some huge value they'll have less than
1327
1328 $currencyInput.value = 999999999999999;
1329 $currencyInput.dispatchEvent(new Event('input'));
1330 setTimeout(function () {
1331 const $depositButton = $stash.querySelector('.btn.blue');
1332
1333 if (!$depositButton.classList.contains('disabled')) {
1334 $depositButton.dispatchEvent(new Event('click'));
1335 } // Clear input
1336
1337
1338 $currencyInput.value = '';
1339 $currencyInput.dispatchEvent(new Event('input'));
1340 }, 0);
1341}
1342
1343},{"../../utils/game":34,"../../utils/ui":38}],13:[function(require,module,exports){
1344"use strict";
1345
1346Object.defineProperty(exports, "__esModule", {
1347 value: true
1348});
1349exports.default = void 0;
1350
1351var _misc = require("../../utils/misc");
1352
1353var _ui = require("../../utils/ui");
1354
1355var _game = require("../../utils/game");
1356
1357var _helper = require("./helper");
1358
1359function addDepositAllButton() {
1360 const $stash = (0, _game.getWindow)(_ui.WindowNames.stash); // If stash is closed or deposit all button is already added, we dont need to do anything
1361
1362 if (!$stash || $stash.querySelector('.js-deposit-all')) {
1363 return;
1364 } // Create deposit all button and add it to stash
1365
1366
1367 const $depositTargetBtn = $stash.querySelector('.slot .grey.gold');
1368 const $depositAllBtn = $depositTargetBtn.cloneNode(true);
1369 const $depositAllText = (0, _misc.makeElement)({
1370 element: 'span',
1371 content: ' ALL'
1372 });
1373 $depositAllBtn.append($depositAllText);
1374 $depositAllBtn.classList.add('js-deposit-all');
1375 $depositAllBtn.classList.remove('active');
1376 $depositTargetBtn.parentElement.insertBefore($depositAllBtn, $depositTargetBtn);
1377 $stash.querySelector('.js-deposit-all').addEventListener('click', _helper.deposit);
1378}
1379
1380var _default = {
1381 name: 'Desposit All Button',
1382 description: 'Adds a button to your stash to quickly deposit all of your money',
1383 run: ({
1384 registerOnDomChange
1385 }) => {
1386 addDepositAllButton();
1387 registerOnDomChange(addDepositAllButton);
1388 }
1389};
1390exports.default = _default;
1391
1392},{"../../utils/game":34,"../../utils/misc":35,"../../utils/ui":38,"./helper":12}],14:[function(require,module,exports){
1393"use strict";
1394
1395Object.defineProperty(exports, "__esModule", {
1396 value: true
1397});
1398exports.dragElement = dragElement;
1399
1400// Influenced by: https://gist.github.com/remarkablemark/5002d27442600510d454a5aeba370579 & https://stackoverflow.com/a/45831670
1401// $draggedElement is the item that will be dragged.
1402// $dragTrigger is optional, if passed, this element that must be held down to drag $draggedElement
1403// If $dragTrigger is not passed, clicking anywhere on $draggedElement will drag it
1404// dragAfterTimeMs is an optional argument. If passed, user has to hold mouse down for that long before being able to drag
1405function dragElement($draggedElement, $dragTrigger, dragAfterTimeMs) {
1406 let offset = [0, 0];
1407 let mouseDownPos = [0, 0];
1408 let elementPos = [0, 0];
1409 let isDown = false;
1410 let downTimeMs = 0; // Time when user last started holding mouse left click
1411
1412 const $trigger = $dragTrigger || $draggedElement;
1413 $trigger.addEventListener('mousedown', e => {
1414 isDown = true;
1415 downTimeMs = Date.now(); // Offset is used when there is a separate $dragTrigger
1416
1417 offset = [$draggedElement.offsetLeft - e.clientX, $draggedElement.offsetTop - e.clientY]; // mouseDownPos and elementPos are used when $draggedElement is also the trigger
1418
1419 mouseDownPos = [e.clientX, e.clientY];
1420 elementPos = [parseInt($draggedElement.style.left) || 0, parseInt($draggedElement.style.top) || 0];
1421 }, true);
1422 document.addEventListener('mouseup', () => {
1423 downTimeMs = 0;
1424 isDown = false;
1425 }, true);
1426 document.addEventListener('mousemove', e => {
1427 e.preventDefault();
1428
1429 if (isDown) {
1430 // If dragAfterTimeMs is set, then user must hold down mouse for specified time before being able to drag
1431 if (dragAfterTimeMs && Date.now() - downTimeMs < dragAfterTimeMs) return;
1432 const deltaX = $dragTrigger ? e.clientX + offset[0] : elementPos[0] + e.clientX - mouseDownPos[0];
1433 const deltaY = $dragTrigger ? e.clientY + offset[1] : elementPos[1] + e.clientY - mouseDownPos[1];
1434 $draggedElement.style.left = `${deltaX}px`;
1435 $draggedElement.style.top = `${deltaY}px`;
1436 }
1437 }, true);
1438}
1439
1440},{}],15:[function(require,module,exports){
1441"use strict";
1442
1443Object.defineProperty(exports, "__esModule", {
1444 value: true
1445});
1446exports.default = void 0;
1447
1448var helpers = _interopRequireWildcard(require("./helpers"));
1449
1450var _state = require("../../utils/state");
1451
1452function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }
1453
1454function _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; }
1455
1456// Drag all windows by their header
1457function draggableUIWindows() {
1458 Array.from(document.querySelectorAll('.window:not(.js-can-move)')).forEach($window => {
1459 $window.classList.add('js-can-move');
1460 helpers.dragElement($window, $window.querySelector('.titleframe'));
1461 });
1462 Array.from(document.querySelectorAll(`
1463 .partyframes:not(.js-can-move),
1464 #ufplayer:not(.js-can-move),
1465 #uftarget:not(.js-can-move),
1466 #skillbar:not(.js-can-move)
1467 `)).forEach($frame => {
1468 $frame.classList.add('js-can-move');
1469 helpers.dragElement($frame, null, 500);
1470 });
1471} // Save dragged UI windows position to state
1472
1473
1474function saveDraggedUIWindows() {
1475 const state = (0, _state.getState)();
1476 Array.from(document.querySelectorAll('.window:not(.js-ui-is-saving)')).forEach($window => {
1477 $window.classList.add('js-ui-is-saving');
1478 const $draggableTarget = $window.querySelector('.titleframe');
1479 const windowName = $draggableTarget.querySelector('[name="title"]').textContent;
1480 $draggableTarget.addEventListener('mouseup', () => {
1481 state.windowsPos[windowName] = $window.getAttribute('style');
1482 (0, _state.saveState)();
1483 });
1484 });
1485
1486 const saveFramePos = ($element, name) => {
1487 if (!$element) return;
1488 $element.classList.add('js-ui-is-saving');
1489 $element.addEventListener('mouseup', () => {
1490 state.windowsPos[name] = $element.getAttribute('style');
1491 });
1492 };
1493
1494 saveFramePos(document.querySelector('.partyframes:not(.js-ui-is-saving)'), 'partyFrame');
1495 saveFramePos(document.querySelector('#ufplayer:not(.js-ui-is-saving)'), 'playerFrame');
1496 saveFramePos(document.querySelector('#uftarget:not(.js-ui-is-saving)'), 'targetFrame');
1497} // Loads draggable UI windows position from state
1498
1499
1500function loadDraggedUIWindowsPositions() {
1501 const state = (0, _state.getState)();
1502 Array.from(document.querySelectorAll('.window:not(.js-has-loaded-pos)')).forEach($window => {
1503 $window.classList.add('js-has-loaded-pos');
1504 const windowName = $window.querySelector('[name="title"]').textContent;
1505 const pos = state.windowsPos[windowName];
1506
1507 if (pos) {
1508 $window.setAttribute('style', pos);
1509 }
1510 });
1511
1512 const loadFramePos = ($element, name) => {
1513 if (!$element) return;
1514 $element.classList.add('js-has-loaded-pos');
1515 const pos = state.windowsPos[name];
1516
1517 if (pos) {
1518 $element.setAttribute('style', pos);
1519 }
1520 };
1521
1522 loadFramePos(document.querySelector('.partyframes:not(.js-has-loaded-pos)'), 'partyFrame');
1523 loadFramePos(document.querySelector('#ufplayer:not(.js-has-loaded-pos)'), 'playerFrame');
1524 loadFramePos(document.querySelector('#uftarget:not(.js-has-loaded-pos)'), 'targetFrame');
1525}
1526
1527var _default = {
1528 name: 'Draggable Windows',
1529 description: 'Allows you to drag windows in the UI',
1530 run: ({
1531 registerOnDomChange
1532 }) => {
1533 draggableUIWindows();
1534 saveDraggedUIWindows();
1535 loadDraggedUIWindowsPositions(); // As windows open, we want to make them draggable
1536
1537 registerOnDomChange(saveDraggedUIWindows);
1538 registerOnDomChange(draggableUIWindows);
1539 registerOnDomChange(loadDraggedUIWindowsPositions);
1540 }
1541};
1542exports.default = _default;
1543
1544},{"../../utils/state":37,"./helpers":14}],16:[function(require,module,exports){
1545"use strict";
1546
1547Object.defineProperty(exports, "__esModule", {
1548 value: true
1549});
1550exports.default = void 0;
1551
1552var _misc = require("../../utils/misc");
1553
1554var _ui = require("../../utils/ui");
1555
1556// The F icon and the UI that appears when you click it
1557function customFriendsList() {
1558 var friendsIconElement = (0, _misc.makeElement)({
1559 element: 'div',
1560 class: 'btn border black js-friends-list-icon',
1561 content: 'F'
1562 }); // Add the icon to the right of Elixir icon
1563
1564 const $elixirIcon = document.querySelector('#sysgem');
1565 $elixirIcon.parentNode.insertBefore(friendsIconElement, $elixirIcon.nextSibling); // Create the friends list UI
1566
1567 document.querySelector('.js-friends-list-icon').addEventListener('click', _ui.toggleFriendsList); // If it was open when the game last closed keep it open
1568
1569 if ((0, _ui.isWindowOpen)(_ui.WindowNames.friendsList)) {
1570 (0, _ui.createFriendsList)();
1571 }
1572}
1573
1574var _default = {
1575 name: 'Friends list',
1576 description: 'Allows access to your friends list from the top right F icon',
1577 run: customFriendsList
1578};
1579exports.default = _default;
1580
1581},{"../../utils/misc":35,"../../utils/ui":38}],17:[function(require,module,exports){
1582"use strict";
1583
1584Object.defineProperty(exports, "__esModule", {
1585 value: true
1586});
1587exports.default = void 0;
1588
1589var _modStart = _interopRequireDefault(require("./_modStart"));
1590
1591var _customSettings = _interopRequireDefault(require("./customSettings"));
1592
1593var _chatContextMenu = _interopRequireDefault(require("./chatContextMenu"));
1594
1595var _chatFilters = _interopRequireDefault(require("./chatFilters"));
1596
1597var _chatTabs = _interopRequireDefault(require("./chatTabs"));
1598
1599var _draggableUI = _interopRequireDefault(require("./draggableUI"));
1600
1601var _friendsList = _interopRequireDefault(require("./friendsList"));
1602
1603var _mapControls = _interopRequireDefault(require("./mapControls"));
1604
1605var _resizableChat = _interopRequireDefault(require("./resizableChat"));
1606
1607var _resizableMap = _interopRequireDefault(require("./resizableMap"));
1608
1609var _selectedWindowIsTop = _interopRequireDefault(require("./selectedWindowIsTop"));
1610
1611var _xpMeter = _interopRequireDefault(require("./xpMeter"));
1612
1613var _merchantFilter = _interopRequireDefault(require("./merchantFilter"));
1614
1615var _itemStatsCopy = _interopRequireDefault(require("./itemStatsCopy"));
1616
1617var _keyPressTracker = _interopRequireDefault(require("./_keyPressTracker"));
1618
1619var _clanActivityTracker = _interopRequireDefault(require("./clanActivityTracker"));
1620
1621var _skillCooldownNumbers = _interopRequireDefault(require("./skillCooldownNumbers"));
1622
1623var _depositAll = _interopRequireDefault(require("./depositAll"));
1624
1625var _lockedItemSlots = _interopRequireDefault(require("./lockedItemSlots"));
1626
1627function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
1628
1629// The array here dictates the order of which mods are executed, from top to bottom
1630var _default = [_modStart.default, _keyPressTracker.default, _resizableMap.default, _mapControls.default, _friendsList.default, _customSettings.default, _resizableChat.default, _chatContextMenu.default, _chatFilters.default, _chatTabs.default, _draggableUI.default, _selectedWindowIsTop.default, _xpMeter.default, _merchantFilter.default, _itemStatsCopy.default, _clanActivityTracker.default, _skillCooldownNumbers.default, _depositAll.default, _lockedItemSlots.default];
1631exports.default = _default;
1632
1633},{"./_keyPressTracker":2,"./_modStart":3,"./chatContextMenu":5,"./chatFilters":6,"./chatTabs":8,"./clanActivityTracker":10,"./customSettings":11,"./depositAll":13,"./draggableUI":15,"./friendsList":16,"./itemStatsCopy":18,"./lockedItemSlots":20,"./mapControls":22,"./merchantFilter":24,"./resizableChat":25,"./resizableMap":27,"./selectedWindowIsTop":28,"./skillCooldownNumbers":30,"./xpMeter":32}],18:[function(require,module,exports){
1634"use strict";
1635
1636Object.defineProperty(exports, "__esModule", {
1637 value: true
1638});
1639exports.default = void 0;
1640
1641var chat = _interopRequireWildcard(require("../../utils/chat"));
1642
1643var _game = require("../../utils/game");
1644
1645var _state = require("../../utils/state");
1646
1647function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }
1648
1649function _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; }
1650
1651async function itemStatsCopy(clickEvent) {
1652 const tempState = (0, _state.getTempState)(); // This mod only triggers if you alt+right click
1653
1654 if (!tempState.keyModifiers.alt) {
1655 return;
1656 }
1657
1658 const $elementMouseIsOver = document.elementFromPoint(clickEvent.clientX, clickEvent.clientY); // It grabs the .overlay class, which is child of the .slot class we need to grab to get the tooltip
1659
1660 const $bagSlot = $elementMouseIsOver.parentNode; // No item in slot
1661
1662 if (!$bagSlot.querySelector('img')) {
1663 return;
1664 } // Once we confirm we want to copy to clipboard, hide context menu
1665
1666
1667 const $itemContextMenuChoice = document.body.querySelector('.container > .panel > .choice');
1668
1669 if (!$itemContextMenuChoice) {
1670 // If context menu isn't open, something is not right - stop what we're doing and exit
1671 // Seen this happen very rarely when testing
1672 return;
1673 }
1674
1675 const $itemContextMenu = $itemContextMenuChoice.parentNode;
1676
1677 if ($itemContextMenu) {
1678 $itemContextMenu.style.display = 'none';
1679 } // Get the texts we want from the tooltip
1680
1681
1682 const getDetailedTooltips = true;
1683 const $tooltip = await (0, _game.getTooltipContent)($bagSlot, getDetailedTooltips);
1684
1685 if (!$tooltip) {
1686 // This _shouldn't_ happen, but very occasionally there is a (likely timing-related) problem getting the tooltip
1687 return;
1688 } // We get the detailed tooltip, which may have a second comparison tooltip. Remove the comparison tooltip if we have it.
1689
1690
1691 const $comparisonTooltip = $tooltip.querySelector('.slotdescription');
1692 if ($comparisonTooltip) $comparisonTooltip.parentNode.removeChild($comparisonTooltip); // Collect item name/stats into strings
1693
1694 const itemName = $tooltip.querySelector('.slottitle').textContent;
1695 const $itemQuality = $tooltip.querySelector('.type span');
1696 const itemQuality = $itemQuality.textContent; // It's not a piece of equipment, just copy item name and exit
1697
1698 if (!itemQuality.includes('%')) {
1699 let trimmedItemName = itemName; // If item name starts with T#, e.g. T1, T5, etc, then this was added onto the detailed tooltip
1700 // It's usually unnecessary information, so we remove it
1701 // (e.g. shows as "T94 Centrifugal Laceration Lv. 4" instead of "Centrifugal Laceration Lv. 4")
1702
1703 if (itemName.substr(0, 2).match(/T[0-9]/)) {
1704 trimmedItemName = itemName.substr(itemName.indexOf(' ') + 1);
1705 }
1706
1707 navigator.clipboard.writeText(trimmedItemName);
1708 chat.addChatMessage(`Copied ${trimmedItemName} to clipboard.`);
1709 return;
1710 } // We only want the lvl number, so pop off the level number from the "Requires Lv. 17" line
1711
1712
1713 const itemLvl = $tooltip.querySelector('.requirements').textContent.split(' ').pop(); // Grab the stats we care about, i.e. not part of the requirements or item type
1714
1715 const $stats = Array.from($tooltip.querySelectorAll(`
1716 .container > .textpurp,
1717 .container > .textblue,
1718 .container > .textgreen:not(.slottitle):not(.requirements),
1719 .container > .textwhite:not(.type)
1720 `));
1721 const statsText = $stats.map($stat => {
1722 // Return quality percentage only if it exists, otherwise return normal stat
1723 const $quality = $stat.querySelector('span');
1724
1725 if ($quality) {
1726 const quality = $quality.textContent;
1727 const statLineChunks = $stat.textContent.replace(/\+\s/g, '+').split(' ');
1728 statLineChunks.pop(); // Remove quality at end
1729
1730 statLineChunks.shift(); // Remove specific +# at the beginning
1731
1732 const statName = statLineChunks.join(' ');
1733 return `${statName} ${quality}`;
1734 } else {
1735 return $stat.textContent.trim();
1736 }
1737 }).join(', ');
1738 navigator.clipboard.writeText(`${itemName} ${itemQuality} Lv.${itemLvl}: ${statsText}`);
1739 chat.addChatMessage(`Copied ${itemName}'s stats to clipboard.`);
1740}
1741
1742var _default = {
1743 name: 'Items stats copy',
1744 description: 'When ctrl+left clicking a piece of equipment in your inventory, its stats will be copied to your clipboard',
1745 run: ({
1746 registerOnRightClick
1747 }) => {
1748 registerOnRightClick(itemStatsCopy);
1749 }
1750};
1751exports.default = _default;
1752
1753},{"../../utils/chat":33,"../../utils/game":34,"../../utils/state":37}],19:[function(require,module,exports){
1754"use strict";
1755
1756Object.defineProperty(exports, "__esModule", {
1757 value: true
1758});
1759exports.lockSlot = lockSlot;
1760exports.initLockedSlots = initLockedSlots;
1761
1762var _state = require("../../utils/state");
1763
1764var _misc = require("../../utils/misc");
1765
1766var _ui = require("../../utils/ui");
1767
1768var _game = require("../../utils/game");
1769
1770function _wireLockSlot($lockedSlot) {
1771 const state = (0, _state.getState)();
1772 const tempState = (0, _state.getTempState)();
1773 const slotNumber = $lockedSlot.getAttribute('data-locked-slot-num');
1774 const $bagSlot = document.querySelector(`#bag${slotNumber}`); // Left clicking works normally, proxy it through
1775
1776 $lockedSlot.addEventListener('click', () => {
1777 $bagSlot.dispatchEvent(new Event('pointerup'));
1778 }); // Hovering to see the tooltip works normally, proxy it through
1779
1780 $lockedSlot.addEventListener('pointerenter', () => {
1781 $bagSlot.dispatchEvent(new Event('pointerenter'));
1782 });
1783 $lockedSlot.addEventListener('pointerleave', () => {
1784 $bagSlot.dispatchEvent(new Event('pointerleave'));
1785 }); // Right clicking removes Drop item from menu, otherwise works normally, proxy it through
1786
1787 $lockedSlot.addEventListener('contextmenu', event => {
1788 // Block shift+right click
1789 if (tempState.keyModifiers.shift) return; // Don't do anything if no item in this slot
1790
1791 if (!$bagSlot.querySelector('img')) return; // Emulate right click on the item to display its context menu
1792
1793 $bagSlot.dispatchEvent(new PointerEvent('pointerup', event));
1794 setTimeout(() => {
1795 const $contextMenuChoices = Array.from(document.querySelectorAll('.container > .panel.context .choice')); // Remove "Drop item" from context menu
1796
1797 $contextMenuChoices.forEach($choice => {
1798 if ($choice.textContent.toLowerCase() === 'drop item') {
1799 $choice.style.display = 'none';
1800 }
1801 }); // Add "Unlock slot" menu item
1802
1803 $contextMenuChoices[0].parentNode.appendChild((0, _misc.makeElement)({
1804 element: 'div',
1805 class: 'choice js-unlock-item',
1806 content: 'Unlock slot'
1807 })); // Wire up "Unlock slot" menu item
1808
1809 const $unlockItemChoice = document.querySelector('.js-unlock-item');
1810 $unlockItemChoice.addEventListener('click', () => {
1811 state.lockedItemSlots.splice(state.lockedItemSlots.indexOf(slotNumber), 1);
1812 (0, _state.saveState)();
1813 $lockedSlot.parentNode.removeChild($lockedSlot); // Hide context menu after clicking unlock (removing it breaks client that tries to remove it later)
1814
1815 const $contextMenu = $unlockItemChoice.parentNode;
1816 $contextMenu.style.display = 'none';
1817 });
1818 }, 0);
1819 });
1820}
1821
1822function lockSlot(slotNumber) {
1823 const $slot = document.querySelector(`#bag${slotNumber}`);
1824 if (!$slot) return; // If slot has already been locked, don't lock it again
1825
1826 if (document.querySelector(`.js-locked-slot[data-locked-slot-num="${slotNumber}"]`)) return;
1827 const $lockedSlot = (0, _misc.makeElement)({
1828 element: 'div',
1829 class: 'js-locked-slot uimod-locked-slot'
1830 });
1831 $lockedSlot.setAttribute('data-locked-slot-num', slotNumber);
1832 $lockedSlot.setAttribute('style', `left: ${$slot.offsetLeft}px; top: ${$slot.offsetTop}px;`);
1833 $slot.parentNode.insertBefore($lockedSlot, $slot);
1834
1835 _wireLockSlot($lockedSlot);
1836}
1837
1838function initLockedSlots() {
1839 const state = (0, _state.getState)();
1840 const $inventory = (0, _game.getWindow)(_ui.WindowNames.inventory);
1841 if (!$inventory || $inventory.classList.contains('js-locked-slots-initd')) return;
1842 $inventory.classList.add('js-locked-slots-initd'); // Initialize locked slots UI
1843
1844 state.lockedItemSlots.forEach(lockSlot);
1845}
1846
1847},{"../../utils/game":34,"../../utils/misc":35,"../../utils/state":37,"../../utils/ui":38}],20:[function(require,module,exports){
1848"use strict";
1849
1850Object.defineProperty(exports, "__esModule", {
1851 value: true
1852});
1853exports.default = void 0;
1854
1855var _ui = require("../../utils/ui");
1856
1857var _game = require("../../utils/game");
1858
1859var _state = require("../../utils/state");
1860
1861var _misc = require("../../utils/misc");
1862
1863var _helpers = require("./helpers");
1864
1865function addLockItemContextMenu() {
1866 const state = (0, _state.getState)();
1867 const $inventory = (0, _game.getWindow)(_ui.WindowNames.inventory);
1868 const $contextMenu = document.querySelector('.container > .panel.context:not(.js-lock-menu-initd)');
1869 if (!$inventory || !$contextMenu) return;
1870 const $elementUnderContextMenu = document.elementFromPoint($contextMenu.offsetLeft, $contextMenu.offsetTop - 10 // Subtract 10px to get element right above context menu, rather than context menu itself
1871 ); // If context menu top left is not inside inventory, then this is not the inventory context menu
1872 // For example, Queue or Party was clicked while inventory was opened
1873
1874 if (!$inventory.contains($elementUnderContextMenu)) return; // Add Lock slot, only if unlock slot doesn't exist
1875 // Use `setTimeout` to wait for `unlock slot` to be added
1876
1877 setTimeout(() => {
1878 // If Lock slot already added, dont add it
1879 if (document.querySelector('.js-lock-item')) return; // If Unlock slot exists, don't add Lock slot
1880
1881 const isLocked = Array.from($contextMenu.querySelectorAll('.choice')).some($choice => $choice.textContent.toLowerCase() === 'unlock slot');
1882 if (isLocked) return;
1883 $contextMenu.appendChild((0, _misc.makeElement)({
1884 element: 'div',
1885 class: 'choice js-lock-item',
1886 content: 'Lock slot'
1887 }));
1888 document.querySelector('.js-lock-item').addEventListener('click', () => {
1889 // Get bag slot element displayed above right click menu
1890 // Overlay of the bag slot is selected by `elementFromPoint
1891 const $bagSlotOverlay = document.elementFromPoint($contextMenu.offsetLeft, $contextMenu.offsetTop - 10); // Parent of overlay is the bag slot. Get its id (e.g. "bag4"), then get the slot number from the id
1892
1893 const bagSlotNum = parseInt($bagSlotOverlay.parentNode.id.substr(3));
1894 state.lockedItemSlots.push(bagSlotNum);
1895 (0, _state.saveState)(); // Hide context menu
1896
1897 $contextMenu.style.display = 'none'; // Add lock slot in UI
1898
1899 (0, _helpers.lockSlot)(bagSlotNum);
1900 });
1901 }, 0);
1902} // Pass `true` as argument to reinitialize even if initd
1903
1904
1905function renderLockedItemSlots() {
1906 const $inventory = (0, _game.getWindow)(_ui.WindowNames.inventory, true);
1907 const $inventoryContainer = $inventory.parentNode; // We listen specifically on the inventory's container to check for `style` changes
1908 // so we know if the inventory has had its visibility toggled
1909
1910 const inventoryObserver = new MutationObserver(_helpers.initLockedSlots);
1911 inventoryObserver.observe($inventoryContainer, {
1912 attributes: true,
1913 childList: false
1914 });
1915 (0, _helpers.initLockedSlots)();
1916} // Removes non-numbers and duplicates from state.lockedItemSlots, and ensures it is an array
1917// This is primarily necessary because the original release had a few bugs that allowed a slot
1918// to be in the state array multiple times, or allowed `null` to be in the array. This isn't expected and caused bugs.
1919
1920
1921function cleanLockedItemState() {
1922 const state = (0, _state.getState)(); // If something really went wrong and lockedItemSlots isn't an array, set it to an empty array
1923
1924 if (!Array.isArray(state.lockedItemSlots)) {
1925 state.lockedItemSlots = [];
1926 (0, _state.saveState)();
1927 return;
1928 } // Remove duplicates and non-numbers
1929
1930
1931 const cleanedLockItems = Array.from(new Set(state.lockedItemSlots)).filter(item => typeof item === 'number');
1932 const itemsAreSame = cleanedLockItems.sort().join() === state.lockedItemSlots.sort().join();
1933
1934 if (!itemsAreSame) {
1935 state.lockedItemSlots = cleanedLockItems;
1936 (0, _state.saveState)();
1937 }
1938}
1939
1940var _default = {
1941 name: 'Locked item slots',
1942 description: 'Allows you to lock inventory slots so you can not drop those items or shift+right click them',
1943 run: ({
1944 registerOnDomChange
1945 }) => {
1946 cleanLockedItemState(); // Initialize locked item overlays
1947
1948 renderLockedItemSlots(); // Add Lock item choice to inventory context menu
1949
1950 addLockItemContextMenu();
1951 registerOnDomChange(addLockItemContextMenu);
1952 }
1953};
1954exports.default = _default;
1955
1956},{"../../utils/game":34,"../../utils/misc":35,"../../utils/state":37,"../../utils/ui":38,"./helpers":19}],21:[function(require,module,exports){
1957"use strict";
1958
1959Object.defineProperty(exports, "__esModule", {
1960 value: true
1961});
1962exports.updateMapOpacity = updateMapOpacity;
1963
1964var _state = require("../../utils/state");
1965
1966// On load, update map opacity to match state
1967// We modify the opacity of the canvas and the background color alpha of the parent container
1968// We do this to allow our opacity buttons to be visible on hover with 100% opacity
1969// (A surprisingly difficult enough task to require this implementation)
1970function updateMapOpacity() {
1971 const state = (0, _state.getState)();
1972 const $map = document.querySelector('.container canvas');
1973 const $mapContainer = document.querySelector('.js-map');
1974 $map.style.opacity = String(state.mapOpacity / 100);
1975 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
1976
1977 let opacity = state.mapOpacity / 100; // This is a slightly lazy browser workaround to fix a bug.
1978 // If the opacity is `1`, and it sets `rgba` to `1`, then the browser changes the
1979 // rgba to rgb, dropping the alpha. We could account for that and add the `alpha` back in
1980 // later, but setting the max opacity to very close to 1 makes sure the issue never crops up.
1981 // Fun fact: 0.99 retains the alpha, but setting this to 0.999 still causes the browser to drop the alpha. Rude.
1982
1983 if (opacity === 1) {
1984 opacity = 0.99;
1985 }
1986
1987 const newBgColor = mapContainerBgColor.replace(/[\d\.]+\)$/g, `${opacity})`);
1988 $mapContainer.style['background-color'] = newBgColor; // Update the button opacity
1989
1990 const $addBtn = document.querySelector('.js-map-opacity-add');
1991 const $minusBtn = document.querySelector('.js-map-opacity-minus'); // Hide plus button if the opacity is max
1992
1993 if (state.mapOpacity === 100) {
1994 $addBtn.style.visibility = 'hidden';
1995 } else {
1996 $addBtn.style.visibility = 'visible';
1997 } // Hide minus button if the opacity is lowest
1998
1999
2000 if (state.mapOpacity === 0) {
2001 $minusBtn.style.visibility = 'hidden';
2002 } else {
2003 $minusBtn.style.visibility = 'visible';
2004 }
2005}
2006
2007},{"../../utils/state":37}],22:[function(require,module,exports){
2008"use strict";
2009
2010Object.defineProperty(exports, "__esModule", {
2011 value: true
2012});
2013exports.default = void 0;
2014
2015var _state = require("../../utils/state");
2016
2017var helpers = _interopRequireWildcard(require("./helpers"));
2018
2019var _misc = require("../../utils/misc");
2020
2021function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }
2022
2023function _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; }
2024
2025function mapControls() {
2026 const state = (0, _state.getState)();
2027 const $map = document.querySelector('.container canvas');
2028
2029 if (!$map.parentNode.classList.contains('js-map')) {
2030 $map.parentNode.classList.add('js-map');
2031 }
2032
2033 const $mapContainer = document.querySelector('.js-map'); // We only use the `js-map-move` button in the `draggableUI` mod
2034
2035 const $mapButtons = (0, _misc.makeElement)({
2036 element: 'div',
2037 class: 'js-map-btns',
2038 content: `
2039 <button class="js-map-opacity-add">+</button>
2040 <button class="js-map-opacity-minus">-</button>
2041 <button class="js-map-reset">r</button>
2042 `
2043 }); // Add it right before the map container div
2044
2045 $map.parentNode.insertBefore($mapButtons, $map);
2046 helpers.updateMapOpacity();
2047 const $addBtn = document.querySelector('.js-map-opacity-add');
2048 const $minusBtn = document.querySelector('.js-map-opacity-minus');
2049 const $resetBtn = document.querySelector('.js-map-reset'); // Hide the buttons if map opacity is maxed/minimum
2050
2051 if (state.mapOpacity === 100) {
2052 $addBtn.style.visibility = 'hidden';
2053 }
2054
2055 if (state.mapOpacity === 0) {
2056 $minusBtn.style.visibility = 'hidden';
2057 } // Wire it up
2058
2059
2060 $addBtn.addEventListener('click', () => {
2061 // Update opacity
2062 state.mapOpacity += 10;
2063 (0, _state.saveState)();
2064 helpers.updateMapOpacity();
2065 });
2066 $minusBtn.addEventListener('click', () => {
2067 // Update opacity
2068 state.mapOpacity -= 10;
2069 (0, _state.saveState)();
2070 helpers.updateMapOpacity();
2071 });
2072 $resetBtn.addEventListener('click', () => {
2073 state.mapOpacity = 70;
2074 state.mapWidth = '174px';
2075 state.mapHeight = '174px';
2076 (0, _state.saveState)();
2077 helpers.updateMapOpacity();
2078 $mapContainer.style.width = state.mapWidth;
2079 $mapContainer.style.height = state.mapHeight;
2080 });
2081 helpers.updateMapOpacity();
2082}
2083
2084var _default = {
2085 name: 'Map controls',
2086 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',
2087 run: mapControls
2088};
2089exports.default = _default;
2090
2091},{"../../utils/misc":35,"../../utils/state":37,"./helpers":21}],23:[function(require,module,exports){
2092"use strict";
2093
2094Object.defineProperty(exports, "__esModule", {
2095 value: true
2096});
2097exports.handleMerchantFilterInputChange = handleMerchantFilterInputChange;
2098exports.deleteMerchantObserver = deleteMerchantObserver;
2099
2100var _game = require("../../utils/game");
2101
2102var _state = require("../../utils/state");
2103
2104function handleMerchantFilterInputChange() {
2105 const $filterInput = document.querySelector('.js-merchant-filter-input');
2106
2107 if (!$filterInput) {
2108 return;
2109 }
2110
2111 const value = $filterInput.value;
2112
2113 if (value) {
2114 _refreshMerchantFilter(); // When we're filtering, start refreshing merchant filter if we haven't already
2115
2116 } // If no filters, include single empty string, to make every item visible
2117
2118
2119 const filters = value.split(',').map(v => v.trim()) || [''];
2120 const $items = Array.from(document.querySelectorAll('.js-merchant-initd .items .slot'));
2121 $items.forEach($item => {
2122 const tooltipContentPromise = (0, _game.getTooltipContent)($item);
2123 tooltipContentPromise.then(tooltipContent => {
2124 if (!tooltipContent) {
2125 // Something weird happened, probably related to lag from looking at tooltips in bulk
2126 // In this case where we unexpectedly don't have the tooltip, just show the item rather than error out
2127 $item.parentNode.style.display = 'grid';
2128 return;
2129 }
2130
2131 let filterMatchCount = 0;
2132 filters.forEach(filter => {
2133 const matchesFilter = tooltipContent.textContent.toLowerCase().includes(filter.toLowerCase());
2134
2135 if (matchesFilter) {
2136 filterMatchCount++;
2137 }
2138 });
2139 const matchesAllFilters = filterMatchCount === filters.length;
2140
2141 if (matchesAllFilters) {
2142 $item.parentNode.style.display = 'grid';
2143 } else {
2144 $item.parentNode.style.display = 'none';
2145 }
2146 });
2147 });
2148}
2149
2150function _refreshMerchantFilter() {
2151 const tempState = (0, _state.getTempState)(); // If we're already observing, we don't need to observe again
2152
2153 if (tempState.merchantLoadingObserver) return;
2154 tempState.merchantLoadingObserver = new MutationObserver(mutation => {
2155 // If spinner is visible, we are loading. Once spinner is not visible, we are no longer loading
2156 if (mutation[0] && mutation[0].addedNodes[0] && mutation[0].addedNodes[0].classList.contains('spinner')) {
2157 tempState.merchantLoading = true;
2158 } else {
2159 // If we were loading and now we aren't, we want to refresh the filters
2160 if (tempState.merchantLoading) {
2161 handleMerchantFilterInputChange();
2162 }
2163
2164 tempState.merchantLoading = false;
2165 }
2166 });
2167 tempState.merchantLoadingObserver.observe(document.querySelector('.js-merchant-initd .buy'), {
2168 attributes: false,
2169 childList: true,
2170 subtree: true
2171 });
2172}
2173
2174function deleteMerchantObserver() {
2175 const tempState = (0, _state.getTempState)();
2176
2177 if (tempState.merchantLoadingObserver) {
2178 tempState.merchantLoading = false;
2179 tempState.merchantLoadingObserver.disconnect();
2180 delete tempState.merchantLoadingObserver;
2181 }
2182}
2183
2184},{"../../utils/game":34,"../../utils/state":37}],24:[function(require,module,exports){
2185"use strict";
2186
2187Object.defineProperty(exports, "__esModule", {
2188 value: true
2189});
2190exports.default = void 0;
2191
2192var _game = require("../../utils/game");
2193
2194var _misc = require("../../utils/misc");
2195
2196var _ui = require("../../utils/ui");
2197
2198var _helpers = require("./helpers");
2199
2200function addMerchantFilter() {
2201 const $merchant = (0, _game.getWindow)('Merchant'); // If merchant is closed or merchant filter input is already added, we dont need to do anything
2202
2203 if (!$merchant || $merchant.querySelector('.js-merchant-filter-input')) {
2204 return;
2205 }
2206
2207 $merchant.classList.add('js-merchant-initd');
2208 $merchant.classList.add('uidom-merchant-with-filters');
2209 (0, _ui.setWindowOpen)(_ui.WindowNames.merchant);
2210 const $lvMaximumField = $merchant.querySelectorAll('input[type="number"]')[1];
2211 const $customSearchField = (0, _misc.makeElement)({
2212 element: 'input',
2213 class: 'js-merchant-filter-input uidom-merchant-input',
2214 type: 'search',
2215 placeholder: 'Filters (comma separated)'
2216 });
2217 $lvMaximumField.parentNode.insertBefore($customSearchField, $lvMaximumField.nextSibling);
2218 $merchant.querySelector('.js-merchant-filter-input').addEventListener('keyup', (0, _misc.debounce)(_helpers.handleMerchantFilterInputChange, 250));
2219}
2220
2221function cleanupMerchantObserver() {
2222 if ((0, _ui.isWindowOpen)(_ui.WindowNames.merchant)) {
2223 const $merchant = document.querySelector('.js-merchant-initd');
2224 if ($merchant) return;
2225 } // Window was set to open but is actually closed, let's clean up...
2226
2227
2228 (0, _ui.setWindowClosed)(_ui.WindowNames.merchant);
2229 (0, _helpers.deleteMerchantObserver)();
2230}
2231
2232var _default = {
2233 name: 'Merchant filter',
2234 description: 'Allows you to specify filters, or search text, for items displayed in the merchant',
2235 run: ({
2236 registerOnDomChange
2237 }) => {
2238 addMerchantFilter();
2239 registerOnDomChange(addMerchantFilter);
2240 registerOnDomChange(() => {
2241 cleanupMerchantObserver();
2242 });
2243 }
2244};
2245exports.default = _default;
2246
2247},{"../../utils/game":34,"../../utils/misc":35,"../../utils/ui":38,"./helpers":23}],25:[function(require,module,exports){
2248"use strict";
2249
2250Object.defineProperty(exports, "__esModule", {
2251 value: true
2252});
2253exports.default = void 0;
2254
2255var _state = require("../../utils/state");
2256
2257function resizableChat() {
2258 const state = (0, _state.getState)(); // Add the appropriate classes
2259
2260 const $chatContainer = document.querySelector('#chat').parentNode;
2261 $chatContainer.classList.add('js-chat-resize'); // Load initial chat and map size
2262
2263 if (state.chatWidth && state.chatHeight) {
2264 $chatContainer.style.width = state.chatWidth;
2265 $chatContainer.style.height = state.chatHeight;
2266 } // Save chat size on resize
2267
2268
2269 const resizeObserverChat = new ResizeObserver(() => {
2270 const chatWidthStr = window.getComputedStyle($chatContainer, null).getPropertyValue('width');
2271 const chatHeightStr = window.getComputedStyle($chatContainer, null).getPropertyValue('height');
2272 const hasWidthChanged = state.chatWidth !== chatWidthStr;
2273 const hasHeightChanged = state.chatHeight !== chatHeightStr;
2274 if (hasWidthChanged) state.chatWidth = chatWidthStr;
2275 if (hasHeightChanged) state.chatHeight = chatHeightStr;
2276 if (hasWidthChanged || hasHeightChanged) (0, _state.saveState)();
2277 });
2278 resizeObserverChat.observe($chatContainer);
2279}
2280
2281var _default = {
2282 name: 'Resizable chat',
2283 description: 'Allows you to resize chat by clicking and dragging from the bottom right of chat',
2284 run: resizableChat
2285};
2286exports.default = _default;
2287
2288},{"../../utils/state":37}],26:[function(require,module,exports){
2289"use strict";
2290
2291Object.defineProperty(exports, "__esModule", {
2292 value: true
2293});
2294exports.mapResizeHandler = mapResizeHandler;
2295exports.triggerMapResize = triggerMapResize;
2296
2297var _state = require("../../utils/state");
2298
2299// When the map container resizes, we want to update the canvas width/height and the state
2300function mapResizeHandler() {
2301 if (!document.querySelector('.layout')) {
2302 return;
2303 }
2304
2305 const state = (0, _state.getState)();
2306 const tempState = (0, _state.getTempState)();
2307 const $map = document.querySelector('.container canvas').parentNode;
2308 const $canvas = $map.querySelector('canvas'); // Get real values of map height/width, excluding padding/margin/etc
2309 // We round the values in this file to prevent unnecessary decimal points in our map or canvas sizes
2310 // For some people these decimal points cause the map to constantly resize, making it pretty unusable.
2311 // Rounding the numbers fixes this.
2312
2313 const mapWidthStr = window.getComputedStyle($map, null).getPropertyValue('width');
2314 const mapHeightStr = window.getComputedStyle($map, null).getPropertyValue('height');
2315 const mapWidth = Math.round(Number(mapWidthStr.slice(0, -2)));
2316 const mapHeight = Math.round(Number(mapHeightStr.slice(0, -2))); // If height/width are 0 or unset, don't resize canvas
2317
2318 if (!mapWidth || !mapHeight) {
2319 return;
2320 }
2321
2322 if ($canvas.width !== mapWidth) {
2323 $canvas.width = mapWidth;
2324 }
2325
2326 if ($canvas.height !== mapHeight) {
2327 $canvas.height = mapHeight;
2328 } // If we're clicking map, i.e. manually resizing, then save state
2329 // Don't save state when minimizing/maximizing map via [M]
2330
2331
2332 if (tempState.clickingMap) {
2333 state.mapWidth = mapWidthStr;
2334 state.mapHeight = mapHeightStr;
2335 (0, _state.saveState)();
2336 } else {
2337 const isMaximized = mapWidth > tempState.lastMapWidth && mapHeight > tempState.lastMapHeight;
2338
2339 if (!isMaximized) {
2340 $map.style.width = state.mapWidth;
2341 $map.style.height = state.mapHeight;
2342 }
2343 } // Store last map width/height in temp state, so we know if we've minimized or maximized
2344
2345
2346 tempState.lastMapWidth = mapWidth;
2347 tempState.lastMapHeight = mapHeight;
2348} // We need to observe canvas resizes to tell when the user presses M to open the big map
2349// At that point, we resize the map to match the canvas
2350
2351
2352function triggerMapResize() {
2353 if (!document.querySelector('.layout')) {
2354 return;
2355 }
2356
2357 const $map = document.querySelector('.container canvas').parentNode;
2358 const $canvas = $map.querySelector('canvas'); // Get real values of map height/width, excluding padding/margin/etc
2359
2360 const mapWidthStr = window.getComputedStyle($map, null).getPropertyValue('width');
2361 const mapHeightStr = window.getComputedStyle($map, null).getPropertyValue('height');
2362 const mapWidth = Math.round(Number(mapWidthStr.slice(0, -2)));
2363 const mapHeight = Math.round(Number(mapHeightStr.slice(0, -2)));
2364 const canvasWidth = Math.round($canvas.width);
2365 const canvasHeight = Math.round($canvas.height); // If height/width are 0 or unset, we don't care about resizing yet
2366
2367 if (!mapWidth || !mapHeight) {
2368 return;
2369 }
2370
2371 if (canvasWidth !== mapWidth) {
2372 $map.style.width = `${canvasWidth}px`;
2373 }
2374
2375 if (canvasHeight !== mapHeight) {
2376 $map.style.height = `${canvasHeight}px`;
2377 }
2378}
2379
2380},{"../../utils/state":37}],27:[function(require,module,exports){
2381"use strict";
2382
2383Object.defineProperty(exports, "__esModule", {
2384 value: true
2385});
2386exports.default = void 0;
2387
2388var _state = require("../../utils/state");
2389
2390var helpers = _interopRequireWildcard(require("./helpers"));
2391
2392var _misc = require("../../utils/misc");
2393
2394function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }
2395
2396function _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; }
2397
2398function resizableMap() {
2399 const state = (0, _state.getState)();
2400 const tempState = (0, _state.getTempState)();
2401 const $map = document.querySelector('.container canvas').parentNode;
2402 const $canvas = $map.querySelector('canvas');
2403 $map.classList.add('js-map-resize'); // Track whether we're clicking (resizing) map or not
2404 // Used to detect if resize changes are manually done, or from minimizing/maximizing map (with [M])
2405
2406 $map.addEventListener('mousedown', () => {
2407 tempState.clickingMap = true;
2408 }); // Sometimes the mouseup event may be registered outside of the map - we account for this
2409
2410 document.body.addEventListener('mouseup', () => {
2411 tempState.clickingMap = false;
2412 });
2413
2414 if (state.mapWidth && state.mapHeight) {
2415 $map.style.width = state.mapWidth;
2416 $map.style.height = state.mapHeight;
2417 helpers.mapResizeHandler(); // Update canvas size on initial load of saved map size
2418 } // On resize of map, resize canvas to match
2419 // Debouncing allows map to be visible while resizing
2420
2421
2422 const debouncedMapResize = (0, _misc.debounce)(helpers.mapResizeHandler, 1);
2423 const resizeObserverMap = new ResizeObserver(debouncedMapResize);
2424 helpers.mapResizeHandler();
2425 resizeObserverMap.observe($map); // We debounce the canvas resize, so it doesn't resize every single
2426 // pixel you move when resizing the DOM. If this were to happen,
2427 // resizing would constantly be interrupted. You'd have to resize a tiny bit,
2428 // lift left click, left click again to resize a tiny bit more, etc.
2429 // Resizing is smooth when we debounce this canvas.
2430
2431 const debouncedTriggerResize = (0, _misc.debounce)(helpers.triggerMapResize, 50);
2432 const resizeObserverCanvas = new ResizeObserver(debouncedTriggerResize);
2433 resizeObserverCanvas.observe($canvas);
2434}
2435
2436var _default = {
2437 name: 'Resizable map',
2438 description: 'Allows you to resize the map by clicking and dragging from the bottom left',
2439 run: resizableMap
2440};
2441exports.default = _default;
2442
2443},{"../../utils/misc":35,"../../utils/state":37,"./helpers":26}],28:[function(require,module,exports){
2444"use strict";
2445
2446Object.defineProperty(exports, "__esModule", {
2447 value: true
2448});
2449exports.default = void 0;
2450
2451// The last clicked UI window displays above all other UI windows
2452// This is useful when, for example, your inventory is near the market window,
2453// and you want the window and the tooltips to display above the market window.
2454function selectedWindowIsTop() {
2455 Array.from(document.querySelectorAll('.window:not(.js-is-top-initd)')).forEach($window => {
2456 $window.classList.add('js-is-top-initd');
2457 $window.addEventListener('mousedown', () => {
2458 // First, make the other is-top window not is-top
2459 const $otherWindowContainer = document.querySelector('.js-is-top');
2460
2461 if ($otherWindowContainer) {
2462 $otherWindowContainer.classList.remove('js-is-top');
2463 } // Then, make our window's container (the z-index container) is-top
2464
2465
2466 $window.parentNode.classList.add('js-is-top');
2467 });
2468 });
2469}
2470
2471var _default = {
2472 name: 'Make Selected Window Top',
2473 description: 'The UI window you click will always be displayed over other UI windows',
2474 run: ({
2475 registerOnDomChange
2476 }) => {
2477 selectedWindowIsTop(); // As windows are opened, we want to enable them to become the top window when they're clicked
2478
2479 registerOnDomChange(selectedWindowIsTop);
2480 }
2481};
2482exports.default = _default;
2483
2484},{}],29:[function(require,module,exports){
2485"use strict";
2486
2487Object.defineProperty(exports, "__esModule", {
2488 value: true
2489});
2490exports.addSkillCooldownNumbers = addSkillCooldownNumbers;
2491
2492var _state = require("../../utils/state");
2493
2494var _misc = require("../../utils/misc");
2495
2496function _getCooldownText(cd) {
2497 const timeBetweenCooldownChecks = cd.latestCooldownTimestamp - cd.initialCooldownTimestamp;
2498 const percentCompletedWithinTime = cd.initialCooldownPcntLeft - cd.latestCooldownPcntLeft;
2499 const secondsForOnePercent = timeBetweenCooldownChecks / percentCompletedWithinTime / 1000;
2500 return Math.floor(secondsForOnePercent * cd.latestCooldownPcntLeft);
2501}
2502
2503function _handleCooldownUpdate(mutations) {
2504 const tempState = (0, _state.getTempState)();
2505 mutations.forEach(mutation => {
2506 if (!mutation.target.classList.contains('cd')) return;
2507 const $cooldownOverlay = mutation.target;
2508 const skillId = $cooldownOverlay.parentNode.id;
2509 const cooldownPercentageLeft = parseInt($cooldownOverlay.style.height);
2510 let cdState = tempState.cooldownNums[skillId]; // If cooldown percentage left is greater than the current initial cooldown pcnt left,
2511 // that means the skill cooldown counter is still tracking an old cooldown.
2512 // This can happen rarely if the user casts the ability the instant it comes off cooldown.
2513 // In this scenario, we want to reset the cooldown state.
2514 // If we don't reset the cooldown state, the cooldown number will be wrong because
2515 // `initialCooldownTime` will be from the previous cast, not the current cast.
2516
2517 if (cdState.initialCooldownPcntLeft && cooldownPercentageLeft >= cdState.initialCooldownPcntLeft) {
2518 cdState.initialCooldownTimestamp = null;
2519 cdState.initialCooldownPcntLeft = null;
2520 cdState.latestCooldownTimestamp = null;
2521 cdState.latestCooldownPcntLeft = null;
2522 cdState.calculationCount = 0;
2523 }
2524
2525 if (!cdState.initialCooldownTimestamp) {
2526 cdState.initialCooldownTimestamp = Date.now();
2527 cdState.initialCooldownPcntLeft = cooldownPercentageLeft;
2528 }
2529
2530 cdState.latestCooldownTimestamp = Date.now();
2531 cdState.latestCooldownPcntLeft = cooldownPercentageLeft;
2532 cdState.calculationCount++; // Minimum number of numbers to figure out an accurate enough real cooldown number = 3
2533 // Set the cooldown number in the UI
2534
2535 if (cdState.calculationCount > 2) {
2536 const $cooldownNum = $cooldownOverlay.querySelector('.js-cooldown-num');
2537 $cooldownNum.innerText = _getCooldownText(cdState);
2538 }
2539 });
2540}
2541
2542function addSkillCooldownNumbers() {
2543 const tempState = (0, _state.getTempState)(); // Add/update cooldowns
2544
2545 const $skillCooldowns = Array.from(document.querySelectorAll('#skillbar .cd:not(.js-cooldown-num-initd'));
2546 $skillCooldowns.forEach($cooldownOverlay => {
2547 $cooldownOverlay.classList.add('js-cooldown-num-initd'); // Add cooldown element to overlay
2548
2549 $cooldownOverlay.appendChild((0, _misc.makeElement)({
2550 element: 'div',
2551 class: 'js-cooldown-num'
2552 }));
2553 const cooldownObserver = new MutationObserver(_handleCooldownUpdate); // Add cooldown number and mutator to state
2554
2555 const skillId = $cooldownOverlay.parentNode.id;
2556 tempState.cooldownNums[skillId] = {
2557 initialCooldownTimestamp: null,
2558 initialCooldownPcntLeft: null,
2559 latestCooldownTimestamp: null,
2560 latestCooldownPcntLeft: null,
2561 calculationCount: 0
2562 }; // Clear preexisting observer if it exists, then set new one to state
2563
2564 if (tempState.cooldownObservers[skillId]) {
2565 tempState.cooldownObservers[skillId].disconnect();
2566 delete tempState.cooldownObservers[skillId];
2567 }
2568
2569 tempState.cooldownObservers[skillId] = cooldownObserver;
2570 cooldownObserver.observe($cooldownOverlay, {
2571 attributes: true
2572 });
2573 });
2574}
2575
2576},{"../../utils/misc":35,"../../utils/state":37}],30:[function(require,module,exports){
2577"use strict";
2578
2579Object.defineProperty(exports, "__esModule", {
2580 value: true
2581});
2582exports.default = void 0;
2583
2584var _state = require("../../utils/state");
2585
2586var _helpers = require("./helpers");
2587
2588function skillCooldownNumbers() {
2589 const tempState = (0, _state.getTempState)(); // If not initialized, initialize with initial observer
2590
2591 const $skillBar = document.querySelector('#skillbar:not(.js-cooldowns-skillbar-initd');
2592 if (!$skillBar) return;
2593 $skillBar.classList.add('js-cooldowns-skillbar-initd');
2594
2595 if (tempState.skillBarObserver) {
2596 tempState.skillBarObserver.disconnect();
2597 delete tempState.skillBarObserver;
2598 }
2599
2600 tempState.skillBarObserver = new MutationObserver(_helpers.addSkillCooldownNumbers);
2601 tempState.skillBarObserver.observe($skillBar, {
2602 subtree: true,
2603 childList: true,
2604 attributes: true
2605 });
2606}
2607
2608var _default = {
2609 name: 'Skill cooldown numbers',
2610 description: 'Overlays time left on cooldown over skill icons',
2611 run: () => {
2612 skillCooldownNumbers();
2613 }
2614};
2615exports.default = _default;
2616
2617},{"../../utils/state":37,"./helpers":29}],31:[function(require,module,exports){
2618"use strict";
2619
2620Object.defineProperty(exports, "__esModule", {
2621 value: true
2622});
2623exports.getCurrentCharacterLvl = getCurrentCharacterLvl;
2624exports.getCurrentXp = getCurrentXp;
2625exports.getNextLevelXp = getNextLevelXp;
2626exports.resetXpMeterState = resetXpMeterState;
2627exports.msToString = msToString;
2628
2629var _state = require("../../utils/state");
2630
2631function getCurrentCharacterLvl() {
2632 return Number(document.querySelector('#ufplayer .bgmana > .left').textContent.split('Lv. ')[1]);
2633}
2634
2635function getCurrentXp() {
2636 return Number(document.querySelector('#expbar .progressBar > .left').textContent.split('/')[0].replace(/,/g, '').trim());
2637}
2638
2639function getNextLevelXp() {
2640 return Number(document.querySelector('#expbar .progressBar > .left').textContent.split('/')[1].replace(/,/g, '').replace('EXP', '').trim());
2641} // user invoked reset of xp meter stats
2642
2643
2644function resetXpMeterState() {
2645 const state = (0, _state.getState)();
2646 state.xpMeterState.xpGains = []; // array of xp deltas every second
2647
2648 state.xpMeterState.averageXp = 0;
2649 state.xpMeterState.gainedXp = 0;
2650 (0, _state.saveState)();
2651 document.querySelector('.js-xp-time').textContent = '-:-:-';
2652}
2653
2654function msToString(ms) {
2655 const pad = value => value < 10 ? `0${value}` : value;
2656
2657 const hours = pad(Math.floor(ms / (1000 * 60 * 60) % 60));
2658 const minutes = pad(Math.floor(ms / (1000 * 60) % 60));
2659 const seconds = pad(Math.floor(ms / 1000 % 60));
2660 return `${hours}:${minutes}:${seconds}`;
2661}
2662
2663},{"../../utils/state":37}],32:[function(require,module,exports){
2664"use strict";
2665
2666Object.defineProperty(exports, "__esModule", {
2667 value: true
2668});
2669exports.default = void 0;
2670
2671var _state = require("../../utils/state");
2672
2673var helpers = _interopRequireWildcard(require("./helpers"));
2674
2675var _ui = require("../../utils/ui");
2676
2677function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }
2678
2679function _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; }
2680
2681// TODO: Consider adding start button to start interval, and stop after X minutes of no EXP
2682// Or maybe watch XP bar and start it once XP bar first moves?
2683// Adds XP Meter DOM icon and window, starts continuous interval to get current xp over time
2684function xpMeter() {
2685 const state = (0, _state.getState)();
2686 const tempState = (0, _state.getTempState)();
2687 (0, _ui.createXpMeter)(); // If it was open when the game last closed keep it open
2688
2689 if ((0, _ui.isWindowOpen)(_ui.WindowNames.xpMeter)) {
2690 (0, _ui.toggleXpMeterVisibility)();
2691 } // Wire up icon and xpmeter window
2692
2693
2694 document.querySelector('.js-sysxp').addEventListener('click', _ui.toggleXpMeterVisibility);
2695 document.querySelector('.js-xpmeter-close-icon').addEventListener('click', _ui.toggleXpMeterVisibility);
2696 document.querySelector('.js-xpmeter-reset-button').addEventListener('click', helpers.resetXpMeterState);
2697 const currentXp = helpers.getCurrentXp();
2698 const currentCharLvl = helpers.getCurrentCharacterLvl();
2699 if (currentXp !== state.xpMeterState.currentXp) state.xpMeterState.currentXp = currentXp;
2700 if (currentCharLvl !== state.xpMeterState.currentLvl) state.xpMeterState.currentLvl = currentCharLvl;
2701 (0, _state.saveState)();
2702 if (tempState.xpMeterInterval) clearInterval(tempState.xpMeterInterval); // every second we run the operations for xp meter, update xps, calc delta, etc
2703 // TODO Cleanup: This interval may not be cleaned up if the UI mod reinitializes,
2704 // e.g. user is away from tab for a while then comes back
2705 // Should confirm if this is an issue, and try to fix it if possible.
2706
2707 tempState.xpMeterInterval = setInterval(() => {
2708 if (!document.querySelector('#expbar')) {
2709 return;
2710 } // This _shouldn't_ happen, but in case it does, reset xp meter state instead of throwing error
2711
2712
2713 if (!Array.isArray(state.xpMeterState.xpGains)) {
2714 helpers.resetXpMeterState();
2715 }
2716
2717 const currentXp = helpers.getCurrentXp();
2718 const nextLvlXp = helpers.getNextLevelXp();
2719 const currentLvl = helpers.getCurrentCharacterLvl(); // Only update and save state if it has changed
2720
2721 const gainedXp = currentXp - state.xpMeterState.currentXp;
2722 const xpGains = currentXp - state.xpMeterState.currentXp;
2723 const averageXp = state.xpMeterState.xpGains.length > 0 ? state.xpMeterState.xpGains.reduce((a, b) => a + b, 0) / state.xpMeterState.xpGains.length : 0; // Our algorithms and session time depend on an xpGain being pushed every second, even if it is 0
2724
2725 state.xpMeterState.xpGains.push(xpGains); // array of xp deltas every second
2726
2727 if (gainedXp !== 0) state.xpMeterState.gainedXp += gainedXp;
2728 if (currentXp !== state.xpMeterState.currentXp) state.xpMeterState.currentXp = currentXp;
2729 if (averageXp !== state.xpMeterState.averageXp) state.xpMeterState.averageXp = averageXp;
2730 (0, _state.saveState)();
2731
2732 if (document.querySelector('.js-xpmeter')) {
2733 document.querySelector('.js-xpm').textContent = parseInt((state.xpMeterState.averageXp * 60).toFixed(0)).toLocaleString();
2734 document.querySelector('.js-xph').textContent = parseInt((state.xpMeterState.averageXp * 60 * 60).toFixed(0)).toLocaleString();
2735 document.querySelector('.js-xpg').textContent = state.xpMeterState.gainedXp.toLocaleString();
2736 document.querySelector('.js-xpl').textContent = (nextLvlXp - currentXp).toLocaleString();
2737 document.querySelector('.js-xp-s-time').textContent = helpers.msToString(state.xpMeterState.xpGains.length * 1000); // need a positive integer for averageXp to calc time left
2738
2739 if (state.xpMeterState.averageXp > 0) document.querySelector('.js-xp-time').textContent = helpers.msToString((nextLvlXp - currentXp) / state.xpMeterState.averageXp * 1000);
2740 }
2741
2742 if (state.xpMeterState.currentLvl < currentLvl) {
2743 helpers.resetXpMeterState();
2744 state.xpMeterState.currentLvl = currentLvl;
2745 (0, _state.saveState)();
2746 }
2747 }, 1000);
2748}
2749
2750var _default = {
2751 name: 'XP Meter',
2752 description: "Tracks your XP/minute and displays how much XP you're getting and lets you know how long until you level up",
2753 run: xpMeter
2754};
2755exports.default = _default;
2756
2757},{"../../utils/state":37,"../../utils/ui":38,"./helpers":31}],33:[function(require,module,exports){
2758"use strict";
2759
2760Object.defineProperty(exports, "__esModule", {
2761 value: true
2762});
2763exports.setGMChatVisibility = setGMChatVisibility;
2764exports.filterAllChat = filterAllChat;
2765exports.whisperPlayer = whisperPlayer;
2766exports.partyPlayer = partyPlayer;
2767exports.addChatMessage = addChatMessage;
2768
2769var _state = require("./state");
2770
2771var _misc = require("./misc");
2772
2773// Updates state.chat.GM and the DOM to make text white/grey depending on if gm chat is visible/filtered
2774// Then filters chat and saves updated chat state
2775function setGMChatVisibility(isGMChatVisible) {
2776 const state = (0, _state.getState)();
2777 const $chatGM = document.querySelector(`.js-chat-gm`);
2778 state.chat.GM = isGMChatVisible;
2779 $chatGM.classList.toggle('textgrey', !state.chat.GM);
2780 filterAllChat();
2781 (0, _state.saveState)();
2782} // Filters all chat based on custom filters
2783
2784
2785function filterAllChat() {
2786 const state = (0, _state.getState)(); // Blocked user filter
2787
2788 Object.keys(state.blockList).forEach(blockedName => {
2789 // Get the `.name` elements from the blocked user, if we haven't already hidden their messages
2790 const $blockedChatNames = Array.from(document.querySelectorAll(`[data-chat-name="${blockedName}"]:not(.js-line-blocked)`)); // Hide each of their messages
2791
2792 $blockedChatNames.forEach($name => {
2793 // Add the class name to $name so we can track whether it's been hidden in our CSS selector $blockedChatNames
2794 $name.classList.add('js-line-blocked');
2795 const $line = $name.parentNode.parentNode.parentNode; // Add the class name to $line so we can visibly hide the entire chat line
2796
2797 $line.classList.add('js-line-blocked');
2798 });
2799 }); // Custom channel filter
2800
2801 Object.keys(state.chat).forEach(channel => {
2802 Array.from(document.querySelectorAll(`.text${channel}.content`)).forEach($textItem => {
2803 const $line = $textItem.parentNode.parentNode;
2804 $line.classList.toggle('js-line-hidden', !state.chat[channel]);
2805 });
2806 });
2807}
2808
2809function enterTextIntoChat(text) {
2810 // Open chat input
2811 const enterEvent = new KeyboardEvent('keydown', {
2812 bubbles: true,
2813 cancelable: true,
2814 keyCode: 13
2815 });
2816 document.body.dispatchEvent(enterEvent); // Place text into chat
2817
2818 const $input = document.querySelector('#chatinput input');
2819 $input.value = text; // Get chat input to recognize slash commands and change the channel
2820 // by triggering the `input` event.
2821 // (Did some debugging to figure out the channel only changes when the
2822 // svelte `input` event listener exists.)
2823
2824 const inputEvent = new KeyboardEvent('input', {
2825 bubbles: true,
2826 cancelable: true
2827 });
2828 $input.dispatchEvent(inputEvent);
2829}
2830
2831function submitChat() {
2832 const $input = document.querySelector('#chatinput input');
2833 const kbEvent = new KeyboardEvent('keydown', {
2834 bubbles: true,
2835 cancelable: true,
2836 keyCode: 13
2837 });
2838 $input.dispatchEvent(kbEvent);
2839} // Automated chat command helpers
2840// (We've been OK'd to do these by the dev - all automation like this should receive approval from the dev)
2841
2842
2843function whisperPlayer(playerName) {
2844 enterTextIntoChat(`/${playerName} `);
2845}
2846
2847function partyPlayer(playerName) {
2848 enterTextIntoChat(`/partyinvite ${playerName}`);
2849 submitChat();
2850} // Pushes message to chat
2851// TODO: The margins for the message are off slightly compared to other messages - why?
2852
2853
2854function addChatMessage(text) {
2855 const newMessageHTML = `
2856 <div class="linewrap svelte-1vrlsr3">
2857 <span class="time svelte-1vrlsr3">00.00</span>
2858 <span class="textuimod content svelte-1vrlsr3">
2859 <span class="capitalize channel svelte-1vrlsr3">UIMod</span>
2860 </span>
2861 <span class="svelte-1vrlsr3">${text}</span>
2862 </div>
2863 `;
2864 const element = (0, _misc.makeElement)({
2865 element: 'article',
2866 class: 'line svelte-1vrlsr3',
2867 content: newMessageHTML
2868 });
2869 const $chat = document.querySelector('#chat');
2870 $chat.appendChild(element); // Scroll to bottom of chat
2871
2872 $chat.scrollTop = $chat.scrollHeight;
2873}
2874
2875},{"./misc":35,"./state":37}],34:[function(require,module,exports){
2876"use strict";
2877
2878Object.defineProperty(exports, "__esModule", {
2879 value: true
2880});
2881exports.getTooltipContent = getTooltipContent;
2882exports.getWindow = getWindow;
2883
2884var _state = require("./state");
2885
2886// Gets the node of a tooltip for any element, emulates shift keypress to get tooltip with quality details
2887// Must be `await`'d to use, e.g. `await getTooltipContent($element)`
2888// Optionally pass `getDetailedTooltips` as `true` if you want detailed tooltips by holding shift
2889// ^ is laggier, do not use when looking at more than one item
2890async function getTooltipContent($elementToHoverOver, getDetailedTooltips) {
2891 const tempState = (0, _state.getTempState)(); // Emulate holding down shift when getting tooltip
2892 // Don't need to emulate if user is already holding it down
2893
2894 if (getDetailedTooltips && !tempState.keyModifiers.shift) {
2895 // Set this so the keymodifiers mod knows our shift press shouldn't be tracked in tempState
2896 tempState.gettingTooltipContentShiftPress = true;
2897 document.body.dispatchEvent(new KeyboardEvent('keydown', {
2898 bubbles: true,
2899 cancelable: true,
2900 key: 'Shift'
2901 }));
2902 }
2903
2904 $elementToHoverOver.dispatchEvent(new Event('pointerenter'));
2905 const closeTooltipPromise = new Promise(resolve => setTimeout(() => {
2906 const resolveWithTooltip = () => {
2907 // If there is no slotdescription at this point, the item element passed very likely has no tooltip
2908 const $tooltip = document.querySelector('.slotdescription');
2909
2910 if (!$tooltip || !$tooltip.cloneNode) {
2911 resolve(false);
2912 } else {
2913 resolve($tooltip.cloneNode(true));
2914 }
2915
2916 if (tempState.gettingTooltipContentShiftPress) {
2917 // Release our emulated shift press
2918 document.body.dispatchEvent(new KeyboardEvent('keyup', {
2919 bubbles: true,
2920 cancelable: true,
2921 key: 'Shift'
2922 }));
2923 tempState.gettingTooltipContentShiftPress = false;
2924 }
2925
2926 $elementToHoverOver.dispatchEvent(new Event('pointerleave'));
2927 }; // Very occasionally the 0ms wait time on our timeout doesn't show the tooltip,
2928 // so we set a second timeout to account for this. Not the most perfect user experience,
2929 // but it rarely hapens, and it's better than getting an error.
2930
2931
2932 if (getDetailedTooltips && !document.querySelector('.slotdescription')) {
2933 setTimeout(resolveWithTooltip, 1);
2934 } else {
2935 resolveWithTooltip();
2936 }
2937 }, 0));
2938 const $tooltip = await closeTooltipPromise;
2939 return $tooltip;
2940} // Use this to get a specific window, rather than using the svelte class, which is not preferable
2941// Only returns window if it is visible. Some windows are kept in DOM at all times, but are not visible until opened, e.g. Inventory.
2942// To get window even if it isn't visible (but is still in DOM), pass `true` to second argument
2943
2944
2945function getWindow(windowTitle, getInvisibleWindow) {
2946 const $specificWindowTitle = Array.from(document.querySelectorAll('.window [name="title"]')).find($windowTitle => $windowTitle.textContent.toLowerCase() === windowTitle.toLowerCase());
2947 const $window = $specificWindowTitle ? $specificWindowTitle.parentNode.parentNode.parentNode : $specificWindowTitle; // If window is invisible, don't return it unless we are overriding with `getInvisibleWindow`
2948
2949 if (!$window || !$window.offsetParent && !getInvisibleWindow) {
2950 return;
2951 } else {
2952 return $specificWindowTitle ? $specificWindowTitle.parentNode.parentNode.parentNode : $specificWindowTitle;
2953 }
2954}
2955
2956},{"./state":37}],35:[function(require,module,exports){
2957"use strict";
2958
2959Object.defineProperty(exports, "__esModule", {
2960 value: true
2961});
2962exports.makeElement = makeElement;
2963exports.debounce = debounce;
2964exports.uuid = uuid;
2965exports.deepClone = deepClone;
2966
2967// Nicer impl to create elements in one method call
2968function makeElement(args) {
2969 const $node = document.createElement(args.element);
2970 if (args.class) $node.className = args.class;
2971 if (args.content) $node.innerHTML = args.content;
2972 if (args.src) $node.src = args.src;
2973 if (args.type) $node.type = args.type;
2974 if (args.placeholder) $node.placeholder = args.placeholder;
2975 return $node;
2976} // Credit: David Walsh
2977
2978
2979function debounce(func, wait, immediate) {
2980 var timeout;
2981 return function () {
2982 var context = this,
2983 args = arguments;
2984
2985 var later = function () {
2986 timeout = null;
2987 if (!immediate) func.apply(context, args);
2988 };
2989
2990 var callNow = immediate && !timeout;
2991 clearTimeout(timeout);
2992 timeout = setTimeout(later, wait);
2993 if (callNow) func.apply(context, args);
2994 };
2995} // Credit: https://gist.github.com/jcxplorer/823878
2996// Generate random UUID string
2997
2998
2999function uuid() {
3000 var uuid = '',
3001 i,
3002 random;
3003
3004 for (i = 0; i < 32; i++) {
3005 random = Math.random() * 16 | 0;
3006
3007 if (i == 8 || i == 12 || i == 16 || i == 20) {
3008 uuid += '-';
3009 }
3010
3011 uuid += (i == 12 ? 4 : i == 16 ? random & 3 | 8 : random).toString(16);
3012 }
3013
3014 return uuid;
3015} // Credit: http://voidcanvas.com/clone-an-object-in-vanilla-js-in-depth/
3016
3017
3018function deepClone(obj) {
3019 //in case of premitives
3020 if (obj === null || typeof obj !== 'object') {
3021 return obj;
3022 } //date objects should be
3023
3024
3025 if (obj instanceof Date) {
3026 return new Date(obj.getTime());
3027 } //handle Array
3028
3029
3030 if (Array.isArray(obj)) {
3031 var clonedArr = [];
3032 obj.forEach(function (element) {
3033 clonedArr.push(deepClone(element));
3034 });
3035 return clonedArr;
3036 } //lastly, handle objects
3037
3038
3039 let clonedObj = new obj.constructor();
3040
3041 for (var prop in obj) {
3042 if (obj.hasOwnProperty(prop)) {
3043 clonedObj[prop] = deepClone(obj[prop]);
3044 }
3045 }
3046
3047 return clonedObj;
3048}
3049
3050},{}],36:[function(require,module,exports){
3051"use strict";
3052
3053Object.defineProperty(exports, "__esModule", {
3054 value: true
3055});
3056exports.friendPlayer = friendPlayer;
3057exports.unfriendPlayer = unfriendPlayer;
3058exports.blockPlayer = blockPlayer;
3059exports.unblockPlayer = unblockPlayer;
3060
3061var _state = require("./state");
3062
3063var chat = _interopRequireWildcard(require("./chat"));
3064
3065var ui = _interopRequireWildcard(require("./ui"));
3066
3067function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }
3068
3069function _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; }
3070
3071function friendPlayer(playerName) {
3072 const state = (0, _state.getState)();
3073
3074 if (state.friendsList[playerName]) {
3075 return;
3076 }
3077
3078 state.friendsList[playerName] = true;
3079 chat.addChatMessage(`${playerName} has been added to your friends list.`);
3080 (0, _state.saveState)(); // If UI is open remake it with new changes
3081
3082 if (ui.isWindowOpen(ui.WindowNames.friendsList)) {
3083 ui.removeFriendsList();
3084 ui.createFriendsList();
3085 }
3086}
3087
3088function unfriendPlayer(playerName) {
3089 const state = (0, _state.getState)();
3090
3091 if (!state.friendsList[playerName]) {
3092 return;
3093 }
3094
3095 delete state.friendsList[playerName];
3096 delete state.friendNotes[playerName];
3097 chat.addChatMessage(`${playerName} is no longer on your friends list.`);
3098 (0, _state.saveState)(); // If UI is open remake it with new changes
3099
3100 if (ui.isWindowOpen(ui.WindowNames.friendsList)) {
3101 ui.removeFriendsList();
3102 ui.createFriendsList();
3103 }
3104} // Adds player to block list, to be filtered out of chat
3105
3106
3107function blockPlayer(playerName) {
3108 const state = (0, _state.getState)();
3109
3110 if (state.blockList[playerName]) {
3111 return;
3112 }
3113
3114 state.blockList[playerName] = true;
3115 chat.filterAllChat();
3116 chat.addChatMessage(`${playerName} has been blocked.`);
3117 (0, _state.saveState)(); // If UI is open remake it with new changes
3118
3119 if (ui.isWindowOpen(ui.WindowNames.blockList)) {
3120 ui.removeBlockList();
3121 ui.createBlockList();
3122 }
3123} // Removes player from block list and makes their messages visible again
3124
3125
3126function unblockPlayer(playerName) {
3127 const state = (0, _state.getState)();
3128 delete state.blockList[playerName];
3129 chat.addChatMessage(`${playerName} has been unblocked.`);
3130 (0, _state.saveState)(); // Make messages visible again
3131
3132 const $chatNames = Array.from(document.querySelectorAll(`.js-line-blocked[data-chat-name="${playerName}"]`));
3133 $chatNames.forEach($name => {
3134 $name.classList.remove('js-line-blocked');
3135 const $line = $name.parentNode.parentNode.parentNode;
3136 $line.classList.remove('js-line-blocked');
3137 });
3138}
3139
3140},{"./chat":33,"./state":37,"./ui":38}],37:[function(require,module,exports){
3141"use strict";
3142
3143Object.defineProperty(exports, "__esModule", {
3144 value: true
3145});
3146exports.getState = getState;
3147exports.getTempState = getTempState;
3148exports.saveState = saveState;
3149exports.loadState = loadState;
3150
3151var _version = require("./version");
3152
3153const STORAGE_STATE_KEY = 'hordesio-uimodsakaiyo-state';
3154let state = {
3155 breakingVersion: _version.BREAKING_VERSION,
3156 chat: {
3157 GM: true
3158 },
3159 windowsPos: {},
3160 blockList: {},
3161 friendsList: {},
3162 mapOpacity: 70,
3163 // e.g. 70 = opacity: 0.7
3164 friendNotes: {},
3165 chatTabs: [],
3166 xpMeterState: {
3167 currentXp: 0,
3168 xpGains: [],
3169 // array of xp deltas every second
3170 averageXp: 0,
3171 gainedXp: 0,
3172 currentLvl: 0
3173 },
3174 openWindows: {
3175 friendsList: false,
3176 blockList: false,
3177 xpMeter: false,
3178 merchant: false
3179 },
3180 clanLastActiveMembers: {},
3181 lockedItemSlots: []
3182}; // tempState is saved only between page refreshes.
3183
3184const tempState = {
3185 // The last name clicked in chat
3186 chatName: null,
3187 lastMapWidth: 0,
3188 lastMapHeight: 0,
3189 xpMeterInterval: null,
3190 // tracks the interval for fetching xp data
3191 keyModifiers: {
3192 shift: false,
3193 control: false,
3194 alt: false
3195 },
3196 // set by _keyModifiers mod
3197 cooldownNums: {},
3198 cooldownObservers: {}
3199};
3200
3201function getState() {
3202 return state;
3203}
3204
3205function getTempState() {
3206 return tempState;
3207}
3208
3209function saveState() {
3210 localStorage.setItem(STORAGE_STATE_KEY, JSON.stringify(state));
3211}
3212
3213function loadState() {
3214 const storedStateJson = localStorage.getItem(STORAGE_STATE_KEY);
3215
3216 if (storedStateJson) {
3217 const storedState = JSON.parse(storedStateJson);
3218
3219 if (storedState.breakingVersion !== _version.BREAKING_VERSION) {
3220 localStorage.setItem(STORAGE_STATE_KEY, JSON.stringify(state));
3221 return;
3222 }
3223
3224 for (let [key, value] of Object.entries(storedState)) {
3225 state[key] = value;
3226 }
3227 }
3228}
3229
3230},{"./version":39}],38:[function(require,module,exports){
3231"use strict";
3232
3233Object.defineProperty(exports, "__esModule", {
3234 value: true
3235});
3236exports.createBlockList = createBlockList;
3237exports.removeBlockList = removeBlockList;
3238exports.createFriendsList = createFriendsList;
3239exports.removeFriendsList = removeFriendsList;
3240exports.toggleFriendsList = toggleFriendsList;
3241exports.toggleXpMeterVisibility = toggleXpMeterVisibility;
3242exports.createXpMeter = createXpMeter;
3243exports.resetUiPositions = resetUiPositions;
3244exports.setWindowOpen = setWindowOpen;
3245exports.setWindowClosed = setWindowClosed;
3246exports.isWindowOpen = isWindowOpen;
3247exports.WindowNames = void 0;
3248
3249var _state = require("./state");
3250
3251var _misc = require("./misc");
3252
3253var chat = _interopRequireWildcard(require("./chat"));
3254
3255var player = _interopRequireWildcard(require("./player"));
3256
3257function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }
3258
3259function _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; }
3260
3261const WindowNames = {
3262 friendsList: 'friendsList',
3263 blockList: 'blockList',
3264 xpMeter: 'xpMeter',
3265 merchant: 'merchant',
3266 clan: 'clan',
3267 stash: 'stash',
3268 inventory: 'inventory'
3269};
3270exports.WindowNames = WindowNames;
3271
3272function createBlockList() {
3273 const state = (0, _state.getState)();
3274 let blockedPlayersHTML = '';
3275 Object.keys(state.blockList).sort().forEach(blockedName => {
3276 blockedPlayersHTML += `
3277 <div data-player-name="${blockedName}">${blockedName}</div>
3278 <div class="btn orange js-unblock-player" data-player-name="${blockedName}">Unblock player</div>
3279 `;
3280 });
3281 const customSettingsHTML = `
3282 <h3 class="textprimary">Blocked players</h3>
3283 <div class="settings uimod-settings">${blockedPlayersHTML}</div>
3284 <p></p>
3285 <div class="btn purp js-close-custom-settings">Close</div>
3286 `;
3287 const $customSettings = (0, _misc.makeElement)({
3288 element: 'div',
3289 class: 'menu panel-black uimod-custom-window js-blocked-list',
3290 content: customSettingsHTML
3291 });
3292 document.body.appendChild($customSettings);
3293 setWindowOpen(WindowNames.blockList); // Wire up all the unblock buttons
3294
3295 Array.from(document.querySelectorAll('.js-unblock-player')).forEach($button => {
3296 $button.addEventListener('click', clickEvent => {
3297 const name = clickEvent.target.getAttribute('data-player-name');
3298 player.unblockPlayer(name); // Remove the blocked player from the list
3299
3300 Array.from(document.querySelectorAll(`.js-blocked-list [data-player-name="${name}"]`)).forEach($element => {
3301 $element.parentNode.removeChild($element);
3302 });
3303 });
3304 }); // And the close button for our custom UI
3305
3306 document.querySelector('.js-close-custom-settings').addEventListener('click', removeBlockList);
3307}
3308
3309function removeBlockList() {
3310 const $customSettingsWindow = document.querySelector('.js-blocked-list');
3311 $customSettingsWindow.parentNode.removeChild($customSettingsWindow);
3312 setWindowClosed(WindowNames.blockList);
3313}
3314
3315function createFriendsList() {
3316 const state = (0, _state.getState)();
3317
3318 if (document.querySelector('.js-friends-list')) {
3319 // Don't open the friends list twice.
3320 return;
3321 }
3322
3323 let friendsListHTML = '';
3324 Object.keys(state.friendsList).sort().forEach(friendName => {
3325 friendsListHTML += `
3326 <div data-player-name="${friendName}">${friendName}</div>
3327 <div class="btn blue js-whisper-player" data-player-name="${friendName}">Whisper</div>
3328 <div class="btn blue js-party-player" data-player-name="${friendName}">Party invite</div>
3329 <div class="btn orange js-unfriend-player" data-player-name="${friendName}">X</div>
3330 <input type="text" class="js-friend-note" data-player-name="${friendName}" value="${state.friendNotes[friendName] || ''}"></input>
3331 `;
3332 });
3333 const customFriendsWindowHTML = `
3334 <div class="titleframe uimod-friends-list-helper">
3335 <div class="textprimary title uimod-friends-list-helper">
3336 <div name="title">Friends list</div>
3337 </div>
3338 <img src="/assets/ui/icons/cross.svg?v=3282286" class="js-close-custom-friends-list btn black svgicon">
3339 </div>
3340 <div class="uimod-friends-intro">To add someone as a friend, click their name in chat and then click Friend :)</div>
3341 <div class="uimod-friends">${friendsListHTML}</div>
3342 `;
3343 const $customFriendsList = (0, _misc.makeElement)({
3344 element: 'div',
3345 class: 'menu window panel-black js-friends-list uimod-custom-window',
3346 content: customFriendsWindowHTML
3347 });
3348 document.body.appendChild($customFriendsList);
3349 setWindowOpen(WindowNames.friendsList); // Wire up the buttons
3350
3351 Array.from(document.querySelectorAll('.js-whisper-player')).forEach($button => {
3352 $button.addEventListener('click', clickEvent => {
3353 const name = clickEvent.target.getAttribute('data-player-name');
3354 player.whisperPlayer(name);
3355 });
3356 });
3357 Array.from(document.querySelectorAll('.js-party-player')).forEach($button => {
3358 $button.addEventListener('click', clickEvent => {
3359 const name = clickEvent.target.getAttribute('data-player-name');
3360 player.partyPlayer(name);
3361 });
3362 });
3363 Array.from(document.querySelectorAll('.js-unfriend-player')).forEach($button => {
3364 $button.addEventListener('click', clickEvent => {
3365 const name = clickEvent.target.getAttribute('data-player-name');
3366 player.unfriendPlayer(name); // Remove the blocked player from the list
3367
3368 Array.from(document.querySelectorAll(`.js-friends-list [data-player-name="${name}"]`)).forEach($element => {
3369 $element.parentNode.removeChild($element);
3370 });
3371 });
3372 });
3373 Array.from(document.querySelectorAll('.js-friend-note')).forEach($element => {
3374 $element.addEventListener('change', clickEvent => {
3375 const name = clickEvent.target.getAttribute('data-player-name');
3376 state.friendNotes[name] = clickEvent.target.value;
3377 });
3378 }); // The close button for our custom UI
3379
3380 document.querySelector('.js-close-custom-friends-list').addEventListener('click', removeFriendsList);
3381}
3382
3383function removeFriendsList() {
3384 const $friendsListWindow = document.querySelector('.js-friends-list');
3385 $friendsListWindow.parentNode.removeChild($friendsListWindow);
3386 setWindowClosed(WindowNames.friendsList);
3387}
3388
3389function toggleFriendsList() {
3390 if (isWindowOpen(WindowNames.friendsList)) {
3391 removeFriendsList();
3392 } else {
3393 createFriendsList();
3394 }
3395}
3396
3397function toggleXpMeterVisibility() {
3398 const xpMeterContainer = document.querySelector('.js-xpmeter'); // Make it if it doesn't exist for some reason
3399
3400 if (!xpMeterContainer) {
3401 createXpMeter();
3402 }
3403
3404 xpMeterContainer.style.display = xpMeterContainer.style.display === 'none' ? 'block' : 'none'; // Save whether xpMeter is currently open or closed in the state
3405
3406 if (xpMeterContainer.style.display === 'none') {
3407 setWindowClosed(WindowNames.xpMeter);
3408 } else {
3409 setWindowOpen(WindowNames.xpMeter);
3410 }
3411}
3412
3413function createXpMeter() {
3414 const $layoutContainer = document.querySelector('body > div.layout > div.container:nth-child(1)');
3415 const $dpsMeterToggleElement = document.querySelector('#systrophy');
3416 const $xpMeterToggleElement = (0, _misc.makeElement)({
3417 element: 'div',
3418 class: 'js-sysxp js-xpmeter-icon btn border black',
3419 content: 'XP'
3420 });
3421 const xpMeterHTMLString = `
3422 <div class="l-corner-lr container uimod-xpmeter-1 js-xpmeter" style="display: none">
3423 <div class="window panel-black uimod-xpmeter-2">
3424 <div class="titleframe uimod-xpmeter-2">
3425 <img src="/assets/ui/icons/trophy.svg?v=3282286" class="titleicon svgicon uimod-xpmeter-2">
3426 <div class="textprimary title uimod-xpmeter-2">
3427 <div name="title">Experience / XP</div>
3428 </div>
3429 <img src="/assets/ui/icons/cross.svg?v=3282286" class="js-xpmeter-close-icon btn black svgicon">
3430 </div>
3431 <div class="slot uimod-xpmeter-2" style="">
3432 <div class="wrapper uimod-xpmeter-1">
3433 <div class="bar uimod-xpmeter-3" style="z-index: 0;">
3434 <div class="progressBar bgc1 uimod-xpmeter-3" style="width: 100%; font-size: 1em;">
3435 <span class="left uimod-xpmeter-3">XP per minute:</span>
3436 <span class="right uimod-xpmeter-3 js-xpm">-</span>
3437 </div>
3438 <div class="progressBar bgc1 uimod-xpmeter-3" style="width: 100%; font-size: 1em;">
3439 <span class="left uimod-xpmeter-3">XP per hour:</span>
3440 <span class="right uimod-xpmeter-3 js-xph">-</span>
3441 </div>
3442 <div class="progressBar bgc1 uimod-xpmeter-3" style="width: 100%; font-size: 1em;">
3443 <span class="left uimod-xpmeter-3">XP Gained:</span>
3444 <span class="right uimod-xpmeter-3 js-xpg">-</span>
3445 </div>
3446 <div class="progressBar bgc1 uimod-xpmeter-3" style="width: 100%; font-size: 1em;">
3447 <span class="left uimod-xpmeter-3">XP Left:</span>
3448 <span class="right uimod-xpmeter-3 js-xpl">-</span>
3449 </div>
3450 <div class="progressBar bgc1 uimod-xpmeter-3" style="width: 100%; font-size: 1em;">
3451 <span class="left uimod-xpmeter-3">Session Time: </span>
3452 <span class="right uimod-xpmeter-3 js-xp-s-time">-</span>
3453 </div>
3454 <div class="progressBar bgc1 uimod-xpmeter-3" style="width: 100%; font-size: 1em;">
3455 <span class="left uimod-xpmeter-3">Time to lvl: </span>
3456 <span class="right uimod-xpmeter-3 js-xp-time">-</span>
3457 </div>
3458 </div>
3459 </div>
3460 <div class="grid buttons marg-top uimod-xpmeter-1 js-xpmeter-reset-button">
3461 <div class="btn grey">Reset</div>
3462 </div>
3463 </div>
3464 </div>
3465 </div>
3466 `;
3467 $dpsMeterToggleElement.parentNode.insertBefore($xpMeterToggleElement, $dpsMeterToggleElement.nextSibling);
3468 const $xpMeterElement = (0, _misc.makeElement)({
3469 element: 'div',
3470 content: xpMeterHTMLString.trim()
3471 });
3472 $layoutContainer.appendChild($xpMeterElement.firstChild);
3473}
3474
3475function resetUiPositions() {
3476 const state = (0, _state.getState)();
3477 state.windowsPos = {};
3478 (0, _state.saveState)();
3479 chat.addChatMessage('Please refresh the page for the reset frame & window positions to take effect.');
3480} // state.openWindows should always only be managed by this file
3481// Sometimes we want to track when a UI window we don't control is opened/closed
3482// We use these methods to help facilitate that
3483// To use these methods correctly, you need to track when the window opens _and_ when it closes
3484// If you don't _need_ to do both those things, then don't do that, and don't use these methods
3485
3486
3487function setWindowOpen(windowName) {
3488 const state = (0, _state.getState)();
3489 state.openWindows[windowName] = true;
3490 (0, _state.saveState)();
3491}
3492
3493function setWindowClosed(windowName) {
3494 const state = (0, _state.getState)();
3495 state.openWindows[windowName] = false;
3496 (0, _state.saveState)();
3497}
3498
3499function isWindowOpen(windowName) {
3500 const state = (0, _state.getState)();
3501 return state.openWindows[windowName];
3502}
3503
3504},{"./chat":33,"./misc":35,"./player":36,"./state":37}],39:[function(require,module,exports){
3505"use strict";
3506
3507Object.defineProperty(exports, "__esModule", {
3508 value: true
3509});
3510exports.VERSION = exports.BREAKING_VERSION = void 0;
3511// If this version is different from the user's stored state,
3512// e.g. they have upgraded the version of this script and there are breaking changes,
3513// then their stored state will be deleted.
3514const BREAKING_VERSION = 1; // Used for initialization message in chat, and userscript version
3515
3516exports.BREAKING_VERSION = BREAKING_VERSION;
3517const VERSION = '1.2.5';
3518exports.VERSION = VERSION;
3519
3520},{}]},{},[1]);