// ==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);
});
})();