您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
script that adds gamepad support to starblast.io, you can play gamepad WITH other clients running.
// ==UserScript== // @name Starblast Gamepad Script // @namespace http://tampermonkey.net/ // @version 4.7 // @description script that adds gamepad support to starblast.io, you can play gamepad WITH other clients running. // @author ঔ✧₱ⱠӾɎɆⱤ Ӿ✧ঔ // @match https://starblast.io/* // @exclude https://starblast.io/modding.html // @license idk what that is // @grant none // @run-at document-idle // ==/UserScript== (() => { const DEADZONE = 0.15; const TURN_RADIUS = 200; let currentShipIndex = 0; let currentUpgradeIndex = 1; const MAX_UPGRADE_INDEX = 8; let activeSelectorType = null; // 'ship', 'upgrade', or null (no selector active) const SELECTION_UI_ID = 'gamepad-selection-ui-unique'; let selectionUI = null; const BUTTON_ACTIONS = { 0: { type: 'mouse', button: 0 }, // A (0): Fire (Left Click) 1: { type: 'key', key: 'alt' }, // B (1): Launch Secondary (Alt) 2: { type: 'key', key: 'g' }, // X (2): Simulate 'G' keypress 3: { type: 'key', key: 'x' }, // Y (3): Mapped to 'x' key (B3) 4: { type: 'key', key: 'v' }, // L1: Throw Gems (v) 5: { type: 'key', key: 'tab' }, // R1: Toggle between teams (Tab) 6: { type: 'key', key: 'control' }, // L2: RCS Toggle (Control) 7: { type: 'mouse', button: 2 }, // R2: Accelerate (Right Click) 11: { type: 'key', key: 'c' }, // Right Stick Click: Chat (c) }; const lastButtonState = {}; const getCanvas = () => { const canvas = document.querySelector('canvas'); return canvas; }; function createSelectionUI() { if (!selectionUI) { selectionUI = document.createElement('div'); selectionUI.id = SELECTION_UI_ID; Object.assign(selectionUI.style, { all: 'unset', position: 'fixed', top: '10px', right: '10px', padding: '8px 14px', backgroundColor: 'rgba(0, 0, 0, 0.85)', color: 'cyan', // Default idle color fontFamily: 'Arial, sans-serif', fontSize: '15px', fontWeight: 'bold', borderRadius: '10px', zIndex: '9999999999999', userSelect: 'none', pointerEvents: 'none', opacity: '1', visibility: 'hidden', // Starts hidden, will be shown by pollGamepad display: 'block', boxShadow: '0 0 10px cyan', // Default idle shadow textShadow: '0 0 6px cyan' // Default idle text shadow }); document.body.appendChild(selectionUI); } else if (!document.body.contains(selectionUI)) { document.body.appendChild(selectionUI); } } // Handles displaying different states of the UI function updateSelectionUI(type, data) { if (!selectionUI) return; // Ensure UI element exists if (type === 'no_gamepad') { // When no gamepad is connected at all selectionUI.style.visibility = 'hidden'; selectionUI.textContent = ''; } else if (type === 'connected_idle') { // Gamepad connected, but not in ship/upgrade selection selectionUI.textContent = 'Gamepad Connected'; selectionUI.style.color = 'cyan'; selectionUI.style.boxShadow = '0 0 10px cyan'; selectionUI.style.textShadow = '0 0 6px cyan'; selectionUI.style.visibility = 'visible'; // Ensure visible } else if (type === 'ship') { selectionUI.textContent = `Ship: ${data}`; selectionUI.style.color = '#f0f'; // Magenta for selection selectionUI.style.boxShadow = '0 0 10px #f0f'; selectionUI.style.textShadow = '0 0 6px #f0f'; selectionUI.style.visibility = 'visible'; // Ensure visible } else if (type === 'upgrade') { selectionUI.textContent = `Upgrade: ${data}`; selectionUI.style.color = '#f0f'; // Magenta for selection selectionUI.style.boxShadow = '0 0 10px #f0f'; selectionUI.style.textShadow = '0 0 6px #f0f'; selectionUI.style.visibility = 'visible'; // Ensure visible } } function cleanupUnknownUI() { const elements = document.body.querySelectorAll('div'); elements.forEach(el => { if (el.id !== SELECTION_UI_ID) { const rect = el.getBoundingClientRect(); const isSmallAndTopLeft = rect.width > 0 && rect.width < 50 && rect.height > 0 && rect.height < 50 && rect.left >= 0 && rect.left < 20 && rect.top >= 0 && rect.top < 20; const isEmpty = !el.textContent.trim() && !el.children.length; if (isSmallAndTopLeft && isEmpty) { el.remove(); } } }); } const dispatchMouseEvent = (type, button) => { const canvas = getCanvas(); const mouseEvent = new MouseEvent(type, { bubbles: true, cancelable: true, button, buttons: 1 << button, clientX: window.innerWidth / 2, clientY: window.innerHeight / 2 }); window.dispatchEvent(mouseEvent); document.dispatchEvent(mouseEvent); if (canvas) { canvas.dispatchEvent(mouseEvent); } }; const dispatchKeyboardEvent = (type, key) => { const canvas = getCanvas(); let eventOptions = { key: key, bubbles: true, cancelable: true }; switch (key.toLowerCase()) { case 'alt': eventOptions.keyCode = 18; eventOptions.which = 18; eventOptions.code = 'AltLeft'; eventOptions.altKey = true; break; case 'shift': case 'shiftleft': eventOptions.keyCode = 16; eventOptions.which = 16; eventOptions.code = 'ShiftLeft'; eventOptions.shiftKey = true; break; case 'shiftright': eventOptions.keyCode = 16; eventOptions.which = 16; eventOptions.code = 'ShiftRight'; eventOptions.shiftKey = true; break; case 'control': eventOptions.keyCode = 17; eventOptions.which = 17; eventOptions.code = 'ControlLeft'; eventOptions.ctrlKey = true; break; case 'tab': eventOptions.keyCode = 9; eventOptions.which = 9; eventOptions.code = 'Tab'; break; case 'c': eventOptions.keyCode = 67; eventOptions.which = 67; eventOptions.code = 'KeyC'; break; case 'v': eventOptions.keyCode = 86; eventOptions.which = 86; eventOptions.code = 'KeyV'; break; case 'x': eventOptions.keyCode = 88; eventOptions.which = 88; eventOptions.code = 'KeyX'; break; case 'g': eventOptions.keyCode = 71; eventOptions.which = 71; eventOptions.code = 'KeyG'; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': eventOptions.keyCode = key.charCodeAt(0); eventOptions.which = eventOptions.keyCode; eventOptions.code = `Digit${key}`; break; default: if (key.length === 1) { eventOptions.keyCode = key.toUpperCase().charCodeAt(0); eventOptions.which = eventOptions.keyCode; eventOptions.code = `Key${key.toUpperCase()}`; } else { eventOptions.code = key; } break; } const keyboardEvent = new KeyboardEvent(type, eventOptions); window.dispatchEvent(keyboardEvent); document.dispatchEvent(keyboardEvent); if (canvas) { canvas.dispatchEvent(keyboardEvent); canvas.focus(); } }; const handleButton = (index, pressed) => { const action = BUTTON_ACTIONS[index]; if (!action) return; if (action.type === 'mouse') { pressed ? dispatchMouseEvent("mousedown", action.button) : dispatchMouseEvent("mouseup", action.button); } else if (action.type === 'key') { if (pressed) { dispatchKeyboardEvent("keydown", action.key); } else { setTimeout(() => dispatchKeyboardEvent("keyup", action.key), 100); } } }; const simulateMouseMove = (deltaX, deltaY) => { const canvas = getCanvas(); if (!canvas) return; canvas.dispatchEvent(new MouseEvent("mousemove", { bubbles: true, cancelable: true, button: 0, buttons: 0, clientX: window.innerWidth / 2 + deltaX, clientY: window.innerHeight / 2 + deltaY, movementX: deltaX, movementY: deltaY })); }; const handleTurn = (xAxis, yAxis) => { const magnitude = Math.sqrt(xAxis ** 2 + yAxis ** 2); if (magnitude < DEADZONE) { simulateMouseMove(0, 0); return; } const adjusted = (magnitude - DEADZONE) / (1 - DEADZONE); const normX = xAxis / magnitude; const normY = yAxis / magnitude; const deltaX = normX * adjusted * TURN_RADIUS; const deltaY = normY * adjusted * TURN_RADIUS; simulateMouseMove(deltaX, deltaY); }; const pollGamepad = () => { let gp = null; const gamepads = navigator.getGamepads?.(); if (gamepads) { for (let i = 0; i < gamepads.length; i++) { const currentGp = gamepads[i]; if (!gp && currentGp && currentGp.buttons.length > 0 && currentGp.axes.length > 0) { gp = currentGp; } } } if (!gp) { updateSelectionUI('no_gamepad', null); // Hide UI when no gamepad activeSelectorType = null; return; } // If gamepad is connected but no selector is active, show idle status if (!activeSelectorType) { updateSelectionUI('connected_idle', null); } const dPadUpPressed = gp.buttons[12] && gp.buttons[12].pressed; const dPadDownPressed = gp.buttons[13] && gp.buttons[13].pressed; const dPadLeftPressed = gp.buttons[14] && gp.buttons[14].pressed; const dPadRightPressed = gp.buttons[15] && gp.buttons[15].pressed; // D-pad Up (Ship Selector Toggle/Confirm) if (dPadUpPressed && !lastButtonState[12]) { if (activeSelectorType === 'ship') { // CONFIRM SHIP and EXIT SELECTION by pressing D-pad Up again dispatchKeyboardEvent("keydown", currentShipIndex.toString()); setTimeout(() => dispatchKeyboardEvent("keyup", currentShipIndex.toString()), 100); activeSelectorType = null; // Exit selection mode // UI will revert to 'connected_idle' in the next pollGamepad cycle } else { // ENTER SHIP SELECTION (or switch from upgrade to ship) if (activeSelectorType === 'upgrade') { activeSelectorType = null; // Exit upgrade mode if active } currentShipIndex = 0; // Default to ship 0 on entry activeSelectorType = 'ship'; updateSelectionUI('ship', currentShipIndex); // Show ship selection UI } } // D-pad Down (Upgrade Selector Toggle/Confirm) if (dPadDownPressed && !lastButtonState[13]) { if (activeSelectorType === 'upgrade') { // CONFIRM UPGRADE and EXIT SELECTION by pressing D-pad Down again dispatchKeyboardEvent("keydown", currentUpgradeIndex.toString()); setTimeout(() => dispatchKeyboardEvent("keyup", currentUpgradeIndex.toString()), 100); activeSelectorType = null; // Exit selection mode // UI will revert to 'connected_idle' in the next pollGamepad cycle } else { // ENTER UPGRADE SELECTION (or switch from ship to upgrade) if (activeSelectorType === 'ship') { activeSelectorType = null; // Exit ship mode if active } currentUpgradeIndex = 1; // Default to upgrade 1 on entry activeSelectorType = 'upgrade'; updateSelectionUI('upgrade', currentUpgradeIndex); // Show upgrade selection UI } } // D-pad Left/Right (Cycle Selector ONLY if a mode is active) if ((dPadLeftPressed && !lastButtonState[14]) || (dPadRightPressed && !lastButtonState[15])) { if (activeSelectorType === 'upgrade') { if (dPadLeftPressed && !lastButtonState[14]) { currentUpgradeIndex = Math.max(1, currentUpgradeIndex - 1); } else if (dPadRightPressed && !lastButtonState[15]) { currentUpgradeIndex = Math.min(MAX_UPGRADE_INDEX, currentUpgradeIndex + 1); } updateSelectionUI('upgrade', currentUpgradeIndex); // ONLY update UI, do NOT apply upgrade yet } else if (activeSelectorType === 'ship') { currentShipIndex = (currentShipIndex === 0) ? 9 : 0; // Toggle between 0 and 9 updateSelectionUI('ship', currentShipIndex); // Update UI for new ship } } lastButtonState[12] = dPadUpPressed; lastButtonState[13] = dPadDownPressed; lastButtonState[14] = dPadLeftPressed; lastButtonState[15] = dPadRightPressed; gp.buttons.forEach((btn, i) => { if (i >= 12 && i <= 15) return; // Skip D-pad buttons const pressed = btn.pressed; if (pressed !== lastButtonState[i]) { handleButton(i, pressed); lastButtonState[i] = pressed; } }); handleTurn(gp.axes[0], gp.axes.length > 1 ? gp.axes[1] : 0); // Ensure axis 1 exists before using it }; const startLoop = () => { createSelectionUI(); setInterval(cleanupUnknownUI, 1000); const loop = () => { pollGamepad(); requestAnimationFrame(loop); }; requestAnimationFrame(loop); }; window.addEventListener('load', () => { setTimeout(startLoop, 15000); }); })();