Advertisement
Josiahiscool73

AA

May 24th, 2025
14
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 16.12 KB | None | 0 0
  1. (function() {
  2. // --- Configuration ---
  3. const MOUSE_SENSITIVITY_X = 0.015;
  4. const MOUSE_SENSITIVITY_Y = 0.015;
  5. const ADS_SENSITIVITY_MULTIPLIER = 0.85;
  6.  
  7. const STATE_POLLING_RATE_MS = 16; // Polling interval in milliseconds (~62.5Hz)
  8. const CONNECTION_DISPATCH_RATE_MS = 100;
  9. const AXIS_DEADZONE = 0.05;
  10.  
  11. // --- Anti-Recoil Configuration ---
  12. const FIRE_BUTTON_INDEX = 7; // RT (Right Trigger)
  13. const ENABLE_ANTI_RECOIL = true;
  14. const ANTI_RECOIL_STRENGTH = 0.008; // Positive value pulls view DOWN
  15.  
  16. // --- ADS Button Configuration (Right Mouse Button maps to this) ---
  17. const ADS_BUTTON_INDEX = 6; // LT (Left Trigger)
  18.  
  19. // --- Strafe-Based Aim Assist Macro (Q Key) Configuration ---
  20. const ENABLE_Q_KEY_AIM_ASSIST_MACRO = true;
  21. const Q_KEY_AIM_ASSIST_STRENGTH = 0.05; // Absolute addition to stick axis (e.g., 0.05 is 5% push)
  22.  
  23. // --- Mouse Look Smoothing Configuration ---
  24. const ENABLE_MOUSE_LOOK_SMOOTHING = false; // Default to false for raw input
  25. const MOUSE_LOOK_SMOOTHING_ALPHA = 0.8; // If true, higher alpha (0.1-0.9) = more responsive, less smooth
  26.  
  27. // --- Xbox Controller Response Curve ---
  28. const RESPONSE_CURVE_EXPONENT = 1.2;
  29. // NEW: Option to disable response curve for mouse-driven look axes for a more linear feel
  30. const APPLY_RESPONSE_CURVE_TO_LOOK_AXES = false; // Set to true to apply curve like physical sticks
  31.  
  32. const keyMap = {
  33. 'w': { t: 'a', a: 1, v: -1 }, 'a': { t: 'a', a: 0, v: -1 },
  34. 's': { t: 'a', a: 1, v: 1 }, 'd': { t: 'a', a: 0, v: 1 },
  35. ' ': { t: 'b', i: 0 }, 'shift': { t: 'b', i: 10 }, 'control': { t: 'b', i: 11 },
  36. 'r': { t: 'b', i: 2 }, 'e': { t: 'b', i: 3 }, 'escape': { t: 'b', i: 9 },
  37. 'tab': { t: 'b', i: 8 }, 'z': { t: 'b', i: FIRE_BUTTON_INDEX },
  38. 'f': { t: 'b', i: 5 }, 'c': { t: 'b', i: 4 }, 'g': { t: 'b', i: 1 },
  39. };
  40. const mouseMap = {
  41. 0: { t: 'b', i: FIRE_BUTTON_INDEX },
  42. 2: { t: 'b', i: ADS_BUTTON_INDEX }
  43. };
  44.  
  45. const controllerState = {
  46. axes: [0.0, 0.0, 0.0, 0.0], // LStickX, LStickY, RStickX, RStickY
  47. buttons: Array(17).fill(false).map((_, i) => ({ pressed: false, touched: false, value: 0.0, index: i })),
  48. axisKeyPresses: { 0: { neg: false, pos: false }, 1: { neg: false, pos: false } }, // For WASD
  49. mouseDeltaX: 0, mouseDeltaY: 0,
  50. isConnected: true, timestamp: performance.now(),
  51. isAimAssistMacroActive: false,
  52. smoothedAxes: [0.0, 0.0, 0.0, 0.0], // Used if ENABLE_MOUSE_LOOK_SMOOTHING is true
  53. };
  54. let pointerLocked = false;
  55. let stateIntervalId = null;
  56. let connectionIntervalId = null;
  57. let gameAreaElement = null;
  58. const originalGetGamepads = navigator.getGamepads?.bind(navigator); // Store original for cleanup
  59.  
  60. // Applies deadzone and response curve
  61. function applyResponseCurveAndDeadzone(value) {
  62. if (Math.abs(value) < AXIS_DEADZONE) return 0.0;
  63. const sign = Math.sign(value);
  64. const normalizedValue = (Math.abs(value) - AXIS_DEADZONE) / (1.0 - AXIS_DEADZONE);
  65. return sign * Math.pow(normalizedValue, RESPONSE_CURVE_EXPONENT);
  66. }
  67.  
  68. // Creates the virtual gamepad object that the browser and game will see
  69. function createGamepadObject() {
  70. const processedAxes = controllerState.axes.map((val, index) => {
  71. if (index < 2) { // Movement axes (0: Left Stick X, 1: Left Stick Y)
  72. return applyResponseCurveAndDeadzone(val);
  73. } else { // Look axes (2: Right Stick X, 3: Right Stick Y) - driven by mouse
  74. if (APPLY_RESPONSE_CURVE_TO_LOOK_AXES) {
  75. return applyResponseCurveAndDeadzone(val);
  76. }
  77. // If not applying curve, just ensure it's within deadzone or pass through
  78. // The deadzone for mouse input is less conventional but kept if curve is off
  79. return Math.abs(val) < AXIS_DEADZONE ? 0.0 : val;
  80. }
  81. });
  82. return {
  83. axes: processedAxes,
  84. buttons: controllerState.buttons,
  85. connected: controllerState.isConnected,
  86. id: "Xbox Controller (XInput STANDARD GAMEPAD)",
  87. index: 0, mapping: "standard", timestamp: controllerState.timestamp,
  88. };
  89. }
  90.  
  91. // Main loop for updating gamepad state based on KBM input
  92. function updateAndSimulateGamepad() {
  93. let currentSensX = MOUSE_SENSITIVITY_X;
  94. let currentSensY = MOUSE_SENSITIVITY_Y;
  95.  
  96. const isADS = controllerState.buttons[ADS_BUTTON_INDEX].pressed;
  97. const isFiring = controllerState.buttons[FIRE_BUTTON_INDEX].pressed;
  98.  
  99. if (isADS) {
  100. currentSensX *= ADS_SENSITIVITY_MULTIPLIER;
  101. currentSensY *= ADS_SENSITIVITY_MULTIPLIER;
  102. }
  103.  
  104. let finalTargetRX = 0; // Calculated Right Stick X (horizontal look)
  105. let finalTargetRY = 0; // Calculated Right Stick Y (vertical look)
  106.  
  107. if (pointerLocked) {
  108. // 1. Base mouse input scaled by sensitivity
  109. let mouseInputX = controllerState.mouseDeltaX * currentSensX;
  110. let mouseInputY = controllerState.mouseDeltaY * currentSensY;
  111.  
  112. finalTargetRX = mouseInputX;
  113. finalTargetRY = mouseInputY;
  114.  
  115. // 2. Apply Q-Key Strafe Aim Assist Macro
  116. if (ENABLE_Q_KEY_AIM_ASSIST_MACRO && controllerState.isAimAssistMacroActive) {
  117. const isStrafingLeft = controllerState.axisKeyPresses[0].neg;
  118. const isStrafingRight = controllerState.axisKeyPresses[0].pos;
  119.  
  120. if (isStrafingLeft) finalTargetRX += Q_KEY_AIM_ASSIST_STRENGTH;
  121. else if (isStrafingRight) finalTargetRX -= Q_KEY_AIM_ASSIST_STRENGTH;
  122. }
  123.  
  124. // 3. Apply Anti-recoil (only when firing AND NOT ADSing with RMB)
  125. if (ENABLE_ANTI_RECOIL && isFiring && !isADS) {
  126. finalTargetRY += ANTI_RECOIL_STRENGTH; // Positive strength pulls view DOWN
  127. }
  128.  
  129. controllerState.mouseDeltaX = 0; // Reset accumulated mouse deltas
  130. controllerState.mouseDeltaY = 0;
  131. } else {
  132. finalTargetRX = 0; finalTargetRY = 0;
  133. controllerState.mouseDeltaX = 0; controllerState.mouseDeltaY = 0;
  134. }
  135.  
  136. // 4. Apply smoothing to look axes if enabled
  137. if (ENABLE_MOUSE_LOOK_SMOOTHING) {
  138. controllerState.smoothedAxes[2] = controllerState.smoothedAxes[2] * (1.0 - MOUSE_LOOK_SMOOTHING_ALPHA) + finalTargetRX * MOUSE_LOOK_SMOOTHING_ALPHA;
  139. controllerState.smoothedAxes[3] = controllerState.smoothedAxes[3] * (1.0 - MOUSE_LOOK_SMOOTHING_ALPHA) + finalTargetRY * MOUSE_LOOK_SMOOTHING_ALPHA;
  140.  
  141. controllerState.axes[2] = Math.max(-1, Math.min(1, controllerState.smoothedAxes[2]));
  142. controllerState.axes[3] = Math.max(-1, Math.min(1, controllerState.smoothedAxes[3]));
  143. } else {
  144. // Direct assignment after all modifications, with clamping
  145. controllerState.axes[2] = Math.max(-1, Math.min(1, finalTargetRX));
  146. controllerState.axes[3] = Math.max(-1, Math.min(1, finalTargetRY));
  147. }
  148.  
  149. controllerState.timestamp = performance.now();
  150.  
  151. // Defensive check: Ensure our getGamepads override is still in place
  152. if (typeof navigator.getGamepads !== 'function' || navigator.getGamepads.toString().indexOf("createGamepadObject") === -1) {
  153. console.warn("KBM Mapper: navigator.getGamepads was overwritten by something else. Re-applying override.");
  154. navigator.getGamepads = function() { return [createGamepadObject(), null, null, null]; };
  155. }
  156. }
  157.  
  158. // --- Event Handlers ---
  159. function handleKeyDown(event) {
  160. const key = event.key.toLowerCase();
  161. if (key === 'q') {
  162. if (!controllerState.isAimAssistMacroActive) controllerState.isAimAssistMacroActive = true;
  163. event.preventDefault(); event.stopPropagation(); // Consume 'q' so it doesn't type if not pointer locked
  164. }
  165. const mappedKey = (key === 'controlleft' || key === 'controlright') ? 'control' : key;
  166. const mapping = keyMap[mappedKey];
  167. if (!mapping) return;
  168. event.preventDefault(); event.stopPropagation();
  169. if (mapping.t === 'b') {
  170. const button = controllerState.buttons[mapping.i];
  171. if (!button.pressed) { button.pressed = true; button.touched = true; button.value = 1.0; }
  172. } else if (mapping.t === 'a') {
  173. const axisState = controllerState.axisKeyPresses[mapping.a];
  174. if (mapping.v < 0) axisState.neg = true; else axisState.pos = true;
  175. if (axisState.neg && axisState.pos) controllerState.axes[mapping.a] = 0;
  176. else controllerState.axes[mapping.a] = axisState.neg ? -1 : (axisState.pos ? 1 : 0);
  177. }
  178. }
  179.  
  180. function handleKeyUp(event) {
  181. const key = event.key.toLowerCase();
  182. if (key === 'q') {
  183. if (controllerState.isAimAssistMacroActive) controllerState.isAimAssistMacroActive = false;
  184. event.preventDefault(); event.stopPropagation();
  185. }
  186. const mappedKey = (key === 'controlleft' || key === 'controlright') ? 'control' : key;
  187. const mapping = keyMap[mappedKey];
  188. if (!mapping) return;
  189. event.preventDefault(); event.stopPropagation();
  190. if (mapping.t === 'b') {
  191. const button = controllerState.buttons[mapping.i];
  192. if (button.pressed) { button.pressed = false; button.touched = false; button.value = 0.0; }
  193. } else if (mapping.t === 'a') {
  194. const axisState = controllerState.axisKeyPresses[mapping.a];
  195. if (mapping.v < 0) axisState.neg = false; else axisState.pos = false;
  196. if (axisState.neg) controllerState.axes[mapping.a] = -1;
  197. else if (axisState.pos) controllerState.axes[mapping.a] = 1;
  198. else controllerState.axes[mapping.a] = 0;
  199. }
  200. }
  201.  
  202. function handleMouseDown(event) {
  203. const mapping = mouseMap[event.button];
  204. if (!mapping || (!pointerLocked && event.target !== gameAreaElement)) return;
  205. event.preventDefault(); event.stopPropagation();
  206. if (mapping.t === 'b') {
  207. const button = controllerState.buttons[mapping.i];
  208. if (!button.pressed) { button.pressed = true; button.touched = true; button.value = 1.0; }
  209. }
  210. }
  211.  
  212. function handleMouseUp(event) {
  213. const mapping = mouseMap[event.button];
  214. if (!mapping) return;
  215. event.preventDefault(); event.stopPropagation();
  216. if (mapping.t === 'b') {
  217. const button = controllerState.buttons[mapping.i];
  218. if (button.pressed) { button.pressed = false; button.touched = false; button.value = 0.0; }
  219. }
  220. }
  221.  
  222. function handleMouseMove(event) {
  223. if (!pointerLocked) return;
  224. controllerState.mouseDeltaX += event.movementX || 0;
  225. controllerState.mouseDeltaY += event.movementY || 0;
  226. }
  227.  
  228. function handlePointerLockChange() {
  229. const lockedElement = document.pointerLockElement;
  230. if (lockedElement === gameAreaElement) {
  231. pointerLocked = true;
  232. } else {
  233. pointerLocked = false;
  234. // Reset look axes and related states
  235. controllerState.axes[2] = 0; controllerState.axes[3] = 0;
  236. controllerState.smoothedAxes[2] = 0; controllerState.smoothedAxes[3] = 0;
  237. controllerState.mouseDeltaX = 0; controllerState.mouseDeltaY = 0;
  238. controllerState.isAimAssistMacroActive = false; // Reset Q key state
  239. // Reset any pressed mouse buttons
  240. Object.values(mouseMap).forEach(mapping => {
  241. if (mapping.t === 'b' && controllerState.buttons[mapping.i].pressed) {
  242. const button = controllerState.buttons[mapping.i];
  243. button.pressed = false; button.touched = false; button.value = 0.0;
  244. }
  245. });
  246. }
  247. }
  248.  
  249. function requestPointerLock() {
  250. if (!gameAreaElement) { console.warn("KBM Mapper: Game area element not found."); return; }
  251. if (document.pointerLockElement !== gameAreaElement) {
  252. gameAreaElement.requestPointerLock().catch(e => { /* console.warn("KBM Mapper: Pointer lock request failed.", e); */ });
  253. }
  254. }
  255.  
  256. // Dispatches the 'gamepadconnected' event for games that listen for it
  257. function dispatchConnectionEvent() {
  258. try {
  259. const gamepad = createGamepadObject();
  260. const gamepadConnectedEvent = new CustomEvent('gamepadconnected', { detail: { gamepad: gamepad } });
  261. window.dispatchEvent(gamepadConnectedEvent);
  262. } catch (e) { console.error("KBM Mapper: Error dispatching connection event", e); }
  263. }
  264.  
  265. // --- Initialization and Cleanup ---
  266. function initialize() {
  267. gameAreaElement = document.getElementById('game-stream') ||
  268. document.querySelector('video[playsinline]') ||
  269. document.querySelector('video') ||
  270. document.body;
  271.  
  272. navigator.getGamepads = function() { return [createGamepadObject(), null, null, null]; };
  273.  
  274. window.addEventListener('keydown', handleKeyDown, { capture: true });
  275. window.addEventListener('keyup', handleKeyUp, { capture: true });
  276.  
  277. const mouseEventTarget = gameAreaElement;
  278. mouseEventTarget.addEventListener('mousemove', handleMouseMove, { capture: true });
  279. mouseEventTarget.addEventListener('mousedown', handleMouseDown, { capture: true });
  280. mouseEventTarget.addEventListener('mouseup', handleMouseUp, { capture: true });
  281. document.addEventListener('pointerlockchange', handlePointerLockChange, false);
  282.  
  283. if (gameAreaElement) gameAreaElement.addEventListener('click', requestPointerLock);
  284.  
  285. try { dispatchConnectionEvent(); } catch (e) {} // Initial connection dispatch
  286.  
  287. if (stateIntervalId) clearInterval(stateIntervalId);
  288. stateIntervalId = setInterval(updateAndSimulateGamepad, STATE_POLLING_RATE_MS);
  289.  
  290. if (connectionIntervalId) clearInterval(connectionIntervalId);
  291. connectionIntervalId = setInterval(dispatchConnectionEvent, CONNECTION_DISPATCH_RATE_MS);
  292.  
  293. 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}`);
  294. console.log("IMPORTANT: You might need to INCREASE your IN-GAME controller sensitivity.");
  295. }
  296.  
  297. window.stopKbmMapper = function() {
  298. if (stateIntervalId) clearInterval(stateIntervalId);
  299. if (connectionIntervalId) clearInterval(connectionIntervalId);
  300. stateIntervalId = null; connectionIntervalId = null;
  301.  
  302. window.removeEventListener('keydown', handleKeyDown, { capture: true });
  303. window.removeEventListener('keyup', handleKeyUp, { capture: true });
  304. if (gameAreaElement) {
  305. gameAreaElement.removeEventListener('mousemove', handleMouseMove, { capture: true });
  306. gameAreaElement.removeEventListener('mousedown', handleMouseDown, { capture: true });
  307. gameAreaElement.removeEventListener('mouseup', handleMouseUp, { capture: true });
  308. gameAreaElement.removeEventListener('click', requestPointerLock);
  309. }
  310. document.removeEventListener('pointerlockchange', handlePointerLockChange, false);
  311. if (document.pointerLockElement) document.exitPointerLock();
  312.  
  313. if (originalGetGamepads) navigator.getGamepads = originalGetGamepads;
  314. else delete navigator.getGamepads;
  315.  
  316. console.log("KBM Mapper: Stopped.");
  317. };
  318.  
  319. if (document.readyState === 'complete' || document.readyState === 'interactive') {
  320. setTimeout(initialize, 100); // Delay slightly for page elements
  321. } else {
  322. document.addEventListener('DOMContentLoaded', () => setTimeout(initialize, 100));
  323. }
  324. })();
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement