您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Control Neopets Battledome using keyboard. Includes HUD with tappable/clickable keys
// ==UserScript== // @name Battledome Keyboard Interface // @description Control Neopets Battledome using keyboard. Includes HUD with tappable/clickable keys // @version 2025.09.10 // @license GNU GPLv3 // @match https://www.neopets.com/dome/arena.phtml* // @author Posterboy // @icon https://images.neopets.com/new_shopkeepers/t_1900.gif // @namespace https://youtube.com/@Neo_Posterboy // @grant none // ==/UserScript== (() => { const d = document, w = window, q = s => d.querySelector(s), qa = s => [...d.querySelectorAll(s)]; const WK = ['A','S','D','F','J','K','L',';'], AK = ['1','2','3','4','5','6','7','8','9','0','-'], BK = ['Z','X','C','V','B','N','M',',','.']; const BTN = { // Action Buttons can be reassigned to any key on Q or Z rows. See Line 201 for more. X: { s: ['#start', '#fight'], l: 'Fight' }, C: { s: '#skipreplay', l: '🔄⏩' }, V: { s: '.end_ack.collect', l: 'Collect' }, B: { s: '#bdplayagain', l: 'Play Again' }, N: { s: '#bdnewfight', l: 'New Fight' }, M: { s: '#bdexit', l: 'Exit' } }; //=========================== // Keyboard Background Images //=========================== const bgImages = [ 'https://images.neopets.com/bd2/h5/barracks/images/barracks_popup_banner.png', 'https://images.neopets.com/games/new_tradingcards/lg_sloth_plot_2004.gif', 'https://images.neopets.com/themes/h5/common/signup/images/bg-jhudora.png', 'https://images.neopets.com/wheels/h5/excitement/images/bg.png', 'https://images.neopets.com/ncmall/collectibles/09_08/nc-collectible.jpg', 'https://portal.neopets.com/images/neopass/banner-top-mobile.png', 'https://images.neopets.com/themes/h5/altadorcup/images/hp-bg-top.png' ]; let currentBgIndex = parseInt(localStorage.getItem('bdKeyboardBgIndex')) || 0; const cycleBackground = () => { currentBgIndex = (currentBgIndex + 1) % bgImages.length; const hud = document.getElementById('keyboard-hud'); if (hud) { hud.style.backgroundImage = `url(${bgImages[currentBgIndex]})`; localStorage.setItem('bdKeyboardBgIndex', currentBgIndex); } }; //======================= // Gather/Set Information //======================= let vKeyPressed = false, weaponSelectCount = 0, lastEquippedWeaponUrl = null; const getWeapons = () => qa('#p1equipment ul li').filter(li => li.querySelector('img.item')); const getAbilities = () => qa('#p1ability td[title]'); const getItemInfo = (url, n = 1) => { let count = 0; for (const item of qa('#p1equipment ul li img.item')) { if (item.src === url && ++count === n) { return { id: item.id, name: item.alt, node: item }; } } return null; }; const getAbilityInfo = url => { for(const node of qa('#p1ability td[title]')){ const inner = node.children[0]?.innerHTML||'', m = inner.match(/img src=\"(.*?)\"/); if(!m) continue; let nodeurl = m[1].includes('https:')?m[1]:'https:'+m[1]; if(nodeurl===url){ if(node.children[0].classList.contains('cooldown')) return -1; return {id:node.children[0].getAttribute('data-ability'),name:node.title,node}; } } return null; }; //====================== // Equip Items/Abilities //====================== const selectSlot = (slot, item, n = 1) => { const isAbility = slot.id === 'p1am'; const info = isAbility ? getAbilityInfo(item) : getItemInfo(item, n); if (info === -1 && isAbility) { alert('WARNING: cooldown!'); return true; } if (info === null) { alert(`ERROR: Item not equipped!\nURL: ${item}`); return true; } const slotid = slot.id.slice(0, -1), input = q(`#${slotid}`); if (input) input.value = info.id; slot.classList.add('selected'); const bg = slot.children[1].style; bg.backgroundPosition = '0 0'; bg.backgroundSize = '60px 60px'; bg.backgroundImage = `url("${item}")`; if (!isAbility) { info.node.style.display = 'none'; slot.addEventListener('click', () => info.node.removeAttribute('style')); } return false; }; w.selectSlot = selectSlot; let _weaponImgs = [], _abilityImgs = []; //================== // Keyboard Mapping //================== const setupKeyboard = (weaponImgs, abilityImgs) => { _weaponImgs = weaponImgs.map(li => li.querySelector('img')); _abilityImgs = abilityImgs.map(td => td.querySelector('img')); d.addEventListener('keydown', e => { const key = e.key.toUpperCase(); //Prevent page from scrolling when pressing spacebar if (e.key === ' ') { e.preventDefault(); cycleBackground(); return; } const wIdx = WK.indexOf(key); if (wIdx !== -1 && _weaponImgs[wIdx]) { const img = _weaponImgs[wIdx]; const slotId = weaponSelectCount % 2 === 0 ? 'p1e1m' : 'p1e2m'; const slot = q(`#${slotId}`); let occurrence = 1; if (weaponSelectCount % 2 === 1) { occurrence = (img.src === lastEquippedWeaponUrl) ? 2 : 1; } else { lastEquippedWeaponUrl = img.src; } if (slot) { selectSlot(slot, img.src, occurrence); weaponSelectCount++; } return; } const aIdx = AK.indexOf(key); if (aIdx !== -1 && _abilityImgs[aIdx]) { const slot = q('#p1am'); if (slot) selectSlot(slot, _abilityImgs[aIdx].src); return; } if (key === 'V') vKeyPressed = true; if (key === 'B' && !vKeyPressed) return; if (BTN[key]) { const target = q(BTN[key].s); if (target) target.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true })); } }); ['#p1e1m', '#p1e2m'].forEach(id => { const el = q(id); if (el) { el.addEventListener('click', () => { weaponSelectCount = 0; lastEquippedWeaponUrl = null; }); } }); }; //============================ // "Play Again" Bug Prevention //============================ (() => { let unlocked = false; const observer = new MutationObserver(() => { const c = q('.end_ack.collect'); if (c && !c.__guarded) { c.__guarded = true; c.addEventListener('click', () => { unlocked = true; }, { once: true }); } const p = q('#bdplayagain'); if (p && !p.__guarded) { p.__guarded = true; p.addEventListener('click', e => { if (!unlocked) { e.stopImmediatePropagation(); e.preventDefault(); } }, true); } }); observer.observe(d.body, { childList: true, subtree: true }); })(); //================= // HUD Construction //================= const createKey = (label, imgSrc = null, actionLabel = null) => { const key = d.createElement('div'); Object.assign(key.style, { width: '65px', height: '60px', margin: '4px', backgroundColor: 'rgba(51,51,51,0.85)', border: '1px solid #555', borderRadius: '6px', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', color: '#fff', fontSize: '14px', position: 'relative', cursor: 'pointer', userSelect: 'none', }); key.className = 'keyboard-key'; if (imgSrc) { const img = d.createElement('img'); img.src = imgSrc; Object.assign(img.style, { width: '36px', height: '36px', objectFit: 'contain' }); key.appendChild(img); } const labelEl = d.createElement('span'); labelEl.textContent = label; labelEl.style.marginTop = '2px'; labelEl.style.fontWeight = 'bold'; labelEl.style.color = '#fff'; key.appendChild(labelEl); if (actionLabel) { const desc = d.createElement('div'); desc.textContent = actionLabel; Object.assign(desc.style, { fontSize: '12px', color: '#fff', marginTop: '2px', fontWeight: 'bold' }); key.appendChild(desc); } // Change these if you remap keys on lines 15-20. Fight Button is yellow and Collect button is green if (label === 'X') { // This is your Fight Button key.style.backgroundColor = 'yellow'; labelEl.style.color = '#000'; if (actionLabel) key.lastChild.style.color = '#000'; } else if (label === 'V') { // This is your Collect Button key.style.backgroundColor = 'green'; labelEl.style.color = '#fff'; if (actionLabel) key.lastChild.style.color = '#fff'; } key.onclick = () => { const keyLabel = label.toUpperCase(); const wIdx = WK.indexOf(keyLabel); if (wIdx !== -1) { const img = _weaponImgs[wIdx]; const slotId = weaponSelectCount % 2 === 0 ? 'p1e1m' : 'p1e2m'; const slot = q(`#${slotId}`); let occurrence = 1; if (weaponSelectCount % 2 === 1) { occurrence = (img.src === lastEquippedWeaponUrl) ? 2 : 1; } else { lastEquippedWeaponUrl = img.src; } if (slot && img) { selectSlot(slot, img.src, occurrence); weaponSelectCount++; } return; } const aIdx = AK.indexOf(keyLabel); if (aIdx !== -1) { const img = _abilityImgs[aIdx]; const slot = q('#p1am'); if (slot && img) { selectSlot(slot, img.src); } return; } if (BTN[keyLabel]) { const target = q(BTN[keyLabel].s); if (target) target.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true })); } if (keyLabel === 'V') vKeyPressed = true; }; return key; }; const createHUD = (weapons, abilities) => { const container = d.createElement('div'); container.id = 'keyboard-hud'; Object.assign(container.style, { width: '980px', margin: '20px auto', padding: '10px', backgroundColor: '#111', border: '2px solid #666', borderRadius: '10px', backgroundImage: `url(${bgImages[currentBgIndex]})`, backgroundSize: 'cover', backgroundPosition: 'center center', backgroundRepeat: 'no-repeat', userSelect: 'none', }); const wImgs = weapons.map(li => li.querySelector('img')); const aImgs = abilities.map(td => td.querySelector('img')); const layout = [AK, ['Q','W','E','R','T','Y','U','I','O','P'], ['A','S','D','F','G','H','J','K','L',';'], BK]; layout.forEach(row => { const rowEl = d.createElement('div'); Object.assign(rowEl.style, { display: 'flex', justifyContent: 'center', marginTop: '6px' }); row.forEach(key => { const wIdx = WK.indexOf(key), aIdx = AK.indexOf(key), btn = BTN[key]; let keyEl; if (wIdx !== -1 && wImgs[wIdx]) keyEl = createKey(key, wImgs[wIdx].src); else if (aIdx !== -1 && aImgs[aIdx]) keyEl = createKey(key, aImgs[aIdx].src); else if (btn) keyEl = createKey(key, null, btn.l); else keyEl = createKey(key); rowEl.appendChild(keyEl); }); container.appendChild(rowEl); }); const spacebarRow = d.createElement('div'); Object.assign(spacebarRow.style, { display: 'flex', justifyContent: 'center', marginTop: '10px' }); const spacebarKey = d.createElement('div'); spacebarKey.className = 'keyboard-key'; Object.assign(spacebarKey.style, { width: '530px', height: '50px', margin: '4px', backgroundColor: 'rgba(51,51,51,0.85)', border: '1px solid #555', borderRadius: '6px', display: 'flex', justifyContent: 'center', alignItems: 'center', userSelect: 'none', cursor: 'default', }); const spaceLabel = d.createElement('span'); spaceLabel.textContent = 'SPACE (Press to change background)'; Object.assign(spaceLabel.style, { color: '#fff', fontWeight: 'bold', fontSize: '16px', userSelect: 'none' }); spacebarKey.appendChild(spaceLabel); spacebarRow.appendChild(spacebarKey); container.appendChild(spacebarRow); const status = q('#statusmsg'); if (status?.parentNode) status.parentNode.insertBefore(container, status.nextSibling); }; //=========== // Initialize //=========== const init = () => { const weapons = getWeapons(), abilities = getAbilities(); if (!weapons.length && !abilities.length) return; createHUD(weapons, abilities); setupKeyboard(weapons, abilities); /* Optional feature to auto scroll to bottom of page so that keyboard is more visible. w.scrollTo(0, d.body.scrollHeight); */ }; w.addEventListener('load', init); })();