// ==UserScript==
// @name Grundos Cafe Battledome Utility (Keyboard)
// @namespace grundos.cafe
// @version 1.1.4
// @description Remember last selected battledome weapons and options, keyboard controls
// @author eleven, wibreth
// @match https://www.grundos.cafe/dome/1p/battle/*
// @match https://www.grundos.cafe/dome/1p/endbattle/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=grundos.cafe
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_addStyle
// @grant GM_deleteValue
// @grant GM_registerMenuCommand
// ==/UserScript==
/* globals $:false */
(function() {
'use strict';
// withdraw before losing/drawing a fight
if ($('#end_blurb').text().includes('You have lost this fight!') || $('#end_blurb').text().includes('You have drawn this fight!')) {
let confirmation = window.confirm("You didn't win! :( Withdraw from this battle?");
if (confirmation) {
window.location.href = 'https://www.grundos.cafe/dome/status/';
return;
}
}
GM_addStyle(`
.bd-table {
display: flex;
flex-wrap: wrap;
justify-content: center;
}
.bd-table > div {
width: 25%;
max-width: 120px;
word-break: break-word;
position: relative;
text-align: center;
}
.bd-table label > div:first-child {
background-size: contain !important;
max-width: 100% !important;
max-height: 100% !important;
aspect-ratio: 1 / 1;
height: auto !important;
}
#bd-form {
width: ${GM_getValue('width', '450')-20}px;
height: ${GM_getValue('height', '700')-20}px;
max-width: none !important;
resize: both;
overflow: auto;
position: absolute;
left: ${GM_getValue('left', 0)}px;
top: ${GM_getValue('top', 0)}px;
background: var(--bgcolor);
padding: 10px;
border-radius: 10px;
box-shadow: 0 0 10px 0 #00000088;
cursor: move;
container-name: bdform;
container-type: size;
}
.bd-table a:nth-child(2) div:first-child {
display: none;
}
.ability-btn {
cursor: pointer;
padding: 0.3em;
display: inline-block;
}
.bd-table a::after {
content: attr(data-hotkey);
position: absolute;
left: 0;
right: 0;
font-size: 30px;
-webkit-text-stroke: 1.5px var(--bgcolor);
text-stroke: 1.5px var(--bgcolor);
top: 0;
font-weight: bold;
text-shadow: 0 0 3px var(--bgcolor), 0 0 3px var(--bgcolor);
pointer-events: none;
}
.bd-table a::before {
content: attr(data-qty);
position: absolute;
right: 0.2em;
pointer-events: none;
}
.ability-radio {
margin-top: 1em;
display: flex;
flex-wrap: wrap;
}
.ability-radio input {
position: absolute !important;
clip: rect(0, 0, 0, 0);
height: 1px;
width: 1px;
border: 0;
overflow: hidden;
}
.ability-radio label {
padding: 0.3em;
margin-right: -1px;
margin-bottom: -1px;
}
.ability-radio label:hover {
cursor: pointer;
}
.ability-radio input:checked + label, .ability-radio input:checked + label:hover {
background-color: var(--grid_select);
}
.ability-radio label::before {
content: '('attr(data-hotkey)') ';
}
@container bdform (max-width: 300px) {
.bd-table > div {
font-size: 10px;
}
@container bdform (max-width: 200px) {
.bd-table > div {
font-size: 6px
}
}`);
let equipment = {}; //name: [array of equipment IDs]
let hotkeys = {}; //key: name of equipment or ability ID or 'GO' for spacebar
let keys = ['P','O','I','U',';','L','K','J','.',',','M','N','V', 'C', 'X', 'Z', 'F', 'D', 'S', 'A', 'R', 'E', 'W', 'Q']; // reverse order
// copy over the original <table> into something easier to work with
// sort alphanumerically to maintain a consistent order and hide duplicates
let bdTable = $('<div class="bd-table">');
$('#bd-form').prepend(bdTable);
let sorted = $('#bd-form table input');
sorted.sort(function(a, b) {
let textA = $(a).parent().text().trim().toLowerCase();
let textB = $(b).parent().text().trim().toLowerCase();
return (textA < textB) ? -1 : (textA > textB) ? 1 : 0;
});
sorted.each(function() {
$(this).prop('checked', false);
const name = $(this).parent().text().trim().toLowerCase();
if (name in equipment) {
equipment[name].push($(this).val());
let original = $(`input[value="${equipment[name][0]}"]`).closest('div');
original.find('a:first-child').attr('data-qty', equipment[name].length);
if (equipment[name].length > 2)
return;
original.append($(this).closest('a'));
return;
}
const key = keys.pop();
let div = $('<div>').append($(this).closest('a').attr('data-hotkey', key));
bdTable.append(div);
hotkeys[key] = name;
equipment[name] = [$(this).prop('id')];
});
$('#bd-form table').hide();
// assign hotkeys create radio buttons that sync to the dropdown for:
// species attack/defend, either berserk or fierce attack, defend, and faerie abilities
// note that low level pets do not have berserk and must use fierce
keys = ['8', '7', '6', '5', '4', '3', '2', '1'];
let radios = $('<div class="ability-radio">');
$('#bd-form .form-row').after(radios);
let berserk = $('#ability option[value="5"]').length ? 5 : 4;
$('#ability option').each(function() {
const value = $(this).val();
const text = $(this).text();
let radio = `<input type="radio" name="ability-options" value="${value}" style="visibility: hidden">`;
if (value < 0 || value >= berserk) {
const key = keys.pop();
radio = `<input type="radio" name="ability-options" value="${value}" id="${text.replace(/\s+/g, '-').toLowerCase()}"><label class="form-control" data-hotkey="${key}" for="${text.replace(/\s+/g, '-').toLowerCase()}">${text}</label>`;
hotkeys[key] = value;
}
radios.append(radio);
});
$('#ability').change(function() {
$(`input[name='ability-options'][value='${$(this).val()}']`).prop('checked', true);
});
$('input[name="ability-options"]').change(function() {
$('#ability').val($(this).val()).trigger('change');
});
// clone the Go!, Next, Rematch buttons and assign spacebar hotkey
if (document.URL.indexOf('/dome/1p/battle/') !== -1) {
let btn = $('#bd-form input[type=submit], #bd-form-end input[type=submit]').clone();
btn.click(() => {
$('input[type=submit]').prop('disabled', true);
$('#bd-form, #bd-form-end').submit();
});
$('#page_content').prepend(btn);
} else if (document.URL.indexOf('/dome/1p/endbattle/') !== -1) {
let btn = $('#bd-form-rematch input[type=submit]').clone();
btn.click(() => {
$('input[type=submit]').prop('disabled', true);
$('#bd-form-rematch').submit();
});
$('#page_content').prepend(btn);
}
hotkeys[' '] = 'GO';
$(document).keydown(function(e) {
const key = String.fromCharCode(e.which);
if(key in hotkeys && hotkeys[key] === 'GO')
e.preventDefault();
});
// keyup listener must distinguish between go, abilities, and equipment
$(document).keyup(function(e) {
const key = String.fromCharCode(e.which);
if(!(key in hotkeys))
return;
let val = hotkeys[key];
if (val === 'GO') {
e.preventDefault();
$('#page_content > input:first-child').click();
}
if (!(val in equipment)) {
$(`input[name='ability-options'][value='${val}']`).prop('checked', true).trigger('change');
return;
}
val = equipment[hotkeys[key]][0];
if (selected.indexOf(val) >= 0 && equipment[hotkeys[key]].length > 1)
val = equipment[hotkeys[key]][1];
$(`input[value="${val}"]`).prop('checked', true);
countWeaps(val);
});
/*
// this is the old version of dragElement() except it didn't work well on Mac/Chrome apparently? idk i dont have one to test with :sob:
let offset = {};
$('#bd-form').prop('draggable', true);
$('#bd-form').on('dragstart', (e) => {
offset.x = e.clientX - e.target.getBoundingClientRect().x;
offset.y = e.clientY - e.target.getBoundingClientRect().y;
});
$('#bd-form').on('dragend', (e) => {
const left = (e.clientX - offset.x) + 'px';
GM_setValue('left', left);
const top = (e.clientY - offset.y) + 'px';
GM_setValue('top', top);
e.target.style.left = left;
e.target.style.top = top;
});
*/
/*
Copyright (c) 2024 by Loren McClaflin (https://codepen.io/Mozillex/pen/PoYmEbz)
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:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
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.
*/
function dragElement(elmnt) {
var pos1 = 0,
pos2 = 0,
pos3 = 0,
pos4 = 0;
addTouchToMouse(elmnt);
elmnt.onmousedown = dragMouseDown;
function dragMouseDown(e) {
e = e || window.event;
if ($(e.target).is('a, select, input, label') || $(e.target).closest('a, select, input, label').length)
return; //don't drag if mousedown on these elements.... there's gotta be an easier way to do this.....
// get the mouse cursor position at startup:
pos3 = e.clientX;
pos4 = e.clientY;
if (e.offsetX > elmnt.offsetWidth - 18 && e.offsetY > elmnt.offsetHeight - 18)
return; // don't drag if mousedown on resize handle
e.preventDefault();
document.onmouseup = closeDragElement;
document.onmousemove = elementDrag;
}
function elementDrag(e) {
e = e || window.event;
e.preventDefault();
// calculate the new cursor position:
pos1 = pos3 - e.clientX;
pos2 = pos4 - e.clientY;
pos3 = e.clientX;
pos4 = e.clientY;
// set the element's new position:
elmnt.style.left = (elmnt.offsetLeft - pos1) + "px";
elmnt.style.top = (elmnt.offsetTop - pos2) + "px";
GM_setValue('left', elmnt.offsetLeft - pos1);
GM_setValue('top', elmnt.offsetTop - pos2);
}
function closeDragElement() {
document.onmouseup = null;
document.onmousemove = null;
}
// Touch support added by Jonathan Arnett: https://codepen.io/sophtwhere
function addTouchToMouse(forEl) {
let doc = document;
if (typeof forEl.removeTouchToMouse === "function") return;
doc.addEventListener("touchstart", touch2Mouse, true);
doc.addEventListener("touchmove", touch2Mouse, true);
doc.addEventListener("touchend", touch2Mouse, true);
var touching = false;
function isValidTouch (el) {
if (el===forEl) return true;
if ((el.parentElement===forEl)&&["INPUT","A","BUTTON"].indexOf(el.tagName)<0) return true;
}
function touch2Mouse(e) {
var theTouch = e.changedTouches[0];
var mouseEv;
if (!isValidTouch(e.target)) return;
switch (e.type) {
case "touchstart":
if (e.touches.length !== 1) return;
touching = true;
mouseEv = "mousedown";
break;
case "touchend":
if (!touching) return;
mouseEv = "mouseup";
touching = false;
break;
case "touchmove":
if (e.touches.length !== 1) return;
mouseEv = "mousemove";
break;
default:
return;
}
var mouseEvent = document.createEvent("MouseEvent");
mouseEvent.initMouseEvent(
mouseEv,
true,
true,
window,
1,
theTouch.screenX,
theTouch.screenY,
theTouch.clientX,
theTouch.clientY,
false,
false,
false,
false,
0,
null
);
theTouch.target.dispatchEvent(mouseEvent);
e.preventDefault();
}
forEl.removeTouchToMouse = function removeTouchToMouse() {
doc.removeEventListener("touchstart", touch2Mouse, true);
doc.removeEventListener("touchmove", touch2Mouse, true);
doc.removeEventListener("touchend", touch2Mouse, true);
};
}
}
const resizeObserver = new ResizeObserver(entries => {
for (const entry of entries) {
const { width, height } = entry.target.getBoundingClientRect();
GM_setValue('width', width);
GM_setValue('height', height);
}
});
if ($('#bd-form').length) {
resizeObserver.observe(document.getElementById('bd-form'));
dragElement(document.getElementById('bd-form'));
}
// remember equipment and options
// mostly written by Eleven
let weapons = [];
let weaponID = '';
if ($('div#combatlog').text().indexOf('The fight commences!') !== -1) {
if (GM_getValue('sw1', false)) weapons.push(GM_getValue('sw1'))
if (GM_getValue('sw2', false)) weapons.push(GM_getValue('sw2'))
}
if (weapons.length === 0) {
weapons = GM_getValue('weapons', []);
}
for (const weapon of weapons) {
$('.bd-table input').each(function() {
if ($(this).val() !== weaponID && $(this).parent().text().trim().toLowerCase() === weapon.toLowerCase()) {
weaponID = $(this).val();
$(this).prop('checked', true);
countWeaps(weaponID);
return false;
}
});
}
if (GM_getValue('ability')) { $('form#bd-form select#ability').val(GM_getValue('ability')).trigger('change') }
if (GM_getValue('power')) { $('form#bd-form select#power').val(GM_getValue('power')) }
let height = !GM_getValue('goheight') ? 100 : GM_getValue('goheight');
let width = !GM_getValue('gowidth') ? 200 : GM_getValue('gowidth');
$('form#bd-form input[type=submit], #page_content > input[type=submit]:first-child').css({'height':height + 'px', 'width':width + 'px'});
let ability = '';
let power = '';
$('form#bd-form').on('submit', function() {
if ($('.bd-table div').length === 0) {
return; // frozen
}
weapons = [];
$('.bd-table input:checked').each(function() {
weapons.push($(this).parent().text().trim());
});
ability = $('form#bd-form select#ability').find(':selected').val();
power = $('form#bd-form select#power').find(':selected').val();
GM_setValue('weapons', weapons);
GM_setValue('ability', ability);
GM_setValue('power', power);
});
GM_registerMenuCommand('Set Starting Weapon 1', function() {
let value = prompt('Set the first starting weapon for each battle.', GM_getValue('sw1', ''));
if (value) GM_setValue('sw1', value.trim());
}, '1');
GM_registerMenuCommand('Set Starting Weapon 2', function() {
let value = prompt('Set the second starting weapon for each battle.', GM_getValue('sw2', ''));
if (value) GM_setValue('sw2', value.trim());
}, '2');
GM_registerMenuCommand('Clear Starting Weapon 1', function() {
GM_deleteValue('sw1');
}, '1');
GM_registerMenuCommand('Clear Starting Weapon 2', function() {
GM_deleteValue('sw2');
}, '2');
GM_registerMenuCommand('Set Button Height', function() {
let value = prompt('Enter the pixel height value of the Go! button.', GM_getValue('goheight', 100));
if (value) GM_setValue('goheight', value.trim());
}, 'h');
GM_registerMenuCommand('Set Button Width', function() {
let value = prompt('Enter the pixel width value of the Go! button.', GM_getValue('gowidth', 200));
if (value) GM_setValue('gowidth', value.trim());
}, 'w');
})();