Survev.io UI mod

QoL features for Survev.io

  1. // ==UserScript==
  2. // @name Survev.io UI mod
  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. (function() {
  14.  
  15.  
  16. var lastCalledTime;
  17. var fps;
  18. var frames = 0;
  19.  
  20. function requestAnimFrame() {
  21. if (!lastCalledTime) {
  22. lastCalledTime = Date.now();
  23. fps = 0;
  24. return;
  25. }
  26. frames++;
  27. var delta = (Date.now() - lastCalledTime) / 1000;
  28. lastCalledTime = Date.now();
  29. fps = (1 / delta).toFixed(0);
  30. }
  31.  
  32. function createFPSCounter() {
  33. var fpsCounter = document.createElement('div');
  34. fpsCounter.id = 'fps-counter';
  35. fpsCounter.style.position = 'fixed';
  36. fpsCounter.style.left = '100px';
  37. fpsCounter.style.top = '10px';
  38. fpsCounter.style.color = 'white';
  39. fpsCounter.style.fontSize = '20px';
  40. fpsCounter.style.fontWeight = 'bold';
  41. fpsCounter.style.zIndex = '1000';
  42. fpsCounter.style.backgroundColor = 'rgba(0, 0, 0, 0)';
  43. fpsCounter.style.padding = '5px';
  44. fpsCounter.style.borderRadius = '5px';
  45. document.body.appendChild(fpsCounter);
  46.  
  47. var lastUpdate = Date.now();
  48.  
  49. function updateFPSCounter() {
  50. requestAnimFrame();
  51. var now = Date.now();
  52. if (now - lastUpdate >= 1000) {
  53. fpsCounter.textContent = `FPS: ${fps}`;
  54. lastUpdate = now;
  55. frames = 0;
  56. }
  57.  
  58. requestAnimationFrame(updateFPSCounter);
  59. }
  60.  
  61. updateFPSCounter();
  62. }
  63.  
  64. createFPSCounter();
  65.  
  66.  
  67.  
  68.  
  69.  
  70. function toggleUncappedFPS(enabled) {
  71. window.requestAnimationFrame = function(callback) {
  72. return setTimeout(callback, 1);
  73. };
  74. }
  75.  
  76. function periodicallyShowKillCounter() {
  77. showKillCounter();
  78. setTimeout(periodicallyShowKillCounter, 100);
  79. }
  80.  
  81. function showKillCounter() {
  82. var killCounter = document.getElementById('ui-kill-counter-wrapper');
  83. if (killCounter) {
  84. killCounter.style.display = 'block';
  85. killCounter.style.position = 'fixed';
  86. killCounter.style.left = '5px';
  87. killCounter.style.top = '-25px';
  88. killCounter.style.color = 'white';
  89. killCounter.style.fontSize = '20px';
  90. killCounter.style.fontWeight = 'bold';
  91. killCounter.style.zIndex = '1000';
  92. killCounter.style.backgroundColor = 'rgba(0, 0, 0, 0)';
  93. killCounter.style.padding = '5px';
  94. killCounter.style.borderRadius = '5px';
  95.  
  96. var counterText = killCounter.querySelector('.counter-text');
  97. if (counterText) {
  98. counterText.style.minWidth = '30px';
  99. }
  100. }
  101. }
  102.  
  103. function calculateAverageBoostWidth() {
  104. var counterLengths = [98.5, 98.5, 147.75, 49.25];
  105. var boostCounters = document.querySelectorAll('#ui-boost-counter .ui-bar-inner');
  106. var totalWidth = 0;
  107.  
  108. boostCounters.forEach(function(counter, index) {
  109. var widthPercentage = parseFloat(counter.style.width);
  110. var unitLength = counterLengths[index];
  111. totalWidth += (widthPercentage / 100) * unitLength;
  112. });
  113.  
  114. var totalUnitLength = counterLengths.reduce((a, b) => a + b, 0);
  115. var averageWidthPercentage = (totalWidth / totalUnitLength) * 100;
  116.  
  117. return averageWidthPercentage.toFixed(2) + "%";
  118. }
  119.  
  120. function toggleUIElementDisplay(enabled) {
  121. if (enabled) {
  122.  
  123. var healthBarWidthCopy = document.createElement('span');
  124. healthBarWidthCopy.id = 'health-bar-width-copy';
  125. healthBarWidthCopy.classList.add('unselectable');
  126. healthBarWidthCopy.style.position = 'fixed';
  127. healthBarWidthCopy.style.fontSize = '25px';
  128. healthBarWidthCopy.style.fontWeight = 'bold';
  129. healthBarWidthCopy.style.display = 'none';
  130.  
  131. var ammoCountCopy = document.createElement('span');
  132. ammoCountCopy.id = 'ammo-count-copy';
  133. ammoCountCopy.classList.add('unselectable');
  134. ammoCountCopy.style.position = 'fixed';
  135. ammoCountCopy.style.fontSize = '25px';
  136. ammoCountCopy.style.fontWeight = 'bold';
  137. ammoCountCopy.style.display = 'none';
  138.  
  139. var weaponNameCopy = document.createElement('span');
  140. weaponNameCopy.id = 'weapon-name-copy';
  141. weaponNameCopy.classList.add('unselectable');
  142. weaponNameCopy.style.position = 'fixed';
  143. weaponNameCopy.style.fontSize = '20px';
  144. weaponNameCopy.style.fontWeight = 'bold';
  145. weaponNameCopy.style.display = 'none';
  146.  
  147. var boostWidthCopy = document.createElement('span');
  148. boostWidthCopy.id = 'boost-width-copy';
  149. boostWidthCopy.classList.add('unselectable');
  150. boostWidthCopy.style.position = 'fixed';
  151. boostWidthCopy.style.fontSize = '20px';
  152. boostWidthCopy.style.fontWeight = 'bold';
  153. boostWidthCopy.style.color = 'orange';
  154. boostWidthCopy.style.display = 'none';
  155.  
  156. function updateHealthBarWidthCopy() {
  157. var healthBar = document.getElementById('ui-health-actual');
  158. if (healthBar && healthBar.offsetWidth > 0 && healthBar.offsetHeight > 0) {
  159. var healthBarWidth = Math.round(parseFloat(healthBar.style.width));
  160. var healthBarColor = healthBar.style.backgroundColor;
  161.  
  162. healthBarWidthCopy.textContent = healthBarWidth + "%";
  163. healthBarWidthCopy.style.color = healthBarColor;
  164. healthBarWidthCopy.style.display = 'block';
  165. } else {
  166. healthBarWidthCopy.style.display = 'none';
  167. }
  168. }
  169.  
  170. function updateAmmoCountCopy() {
  171. var ammoCountElement = document.getElementById('ui-current-clip');
  172.  
  173. if (ammoCountElement && window.getComputedStyle(ammoCountElement).display !== 'none' && parseFloat(window.getComputedStyle(ammoCountElement).opacity) > 0) {
  174. var ammoCount = ammoCountElement.textContent;
  175. ammoCountCopy.textContent = ammoCount;
  176. ammoCountCopy.style.color = ammoCountElement.style.color;
  177. ammoCountCopy.style.display = 'block';
  178. } else {
  179. ammoCountCopy.style.display = 'none';
  180. }
  181. }
  182.  
  183. function updateWeaponNameCopy() {
  184. var equippedWeapon = document.querySelector('.ui-weapon-switch[style*="background-color: rgba(0, 0, 0, 0.4)"], .ui-weapon-switch[style*="opacity: 1"]');
  185. if (equippedWeapon) {
  186. var weaponName = equippedWeapon.querySelector('.ui-weapon-name').textContent;
  187. weaponNameCopy.textContent = weaponName;
  188. weaponNameCopy.style.color = 'white';
  189. weaponNameCopy.style.display = 'block';
  190. } else {
  191. weaponNameCopy.style.display = 'none';
  192. }
  193. }
  194.  
  195. function updateBoostWidthCopy() {
  196. var boostElement = document.getElementById('ui-boost-counter');
  197. if (boostElement && window.getComputedStyle(boostElement).display !== 'none' && parseFloat(window.getComputedStyle(boostElement).opacity) > 0) {
  198. var averageBoostWidth = calculateAverageBoostWidth();
  199. boostWidthCopy.textContent = averageBoostWidth;
  200. boostWidthCopy.style.display = 'block';
  201. } else {
  202. boostWidthCopy.style.display = 'none';
  203. }
  204. }
  205.  
  206. function followCursor(event) {
  207. healthBarWidthCopy.style.left = `${event.clientX - 70}px`;
  208. healthBarWidthCopy.style.top = `${event.clientY + 25}px`;
  209.  
  210. ammoCountCopy.style.left = `${event.clientX + 40}px`;
  211. ammoCountCopy.style.top = `${event.clientY + 25}px`;
  212.  
  213. weaponNameCopy.style.left = `${event.clientX + 40}px`;
  214. weaponNameCopy.style.top = `${event.clientY + 50}px`;
  215.  
  216. boostWidthCopy.style.left = `${event.clientX - 70}px`;
  217. boostWidthCopy.style.top = `${event.clientY + 50}px`;
  218. }
  219.  
  220. document.addEventListener('mousemove', followCursor);
  221.  
  222. healthBarWidthCopy.style.webkitTouchCallout = 'none'; /* iOS Safari */
  223. healthBarWidthCopy.style.webkitUserSelect = 'none'; /* Safari */
  224. healthBarWidthCopy.style.userSelect = 'none'; /* Standard syntax */
  225.  
  226. ammoCountCopy.style.webkitTouchCallout = 'none'; /* iOS Safari */
  227. ammoCountCopy.style.webkitUserSelect = 'none'; /* Safari */
  228. ammoCountCopy.style.userSelect = 'none'; /* Standard syntax */
  229.  
  230. weaponNameCopy.style.webkitTouchCallout = 'none'; /* iOS Safari */
  231. weaponNameCopy.style.webkitUserSelect = 'none'; /* Safari */
  232. weaponNameCopy.style.userSelect = 'none'; /* Standard syntax */
  233.  
  234. boostWidthCopy.style.webkitTouchCallout = 'none'; /* iOS Safari */
  235. boostWidthCopy.style.webkitUserSelect = 'none'; /* Safari */
  236. boostWidthCopy.style.userSelect = 'none'; /* Standard syntax */
  237.  
  238. document.body.appendChild(healthBarWidthCopy);
  239. document.body.appendChild(ammoCountCopy);
  240. document.body.appendChild(weaponNameCopy);
  241. document.body.appendChild(boostWidthCopy);
  242.  
  243. updateHealthBarWidthCopy();
  244. updateAmmoCountCopy();
  245. updateWeaponNameCopy();
  246. updateBoostWidthCopy();
  247.  
  248. var healthObserver = new MutationObserver(updateHealthBarWidthCopy);
  249. var healthTargetNode = document.getElementById('ui-health-actual');
  250. if (healthTargetNode) {
  251. healthObserver.observe(healthTargetNode, { attributes: true, attributeFilter: ['style', 'class'] });
  252. }
  253. if (healthTargetNode && healthTargetNode.parentElement) {
  254. healthObserver.observe(healthTargetNode.parentElement, { attributes: true, attributeFilter: ['style', 'class'] });
  255. }
  256.  
  257. var ammoObserver = new MutationObserver(updateAmmoCountCopy);
  258. var ammoTargetNode = document.getElementById('ui-current-clip');
  259. if (ammoTargetNode) {
  260. ammoObserver.observe(ammoTargetNode, { attributes: true, childList: true, subtree: true });
  261. }
  262.  
  263. var weaponObserver = new MutationObserver(updateWeaponNameCopy);
  264. var weaponTargetNodes = document.querySelectorAll('.ui-weapon-switch');
  265. weaponTargetNodes.forEach(function(node) {
  266. weaponObserver.observe(node, { attributes: true, attributeFilter: ['style', 'class'] });
  267. });
  268.  
  269. var boostObserver = new MutationObserver(updateBoostWidthCopy);
  270. var boostTargetNodes = document.querySelectorAll('#ui-boost-counter .ui-bar-inner');
  271. boostTargetNodes.forEach(function(node) {
  272. boostObserver.observe(node, { attributes: true, attributeFilter: ['style', 'class'] });
  273. });
  274.  
  275. } else {
  276. var healthBarWidthCopy = document.getElementById('health-bar-width-copy');
  277. if (healthBarWidthCopy) {
  278. healthBarWidthCopy.parentNode.removeChild(healthBarWidthCopy);
  279. }
  280.  
  281. var ammoCountCopy = document.getElementById('ammo-count-copy');
  282. if (ammoCountCopy) {
  283. ammoCountCopy.parentNode.removeChild(ammoCountCopy);
  284. }
  285.  
  286. var weaponNameCopy = document.getElementById('weapon-name-copy');
  287. if (weaponNameCopy) {
  288. weaponNameCopy.parentNode.removeChild(weaponNameCopy);
  289. }
  290.  
  291. var boostWidthCopy = document.getElementById('boost-width-copy');
  292. if (boostWidthCopy) {
  293. boostWidthCopy.parentNode.removeChild(boostWidthCopy);
  294. }
  295. }
  296. }
  297.  
  298. toggleUIElementDisplay(true);
  299. showKillCounter();
  300. periodicallyShowKillCounter();
  301. })();