Gyropad

Simulate a gamepad with the device's gyroscope

  1. // ==UserScript==
  2. // @name Gyropad
  3. // @namespace https://github.com/aortizu/gyropad
  4. // @version 1.0.4
  5. // @description Simulate a gamepad with the device's gyroscope
  6. // @license MIT
  7. // @author Reklaw
  8. // @match https://play.geforcenow.com/*
  9. // @match https://www.xbox.com/*/play*
  10. // @match https://cloud.boosteroid.com/*
  11. // @icon https://icons.iconarchive.com/icons/paomedia/small-n-flat/72/gamepad-icon.png
  12. // @grant none
  13. // ==/UserScript==
  14. (function () {
  15. 'use strict';
  16. const MAX_ANGLE = 45;
  17. const MOVEMENT_THRESHOLD = 0.03;
  18. const DEFAULT_OPACITY = 0.3;
  19. const STICK_RADIUS = 45;
  20. const realGamepads = navigator.getGamepads.bind(navigator);
  21. const clamp = (num, min, max) => Math.max(min, Math.min(num, max));
  22. let enabled = true;
  23. let horizontal = false;
  24. let vertical = false;
  25. let controllerEnable = false;
  26. let showConfigController = false;
  27. let deadzone = 0.1;
  28. let alpha = 0.1;
  29. let smoothedX = 0;
  30. let smoothedY = 0;
  31. let isRight = true;
  32. let leftStickMoved = false;
  33. let rightStickMoved = false;
  34. let trigger = -1;
  35. let posX = 0;
  36. let posY = 0;
  37. let scale = 1;
  38. let elementSelected = null;
  39. let toggleButton = null;
  40. let opacity = DEFAULT_OPACITY;
  41. let simulatedStick = { x1: 0.0, y1: 0.0, x2: 0.0, y2: 0.0 };
  42. let simulatedGamepad = {
  43. id: "Xbox One Game Controller (STANDARD GAMEPAD)",
  44. index: 0,
  45. connected: true,
  46. mapping: "standard",
  47. buttons: Array(17).fill({ pressed: false, touched: false, value: 0 }),
  48. axes: [0,0,0,0,0,0],
  49. timestamp: 0.0,
  50. vibrationActuator: null
  51. };
  52.  
  53. const createButton = (text, styles, eventListeners) => {
  54. const button = document.createElement('button');
  55. button.textContent = text;
  56. Object.assign(button.style, styles);
  57. for (const event in eventListeners) {
  58. button.addEventListener(event, eventListeners[event]);
  59. }
  60. return button;
  61. };
  62.  
  63. function setTrigger(triggerValue) {
  64. trigger = triggerValue;
  65. enabled = trigger < 0;
  66. }
  67.  
  68. navigator.getGamepads = function () {
  69. const gamepads = realGamepads();
  70. let gamepadList = [...gamepads];
  71. if (gamepads[0] && !controllerEnable) {
  72. const gamepad = gamepads[0];
  73. toggleButton.style.background = enabled ? "#00A86B" : "#FF4D4D";
  74. simulatedGamepad.buttons = gamepad.buttons.map(btn => ({ pressed: btn.pressed, value: btn.value }));
  75. simulatedGamepad.axes = [...gamepad.axes];
  76. simulatedGamepad.timestamp = performance.now();
  77. const isUsingRightStick = isRight;
  78. const stickX = gamepad.axes[isUsingRightStick ? 2 : 0];
  79. const stickY = gamepad.axes[isUsingRightStick ? 3 : 1];
  80. const stickMoved = Math.abs(stickX) > MOVEMENT_THRESHOLD || Math.abs(stickY) > MOVEMENT_THRESHOLD;
  81. if (!stickMoved && (enabled || trigger === -1)) {
  82. simulatedGamepad.axes[isUsingRightStick ? 2 : 0] = simulatedStick[isUsingRightStick ? 'x2' : 'x1'];
  83. simulatedGamepad.axes[isUsingRightStick ? 3 : 1] = simulatedStick[isUsingRightStick ? 'y2' : 'y1'];
  84. simulatedGamepad.timestamp = performance.now();
  85. }
  86. } else if (controllerEnable) {
  87. toggleButton.style.background = enabled ? "#00A86B" : "#FF4D4D";
  88. simulatedGamepad.axes = [simulatedStick.x1, simulatedStick.y1, simulatedStick.x2, simulatedStick.y2,0,0];
  89. simulatedGamepad.timestamp = performance.now();
  90. //gamepadList[0] = simulatedGamepad;
  91. }
  92. return [simulatedGamepad];
  93. };
  94.  
  95. function gameLoop() {
  96. const gamepads = navigator.getGamepads();
  97. if (gamepads[0] && trigger !== -1 && gamepads[0].buttons[trigger]) {
  98. const currentPressed = gamepads[0].buttons[trigger].pressed;
  99. if(currentPressed){
  100. enabled = true;
  101. }else{
  102. enabled = false;
  103. }
  104. }
  105. requestAnimationFrame(gameLoop);
  106. }
  107.  
  108. function handleDeviceMotion(event) {
  109. if (!enabled) return;
  110. const smoothFactor = 1.1 - alpha;
  111. let rawX = event.rotationRate.alpha || 0;
  112. let rawY = event.rotationRate.beta || 0;
  113. rawX = Math.abs(rawX) < deadzone ? 0 : rawX;
  114. rawY = Math.abs(rawY) < deadzone ? 0 : rawY;
  115. smoothedX = smoothFactor * rawX + (1 - smoothFactor) * smoothedX;
  116. smoothedY = smoothFactor * rawY + (1 - smoothFactor) * smoothedY;
  117. let normX = clamp(smoothedX / MAX_ANGLE, -1, 1);
  118. let normY = clamp(smoothedY / MAX_ANGLE, -1, 1);
  119. normX = horizontal ? -normX : normX;
  120. normY = vertical ? -normY : normY;
  121. if (isRight) {
  122. simulatedStick.x2 = normX;
  123. simulatedStick.y2 = normY;
  124. } else {
  125. simulatedStick.x1 = normX;
  126. simulatedStick.y1 = normY;
  127. }
  128. }
  129.  
  130. if (DeviceMotionEvent && typeof DeviceMotionEvent.requestPermission === "function") {
  131. DeviceOrientationEvent.requestPermission()
  132. .then(permissionState => {
  133. if (permissionState === "granted") {
  134. window.addEventListener("devicemotion", handleDeviceMotion);
  135. } else {
  136. console.error("Permission denied to access the gyroscope");
  137. }
  138. })
  139. .catch(console.error);
  140. } else {
  141. window.addEventListener("devicemotion", handleDeviceMotion);
  142. }
  143.  
  144. gameLoop();
  145.  
  146. function createUI() {
  147. const screenWidth = window.innerWidth;
  148. const screenHeight = window.innerHeight;
  149. const containerStyles = {
  150. position: "fixed",
  151. top: "10px",
  152. right: "10px",
  153. width: "40%",
  154. padding: "10px",
  155. background: "rgba(0,0,0,0.8)",
  156. color: "white",
  157. borderRadius: "10px",
  158. fontFamily: "Arial, sans-serif",
  159. fontSize: "3vh",
  160. zIndex: "9999",
  161. textAlign: "center",
  162. boxShadow: "0px 0px 10px rgba(255,255,255,0.2)",
  163. display: "none"
  164. };
  165. const elementsStyles = {
  166. position: "relative",
  167. background: "rgba(0,0,0,0)",
  168. color: "white",
  169. fontFamily: "Arial, sans-serif",
  170. fontSize: "3vh",
  171. zIndex: "9999",
  172. textAlign: "center",
  173. maxHeight: "70vh",
  174. overflowY: "auto"
  175. };
  176. const buttonStyles = {
  177. marginTop: "10px",
  178. width: "100%",
  179. background: "#009CDA",
  180. color: "white",
  181. border: "none",
  182. padding: "5px",
  183. cursor: "pointer"
  184. };
  185. const inputStyles = {
  186. width: "100%"
  187. };
  188. const selectStyles = {
  189. width: "100%",
  190. marginTop: "5px",
  191. backgroundColor: "#444",
  192. color: "white"
  193. };
  194.  
  195. let uiContainer = document.createElement("div");
  196. uiContainer.id = "gamepad-ui";
  197. Object.assign(uiContainer.style, containerStyles);
  198.  
  199. let uiElements = document.createElement("div");
  200. uiElements.id = "elements-ui";
  201. Object.assign(uiElements.style, elementsStyles);
  202.  
  203. let closeButton = createButton("❌", {
  204. fontSize: "4vh",
  205. textAlign: "left",
  206. paddingLeft: "10px",
  207. width: "100%",
  208. background: "#00000000",
  209. color: "white",
  210. border: "none",
  211. cursor: "pointer"
  212. }, {
  213. click: () => {
  214. uiContainer.style.display = "none";
  215. }
  216. });
  217.  
  218. let sensitivityLabel = document.createElement("label");
  219. sensitivityLabel.textContent = "Smoth Factor: " + Math.round((alpha * 10));
  220. sensitivityLabel.style.marginTop = "10px";
  221. let sensitivityInput = document.createElement("input");
  222. sensitivityInput.type = "range";
  223. sensitivityInput.min = "0.1";
  224. sensitivityInput.max = "1";
  225. sensitivityInput.step = "0.1";
  226. sensitivityInput.value = alpha;
  227. Object.assign(sensitivityInput.style, inputStyles);
  228. sensitivityInput.oninput = function () {
  229. alpha = parseFloat(this.value);
  230. sensitivityLabel.textContent = "Smoth Factor: " + Math.round(alpha * 10);
  231. };
  232.  
  233. let deadzoneLabel = document.createElement("label");
  234. deadzoneLabel.textContent = "Dead Zone: " + deadzone * 10;
  235. let deadzoneInput = document.createElement("input");
  236. deadzoneInput.type = "range";
  237. deadzoneInput.min = "0.1";
  238. deadzoneInput.max = "1";
  239. deadzoneInput.step = "0.1";
  240. deadzoneInput.value = deadzone;
  241. Object.assign(deadzoneInput.style, inputStyles);
  242. deadzoneInput.oninput = function () {
  243. deadzone = parseFloat(this.value);
  244. deadzoneLabel.textContent = "Dead Zone: " + deadzone * 10;
  245. };
  246.  
  247. let stickButton = createButton(isRight ? "Use Right Stick 🕹️➡️" : "Use Left Stick 🕹️⬅️", buttonStyles, {
  248. click: () => {
  249. isRight = !isRight;
  250. stickButton.textContent = isRight ? "Use Right Stick 🕹️➡️" : "Use Left Stick 🕹️⬅️";
  251. }
  252. });
  253.  
  254. let horizontalButton = createButton(
  255. `Reverse Horizontal: ${horizontal ? "ON 🔃↕️✅" : "OFF 🔃↕️🚫"}`,
  256. { ...buttonStyles, background: horizontal ? "#00A86B" : "#FF4D4D" },
  257. {
  258. click: () => {
  259. horizontal = !horizontal;
  260. horizontalButton.textContent = `Reverse Horizontal: ${horizontal ? "ON 🔃↕️✅" : "OFF 🔃↕️🚫"}`;
  261. horizontalButton.style.background = horizontal ? "#00A86B" : "#FF4D4D";
  262. }
  263. }
  264. );
  265.  
  266. let verticalButton = createButton(
  267. `Reverse Vertical: ${vertical ? "ON 🔃↔️✅" : "OFF 🔃↔️🚫"}`,
  268. { ...buttonStyles, background: vertical ? "#00A86B" : "#FF4D4D", marginBottom: "10px" },
  269. {
  270. click: () => {
  271. vertical = !vertical;
  272. verticalButton.textContent = `Reverse Vertical: ${vertical ? "ON 🔃↔️✅" : "OFF 🔃↔️🚫"}`;
  273. verticalButton.style.background = vertical ? "#00A86B" : "#FF4D4D";
  274. }
  275. }
  276. );
  277.  
  278. let buttonLabel = document.createElement("label");
  279. buttonLabel.textContent = "Trigger Button:";
  280. let triggerSelect = document.createElement("select");
  281. Object.assign(triggerSelect.style, selectStyles);
  282. let buttonNames = [
  283. "🚫-DISABLED-🚫", "A", "B", "X", "Y", "LB", "RB", "LT", "RT", "SELECT", "START", "LS", "RS",
  284. "DPAD UP", "DPAD DOWN", "DPAD LEFT", "DPAD RIGHT",
  285. ];
  286.  
  287. buttonNames.forEach((btnName, index) => {
  288. let option = document.createElement("option");
  289. option.value = index;
  290. option.textContent = btnName;
  291. triggerSelect.appendChild(option);
  292. });
  293.  
  294. triggerSelect.onchange = function () {
  295. let value = parseInt(this.value);
  296. setTrigger(value - 1);
  297. enabled = trigger === -1 ? true : false;
  298. toggleButton.style.background = enabled ? "#00A86B" : "#FF4D4D";
  299. };
  300.  
  301. let toggleEnabledButton = createButton(
  302. enabled ? "Disable Gyroscope 🚫" : "Activate Gyroscope 🔄",
  303. { ...buttonStyles, background: enabled ? "#FF4D4D" : "#00A86B", marginBottom: "10px" },
  304. {
  305. click: () => {
  306. enabled = !enabled;
  307. toggleEnabledButton.textContent = enabled ? "Disable Gyroscope 🚫" : "Activate Gyroscope 🔄";
  308. toggleEnabledButton.style.background = enabled ? "#FF4D4D" : "#00A86B";
  309. sensitivityLabel.style.display = enabled ? "block" : "none";
  310. sensitivityInput.style.display = enabled ? "block" : "none";
  311. deadzoneLabel.style.display = enabled ? "block" : "none";
  312. deadzoneInput.style.display = enabled ? "block" : "none";
  313. stickButton.style.display = enabled ? "block" : "none";
  314. horizontalButton.style.display = enabled ? "block" : "none";
  315. verticalButton.style.display = enabled ? "block" : "none";
  316. buttonLabel.style.display = enabled ? "block" : "none";
  317. triggerSelect.style.display = enabled ? "block" : "none";
  318. }
  319. }
  320. );
  321.  
  322. let enableController = createButton(
  323. controllerEnable ? "Disable Virtual Controller 🎮" : "Activate Virtual Controller 🎮",
  324. { ...buttonStyles, background: controllerEnable ? "#00A86B" : "#FF4D4D" },
  325. {
  326. click: () => {
  327. if (screen.orientation.type.includes('landscape')) {
  328. let gamepads = realGamepads();
  329. if (gamepads[0]) {
  330. showToast("There Is A Real Gamepad Connected 🎮 So Virtual Gamepad is Not Available 🚫");
  331. } else {
  332. controllerEnable = !controllerEnable;
  333. if (controllerEnable) {
  334. if (window.location.hostname === "cloud.boosteroid.com") {
  335. const event = new Event("gamepadconnected");
  336. Object.defineProperty(event, "gamepad", { value: simulatedGamepad, configurable: true });
  337. window.dispatchEvent(event);
  338. }
  339. showControls();
  340. } else {
  341. if (window.location.hostname === "cloud.boosteroid.com") {
  342. const event = new Event("gamepaddisconnected");
  343. Object.defineProperty(event, "gamepad", { value: simulatedGamepad, configurable: true });
  344. window.dispatchEvent(event);
  345. }
  346. hideControls();
  347. }
  348. }
  349. } else {
  350. showToast("Only In Landscape Orientation");
  351. }
  352. }
  353. }
  354. );
  355.  
  356. // configController Button to toggle "enabled"
  357. let configController = createButton(
  358. showConfigController ? "Close Controller Config ⚙️" : "Open Controller Config ⚙️",
  359. { ...buttonStyles, background: "#009CDA", display: "none" },
  360. {
  361. click: () => {
  362. showConfigController = !showConfigController;
  363. configController.textContent = showConfigController ? "Close Controller Config ⚙️" : "Open Controller Config ⚙️";
  364. uiControllerContainer.style.display = showConfigController ? "block" : "none";
  365. }
  366. }
  367. );
  368.  
  369. const createVirtualButton = (text, color, bottom, right, eventListeners) => {
  370. const button = createButton(text, {
  371. background: "#444",
  372. opacity: DEFAULT_OPACITY,
  373. position: "fixed",
  374. width: "10vh",
  375. height: "10vh",
  376. borderRadius: "50%",
  377. border: "none",
  378. color: color,
  379. fontSize: "3vh",
  380. fontFamily: "Arial, sans-serif",
  381. userSelect: "none",
  382. display: "none",
  383. bottom: bottom,
  384. right: right,
  385. textAlign: "center",
  386. zIndex: "9000",
  387. }, eventListeners);
  388.  
  389. const touchStartHandler = (e) => {
  390. e.preventDefault();
  391. button.style.filter = "brightness(150%)";
  392. button.style.transform = "scale(0.95)";
  393. button.style.transition = "all 0.1s";
  394. const buttonIndex = getButtonIndex(text);
  395. simulatedGamepad.buttons[buttonIndex] = { pressed: true, touched: true, value: 1 };
  396. simulatedGamepad.timestamp = performance.now();
  397. if (trigger === buttonIndex) {
  398. enabled = true;
  399. }
  400. };
  401.  
  402. const touchEndHandler = (e) => {
  403. e.preventDefault();
  404. button.style.filter = "brightness(100%)";
  405. button.style.transform = "scale(1)";
  406. const buttonIndex = getButtonIndex(text);
  407. simulatedGamepad.buttons[buttonIndex] = { pressed: false, touched: false, value: 0 };
  408. simulatedGamepad.timestamp = performance.now();
  409. if (trigger === buttonIndex) {
  410. enabled = false;
  411. }
  412. };
  413.  
  414. button.addEventListener('touchstart', touchStartHandler);
  415. button.addEventListener('touchend', touchEndHandler);
  416.  
  417. return button;
  418. };
  419.  
  420. const getButtonIndex = (buttonText) => {
  421. switch (buttonText) {
  422. case "A": return 0;
  423. case "B": return 1;
  424. case "X": return 2;
  425. case "Y": return 3;
  426. case "LB": return 4;
  427. case "RB": return 5;
  428. case "LT": return 6;
  429. case "RT": return 7;
  430. case "SELECT": return 8;
  431. case "START": return 9;
  432. case "L3": return 10;
  433. case "R3": return 11;
  434. case "↑": return 12;
  435. case "↓": return 13;
  436. case "←": return 14;
  437. case "→": return 15;
  438. default: return -1;
  439. }
  440. };
  441.  
  442. let buttonA = createVirtualButton("A", "#0F0", "25vh", "20vh");
  443. let buttonB = createVirtualButton("B", "#F00", "35vh", "10vh");
  444. let buttonX = createVirtualButton("X", "#00F", "35vh", "30vh");
  445. let buttonY = createVirtualButton("Y", "#FF0", "45vh", "20vh");
  446. let down = createVirtualButton("↓", "#FFF", "25vh", null, null);
  447. down.style.left = "20vh";
  448. down.style.borderRadius = "10%";
  449. let right = createVirtualButton("→", "#FFF", "35vh", null, null);
  450. right.style.left = "30vh";
  451. right.style.borderRadius = "10%";
  452. let left = createVirtualButton("←", "#FFF", "35vh", null, null);
  453. left.style.left = "10vh";
  454. left.style.borderRadius = "10%";
  455. let up = createVirtualButton("↑", "#FFF", "45vh", null, null);
  456. up.style.left = "20vh";
  457. up.style.borderRadius = "10%";
  458. let lt = createVirtualButton("LT", "#FFF", "89vh", null, null);
  459. lt.style.width = "15vw";
  460. lt.style.left = "3vw";
  461. lt.style.borderRadius = "10%";
  462. let rt = createVirtualButton("RT", "#FFF", "89vh", null, null);
  463. rt.style.width = "15vw";
  464. rt.style.right = "3vw";
  465. rt.style.borderRadius = "10%";
  466. let lb = createVirtualButton("LB", "#FFF", "75vh", null, null);
  467. lb.style.width = "15vw";
  468. lb.style.left = "3vw";
  469. lb.style.borderRadius = "10%";
  470. let rb = createVirtualButton("RB", "#FFF", "75vh", null, null);
  471. rb.style.width = "15vw";
  472. rb.style.right = "3vw";
  473. rb.style.borderRadius = "10%";
  474. let start = createVirtualButton("START", "#FFF", "8vh", null, null);
  475. start.style.width = "8vw";
  476. start.style.right = "40vw";
  477. start.style.height = "4vh";
  478. start.style.borderRadius = "10%";
  479. let select = createVirtualButton("SELECT", "#FFF", "8vh", null, null);
  480. select.style.width = "8vw";
  481. select.style.left = "40vw";
  482. select.style.height = "4vh";
  483. select.style.borderRadius = "10%";
  484. let l3 = createVirtualButton("L3", "#FFF", "8vh", null, null);
  485. l3.style.left = "10vh";
  486. l3.style.borderRadius = "10%";
  487. let r3 = createVirtualButton("R3", "#FFF", "8vh", null, null);
  488. r3.style.right = "10vh";
  489. r3.style.borderRadius = "10%";
  490.  
  491. const createStickContainer = (isLeft) => {
  492. const container = document.createElement('div');
  493. container.style.position = "fixed";
  494. container.style.width = "12vw";
  495. container.style.height = "12vw";
  496. container.style.background = "#444";
  497. container.style.opacity = DEFAULT_OPACITY;
  498. container.style.borderRadius = "50%";
  499. container.style.bottom = "8vh";
  500. container.style[isLeft ? 'left' : 'right'] = "20vw";
  501. container.style.display = "none";
  502. container.style.zIndex = "9000";
  503. const stick = document.createElement('div');
  504. stick.style.position = "absolute";
  505. stick.style.width = "50%";
  506. stick.style.height = "50%";
  507. stick.style.background = "#fff";
  508. stick.style.opacity = DEFAULT_OPACITY;
  509. stick.style.borderRadius = "50%";
  510. stick.style.display = "flex";
  511. stick.style.justifyContent = "center";
  512. stick.style.alignItems = "center";
  513. stick.style.left = "25%";
  514. stick.style.top = "25%";
  515. stick.style.transition = "transform 0.1s";
  516. stick.style.touchAction = "none";
  517. let activeTouch = null;
  518. let stickMoved = false;
  519. container.addEventListener('touchstart', (e) => {
  520. e.preventDefault();
  521. for (let touch of e.touches) {
  522. if (container.contains(touch.target)) {
  523. activeTouch = touch;
  524. break;
  525. }
  526. }
  527. stickMoved = true;
  528. container.style.filter = "brightness(150%)";
  529. container.style.transform = "scale(0.95)";
  530. container.style.transition = "all 0.1s";
  531. });
  532. container.addEventListener('touchmove', (e) => {
  533. if (!activeTouch) return;
  534. stick.style.transition = "none";
  535. const containerRect = container.getBoundingClientRect();
  536. const centerX = containerRect.width / 2;
  537. const centerY = containerRect.height / 2;
  538. const touch = Array.from(e.touches).find(t => t.identifier === activeTouch.identifier);
  539.  
  540. if (touch) {
  541. const dx = touch.clientX - containerRect.left - centerX;
  542. const dy = touch.clientY - containerRect.top - centerY;
  543.  
  544. let normX = dx / STICK_RADIUS;
  545. let normY = dy / STICK_RADIUS;
  546.  
  547. const magnitude = Math.hypot(normX, normY);
  548. if (magnitude > 1) {
  549. normX /= magnitude;
  550. normY /= magnitude;
  551. }
  552.  
  553. const realDistance = Math.hypot(dx, dy);
  554. const clampedDistance = Math.min(realDistance, STICK_RADIUS);
  555. const angle = Math.atan2(dy, dx);
  556. const dispX = Math.cos(angle) * clampedDistance;
  557. const dispY = Math.sin(angle) * clampedDistance;
  558.  
  559. stick.style.transform = `translate(${dispX}px, ${dispY}px)`;
  560. simulatedStick[isLeft ? 'x1' : 'x2'] = normX;
  561. simulatedStick[isLeft ? 'y1' : 'y2'] = normY;
  562. simulatedGamepad.timestamp = performance.now();
  563. }
  564. });
  565. container.addEventListener('touchend', (e) => {
  566. activeTouch = null;
  567. stick.style.transition = "all 0.1s";
  568. stick.style.transform = 'translate(0, 0)';
  569. container.style.filter = "brightness(100%)";
  570. container.style.transform = "scale(1)";
  571. simulatedStick[isLeft ? 'x1' : 'x2'] = 0;
  572. simulatedStick[isLeft ? 'y1' : 'y2'] = 0;
  573. simulatedGamepad.timestamp = performance.now();
  574. stickMoved = false;
  575. });
  576. container.appendChild(stick);
  577. return container;
  578. };
  579.  
  580. let stickRightContainer = createStickContainer(false);
  581. let stickLeftContainer = createStickContainer(true);
  582.  
  583. let uiControllerContainer = document.createElement("div");
  584. uiControllerContainer.id = "controls-ui";
  585. Object.assign(uiControllerContainer.style, {
  586. position: "fixed",
  587. top: "10px",
  588. left: "10px",
  589. width: "30%",
  590. padding: "10px",
  591. background: "rgba(0,0,0,0.8)",
  592. color: "white",
  593. borderRadius: "10px",
  594. fontFamily: "Arial, sans-serif",
  595. fontSize: "3vh",
  596. zIndex: "9999",
  597. textAlign: "center",
  598. boxShadow: "0px 0px 10px rgba(255,255,255,0.2)",
  599. display: "none",
  600. maxHeight: "80vh",
  601. overflowY: "auto"
  602. });
  603.  
  604. let closeControllerButton = createButton("❌", {
  605. fontSize: "4vh",
  606. textAlign: "right",
  607. paddingRight: "10px",
  608. width: "100%",
  609. background: "#0000",
  610. color: "white",
  611. border: "none",
  612. cursor: "pointer"
  613. }, {
  614. click: () => {
  615. showConfigController = !showConfigController;
  616. positionXLabel.style.display = "none";
  617. positionYLabel.style.display = "none";
  618. positionXInput.style.display = "none";
  619. positionYInput.style.display = "none";
  620. sizeLabel.style.display = "none";
  621. sizeInput.style.display = "none";
  622. if (elementSelected) {
  623. elementSelected.style.border = "none";
  624. elementSelected.style.opacity = opacity;
  625. elementSelected = null;
  626. }
  627. uiControllerContainer.style.display = "none";
  628. }
  629. });
  630.  
  631. let opacityLabel = document.createElement("label");
  632. opacityLabel.textContent = "Opacity: " + Math.round((opacity * 100));
  633. let opacityInput = document.createElement("input");
  634. opacityInput.type = "range";
  635. opacityInput.min = "0";
  636. opacityInput.max = "1";
  637. opacityInput.step = "0.01";
  638. opacityInput.value = opacity;
  639. Object.assign(opacityInput.style, inputStyles);
  640. opacityInput.oninput = function () {
  641. opacity = parseFloat(this.value);
  642. updateOpacity();
  643. opacityLabel.textContent = "Opacity: " + Math.round(opacity * 100);
  644. };
  645.  
  646. let elementLabel = document.createElement("label");
  647. elementLabel.textContent = "Select Element To Modify:";
  648. let elementSelect = document.createElement("select");
  649. Object.assign(elementSelect.style, selectStyles);
  650. let elementsNames = [
  651. "🚫-NONE-🚫", "A", "B", "X", "Y", "LB", "RB", "LT", "RT", "SELECT", "START", "LS", "RS",
  652. "DPAD UP", "DPAD DOWN", "DPAD LEFT", "DPAD RIGHT", "RIGHT STICK", "LEFT STICK"
  653. ];
  654. elementsNames.forEach((btnName, index) => {
  655. let option = document.createElement("option");
  656. option.value = index;
  657. option.textContent = btnName;
  658. elementSelect.appendChild(option);
  659. });
  660. elementSelect.onchange = function () {
  661. let value = parseInt(this.value);
  662. let index = value - 1;
  663. if (index == -1) {
  664. positionXLabel.style.display = "none";
  665. positionYLabel.style.display = "none";
  666. positionXInput.style.display = "none";
  667. positionYInput.style.display = "none";
  668. sizeLabel.style.display = "none";
  669. sizeInput.style.display = "none";
  670. if (elementSelected) {
  671. elementSelected.style.border = "none";
  672. elementSelected.style.opacity = opacity;
  673. elementSelected = null;
  674. }
  675. } else {
  676. uiContainer.style.display = "none";
  677. elementSelected = selectElement(index);
  678. elementSelected.style.border = "3px solid red";
  679. elementSelected.style.opacity = 1;
  680. posX = Math.round(elementSelected.getBoundingClientRect().x);
  681. posY = Math.round(elementSelected.getBoundingClientRect().y);
  682. positionXInput.value = posX;
  683. positionYInput.value = posY;
  684. positionXLabel.textContent = "Position In X: " + posX;
  685. positionYLabel.textContent = "Position In Y: " + posY;
  686. positionXLabel.style.display = "block";
  687. positionYLabel.style.display = "block";
  688. positionXInput.style.display = "block";
  689. positionYInput.style.display = "block";
  690. sizeLabel.style.display = "block";
  691. sizeInput.style.display = "block";
  692. }
  693. };
  694.  
  695. let positionXLabel = document.createElement("label");
  696. positionXLabel.textContent = "Position In X: " + posX;
  697. positionXLabel.style.marginTop = "10px";
  698. positionXLabel.style.display = "none";
  699. let positionXInput = document.createElement("input");
  700. positionXInput.type = "range";
  701. positionXInput.min = "0";
  702. positionXInput.max = screenWidth;
  703. positionXInput.step = "1";
  704. positionXInput.value = posX;
  705. positionXInput.style.display = "none";
  706. positionXInput.style.width = "100%";
  707. positionXInput.oninput = function () {
  708. posX = parseFloat(this.value);
  709. elementSelected.style.removeProperty("left");
  710. elementSelected.style.removeProperty("right");
  711. elementSelected.style.left = `${posX}px`;
  712. positionXLabel.textContent = "Position In X: " + posX;
  713. };
  714.  
  715. let positionYLabel = document.createElement("label");
  716. positionYLabel.textContent = "Position In Y: " + posY;
  717. positionYLabel.style.marginTop = "10px";
  718. positionYLabel.style.display = "none";
  719. let positionYInput = document.createElement("input");
  720. positionYInput.type = "range";
  721. positionYInput.min = "0";
  722. positionYInput.max = screenHeight;
  723. positionYInput.step = "1";
  724. positionYInput.value = posY;
  725. positionYInput.style.display = "none";
  726. positionYInput.style.width = "100%";
  727. positionYInput.oninput = function () {
  728. posY = parseFloat(this.value);
  729. elementSelected.style.removeProperty("top");
  730. elementSelected.style.removeProperty("down");
  731. elementSelected.style.top = `${posY}px`;
  732. positionYLabel.textContent = "Position In Y: " + posY;
  733. };
  734.  
  735. let sizeLabel = document.createElement("label");
  736. sizeLabel.textContent = "Scale: " + scale;
  737. sizeLabel.style.marginTop = "10px";
  738. sizeLabel.style.display = "none";
  739. let sizeInput = document.createElement("input");
  740. sizeInput.type = "range";
  741. sizeInput.min = "0.5";
  742. sizeInput.max = "3";
  743. sizeInput.step = "0.1";
  744. sizeInput.value = scale;
  745. sizeInput.style.display = "none";
  746. sizeInput.style.width = "100%";
  747. sizeInput.oninput = function () {
  748. scale = parseFloat(this.value);
  749. elementSelected.style.removeProperty("top");
  750. elementSelected.style.removeProperty("down");
  751. elementSelected.style.transform = `scale(${scale})`;
  752. sizeLabel.textContent = "Scale: " + scale;
  753. };
  754.  
  755. toggleButton = document.createElement("button");
  756. toggleButton.textContent = "✜";
  757. toggleButton.style.position = "fixed";
  758. toggleButton.style.fontSize = "5vh";
  759. toggleButton.style.textAlign = "center";
  760. toggleButton.style.fontFamily = "Arial, sans-serif";
  761. toggleButton.style.top = "10%";
  762. toggleButton.style.right = "0vw";
  763. toggleButton.style.background = enabled ? "#00A86B" : "#FF4D4D";
  764. toggleButton.style.opacity = 0.5;
  765. toggleButton.style.color = "#FFF8";
  766. toggleButton.style.border = "none";
  767. toggleButton.style.borderRadius = "50%";
  768. toggleButton.style.width = "8vh";
  769. toggleButton.style.height = "8vh";
  770. toggleButton.style.zIndex = "10000";
  771.  
  772. function showControls() {
  773. controllerEnable = true;
  774. enableController.textContent = controllerEnable ? "Disable Virtual Controller 🎮" : "Activate Virtual Controller 🎮";
  775. enableController.style.background = controllerEnable ? "#00A86B" : "#FF4D4D";
  776. configController.style.display = "block";
  777. buttonA.style.display = "block";
  778. buttonB.style.display = "block";
  779. buttonX.style.display = "block";
  780. buttonY.style.display = "block";
  781. up.style.display = "block";
  782. down.style.display = "block";
  783. left.style.display = "block";
  784. right.style.display = "block";
  785. lt.style.display = "block";
  786. rt.style.display = "block";
  787. lb.style.display = "block";
  788. rb.style.display = "block";
  789. l3.style.display = "block";
  790. r3.style.display = "block";
  791. select.style.display = "block";
  792. start.style.display = "block";
  793. stickLeftContainer.style.display = "block";
  794. stickRightContainer.style.display = "block";
  795. }
  796.  
  797. function hideControls() {
  798. controllerEnable = false;
  799. enableController.textContent = controllerEnable ? "Disable Virtual Controller 🎮" : "Activate Virtual Controller 🎮";
  800. enableController.style.background = controllerEnable ? "#00A86B" : "#FF4D4D";
  801. configController.style.display = "none";
  802. uiControllerContainer.style.display = "none";
  803. buttonA.style.display = "none";
  804. buttonB.style.display = "none";
  805. buttonX.style.display = "none";
  806. buttonY.style.display = "none";
  807. up.style.display = "none";
  808. down.style.display = "none";
  809. left.style.display = "none";
  810. right.style.display = "none";
  811. lt.style.display = "none";
  812. rt.style.display = "none";
  813. lb.style.display = "none";
  814. rb.style.display = "none";
  815. l3.style.display = "none";
  816. r3.style.display = "none";
  817. select.style.display = "none";
  818. start.style.display = "none";
  819. stickLeftContainer.style.display = "none";
  820. stickRightContainer.style.display = "none";
  821. }
  822.  
  823. function updateOpacity() {
  824. buttonA.style.opacity = opacity;
  825. buttonB.style.opacity = opacity;
  826. buttonX.style.opacity = opacity;
  827. buttonY.style.opacity = opacity;
  828. up.style.opacity = opacity;
  829. down.style.opacity = opacity;
  830. left.style.opacity = opacity;
  831. right.style.opacity = opacity;
  832. lt.style.opacity = opacity;
  833. rt.style.opacity = opacity;
  834. lb.style.opacity = opacity;
  835. rb.style.opacity = opacity;
  836. l3.style.opacity = opacity;
  837. r3.style.opacity = opacity;
  838. select.style.opacity = opacity;
  839. start.style.opacity = opacity;
  840. stickLeftContainer.style.opacity = opacity;
  841. stickRightContainer.style.opacity = opacity;
  842. toggleButton.style.opacity = opacity;
  843. }
  844.  
  845. function selectElement(element) {
  846. switch (element) {
  847. case 0:
  848. return buttonA;
  849. case 1:
  850. return buttonB;
  851. case 2:
  852. return buttonX;
  853. case 3:
  854. return buttonY;
  855. case 4:
  856. return lb;
  857. case 5:
  858. return rb;
  859. case 6:
  860. return lt;
  861. case 7:
  862. return rt;
  863. case 8:
  864. return select;
  865. case 9:
  866. return start;
  867. case 10:
  868. return l3;
  869. case 11:
  870. return r3;
  871. case 12:
  872. return up;
  873. case 13:
  874. return down;
  875. case 14:
  876. return left;
  877. case 15:
  878. return right;
  879. case 16:
  880. return stickRightContainer;
  881. case 17:
  882. return stickLeftContainer;
  883. }
  884. }
  885.  
  886. function showToast(message) {
  887. const toast = document.createElement("div");
  888. toast.textContent = message;
  889. toast.style.position = "fixed";
  890. toast.style.textAlign = "center";
  891. toast.style.bottom = "20px";
  892. toast.style.left = "50%";
  893. toast.style.transform = "translateX(-50%)";
  894. toast.style.background = "rgba(0, 0, 0, 0.8)";
  895. toast.style.color = "white";
  896. toast.style.padding = "12px 20px";
  897. toast.style.borderRadius = "8px";
  898. toast.style.fontSize = "3vh";
  899. toast.style.boxShadow = "0px 4px 10px rgba(0, 0, 0, 0.3)";
  900. toast.style.zIndex = "9999";
  901. toast.style.opacity = "0";
  902. toast.style.transition = "opacity 0.5s ease-in-out";
  903. document.body.appendChild(toast);
  904. setTimeout(() => {
  905. toast.style.opacity = "1";
  906. }, 100);
  907. setTimeout(() => {
  908. toast.style.opacity = "0";
  909. setTimeout(() => {
  910. toast.remove();
  911. }, 500);
  912. }, 5000);
  913.  
  914. }
  915.  
  916. screen.orientation.addEventListener("change", function () {
  917. if (screen.orientation.type.includes('portrait')) {
  918. hideControls();
  919. }
  920. });
  921.  
  922. toggleButton.ontouchstart = function (event) {
  923. event.preventDefault();
  924. let touch = event.touches[0];
  925. let rect = toggleButton.getBoundingClientRect();
  926. let shiftX = touch.clientX - rect.left;
  927. let shiftY = touch.clientY - rect.top;
  928. let startX = touch.clientX;
  929. let startY = touch.clientY;
  930. let moved = false;
  931. function moveAt(clientX, clientY) {
  932. toggleButton.style.left = clientX - shiftX + "px";
  933. toggleButton.style.top = clientY - shiftY + "px";
  934. }
  935. function onTouchMove(event) {
  936. let currentTouch = event.touches[0];
  937. moveAt(currentTouch.clientX, currentTouch.clientY);
  938. if (Math.abs(currentTouch.clientX - startX) > 30 ||
  939. Math.abs(currentTouch.clientY - startY) > 30) {
  940. moved = true;
  941. }
  942. }
  943. function onTouchEnd() {
  944. document.removeEventListener("touchmove", onTouchMove);
  945. document.removeEventListener("touchend", onTouchEnd);
  946. if (!moved) {
  947. uiContainer.style.display = "block";
  948. }
  949. }
  950. document.addEventListener("touchmove", onTouchMove);
  951. document.addEventListener("touchend", onTouchEnd);
  952. };
  953.  
  954. document.addEventListener("fullscreenchange", () => {
  955. let fullscreenElement = document.getElementById("fullscreen-container") ? document.getElementById("fullscreen-container") : document.getElementById("StreamHud");
  956. if (fullscreenElement) {
  957. fullscreenElement.appendChild(toggleButton);
  958. fullscreenElement.appendChild(uiContainer);
  959. fullscreenElement.appendChild(buttonA);
  960. fullscreenElement.appendChild(buttonB);
  961. fullscreenElement.appendChild(buttonX);
  962. fullscreenElement.appendChild(buttonY);
  963. fullscreenElement.appendChild(up);
  964. fullscreenElement.appendChild(down);
  965. fullscreenElement.appendChild(right);
  966. fullscreenElement.appendChild(left);
  967. fullscreenElement.appendChild(lt);
  968. fullscreenElement.appendChild(rt);
  969. fullscreenElement.appendChild(lb);
  970. fullscreenElement.appendChild(rb);
  971. fullscreenElement.appendChild(l3);
  972. fullscreenElement.appendChild(r3);
  973. fullscreenElement.appendChild(select);
  974. fullscreenElement.appendChild(start);
  975. fullscreenElement.appendChild(stickLeftContainer);
  976. fullscreenElement.appendChild(stickRightContainer);
  977. fullscreenElement.appendChild(uiControllerContainer);
  978. }
  979. });
  980.  
  981. uiContainer.appendChild(closeButton);
  982. uiContainer.appendChild(uiElements);
  983. uiElements.appendChild(enableController);
  984. uiElements.appendChild(configController);
  985. uiElements.appendChild(toggleEnabledButton);
  986. uiElements.appendChild(sensitivityLabel);
  987. uiElements.appendChild(sensitivityInput);
  988. uiElements.appendChild(deadzoneLabel);
  989. uiElements.appendChild(deadzoneInput);
  990. uiElements.appendChild(stickButton);
  991. uiElements.appendChild(horizontalButton);
  992. uiElements.appendChild(verticalButton);
  993. uiElements.appendChild(buttonLabel);
  994. uiElements.appendChild(triggerSelect);
  995. uiControllerContainer.appendChild(closeControllerButton);
  996. uiControllerContainer.appendChild(opacityLabel);
  997. uiControllerContainer.appendChild(opacityInput);
  998. uiControllerContainer.appendChild(elementLabel);
  999. uiControllerContainer.appendChild(elementSelect);
  1000. uiControllerContainer.appendChild(positionXLabel);
  1001. uiControllerContainer.appendChild(positionXInput);
  1002. uiControllerContainer.appendChild(positionYLabel);
  1003. uiControllerContainer.appendChild(positionYInput);
  1004. uiControllerContainer.appendChild(sizeLabel);
  1005. uiControllerContainer.appendChild(sizeInput);
  1006. document.body.appendChild(buttonA);
  1007. document.body.appendChild(buttonB);
  1008. document.body.appendChild(buttonX);
  1009. document.body.appendChild(buttonY);
  1010. document.body.appendChild(up);
  1011. document.body.appendChild(down);
  1012. document.body.appendChild(right);
  1013. document.body.appendChild(left);
  1014. document.body.appendChild(lt);
  1015. document.body.appendChild(rt);
  1016. document.body.appendChild(lb);
  1017. document.body.appendChild(rb);
  1018. document.body.appendChild(l3);
  1019. document.body.appendChild(r3);
  1020. document.body.appendChild(select);
  1021. document.body.appendChild(start);
  1022. document.body.appendChild(stickLeftContainer);
  1023. document.body.appendChild(stickRightContainer);
  1024. document.body.appendChild(toggleButton);
  1025. document.body.appendChild(uiContainer);
  1026. document.body.appendChild(uiControllerContainer);
  1027. }
  1028. createUI();
  1029. })();