Grundos Cafe Battledome Utility (Keyboard)

Remember last selected battledome weapons and options, keyboard controls

  1. // ==UserScript==
  2. // @name Grundos Cafe Battledome Utility (Keyboard)
  3. // @namespace grundos.cafe
  4. // @version 1.2.1
  5. // @description Remember last selected battledome weapons and options, keyboard controls
  6. // @author eleven, wibreth
  7. // @match https://www.grundos.cafe/dome/1p/battle/*
  8. // @match https://www.grundos.cafe/dome/1p/endbattle/*
  9. // @icon https://www.google.com/s2/favicons?sz=64&domain=grundos.cafe
  10. // @grant GM_getValue
  11. // @grant GM_setValue
  12. // @grant GM_addStyle
  13. // @grant GM_deleteValue
  14. // @grant GM_registerMenuCommand
  15. // ==/UserScript==
  16.  
  17. /* globals $:false */
  18.  
  19.  
  20. (function() {
  21. 'use strict';
  22.  
  23. // withdraw before losing/drawing a fight
  24. if ($('#end_blurb').text().includes('You have lost this fight!') || $('#end_blurb').text().includes('You have drawn this fight!')) {
  25. let confirmation = window.confirm("You didn't win! :( Withdraw from this battle?");
  26. if (confirmation) {
  27. window.location.href = 'https://www.grundos.cafe/dome/status/';
  28. return;
  29. }
  30. }
  31.  
  32. GM_addStyle(`
  33. form#bd-form-end input[type=submit], form#bd-form input[type=submit], .cloned-btn {
  34. width: ${!GM_getValue('gowidth') ? 200 : GM_getValue('gowidth')}px;
  35. height: ${!GM_getValue('goheight') ? 200 : GM_getValue('goheight')}px;
  36. resize: both;
  37. overflow: hidden;
  38. }
  39. .cloned-btn input {
  40. width: 100%;
  41. height: 100% !important;
  42. display: block;
  43. }
  44. #bd-form .button-group, #bd-form-end {
  45. width: initial;
  46. align-items: center;
  47. }
  48. #equipment .itemList {
  49. display: flex;
  50. flex-wrap: wrap;
  51. gap: initial;
  52. padding: initial;
  53. align-items: stretch;
  54. }
  55. #equipment .itemList .relic {
  56. width: 25%;
  57. max-width: 120px;
  58. word-break: break-word;
  59. position: relative;
  60. margin: 0 auto;
  61. }
  62. #equipment .itemList label {
  63. display: flex;
  64. flex-direction: column;
  65. justify-content: space-between;
  66. align-items: center;
  67. height: 100%;
  68. }
  69. #equipment .itemList .med-image {
  70. max-width: 100% !important;
  71. max-height: 100% !important;
  72. aspect-ratio: 1 / 1;
  73. height: auto !important;
  74. }
  75. #equipment .itemList .relic-span {
  76. height: auto;
  77. }
  78. #bd-form {
  79. width: ${GM_getValue('width', '450')-20}px;
  80. height: ${GM_getValue('height', '700')-20}px;
  81. max-width: none !important;
  82. resize: both;
  83. overflow: auto;
  84. position: absolute;
  85. left: ${GM_getValue('left', 0)}px;
  86. top: ${GM_getValue('top', 0)}px;
  87. background: var(--bgcolor);
  88. padding: 10px;
  89. border-radius: 10px;
  90. box-shadow: 0 0 10px 0 #00000088;
  91. cursor: move;
  92. container-name: bdform;
  93. container-type: size;
  94. }
  95. .relic-checkbox-container::before {
  96. content: attr(data-hotkey);
  97. position: absolute;
  98. left: 0;
  99. right: 0;
  100. font-size: 30px;
  101. -webkit-text-stroke: 1.5px var(--bgcolor);
  102. text-stroke: 1.5px var(--bgcolor);
  103. top: 0;
  104. font-weight: bold;
  105. text-shadow: 0 0 3px var(--bgcolor), 0 0 3px var(--bgcolor);
  106. pointer-events: none;
  107. height: min-content;
  108. }
  109. .relic-checkbox-container::after {
  110. content: attr(data-qty);
  111. margin: -3px 0 3px;
  112. display: block;
  113. pointer-events: none;
  114. }
  115. .ability-btn {
  116. cursor: pointer;
  117. padding: 0.3em;
  118. display: inline-block;
  119. }
  120. .ability-radio {
  121. margin-top: 1em;
  122. display: flex;
  123. flex-wrap: wrap;
  124. }
  125. .ability-radio input {
  126. position: absolute !important;
  127. clip: rect(0, 0, 0, 0);
  128. height: 1px;
  129. width: 1px;
  130. border: 0;
  131. overflow: hidden;
  132. }
  133. .ability-radio label {
  134. padding: 0.3em;
  135. margin-right: -1px;
  136. margin-bottom: -1px;
  137. }
  138. .ability-radio label:hover {
  139. cursor: pointer;
  140. }
  141. .ability-radio input:checked + label, .ability-radio input:checked + label:hover {
  142. background-color: var(--grid_select);
  143. }
  144. .ability-radio label::before {
  145. content: '('attr(data-hotkey)') ';
  146. }
  147. #bd-grid textarea {
  148. display: block;
  149. }
  150. @container bdform (max-width: 300px) {
  151. #equipment .itemList .relic-span {
  152. font-size: 10px;
  153. }
  154. }
  155. @container bdform (max-width: 200px) {
  156. #equipment .itemList .relic-span {
  157. font-size: 6px
  158. }
  159. }`);
  160.  
  161. let equipment = {}; //name: [array of equipment IDs]
  162. let hotkeys = {}; //keycode: name of equipment or ability ID or 'GO'
  163. let keycodes = ['KeyP','KeyO','KeyI','KeyU','Semicolon','KeyL','KeyK','KeyJ','Period','Comma','KeyM','KeyN','KeyV', 'KeyC', 'KeyX', 'KeyZ', 'KeyF', 'KeyD', 'KeyS', 'KeyA', 'KeyR', 'KeyE', 'KeyW', 'KeyQ']; // reverse order
  164. let keys = ['P','O','I','U',';','L','K','J','.',',','M','N','V', 'C', 'X', 'Z', 'F', 'D', 'S', 'A', 'R', 'E', 'W', 'Q'];
  165.  
  166.  
  167. // the priority list for ordering equipment puts reflectors first, then %-blockers, constants, blockers, and breakable/one-use items.
  168. // original list compiled by Chris (thesovereign)
  169. let original_priority = ['kings lens','nsu detector','ultimate dark reflectorb','ultra dark reflectorb','ultra dual shovel','shovel plus','superior reflection shield','combo battle mirror','dual battle mirror','cloud charm','triple turbo dryer','turbo dryer','u-bend of great justice','mega u-bend','umbrella shield','super u-bend','reflectozap 2000','flame reflectozap','turbo flame reflector','defender of neopia cape','wand of the dark faerie','rod of dark nova','stone hourglass','alien aisha scrambler','chia leaping boots','tornado ring','greater staff of the earth faerie','sad spell','sponge shield','grimoire of thade','mask of coltzan','air faeries fan','scroll of the western winds','jhudoras dark collar','air faerie crown','scroll of dark nova','portable cloud','super attack pea','seasonal attack pea','attack pea','faerie slingshot','monoceraptors claw','ghostkersword','sword of skardsen','noxious carrot blade','pirate captains cutlass','bony grarrl club','kougra brush','golden butter knife','scroll of ultranova','scroll of supernova','faerie tabard','ghostkershield','patched magic hat','irregulation chainmail','thyoras tear','downsize power plus','castle defenders shield','jade scorchstone','rainbow scorchstone','purple scorchstone','green scorchstone','blue scorchstone','red scorchstone','bronze scorchstone','obsidian scorchstone','greater healing scroll','great snowball','jewelled scarab','wocky wand of darkness','moehog healing root','kacheek life potion','elephante unguent','scorchio wand','gelert healing remedy','flotsam ice shell','moehog skull','snowball machine','sleep ray','freezing potion','h4000 helmet','scroll of freezing','wand of the snow faerie','crystal boomerang','ghostkerbomb','hubrids puzzle box','evil hubrid statue','dark nova','ultranova','supernova'];
  170. let priority = original_priority;
  171. priority = GM_getValue('priority', priority);
  172.  
  173. function customOrder(a, b) {
  174. let textA = $(a).find('.relic-span').text().trim().toLowerCase();
  175. let textB = $(b).find('.relic-span').text().trim().toLowerCase();
  176. if (priority.indexOf(textA) < 0 || priority.indexOf(textB) < 0) return 9999;
  177. return priority.indexOf(textA) - priority.indexOf(textB);
  178. }
  179. function parsePriority(input) {
  180. return input
  181. .split(',')
  182. .map(item => item.trim())
  183. .filter(item => item.length > 0);
  184. }
  185.  
  186. // setting a custom priority list
  187. $('#equipment').after(`
  188. <details>
  189. <summary>Edit equipment order</summary>
  190. Please note, reflectors should be placed before shields with a % block for full effectiveness.
  191. <textarea id="textarea-priority" rows="4" cols="50"></textarea>
  192. <button id="btn-priority">Submit</button>
  193. <button id="btn-reset-priority">Reset</button>
  194. </details>
  195. `);
  196. let textareaPriority = $('#textarea-priority').val(priority.join(','));
  197. $('#btn-priority').on('click', function () {
  198. priority = parsePriority(textareaPriority.val());
  199. GM_setValue('priority', priority);
  200. textareaPriority.val(priority.join(','));
  201. sortEquipment();
  202.  
  203. $(this).prop('disabled', true).text('List updated!');
  204. setTimeout(() => {
  205. $(this).prop('disabled', false).text('Submit');
  206. }, 1500);
  207. });
  208. $('#btn-reset-priority').on('click', function () {
  209. priority = original_priority;
  210. GM_setValue('priority', priority);
  211. textareaPriority.val(priority.join(','));
  212. sortEquipment();
  213.  
  214. $(this).prop('disabled', true).text('List updated!');
  215. setTimeout(() => {
  216. $(this).prop('disabled', false).text('Submit');
  217. }, 1500);
  218. });
  219.  
  220. // sort the equipment list
  221. function sortEquipment() {
  222. let sorted = $('#equipment .itemList .relic');
  223. sorted.sort(customOrder);
  224. $('#equipment .itemList .relic').detach();
  225. $('#equipment .itemList').append(sorted);
  226. }
  227. sortEquipment();
  228.  
  229.  
  230. selected = [];
  231. let inputs = $('#bd-form .relic-checkbox-container input');
  232. inputs.each(function() {
  233. $(this).prop('checked', false); //set everything to false first
  234. const name = $(this).parent().siblings().eq(1).text().trim().toLowerCase();
  235. if (name in equipment) {
  236. // condense duplicates
  237. $(this).closest('.relic').hide();
  238. equipment[name].push($(this).val());
  239. let original = $(`input[value="${equipment[name][0]}"]`).parent();
  240. original.attr('data-qty', equipment[name].length);
  241. if (equipment[name].length > 2) {
  242. return;
  243. }
  244. original.append($(this));
  245. return;
  246. }
  247. const key = keycodes.pop();
  248. $(this).parent().attr('data-hotkey', keys.pop());
  249. hotkeys[key] = name;
  250. equipment[name] = [$(this).prop('id')];
  251. });
  252. $('#bd-form table').hide();
  253.  
  254. // assign hotkeys create radio buttons that sync to the dropdown for:
  255. // species attack/defend, either berserk or fierce attack, defend, and faerie abilities
  256. // note that low level pets do not have berserk and must use fierce
  257. keycodes = ['Digit8', 'Digit7', 'Digit6', 'Digit5', 'Digit4', 'Digit3', 'Digit2', 'Digit1'];
  258. keys = ['8', '7', '6', '5', '4', '3', '2', '1'];
  259. let radios = $('<div class="ability-radio">');
  260. $('#bd-form .form-row').after(radios);
  261. let berserk = $('#ability option[value="5"]').length ? 5 : 4;
  262. $('#ability option').each(function() {
  263. const value = $(this).val();
  264. const text = $(this).text();
  265. let radio = `<input type="radio" name="ability-options" value="${value}" style="visibility: hidden">`;
  266. if (value < 0 || value >= berserk) {
  267. const key = keycodes.pop();
  268. radio = `<input type="radio" name="ability-options" value="${value}" id="${text.replace(/\s+/g, '-').toLowerCase()}"><label class="form-control" data-hotkey="${keys.pop()}" for="${text.replace(/\s+/g, '-').toLowerCase()}">${text}</label>`;
  269. hotkeys[key] = value;
  270. }
  271. radios.append(radio);
  272. });
  273. $('#ability').change(function() {
  274. $(`input[name='ability-options'][value='${$(this).val()}']`).prop('checked', true);
  275. });
  276. $('input[name="ability-options"]').change(function() {
  277. $('#ability').val($(this).val()).trigger('change');
  278. });
  279.  
  280. // clone the Go!, Next, Rematch buttons and assign spacebar hotkey
  281. if (document.URL.indexOf('/dome/1p/battle/') !== -1) {
  282. let btn = $('#bd-form input[type=submit], #bd-form-end input[type=submit]').clone();
  283. btn.click(() => {
  284. $('input[type=submit]').prop('disabled', true);
  285. $('#bd-form, #bd-form-end').submit();
  286. });
  287. $('#page_content').prepend($('<div class="cloned-btn">').append(btn));
  288. } else if (document.URL.indexOf('/dome/1p/endbattle/') !== -1) {
  289. let btn = $('#bd-form-rematch input[type=submit]').clone();
  290. btn.click(() => {
  291. $('input[type=submit]').prop('disabled', true);
  292. $('#bd-form-rematch').submit();
  293. });
  294. $('#page_content').prepend($('<div class="cloned-btn">').append(btn))
  295. }
  296. hotkeys['Space'] = 'GO';
  297. hotkeys['NumpadEnter'] = 'GO';
  298.  
  299. // set up hotkey listeners to make the hotkeys actually work
  300. // the hotkeys are disabled if the cursor is focused on a textarea/input field
  301. window.addEventListener('keydown', (e) => {
  302. if ($(e.target).is('textarea, input[type=text], [contenteditable]'))
  303. return;
  304. const key = e.code;
  305. if(key in hotkeys && hotkeys[key] === 'GO')
  306. e.preventDefault();
  307. }, true, );
  308. // keyup listener must distinguish between go, abilities, and equipment
  309. window.addEventListener('keyup', (e) => {
  310. if ($(e.target).is('textarea, input[type=text], [contenteditable]'))
  311. return;
  312. const key = e.code;
  313. if(!(key in hotkeys))
  314. return;
  315. let val = hotkeys[key];
  316. if (val === 'GO') {
  317. e.preventDefault();
  318. $('.cloned-btn input').click();
  319. return;
  320. }
  321. if (!(val in equipment)) { //ability
  322. $(`input[name='ability-options'][value='${val}']`).prop('checked', true).trigger('change');
  323. return;
  324. }
  325. val = equipment[hotkeys[key]][0];
  326. if (selected.indexOf(val) >= 0 && equipment[hotkeys[key]].length > 1)
  327. val = equipment[hotkeys[key]][1];
  328. $(`input[value="${val}"]`).prop('checked', true);
  329. countWeaps(val);
  330. }, true, );
  331.  
  332.  
  333.  
  334. /*
  335. Copyright (c) 2024 by Loren McClaflin (https://codepen.io/Mozillex/pen/PoYmEbz)
  336. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
  337. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
  338. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  339. */
  340. function dragElement(elmnt) {
  341. var pos1 = 0,
  342. pos2 = 0,
  343. pos3 = 0,
  344. pos4 = 0;
  345.  
  346. addTouchToMouse(elmnt);
  347. elmnt.onmousedown = dragMouseDown;
  348.  
  349. function dragMouseDown(e) {
  350. e = e || window.event;
  351. if ($(e.target).is('a, select, input, label') || $(e.target).closest('a, select, input, label').length)
  352. return; //don't drag if mousedown on these elements.... there's gotta be an easier way to do this.....
  353. // get the mouse cursor position at startup:
  354. pos3 = e.clientX;
  355. pos4 = e.clientY;
  356. if (e.offsetX > elmnt.offsetWidth - 18 && e.offsetY > elmnt.offsetHeight - 18)
  357. return; // don't drag if mousedown on resize handle
  358. e.preventDefault();
  359. document.onmouseup = closeDragElement;
  360. document.onmousemove = elementDrag;
  361. }
  362.  
  363. function elementDrag(e) {
  364. e = e || window.event;
  365. e.preventDefault();
  366. // calculate the new cursor position:
  367. pos1 = pos3 - e.clientX;
  368. pos2 = pos4 - e.clientY;
  369. pos3 = e.clientX;
  370. pos4 = e.clientY;
  371. // set the element's new position:
  372. elmnt.style.left = (elmnt.offsetLeft - pos1) + "px";
  373. elmnt.style.top = (elmnt.offsetTop - pos2) + "px";
  374. GM_setValue('left', elmnt.offsetLeft - pos1);
  375. GM_setValue('top', elmnt.offsetTop - pos2);
  376. }
  377.  
  378. function closeDragElement() {
  379. document.onmouseup = null;
  380. document.onmousemove = null;
  381. }
  382.  
  383.  
  384. // Touch support added by Jonathan Arnett: https://codepen.io/sophtwhere
  385. function addTouchToMouse(forEl) {
  386. let doc = document;
  387.  
  388. if (typeof forEl.removeTouchToMouse === "function") return;
  389.  
  390. doc.addEventListener("touchstart", touch2Mouse, true);
  391. doc.addEventListener("touchmove", touch2Mouse, true);
  392. doc.addEventListener("touchend", touch2Mouse, true);
  393. var touching = false;
  394.  
  395. function isValidTouch (el) {
  396. if (el===forEl) return true;
  397.  
  398. if ((el.parentElement===forEl)&&["INPUT","A","BUTTON"].indexOf(el.tagName)<0) return true;
  399. }
  400. function touch2Mouse(e) {
  401. var theTouch = e.changedTouches[0];
  402. var mouseEv;
  403.  
  404. if (!isValidTouch(e.target)) return;
  405.  
  406. switch (e.type) {
  407. case "touchstart":
  408. if (e.touches.length !== 1) return;
  409. touching = true;
  410. mouseEv = "mousedown";
  411. break;
  412. case "touchend":
  413. if (!touching) return;
  414. mouseEv = "mouseup";
  415. touching = false;
  416. break;
  417. case "touchmove":
  418. if (e.touches.length !== 1) return;
  419. mouseEv = "mousemove";
  420. break;
  421. default:
  422. return;
  423. }
  424.  
  425. var mouseEvent = document.createEvent("MouseEvent");
  426. mouseEvent.initMouseEvent(
  427. mouseEv,
  428. true,
  429. true,
  430. window,
  431. 1,
  432. theTouch.screenX,
  433. theTouch.screenY,
  434. theTouch.clientX,
  435. theTouch.clientY,
  436. false,
  437. false,
  438. false,
  439. false,
  440. 0,
  441. null
  442. );
  443. theTouch.target.dispatchEvent(mouseEvent);
  444.  
  445. e.preventDefault();
  446. }
  447.  
  448. forEl.removeTouchToMouse = function removeTouchToMouse() {
  449. doc.removeEventListener("touchstart", touch2Mouse, true);
  450. doc.removeEventListener("touchmove", touch2Mouse, true);
  451. doc.removeEventListener("touchend", touch2Mouse, true);
  452. };
  453. }
  454. }
  455.  
  456. const formResizeObserver = new ResizeObserver(entries => {
  457. for (const entry of entries) {
  458. const { width, height } = entry.target.getBoundingClientRect();
  459. GM_setValue('width', width);
  460. GM_setValue('height', height);
  461. }
  462. });
  463.  
  464. // allow the equipment list to be repositionable and resizable
  465. if ($('#bd-form').length) {
  466. formResizeObserver.observe(document.getElementById('bd-form'));
  467. dragElement(document.getElementById('bd-form'));
  468. }
  469.  
  470. // remember equipment and options
  471. // mostly written by Eleven
  472. let weapons = [];
  473. let weaponID = '';
  474. if ($('div#combatlog').text().indexOf('The fight commences!') !== -1) {
  475. if (GM_getValue('sw1', false)) weapons.push(GM_getValue('sw1'))
  476. if (GM_getValue('sw2', false)) weapons.push(GM_getValue('sw2'))
  477. }
  478. if (weapons.length === 0) {
  479. weapons = GM_getValue('weapons', []);
  480. }
  481.  
  482. for (const weapon of weapons) {
  483. $('#equipment .itemList input').each(function() {
  484. if ($(this).val() !== weaponID && $(this).parent().siblings().eq(1).text().trim().toLowerCase() === weapon.toLowerCase()) {
  485. weaponID = $(this).val();
  486. $(this).prop('checked', true);
  487. countWeaps(weaponID);
  488. return false;
  489. }
  490. });
  491. }
  492.  
  493. if (GM_getValue('ability')) { $('form#bd-form select#ability').val(GM_getValue('ability')).trigger('change') }
  494. if (GM_getValue('power')) { $('form#bd-form select#power').val(GM_getValue('power')) }
  495.  
  496. let ability = '';
  497. let power = '';
  498.  
  499. $('form#bd-form').on('submit', function() {
  500. if ($('#equipment .relic').length === 0) {
  501. return; // frozen
  502. }
  503.  
  504. weapons = [];
  505. $('#equipment .itemList input:checked').each(function() {
  506. weapons.push($(this).parent().siblings().eq(1).text().trim());
  507. });
  508.  
  509. ability = $('form#bd-form select#ability').find(':selected').val();
  510. power = $('form#bd-form select#power').find(':selected').val();
  511. GM_setValue('weapons', weapons);
  512. GM_setValue('ability', ability);
  513. GM_setValue('power', power);
  514. });
  515.  
  516. //Go/next/rematch button sizing
  517. const btnTop = document.querySelector('.cloned-btn input');
  518. const btnBottom = document.querySelector('form#bd-form input[type=submit], form#bd-form-end input[type=submit]');
  519.  
  520. const btnResizeObserver = new ResizeObserver(entries => {
  521. for (const entry of entries) {
  522. const resizedButton = entry.target;
  523. const { width, height } = resizedButton.getBoundingClientRect();
  524. btnBottom.style.width = `${width}px`;
  525. btnBottom.style.height = `${height}px`;
  526. GM_setValue('gowidth', width);
  527. GM_setValue('goheight', height);
  528. }
  529. });
  530.  
  531. btnResizeObserver.observe(btnTop);
  532.  
  533. GM_registerMenuCommand('Set Starting Weapon 1', function() {
  534. let value = prompt('Set the first starting weapon for each battle.', GM_getValue('sw1', ''));
  535. if (value) GM_setValue('sw1', value.trim());
  536. }, '1');
  537. GM_registerMenuCommand('Set Starting Weapon 2', function() {
  538. let value = prompt('Set the second starting weapon for each battle.', GM_getValue('sw2', ''));
  539. if (value) GM_setValue('sw2', value.trim());
  540. }, '2');
  541. GM_registerMenuCommand('Clear Starting Weapons', function() {
  542. GM_deleteValue('sw1');
  543. GM_deleteValue('sw2');
  544. }, 'c');
  545.  
  546. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址