您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Customizable key rebinding interface for Florr.io
// ==UserScript== // @name Florr.io Rebinds // @namespace http://tampermonkey.net/ // @version 1.0.0 // @description Customizable key rebinding interface for Florr.io // @author VortexPrime // @match https://florr.io/* // @icon https://www.google.com/s2/favicons?sz=64&domain=florr.io // @grant none // @run-at document-start // @license MIT // ==/UserScript== (function() { 'use strict'; document.addEventListener('DOMContentLoaded', function() { const keybinds = { '0': null, '1': null, '2': null, '3': null, '4': null, '5': null, '6': null, '7': null, '8': null, '9': null, 'R': null, 'L': null, 'K': null, 'M': null }; const bindableKeys = new Set(Object.keys(keybinds)); const reverseKeybinds = {}; const keyState = {}; function updateReverseMapping() { Object.keys(reverseKeybinds).forEach(key => delete reverseKeybinds[key]); Object.keys(keybinds).forEach(target => { const bind = keybinds[target]; if (bind) { reverseKeybinds[bind.toUpperCase()] = target; } }); } updateReverseMapping(); const keyDisplayMap = { 'ESCAPE': 'ESC', 'BACKSPACE': 'BSP', 'DELETE': 'DEL', 'INSERT': 'INS', 'PAGEUP': 'PUP', 'PAGEDOWN': 'PDN', 'ARROWUP': 'UP', 'ARROWDOWN': 'DWN', 'ARROWLEFT': 'LFT', 'ARROWRIGHT': 'RGT', 'SPACE': 'SPC', 'CONTROL': 'CTL', 'SHIFT': 'SHF', 'ENTER': 'ENT', 'TAB': 'TAB' }; const keyCodeMap = { '0': 'Digit0', '1': 'Digit1', '2': 'Digit2', '3': 'Digit3', '4': 'Digit4', '5': 'Digit5', '6': 'Digit6', '7': 'Digit7', '8': 'Digit8', '9': 'Digit9', 'A': 'KeyA', 'B': 'KeyB', 'C': 'KeyC', 'D': 'KeyD', 'E': 'KeyE', 'F': 'KeyF', 'G': 'KeyG', 'H': 'KeyH', 'I': 'KeyI', 'J': 'KeyJ', 'K': 'KeyK', 'L': 'KeyL', 'M': 'KeyM', 'N': 'KeyN', 'O': 'KeyO', 'P': 'KeyP', 'Q': 'KeyQ', 'R': 'KeyR', 'S': 'KeyS', 'T': 'KeyT', 'U': 'KeyU', 'V': 'KeyV', 'W': 'KeyW', 'X': 'KeyX', 'Y': 'KeyY', 'Z': 'KeyZ', 'SPACE': 'Space', 'SHIFT': 'ShiftLeft', 'CONTROL': 'ControlLeft', 'TAB': 'Tab' }; const groupDescriptions = { loadout: 'Loadout Slots', utility: { 'R': 'Swap all loadouts', 'L': 'Load saved builds', 'K': 'Load saved builds', 'M': 'Toggle expanded minimap' } }; const style = document.createElement('style'); style.textContent = ` .florrBinds-modal { position: absolute; top: 50px; left: 50px; width: 280px; background-color: #374151; border-radius: 8px; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3); border: 1px solid #4B5563; font-family: Arial, sans-serif; color: #E5E7EB; z-index: 10000; overflow: hidden; user-select: none; } .florrBinds-modal.hidden { display: none; } .florrBinds-header { background-color: #1F2937; padding: 10px 16px; border-bottom: 1px solid #4B5563; cursor: move; display: flex; justify-content: space-between; } .florrBinds-header-left, .florrBinds-header-right { display: flex; flex-direction: column; justify-content: center; } .florrBinds-header-right { align-items: flex-end; text-align: right; } .florrBinds-title { color: #FBBF24; font-weight: bold; font-size: 18px; margin: 0; padding: 0; line-height: 1.2; } .florrBinds-subtitle { color: #9CA3AF; font-size: 12px; margin: 0; } .florrBinds-author { color: #9CA3AF; font-size: 11px; margin-top: 2px; } .florrBinds-author a { color: #93C5FD; text-decoration: none; } .florrBinds-author a:hover { text-decoration: underline; } .florrBinds-drag-hint { color: #6B7280; font-size: 11px; margin-bottom: 2px; } .florrBinds-content { padding: 8px; overflow: hidden; } .florrBinds-modal.collapsed .florrBinds-content, .florrBinds-modal.collapsed .florrBinds-footer { display: none; } .florrBinds-group { margin-bottom: 8px; } .florrBinds-group-title { color: #9CA3AF; font-size: 14px; margin-bottom: 4px; padding-bottom: 4px; border-bottom: 1px solid #4B5563; } .florrBinds-loadout-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 8px; } .florrBinds-key-item { display: flex; flex-direction: column; align-items: center; margin-bottom: 6px; } .florrBinds-key-label { color: #D1D5DB; font-size: 14px; margin-bottom: 4px; } .florrBinds-key-value { background-color: #1F2937; color: #FFF; padding: 4px 12px; border-radius: 4px; border: 1px solid #4B5563; font-family: monospace; font-size: 14px; cursor: pointer; text-align: center; width: 100%; box-sizing: border-box; transition: transform 0.2s ease; } .florrBinds-key-value:hover { transform: translateY(-2px); } .florrBinds-key-value.unbound { background-color: #111827; color: #9CA3AF; border-color: #4B5563; } .florrBinds-utility { display: none; } .florrBinds-utility.show { display: block; } .florrBinds-utility-item { display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px; } .florrBinds-utility-info { display: flex; align-items: center; min-width: 0; flex: 1; } .florrBinds-utility-key { color: #D1D5DB; font-size: 14px; margin-right: 8px; flex-shrink: 0; } .florrBinds-utility-desc { color: #9CA3AF; font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .florrBinds-key-value-container { flex-shrink: 0; width: 60px; text-align: right; } .florrBinds-utility .florrBinds-key-value { width: 100%; box-sizing: border-box; text-align: center; } .florrBinds-toggle { width: 100%; text-align: center; color: #60A5FA; background: none; border: none; padding: 4px; margin-bottom: 6px; font-size: 14px; cursor: pointer; } .florrBinds-toggle:hover { color: #93C5FD; } .florrBinds-footer { display: flex; justify-content: space-between; align-items: center; padding: 8px; border-top: 1px solid #4B5563; } .florrBinds-reset { background-color: #2563EB; color: white; border: none; border-radius: 4px; padding: 4px 8px; font-size: 12px; cursor: pointer; } .florrBinds-reset:hover { background-color: #3B82F6; } .florrBinds-hint { color: #9CA3AF; font-size: 12px; } .florrBinds-resize { position: absolute; bottom: 0; right: 0; width: 12px; height: 12px; cursor: ew-resize; display: flex; align-items: center; justify-content: center; } .florrBinds-resize-icon { width: 0; height: 0; border-style: solid; border-width: 0 0 8px 8px; border-color: transparent transparent rgba(200, 200, 200, 0.4) transparent; } .florrBinds-status { position: absolute; bottom: 8px; left: 8px; padding: 4px 8px; background-color: rgba(0, 0, 0, 0.5); color: white; border-radius: 4px; font-size: 12px; pointer-events: none; opacity: 0; transition: opacity 0.3s ease; } .florrBinds-status.show { opacity: 1; } `; document.head.appendChild(style); const modal = document.createElement('div'); modal.className = 'florrBinds-modal'; const header = document.createElement('div'); header.className = 'florrBinds-header'; const headerLeft = document.createElement('div'); headerLeft.className = 'florrBinds-header-left'; const title = document.createElement('h3'); title.className = 'florrBinds-title'; title.textContent = 'Florr.io Binds'; const subtitle = document.createElement('p'); subtitle.className = 'florrBinds-subtitle'; subtitle.textContent = 'Rebind specific keys!'; headerLeft.appendChild(title); headerLeft.appendChild(subtitle); const headerRight = document.createElement('div'); headerRight.className = 'florrBinds-header-right'; const dragHint = document.createElement('div'); dragHint.className = 'florrBinds-drag-hint'; dragHint.textContent = '(Drag to move)'; const author = document.createElement('div'); author.className = 'florrBinds-author'; author.innerHTML = 'by <a href="https://ashish.top" target="_blank">VortexPrime</a>'; headerRight.appendChild(dragHint); headerRight.appendChild(author); header.appendChild(headerLeft); header.appendChild(headerRight); modal.appendChild(header); const content = document.createElement('div'); content.className = 'florrBinds-content'; const loadoutGroup = document.createElement('div'); loadoutGroup.className = 'florrBinds-group'; const loadoutTitle = document.createElement('h4'); loadoutTitle.className = 'florrBinds-group-title'; loadoutTitle.textContent = groupDescriptions.loadout; loadoutGroup.appendChild(loadoutTitle); const loadoutGrid = document.createElement('div'); loadoutGrid.className = 'florrBinds-loadout-grid'; for (let i = 0; i <= 9; i++) { const key = i.toString(); const keyItem = document.createElement('div'); keyItem.className = 'florrBinds-key-item'; const keyLabel = document.createElement('span'); keyLabel.className = 'florrBinds-key-label'; keyLabel.textContent = key; const keyValue = document.createElement('div'); keyValue.className = `florrBinds-key-value ${keybinds[key] ? '' : 'unbound'}`; keyValue.textContent = keybinds[key] ? formatKeyDisplay(keybinds[key]) : '-'; keyValue.dataset.key = key; keyItem.appendChild(keyLabel); keyItem.appendChild(keyValue); loadoutGrid.appendChild(keyItem); } loadoutGroup.appendChild(loadoutGrid); content.appendChild(loadoutGroup); const utilityGroup = document.createElement('div'); utilityGroup.className = 'florrBinds-group florrBinds-utility'; const utilityTitle = document.createElement('h4'); utilityTitle.className = 'florrBinds-group-title'; utilityTitle.textContent = 'Utility Keys'; utilityGroup.appendChild(utilityTitle); const utilityKeys = ['R', 'L', 'K', 'M']; for (const key of utilityKeys) { const utilityItem = document.createElement('div'); utilityItem.className = 'florrBinds-utility-item'; const utilityInfo = document.createElement('div'); utilityInfo.className = 'florrBinds-utility-info'; const utilityKey = document.createElement('span'); utilityKey.className = 'florrBinds-utility-key'; utilityKey.textContent = key; const utilityDesc = document.createElement('span'); utilityDesc.className = 'florrBinds-utility-desc'; utilityDesc.textContent = groupDescriptions.utility[key]; utilityInfo.appendChild(utilityKey); utilityInfo.appendChild(utilityDesc); const keyValueContainer = document.createElement('div'); keyValueContainer.className = 'florrBinds-key-value-container'; const keyValue = document.createElement('div'); keyValue.className = `florrBinds-key-value ${keybinds[key] ? '' : 'unbound'}`; keyValue.textContent = keybinds[key] ? formatKeyDisplay(keybinds[key]) : '-'; keyValue.dataset.key = key; keyValueContainer.appendChild(keyValue); utilityItem.appendChild(utilityInfo); utilityItem.appendChild(keyValueContainer); utilityGroup.appendChild(utilityItem); } content.appendChild(utilityGroup); const toggleBtn = document.createElement('button'); toggleBtn.className = 'florrBinds-toggle'; toggleBtn.textContent = 'Show More »'; content.appendChild(toggleBtn); const footer = document.createElement('div'); footer.className = 'florrBinds-footer'; const resetBtn = document.createElement('button'); resetBtn.className = 'florrBinds-reset'; resetBtn.textContent = 'Reset All'; const hint = document.createElement('div'); hint.className = 'florrBinds-hint'; hint.textContent = 'Toggle using Right Shift'; footer.appendChild(resetBtn); footer.appendChild(hint); modal.appendChild(content); modal.appendChild(footer); const resizeHandle = document.createElement('div'); resizeHandle.className = 'florrBinds-resize'; const resizeIcon = document.createElement('div'); resizeIcon.className = 'florrBinds-resize-icon'; resizeHandle.appendChild(resizeIcon); modal.appendChild(resizeHandle); const statusNotification = document.createElement('div'); statusNotification.className = 'florrBinds-status'; document.body.appendChild(statusNotification); document.body.appendChild(modal); let isDragging = false; let dragOffsetX = 0; let dragOffsetY = 0; let isResizing = false; let originalWidth = 280; let originalMouseX = 0; let isRebinding = false; let rebindElement = null; function formatKeyDisplay(key) { if (!key) return '-'; const upperKey = key.toUpperCase(); return keyDisplayMap[upperKey] || (upperKey.length > 3 ? upperKey.substring(0, 3) : upperKey); } function showStatus(message, duration = 2000) { statusNotification.textContent = message; statusNotification.classList.add('show'); setTimeout(() => { statusNotification.classList.remove('show'); }, duration); } function simulateKeyEvent(type, key, code) { const event = new KeyboardEvent(type, { key: key, code: code, keyCode: key.charCodeAt(0), which: key.charCodeAt(0), bubbles: true, cancelable: true }); document.dispatchEvent(event); } function getCodeForKey(key) { if (key >= '0' && key <= '9') { return `Digit${key}`; } return keyCodeMap[key.toUpperCase()] || `Key${key.toUpperCase()}`; } toggleBtn.addEventListener('click', () => { const utilitySection = document.querySelector('.florrBinds-utility'); utilitySection.classList.toggle('show'); toggleBtn.textContent = utilitySection.classList.contains('show') ? '« Show Less' : 'Show More »'; }); header.addEventListener('contextmenu', (e) => { e.preventDefault(); modal.classList.toggle('collapsed'); return false; }); header.addEventListener('mousedown', (e) => { if (e.button === 0) { isDragging = true; dragOffsetX = e.clientX - modal.offsetLeft; dragOffsetY = e.clientY - modal.offsetTop; e.preventDefault(); } }); resizeHandle.addEventListener('mousedown', (e) => { isResizing = true; originalWidth = modal.offsetWidth; originalMouseX = e.clientX; e.preventDefault(); e.stopPropagation(); }); document.addEventListener('mousemove', (e) => { if (isDragging) { modal.style.left = (e.clientX - dragOffsetX) + 'px'; modal.style.top = (e.clientY - dragOffsetY) + 'px'; } else if (isResizing) { const width = originalWidth + (e.clientX - originalMouseX); if (width >= 240) { modal.style.width = width + 'px'; } } }); document.addEventListener('mouseup', () => { isDragging = false; isResizing = false; }); document.addEventListener('keydown', (e) => { if (e.key === 'Shift' && e.location === 2) { modal.classList.toggle('hidden'); e.preventDefault(); e.stopPropagation(); return; } if (isRebinding) return; const pressedKey = e.key.toUpperCase(); if (reverseKeybinds[pressedKey]) { const targetKey = reverseKeybinds[pressedKey]; if (!keyState[pressedKey]) { keyState[pressedKey] = true; const targetCode = getCodeForKey(targetKey); simulateKeyEvent('keydown', targetKey, targetCode); } } }, true); document.addEventListener('keyup', (e) => { if (isRebinding) return; const releasedKey = e.key.toUpperCase(); if (reverseKeybinds[releasedKey]) { const targetKey = reverseKeybinds[releasedKey]; if (keyState[releasedKey]) { keyState[releasedKey] = false; const targetCode = getCodeForKey(targetKey); simulateKeyEvent('keyup', targetKey, targetCode); } } }, true); const keyElements = document.querySelectorAll('.florrBinds-key-value'); keyElements.forEach(element => { element.addEventListener('click', () => { if (isRebinding) { rebindElement.style.boxShadow = ''; isRebinding = false; } keyElements.forEach(el => el.style.boxShadow = ''); element.style.boxShadow = '0 0 0 2px #60A5FA'; isRebinding = true; rebindElement = element; showStatus(`Press any key to bind to ${element.dataset.key}. ESC/BACKSPACE to unbind.`); const captureKey = (e) => { e.preventDefault(); e.stopPropagation(); const key = e.key.toUpperCase(); const targetKey = element.dataset.key; let conflictKey = null; for (const k in keybinds) { if (keybinds[k] && keybinds[k].toUpperCase() === key) { conflictKey = k; break; } } if (conflictKey && conflictKey !== targetKey) { showStatus(`Warning: "${key}" was already bound to ${conflictKey}. Binding swapped.`, 3000); const conflictElement = document.querySelector(`.florrBinds-key-value[data-key="${conflictKey}"]`); if (conflictElement) { conflictElement.textContent = '-'; conflictElement.classList.add('unbound'); keybinds[conflictKey] = null; } } if (key === 'ESCAPE' || key === 'BACKSPACE') { element.textContent = '-'; element.classList.add('unbound'); keybinds[targetKey] = null; showStatus(`Removed binding for ${targetKey}`); } else { element.textContent = formatKeyDisplay(key); element.classList.remove('unbound'); keybinds[targetKey] = key; showStatus(`Bound ${targetKey} to ${key}`); } updateReverseMapping(); element.style.boxShadow = ''; isRebinding = false; rebindElement = null; document.removeEventListener('keydown', captureKey); }; document.addEventListener('keydown', captureKey); }); }); resetBtn.addEventListener('click', () => { for (const key in keybinds) { keybinds[key] = null; const element = document.querySelector(`.florrBinds-key-value[data-key="${key}"]`); if (element) { element.textContent = '-'; element.classList.add('unbound'); } } keybinds['0'] = 'V'; keybinds['1'] = 'B'; keybinds['2'] = 'F'; keybinds['3'] = 'G'; keybinds['4'] = 'E'; keybinds['9'] = 'P'; keybinds['K'] = 'X'; for (const key in keybinds) { if (keybinds[key]) { const element = document.querySelector(`.florrBinds-key-value[data-key="${key}"]`); if (element) { element.textContent = formatKeyDisplay(keybinds[key]); element.classList.remove('unbound'); } } } updateReverseMapping(); Object.keys(keyState).forEach(key => delete keyState[key]); showStatus('All keybinds reset to default'); }); updateReverseMapping(); modal.classList.add('hidden'); showStatus('Keybinds loaded! Press Right Shift to toggle the modal.', 3000); }); })();