Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- (function() {
- // --- Configuration ---
- const MOUSE_SENSITIVITY_X = 0.015;
- const MOUSE_SENSITIVITY_Y = 0.015;
- const ADS_SENSITIVITY_MULTIPLIER = 0.85;
- const STATE_POLLING_RATE_MS = 16; // Polling interval in milliseconds (~62.5Hz)
- const CONNECTION_DISPATCH_RATE_MS = 100;
- const AXIS_DEADZONE = 0.05;
- // --- Anti-Recoil Configuration ---
- const FIRE_BUTTON_INDEX = 7; // RT (Right Trigger)
- const ENABLE_ANTI_RECOIL = true;
- const ANTI_RECOIL_STRENGTH = 0.008; // Positive value pulls view DOWN
- // --- ADS Button Configuration (Right Mouse Button maps to this) ---
- const ADS_BUTTON_INDEX = 6; // LT (Left Trigger)
- // --- Strafe-Based Aim Assist Macro (Q Key) Configuration ---
- const ENABLE_Q_KEY_AIM_ASSIST_MACRO = true;
- const Q_KEY_AIM_ASSIST_STRENGTH = 0.05; // Absolute addition to stick axis (e.g., 0.05 is 5% push)
- // --- Mouse Look Smoothing Configuration ---
- const ENABLE_MOUSE_LOOK_SMOOTHING = false; // Default to false for raw input
- const MOUSE_LOOK_SMOOTHING_ALPHA = 0.8; // If true, higher alpha (0.1-0.9) = more responsive, less smooth
- // --- Xbox Controller Response Curve ---
- const RESPONSE_CURVE_EXPONENT = 1.2;
- // NEW: Option to disable response curve for mouse-driven look axes for a more linear feel
- const APPLY_RESPONSE_CURVE_TO_LOOK_AXES = false; // Set to true to apply curve like physical sticks
- const keyMap = {
- 'w': { t: 'a', a: 1, v: -1 }, 'a': { t: 'a', a: 0, v: -1 },
- 's': { t: 'a', a: 1, v: 1 }, 'd': { t: 'a', a: 0, v: 1 },
- ' ': { t: 'b', i: 0 }, 'shift': { t: 'b', i: 10 }, 'control': { t: 'b', i: 11 },
- 'r': { t: 'b', i: 2 }, 'e': { t: 'b', i: 3 }, 'escape': { t: 'b', i: 9 },
- 'tab': { t: 'b', i: 8 }, 'z': { t: 'b', i: FIRE_BUTTON_INDEX },
- 'f': { t: 'b', i: 5 }, 'c': { t: 'b', i: 4 }, 'g': { t: 'b', i: 1 },
- };
- const mouseMap = {
- 0: { t: 'b', i: FIRE_BUTTON_INDEX },
- 2: { t: 'b', i: ADS_BUTTON_INDEX }
- };
- const controllerState = {
- axes: [0.0, 0.0, 0.0, 0.0], // LStickX, LStickY, RStickX, RStickY
- buttons: Array(17).fill(false).map((_, i) => ({ pressed: false, touched: false, value: 0.0, index: i })),
- axisKeyPresses: { 0: { neg: false, pos: false }, 1: { neg: false, pos: false } }, // For WASD
- mouseDeltaX: 0, mouseDeltaY: 0,
- isConnected: true, timestamp: performance.now(),
- isAimAssistMacroActive: false,
- smoothedAxes: [0.0, 0.0, 0.0, 0.0], // Used if ENABLE_MOUSE_LOOK_SMOOTHING is true
- };
- let pointerLocked = false;
- let stateIntervalId = null;
- let connectionIntervalId = null;
- let gameAreaElement = null;
- const originalGetGamepads = navigator.getGamepads?.bind(navigator); // Store original for cleanup
- // Applies deadzone and response curve
- function applyResponseCurveAndDeadzone(value) {
- if (Math.abs(value) < AXIS_DEADZONE) return 0.0;
- const sign = Math.sign(value);
- const normalizedValue = (Math.abs(value) - AXIS_DEADZONE) / (1.0 - AXIS_DEADZONE);
- return sign * Math.pow(normalizedValue, RESPONSE_CURVE_EXPONENT);
- }
- // Creates the virtual gamepad object that the browser and game will see
- function createGamepadObject() {
- const processedAxes = controllerState.axes.map((val, index) => {
- if (index < 2) { // Movement axes (0: Left Stick X, 1: Left Stick Y)
- return applyResponseCurveAndDeadzone(val);
- } else { // Look axes (2: Right Stick X, 3: Right Stick Y) - driven by mouse
- if (APPLY_RESPONSE_CURVE_TO_LOOK_AXES) {
- return applyResponseCurveAndDeadzone(val);
- }
- // If not applying curve, just ensure it's within deadzone or pass through
- // The deadzone for mouse input is less conventional but kept if curve is off
- return Math.abs(val) < AXIS_DEADZONE ? 0.0 : val;
- }
- });
- return {
- axes: processedAxes,
- buttons: controllerState.buttons,
- connected: controllerState.isConnected,
- id: "Xbox Controller (XInput STANDARD GAMEPAD)",
- index: 0, mapping: "standard", timestamp: controllerState.timestamp,
- };
- }
- // Main loop for updating gamepad state based on KBM input
- function updateAndSimulateGamepad() {
- let currentSensX = MOUSE_SENSITIVITY_X;
- let currentSensY = MOUSE_SENSITIVITY_Y;
- const isADS = controllerState.buttons[ADS_BUTTON_INDEX].pressed;
- const isFiring = controllerState.buttons[FIRE_BUTTON_INDEX].pressed;
- if (isADS) {
- currentSensX *= ADS_SENSITIVITY_MULTIPLIER;
- currentSensY *= ADS_SENSITIVITY_MULTIPLIER;
- }
- let finalTargetRX = 0; // Calculated Right Stick X (horizontal look)
- let finalTargetRY = 0; // Calculated Right Stick Y (vertical look)
- if (pointerLocked) {
- // 1. Base mouse input scaled by sensitivity
- let mouseInputX = controllerState.mouseDeltaX * currentSensX;
- let mouseInputY = controllerState.mouseDeltaY * currentSensY;
- finalTargetRX = mouseInputX;
- finalTargetRY = mouseInputY;
- // 2. Apply Q-Key Strafe Aim Assist Macro
- if (ENABLE_Q_KEY_AIM_ASSIST_MACRO && controllerState.isAimAssistMacroActive) {
- const isStrafingLeft = controllerState.axisKeyPresses[0].neg;
- const isStrafingRight = controllerState.axisKeyPresses[0].pos;
- if (isStrafingLeft) finalTargetRX += Q_KEY_AIM_ASSIST_STRENGTH;
- else if (isStrafingRight) finalTargetRX -= Q_KEY_AIM_ASSIST_STRENGTH;
- }
- // 3. Apply Anti-recoil (only when firing AND NOT ADSing with RMB)
- if (ENABLE_ANTI_RECOIL && isFiring && !isADS) {
- finalTargetRY += ANTI_RECOIL_STRENGTH; // Positive strength pulls view DOWN
- }
- controllerState.mouseDeltaX = 0; // Reset accumulated mouse deltas
- controllerState.mouseDeltaY = 0;
- } else {
- finalTargetRX = 0; finalTargetRY = 0;
- controllerState.mouseDeltaX = 0; controllerState.mouseDeltaY = 0;
- }
- // 4. Apply smoothing to look axes if enabled
- if (ENABLE_MOUSE_LOOK_SMOOTHING) {
- controllerState.smoothedAxes[2] = controllerState.smoothedAxes[2] * (1.0 - MOUSE_LOOK_SMOOTHING_ALPHA) + finalTargetRX * MOUSE_LOOK_SMOOTHING_ALPHA;
- controllerState.smoothedAxes[3] = controllerState.smoothedAxes[3] * (1.0 - MOUSE_LOOK_SMOOTHING_ALPHA) + finalTargetRY * MOUSE_LOOK_SMOOTHING_ALPHA;
- controllerState.axes[2] = Math.max(-1, Math.min(1, controllerState.smoothedAxes[2]));
- controllerState.axes[3] = Math.max(-1, Math.min(1, controllerState.smoothedAxes[3]));
- } else {
- // Direct assignment after all modifications, with clamping
- controllerState.axes[2] = Math.max(-1, Math.min(1, finalTargetRX));
- controllerState.axes[3] = Math.max(-1, Math.min(1, finalTargetRY));
- }
- controllerState.timestamp = performance.now();
- // Defensive check: Ensure our getGamepads override is still in place
- if (typeof navigator.getGamepads !== 'function' || navigator.getGamepads.toString().indexOf("createGamepadObject") === -1) {
- console.warn("KBM Mapper: navigator.getGamepads was overwritten by something else. Re-applying override.");
- navigator.getGamepads = function() { return [createGamepadObject(), null, null, null]; };
- }
- }
- // --- Event Handlers ---
- function handleKeyDown(event) {
- const key = event.key.toLowerCase();
- if (key === 'q') {
- if (!controllerState.isAimAssistMacroActive) controllerState.isAimAssistMacroActive = true;
- event.preventDefault(); event.stopPropagation(); // Consume 'q' so it doesn't type if not pointer locked
- }
- const mappedKey = (key === 'controlleft' || key === 'controlright') ? 'control' : key;
- const mapping = keyMap[mappedKey];
- if (!mapping) return;
- event.preventDefault(); event.stopPropagation();
- if (mapping.t === 'b') {
- const button = controllerState.buttons[mapping.i];
- if (!button.pressed) { button.pressed = true; button.touched = true; button.value = 1.0; }
- } else if (mapping.t === 'a') {
- const axisState = controllerState.axisKeyPresses[mapping.a];
- if (mapping.v < 0) axisState.neg = true; else axisState.pos = true;
- if (axisState.neg && axisState.pos) controllerState.axes[mapping.a] = 0;
- else controllerState.axes[mapping.a] = axisState.neg ? -1 : (axisState.pos ? 1 : 0);
- }
- }
- function handleKeyUp(event) {
- const key = event.key.toLowerCase();
- if (key === 'q') {
- if (controllerState.isAimAssistMacroActive) controllerState.isAimAssistMacroActive = false;
- event.preventDefault(); event.stopPropagation();
- }
- const mappedKey = (key === 'controlleft' || key === 'controlright') ? 'control' : key;
- const mapping = keyMap[mappedKey];
- if (!mapping) return;
- event.preventDefault(); event.stopPropagation();
- if (mapping.t === 'b') {
- const button = controllerState.buttons[mapping.i];
- if (button.pressed) { button.pressed = false; button.touched = false; button.value = 0.0; }
- } else if (mapping.t === 'a') {
- const axisState = controllerState.axisKeyPresses[mapping.a];
- if (mapping.v < 0) axisState.neg = false; else axisState.pos = false;
- if (axisState.neg) controllerState.axes[mapping.a] = -1;
- else if (axisState.pos) controllerState.axes[mapping.a] = 1;
- else controllerState.axes[mapping.a] = 0;
- }
- }
- function handleMouseDown(event) {
- const mapping = mouseMap[event.button];
- if (!mapping || (!pointerLocked && event.target !== gameAreaElement)) return;
- event.preventDefault(); event.stopPropagation();
- if (mapping.t === 'b') {
- const button = controllerState.buttons[mapping.i];
- if (!button.pressed) { button.pressed = true; button.touched = true; button.value = 1.0; }
- }
- }
- function handleMouseUp(event) {
- const mapping = mouseMap[event.button];
- if (!mapping) return;
- event.preventDefault(); event.stopPropagation();
- if (mapping.t === 'b') {
- const button = controllerState.buttons[mapping.i];
- if (button.pressed) { button.pressed = false; button.touched = false; button.value = 0.0; }
- }
- }
- function handleMouseMove(event) {
- if (!pointerLocked) return;
- controllerState.mouseDeltaX += event.movementX || 0;
- controllerState.mouseDeltaY += event.movementY || 0;
- }
- function handlePointerLockChange() {
- const lockedElement = document.pointerLockElement;
- if (lockedElement === gameAreaElement) {
- pointerLocked = true;
- } else {
- pointerLocked = false;
- // Reset look axes and related states
- controllerState.axes[2] = 0; controllerState.axes[3] = 0;
- controllerState.smoothedAxes[2] = 0; controllerState.smoothedAxes[3] = 0;
- controllerState.mouseDeltaX = 0; controllerState.mouseDeltaY = 0;
- controllerState.isAimAssistMacroActive = false; // Reset Q key state
- // Reset any pressed mouse buttons
- Object.values(mouseMap).forEach(mapping => {
- if (mapping.t === 'b' && controllerState.buttons[mapping.i].pressed) {
- const button = controllerState.buttons[mapping.i];
- button.pressed = false; button.touched = false; button.value = 0.0;
- }
- });
- }
- }
- function requestPointerLock() {
- if (!gameAreaElement) { console.warn("KBM Mapper: Game area element not found."); return; }
- if (document.pointerLockElement !== gameAreaElement) {
- gameAreaElement.requestPointerLock().catch(e => { /* console.warn("KBM Mapper: Pointer lock request failed.", e); */ });
- }
- }
- // Dispatches the 'gamepadconnected' event for games that listen for it
- function dispatchConnectionEvent() {
- try {
- const gamepad = createGamepadObject();
- const gamepadConnectedEvent = new CustomEvent('gamepadconnected', { detail: { gamepad: gamepad } });
- window.dispatchEvent(gamepadConnectedEvent);
- } catch (e) { console.error("KBM Mapper: Error dispatching connection event", e); }
- }
- // --- Initialization and Cleanup ---
- function initialize() {
- gameAreaElement = document.getElementById('game-stream') ||
- document.querySelector('video[playsinline]') ||
- document.querySelector('video') ||
- document.body;
- navigator.getGamepads = function() { return [createGamepadObject(), null, null, null]; };
- window.addEventListener('keydown', handleKeyDown, { capture: true });
- window.addEventListener('keyup', handleKeyUp, { capture: true });
- const mouseEventTarget = gameAreaElement;
- mouseEventTarget.addEventListener('mousemove', handleMouseMove, { capture: true });
- mouseEventTarget.addEventListener('mousedown', handleMouseDown, { capture: true });
- mouseEventTarget.addEventListener('mouseup', handleMouseUp, { capture: true });
- document.addEventListener('pointerlockchange', handlePointerLockChange, false);
- if (gameAreaElement) gameAreaElement.addEventListener('click', requestPointerLock);
- try { dispatchConnectionEvent(); } catch (e) {} // Initial connection dispatch
- if (stateIntervalId) clearInterval(stateIntervalId);
- stateIntervalId = setInterval(updateAndSimulateGamepad, STATE_POLLING_RATE_MS);
- if (connectionIntervalId) clearInterval(connectionIntervalId);
- connectionIntervalId = setInterval(dispatchConnectionEvent, CONNECTION_DISPATCH_RATE_MS);
- console.log(`KBM Mapper Initialized. Mouse Sens: ${MOUSE_SENSITIVITY_X}, Polling: ${STATE_POLLING_RATE_MS}ms. Look Axis Curve: ${APPLY_RESPONSE_CURVE_TO_LOOK_AXES}`);
- console.log("IMPORTANT: You might need to INCREASE your IN-GAME controller sensitivity.");
- }
- window.stopKbmMapper = function() {
- if (stateIntervalId) clearInterval(stateIntervalId);
- if (connectionIntervalId) clearInterval(connectionIntervalId);
- stateIntervalId = null; connectionIntervalId = null;
- window.removeEventListener('keydown', handleKeyDown, { capture: true });
- window.removeEventListener('keyup', handleKeyUp, { capture: true });
- if (gameAreaElement) {
- gameAreaElement.removeEventListener('mousemove', handleMouseMove, { capture: true });
- gameAreaElement.removeEventListener('mousedown', handleMouseDown, { capture: true });
- gameAreaElement.removeEventListener('mouseup', handleMouseUp, { capture: true });
- gameAreaElement.removeEventListener('click', requestPointerLock);
- }
- document.removeEventListener('pointerlockchange', handlePointerLockChange, false);
- if (document.pointerLockElement) document.exitPointerLock();
- if (originalGetGamepads) navigator.getGamepads = originalGetGamepads;
- else delete navigator.getGamepads;
- console.log("KBM Mapper: Stopped.");
- };
- if (document.readyState === 'complete' || document.readyState === 'interactive') {
- setTimeout(initialize, 100); // Delay slightly for page elements
- } else {
- document.addEventListener('DOMContentLoaded', () => setTimeout(initialize, 100));
- }
- })();
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement