Survev.io UI mod v2.5

QoL features for Survev.io

  1. // ==UserScript==
  2. // @name Survev.io UI mod v2.5
  3. // @namespace http://tampermonkey.net/
  4. // @version 2024-08-31
  5. // @description QoL features for Survev.io
  6. // @author Blubbled
  7. // @match https://survev.io/
  8. // @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
  9. // @grant none
  10. // @license MIT
  11. // ==/UserScript==
  12.  
  13.  
  14. (function() {
  15.  
  16. var lastCalledTime;
  17. var fps;
  18. var frameTimes = [];
  19. var maxFrames = 100;
  20. var uncappedFPS = false;
  21. var uiElementsEnabled = true;
  22. var fpsCounterEnabled = true;
  23.  
  24. function requestAnimFrame() {
  25. if (!lastCalledTime) {
  26. lastCalledTime = Date.now();
  27. fps = 0;
  28. return;
  29. }
  30.  
  31. var currentTime = Date.now();
  32. var delta = (currentTime - lastCalledTime) / 1000;
  33. lastCalledTime = currentTime;
  34.  
  35. frameTimes.push(delta);
  36.  
  37. if (frameTimes.length > maxFrames) {
  38. frameTimes.shift();
  39. }
  40.  
  41. var totalTime = frameTimes.reduce((sum, time) => sum + time, 0);
  42. fps = (frameTimes.length / totalTime).toFixed(0);
  43. }
  44.  
  45. function createFPSCounter() {
  46. var fpsCounter = document.createElement('div');
  47. fpsCounter.id = 'fps-counter';
  48. fpsCounter.style.position = 'fixed';
  49. fpsCounter.style.left = '10px';
  50. fpsCounter.style.top = '130px';
  51. fpsCounter.style.color = 'white';
  52. fpsCounter.style.fontSize = '20px';
  53. fpsCounter.style.fontWeight = 'bold';
  54. fpsCounter.style.zIndex = '1000';
  55. fpsCounter.style.backgroundColor = 'rgba(0, 0, 0, 0)';
  56. fpsCounter.style.padding = '5px';
  57. fpsCounter.style.borderRadius = '5px';
  58. document.body.appendChild(fpsCounter);
  59.  
  60. var lastUpdate = Date.now();
  61.  
  62. function updateFPSCounter() {
  63. requestAnimFrame();
  64. var now = Date.now();
  65. if (now - lastUpdate >= 1000) {
  66. fpsCounter.textContent = `FPS: ${fps}`;
  67. lastUpdate = now;
  68. }
  69.  
  70. requestAnimationFrame(updateFPSCounter);
  71. }
  72.  
  73. updateFPSCounter();
  74. }
  75.  
  76. createFPSCounter();
  77.  
  78.  
  79. function toggleFPSCounter(enabled) {
  80. var fpsCounter = document.getElementById('fps-counter');
  81. if (enabled) {
  82. fpsCounter.style.display = 'block';
  83. } else {
  84. fpsCounter.style.display = 'none';
  85. }
  86. }
  87.  
  88.  
  89. var settingsButton = document.createElement('button');
  90. settingsButton.id = 'settings-button';
  91. settingsButton.textContent = 'Mod Settings';
  92. settingsButton.style.position = 'fixed';
  93. settingsButton.style.left = '10px';
  94. settingsButton.style.top = '50%';
  95. settingsButton.style.transform = 'translateY(-50%)';
  96. settingsButton.style.padding = '10px';
  97. settingsButton.style.fontSize = '18px';
  98. settingsButton.style.backgroundColor = 'rgba(0, 0, 0, 0.8)';
  99. settingsButton.style.color = 'white';
  100. settingsButton.style.zIndex = '1000';
  101. settingsButton.style.display = 'none';
  102. document.body.appendChild(settingsButton);
  103.  
  104.  
  105. var settingsTab = document.createElement('div');
  106. settingsTab.id = 'settings-tab';
  107. settingsTab.style.position = 'fixed';
  108. settingsTab.style.left = '200px';
  109. settingsTab.style.top = '50%';
  110. settingsTab.style.width = '300px';
  111. settingsTab.style.height = '200px';
  112. settingsTab.style.backgroundColor = 'rgba(0, 0, 0, 0.8)';
  113. settingsTab.style.color = 'white';
  114. settingsTab.style.padding = '20px';
  115. settingsTab.style.borderRadius = '10px';
  116. settingsTab.style.display = 'none';
  117. settingsTab.innerHTML = '<h2>Mod Settings (a little buggy, re-enable uncapped FPS to update cursor info)</h2>';
  118. document.body.appendChild(settingsTab);
  119.  
  120. var fpsCheckbox = document.createElement('input');
  121. fpsCheckbox.type = 'checkbox';
  122. fpsCheckbox.id = 'uncapped-fps-checkbox';
  123. var fpsLabel = document.createElement('label');
  124. fpsLabel.setAttribute('for', 'uncapped-fps-checkbox');
  125. fpsLabel.textContent = 'Enable Uncapped FPS';
  126. settingsTab.appendChild(fpsCheckbox);
  127. settingsTab.appendChild(fpsLabel);
  128. settingsTab.appendChild(document.createElement('br'));
  129.  
  130.  
  131. var fpsCounterCheckbox = document.createElement('input');
  132. fpsCounterCheckbox.type = 'checkbox';
  133. fpsCounterCheckbox.id = 'fps-counter-checkbox';
  134. var fpsCounterLabel = document.createElement('label');
  135. fpsCounterLabel.setAttribute('for', 'fps-counter-checkbox');
  136. fpsCounterLabel.textContent = 'Show FPS Counter';
  137. settingsTab.appendChild(fpsCounterCheckbox);
  138. settingsTab.appendChild(fpsCounterLabel);
  139. settingsTab.appendChild(document.createElement('br'));
  140.  
  141.  
  142. var uiCheckbox = document.createElement('input');
  143. uiCheckbox.type = 'checkbox';
  144. uiCheckbox.id = 'ui-elements-checkbox';
  145. var uiLabel = document.createElement('label');
  146. uiLabel.setAttribute('for', 'ui-elements-checkbox');
  147. uiLabel.textContent = 'Enable Cursor Info';
  148. settingsTab.appendChild(uiCheckbox);
  149. settingsTab.appendChild(uiLabel);
  150.  
  151. function toggleUncappedFPS(enabled) {
  152. if (enabled) {
  153. window.requestAnimationFrame = function(callback) {
  154. return setTimeout(callback, 1);
  155. };
  156. } else {
  157. window.requestAnimationFrame = function(callback) {
  158. return window.setTimeout(callback, 1000 / 60);
  159. };
  160. }
  161. }
  162.  
  163.  
  164. function toggleUIElementDisplay(enabled) {
  165. if (enabled) {
  166. console.log("UI Elements Enabled");
  167. document.body.classList.remove('ui-hidden');
  168. } else {
  169. console.log("UI Elements Disabled");
  170. document.body.classList.add('ui-hidden');
  171. }
  172. }
  173.  
  174.  
  175. var style = document.createElement('style');
  176. style.innerHTML = `
  177. .ui-hidden .your-ui-element-class {
  178. display: none;
  179. }
  180. `;
  181. document.head.appendChild(style);
  182.  
  183.  
  184. function updateSettingsButtonVisibility() {
  185. var equippedWeapon = document.querySelector('.ui-weapon-switch[style*="background-color: rgba(0, 0, 0, 0.4)"], .ui-weapon-switch[style*="opacity: 1"]');
  186. if (!equippedWeapon) {
  187. settingsButton.style.display = 'block';
  188. } else {
  189. settingsButton.style.display = 'none';
  190. settingsTab.style.display = 'none';
  191. }
  192. }
  193.  
  194.  
  195. setInterval(updateSettingsButtonVisibility, 100);
  196.  
  197.  
  198. settingsButton.addEventListener('click', function() {
  199. if (settingsTab.style.display === 'none') {
  200. settingsTab.style.display = 'block';
  201. } else {
  202. settingsTab.style.display = 'none';
  203. }
  204. });
  205.  
  206.  
  207. function loadSettings() {
  208. var uncappedFPSSetting = localStorage.getItem('uncappedFPS') === 'true';
  209. var uiElementsSetting = localStorage.getItem('uiElementsEnabled') === 'true';
  210. var fpsCounterSetting = localStorage.getItem('fpsCounterEnabled') === 'true';
  211.  
  212. fpsCheckbox.checked = uncappedFPSSetting;
  213. uiCheckbox.checked = uiElementsSetting;
  214. fpsCounterCheckbox.checked = fpsCounterSetting;
  215.  
  216. toggleUncappedFPS(uncappedFPSSetting);
  217. toggleUIElementDisplay(uiElementsSetting);
  218. toggleFPSCounter(fpsCounterSetting);
  219. }
  220.  
  221. function saveSettings() {
  222. var uncappedFPSSetting = fpsCheckbox.checked;
  223. var uiElementsSetting = uiCheckbox.checked;
  224. var fpsCounterSetting = fpsCounterCheckbox.checked;
  225.  
  226. localStorage.setItem('uncappedFPS', uncappedFPSSetting);
  227. localStorage.setItem('uiElementsEnabled', uiElementsSetting);
  228. localStorage.setItem('fpsCounterEnabled', fpsCounterSetting);
  229.  
  230. toggleUncappedFPS(uncappedFPSSetting);
  231. toggleUIElementDisplay(uiElementsSetting);
  232. toggleFPSCounter(fpsCounterSetting);
  233. }
  234.  
  235. loadSettings();
  236.  
  237.  
  238. fpsCheckbox.addEventListener('change', saveSettings);
  239. uiCheckbox.addEventListener('change', saveSettings);
  240. fpsCounterCheckbox.addEventListener('change', saveSettings);
  241.  
  242.  
  243.  
  244.  
  245.  
  246.  
  247. function periodicallyShowKillCounter() {
  248. showKillCounter();
  249. setTimeout(periodicallyShowKillCounter, 100);
  250. }
  251.  
  252. function showKillCounter() {
  253. var killCounter = document.getElementById('ui-kill-counter-wrapper');
  254. if (killCounter) {
  255. killCounter.style.display = 'block';
  256. killCounter.style.position = 'fixed';
  257. killCounter.style.left = '5px';
  258. killCounter.style.top = '-25px';
  259. killCounter.style.color = 'white';
  260. killCounter.style.fontSize = '20px';
  261. killCounter.style.fontWeight = 'bold';
  262. killCounter.style.zIndex = '1000';
  263. killCounter.style.backgroundColor = 'rgba(0, 0, 0, 0)';
  264. killCounter.style.padding = '5px';
  265. killCounter.style.borderRadius = '5px';
  266.  
  267. var counterText = killCounter.querySelector('.counter-text');
  268. if (counterText) {
  269. counterText.style.minWidth = '30px';
  270. }
  271. }
  272. }
  273.  
  274. function calculateAverageBoostWidth() {
  275. var counterLengths = [98.5, 98.5, 147.75, 49.25];
  276. var boostCounters = document.querySelectorAll('#ui-boost-counter .ui-bar-inner');
  277. var totalWidth = 0;
  278.  
  279. boostCounters.forEach(function(counter, index) {
  280. var widthPercentage = parseFloat(counter.style.width);
  281. var unitLength = counterLengths[index];
  282. totalWidth += (widthPercentage / 100) * unitLength;
  283. });
  284.  
  285. var totalUnitLength = counterLengths.reduce((a, b) => a + b, 0);
  286. var averageWidthPercentage = (totalWidth / totalUnitLength) * 100;
  287.  
  288. return averageWidthPercentage.toFixed(2) + "%";
  289. }
  290.  
  291. function toggleUIElementDisplay(enabled) {
  292. if (enabled) {
  293.  
  294. var healthBarWidthCopy = document.createElement('span');
  295. healthBarWidthCopy.id = 'health-bar-width-copy';
  296. healthBarWidthCopy.classList.add('unselectable');
  297. healthBarWidthCopy.style.position = 'fixed';
  298. healthBarWidthCopy.style.fontSize = '25px';
  299. healthBarWidthCopy.style.fontWeight = 'bold';
  300. healthBarWidthCopy.style.display = 'none';
  301.  
  302. var ammoCountCopy = document.createElement('span');
  303. ammoCountCopy.id = 'ammo-count-copy';
  304. ammoCountCopy.classList.add('unselectable');
  305. ammoCountCopy.style.position = 'fixed';
  306. ammoCountCopy.style.fontSize = '25px';
  307. ammoCountCopy.style.fontWeight = 'bold';
  308. ammoCountCopy.style.display = 'none';
  309.  
  310. var weaponNameCopy = document.createElement('span');
  311. weaponNameCopy.id = 'weapon-name-copy';
  312. weaponNameCopy.classList.add('unselectable');
  313. weaponNameCopy.style.position = 'fixed';
  314. weaponNameCopy.style.fontSize = '20px';
  315. weaponNameCopy.style.fontWeight = 'bold';
  316. weaponNameCopy.style.display = 'none';
  317.  
  318. var boostWidthCopy = document.createElement('span');
  319. boostWidthCopy.id = 'boost-width-copy';
  320. boostWidthCopy.classList.add('unselectable');
  321. boostWidthCopy.style.position = 'fixed';
  322. boostWidthCopy.style.fontSize = '20px';
  323. boostWidthCopy.style.fontWeight = 'bold';
  324. boostWidthCopy.style.color = 'orange';
  325. boostWidthCopy.style.display = 'none';
  326.  
  327. function updateHealthBarWidthCopy() {
  328. var healthBar = document.getElementById('ui-health-actual');
  329. if (healthBar && healthBar.offsetWidth > 0 && healthBar.offsetHeight > 0) {
  330. var healthBarWidth = Math.round(parseFloat(healthBar.style.width));
  331. var healthBarColor = healthBar.style.backgroundColor;
  332.  
  333. healthBarWidthCopy.textContent = healthBarWidth + "%";
  334. healthBarWidthCopy.style.color = healthBarColor;
  335. healthBarWidthCopy.style.display = 'block';
  336. } else {
  337. healthBarWidthCopy.style.display = 'none';
  338. }
  339. }
  340.  
  341. function updateAmmoCountCopy() {
  342. var ammoCountElement = document.getElementById('ui-current-clip');
  343.  
  344. if (ammoCountElement && window.getComputedStyle(ammoCountElement).display !== 'none' && parseFloat(window.getComputedStyle(ammoCountElement).opacity) > 0) {
  345. var ammoCount = ammoCountElement.textContent;
  346. ammoCountCopy.textContent = ammoCount;
  347. ammoCountCopy.style.color = ammoCountElement.style.color;
  348. ammoCountCopy.style.display = 'block';
  349. } else {
  350. ammoCountCopy.style.display = 'none';
  351. }
  352. }
  353.  
  354. function updateWeaponNameCopy() {
  355. var equippedWeapon = document.querySelector('.ui-weapon-switch[style*="background-color: rgba(0, 0, 0, 0.4)"], .ui-weapon-switch[style*="opacity: 1"]');
  356. if (equippedWeapon) {
  357. var weaponName = equippedWeapon.querySelector('.ui-weapon-name').textContent;
  358. weaponNameCopy.textContent = weaponName;
  359. weaponNameCopy.style.color = 'white';
  360. weaponNameCopy.style.display = 'block';
  361. } else {
  362. weaponNameCopy.style.display = 'none';
  363. }
  364. }
  365.  
  366. function updateBoostWidthCopy() {
  367. var boostElement = document.getElementById('ui-boost-counter');
  368. if (boostElement && window.getComputedStyle(boostElement).display !== 'none' && parseFloat(window.getComputedStyle(boostElement).opacity) > 0) {
  369. var averageBoostWidth = calculateAverageBoostWidth();
  370. boostWidthCopy.textContent = averageBoostWidth;
  371. boostWidthCopy.style.display = 'block';
  372. } else {
  373. boostWidthCopy.style.display = 'none';
  374. }
  375. }
  376.  
  377. function followCursor(event) {
  378. healthBarWidthCopy.style.left = `${event.clientX - 70}px`;
  379. healthBarWidthCopy.style.top = `${event.clientY + 25}px`;
  380.  
  381. ammoCountCopy.style.left = `${event.clientX + 40}px`;
  382. ammoCountCopy.style.top = `${event.clientY + 25}px`;
  383.  
  384. weaponNameCopy.style.left = `${event.clientX + 40}px`;
  385. weaponNameCopy.style.top = `${event.clientY + 50}px`;
  386.  
  387. boostWidthCopy.style.left = `${event.clientX - 70}px`;
  388. boostWidthCopy.style.top = `${event.clientY + 50}px`;
  389. }
  390.  
  391. document.addEventListener('mousemove', followCursor);
  392.  
  393. healthBarWidthCopy.style.webkitTouchCallout = 'none'; /* iOS Safari */
  394. healthBarWidthCopy.style.webkitUserSelect = 'none'; /* Safari */
  395. healthBarWidthCopy.style.userSelect = 'none'; /* Standard syntax */
  396.  
  397. ammoCountCopy.style.webkitTouchCallout = 'none'; /* iOS Safari */
  398. ammoCountCopy.style.webkitUserSelect = 'none'; /* Safari */
  399. ammoCountCopy.style.userSelect = 'none'; /* Standard syntax */
  400.  
  401. weaponNameCopy.style.webkitTouchCallout = 'none'; /* iOS Safari */
  402. weaponNameCopy.style.webkitUserSelect = 'none'; /* Safari */
  403. weaponNameCopy.style.userSelect = 'none'; /* Standard syntax */
  404.  
  405. boostWidthCopy.style.webkitTouchCallout = 'none'; /* iOS Safari */
  406. boostWidthCopy.style.webkitUserSelect = 'none'; /* Safari */
  407. boostWidthCopy.style.userSelect = 'none'; /* Standard syntax */
  408.  
  409. document.body.appendChild(healthBarWidthCopy);
  410. document.body.appendChild(ammoCountCopy);
  411. document.body.appendChild(weaponNameCopy);
  412. document.body.appendChild(boostWidthCopy);
  413.  
  414. updateHealthBarWidthCopy();
  415. updateAmmoCountCopy();
  416. updateWeaponNameCopy();
  417. updateBoostWidthCopy();
  418.  
  419. var healthObserver = new MutationObserver(updateHealthBarWidthCopy);
  420. var healthTargetNode = document.getElementById('ui-health-actual');
  421. if (healthTargetNode) {
  422. healthObserver.observe(healthTargetNode, { attributes: true, attributeFilter: ['style', 'class'] });
  423. }
  424. if (healthTargetNode && healthTargetNode.parentElement) {
  425. healthObserver.observe(healthTargetNode.parentElement, { attributes: true, attributeFilter: ['style', 'class'] });
  426. }
  427.  
  428. var ammoObserver = new MutationObserver(updateAmmoCountCopy);
  429. var ammoTargetNode = document.getElementById('ui-current-clip');
  430. if (ammoTargetNode) {
  431. ammoObserver.observe(ammoTargetNode, { attributes: true, childList: true, subtree: true });
  432. }
  433.  
  434. var weaponObserver = new MutationObserver(updateWeaponNameCopy);
  435. var weaponTargetNodes = document.querySelectorAll('.ui-weapon-switch');
  436. weaponTargetNodes.forEach(function(node) {
  437. weaponObserver.observe(node, { attributes: true, attributeFilter: ['style', 'class'] });
  438. });
  439.  
  440. var boostObserver = new MutationObserver(updateBoostWidthCopy);
  441. var boostTargetNodes = document.querySelectorAll('#ui-boost-counter .ui-bar-inner');
  442. boostTargetNodes.forEach(function(node) {
  443. boostObserver.observe(node, { attributes: true, attributeFilter: ['style', 'class'] });
  444. });
  445.  
  446. } else {
  447. var healthBarWidthCopy = document.getElementById('health-bar-width-copy');
  448. if (healthBarWidthCopy) {
  449. healthBarWidthCopy.parentNode.removeChild(healthBarWidthCopy);
  450. }
  451.  
  452. var ammoCountCopy = document.getElementById('ammo-count-copy');
  453. if (ammoCountCopy) {
  454. ammoCountCopy.parentNode.removeChild(ammoCountCopy);
  455. }
  456.  
  457. var weaponNameCopy = document.getElementById('weapon-name-copy');
  458. if (weaponNameCopy) {
  459. weaponNameCopy.parentNode.removeChild(weaponNameCopy);
  460. }
  461.  
  462. var boostWidthCopy = document.getElementById('boost-width-copy');
  463. if (boostWidthCopy) {
  464. boostWidthCopy.parentNode.removeChild(boostWidthCopy);
  465. }
  466. }
  467. }
  468.  
  469. toggleUIElementDisplay(true);
  470. showKillCounter();
  471. periodicallyShowKillCounter();
  472. })();