Powerline Spectator Mode

Powerline.io Spectator Mode

  1. // ==UserScript==
  2. // @name Powerline Spectator Mode
  3. // @author Rumini - Discord: rumini
  4. // @description Powerline.io Spectator Mode
  5. // @version 1.0
  6. // @match *://powerline.io/*
  7. // @icon https://i.imgur.com/9k4SFr0.png
  8. // @license MIT
  9. // @grant none
  10. // @namespace https://greasyfork.org/users/1356205
  11. // ==/UserScript==
  12.  
  13. if (window.location.href === 'https://powerline.io/') {
  14. window.location.href = 'https://powerline.io/maindev.html';
  15. }
  16.  
  17. (function () {
  18. 'use strict';
  19.  
  20. let currentSpectateIndex = 0;
  21. let switcherButton;
  22. let overlayToggleButton;
  23. let isSpectating = false;
  24. let infoDiv;
  25. let updateInterval;
  26. let lastKnownLocalPlayerID = 0;
  27. let overlayVisible = true;
  28. let forceOverlayVisible = false;
  29.  
  30. function waitForGame(callback) {
  31. if (typeof network !== 'undefined' && typeof entities !== 'undefined' && typeof Snake !== 'undefined') {
  32. callback();
  33. } else {
  34. setTimeout(() => waitForGame(callback), 100);
  35. }
  36. }
  37.  
  38. function createSpectateSwitcherUI() {
  39. const container = document.createElement('div');
  40. container.id = 'spectate-switcher-container';
  41. container.style.cssText = `
  42. position: fixed;
  43. bottom: 20px;
  44. left: 50%;
  45. transform: translateX(-50%);
  46. z-index: 1000;
  47. opacity: 0;
  48. transition: opacity 0.3s ease-in-out;
  49. display: flex;
  50. align-items: center;
  51. gap: 10px;
  52. `;
  53.  
  54. switcherButton = document.createElement('button');
  55. switcherButton.id = 'spectate-switcher';
  56. switcherButton.style.cssText = `
  57. background-color: rgba(0, 58, 58, 0.4);
  58. color: #05ffff;
  59. border: 2px solid rgba(5, 255, 255, 0.5);
  60. border-radius: 10px;
  61. padding: 10px 20px;
  62. font-family: 'Arial Black', sans-serif;
  63. font-size: 16px;
  64. cursor: pointer;
  65. backdrop-filter: blur(5px);
  66. -webkit-backdrop-filter: blur(5px);
  67. box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
  68. transition: all 0.3s ease;
  69. `;
  70. switcherButton.textContent = 'Switch Spectate';
  71.  
  72. switcherButton.addEventListener('click', switchSpectatePlayer);
  73. switcherButton.addEventListener('mouseover', () => {
  74. switcherButton.style.backgroundColor = 'rgba(0, 88, 88, 0.5)';
  75. switcherButton.style.transform = 'scale(1.05)';
  76. switcherButton.style.boxShadow = '0 0 15px rgba(5, 255, 255, 0.2)';
  77. });
  78. switcherButton.addEventListener('mouseout', () => {
  79. switcherButton.style.backgroundColor = 'rgba(0, 58, 58, 0.3)';
  80. switcherButton.style.transform = 'scale(1)';
  81. switcherButton.style.boxShadow = 'none';
  82. });
  83.  
  84. overlayToggleButton = document.createElement('div');
  85. overlayToggleButton.id = 'overlay-toggle-button';
  86. overlayToggleButton.style.cssText = `
  87. width: 50px;
  88. height: 50px;
  89. border-radius: 50%;
  90. background-color: rgba(0, 58, 58, 0.4);
  91. border: 2px solid rgba(5, 255, 255, 0.5);
  92. display: flex;
  93. align-items: center;
  94. justify-content: center;
  95. cursor: pointer;
  96. transition: all 0.3s ease;
  97. `;
  98.  
  99. overlayToggleButton.innerHTML = `
  100. <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512" width="24" height="24">
  101. <path fill="#00ffff" d="M15 15C24.4 5.7 39.6 5.7 49 15l63 63L112 40c0-13.3 10.7-24 24-24s24 10.7 24 24l0 96c0 13.3-10.7 24-24 24l-96 0c-13.3 0-24-10.7-24-24s10.7-24 24-24l38.1 0L15 49C5.7 39.6 5.7 24.4 15 15zM133.5 243.9C158.6 193.6 222.7 112 320 112s161.4 81.6 186.5 131.9c3.8 7.6 3.8 16.5 0 24.2C481.4 318.4 417.3 400 320 400s-161.4-81.6-186.5-131.9c-3.8-7.6-3.8-16.5 0-24.2zM320 320a64 64 0 1 0 0-128 64 64 0 1 0 0 128zM591 15c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9l-63 63 38.1 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-96 0c-13.3 0-24-10.7-24-24l0-96c0-13.3 10.7-24 24-24s24 10.7 24 24l0 38.1 63-63zM15 497c-9.4-9.4-9.4-24.6 0-33.9l63-63L40 400c-13.3 0-24-10.7-24-24s10.7-24 24-24l96 0c13.3 0 24 10.7 24 24l0 96c0 13.3-10.7 24-24 24s-24-10.7-24-24l0-38.1L49 497c-9.4 9.4-24.6 9.4-33.9 0zm576 0l-63-63 0 38.1c0 13.3-10.7 24-24 24s-24-10.7-24-24l0-96c0-13.3 10.7-24 24-24l96 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-38.1 0 63 63c9.4 9.4 9.4 24.6 0 33.9s-24.6 9.4-33.9 0z"/>
  102. </svg>
  103. `;
  104.  
  105. overlayToggleButton.addEventListener('click', toggleOverlay);
  106. overlayToggleButton.addEventListener('mouseover', () => {
  107. overlayToggleButton.style.backgroundColor = 'rgba(0, 88, 88, 0.5)';
  108. overlayToggleButton.style.transform = 'scale(1.1)';
  109. overlayToggleButton.style.boxShadow = '0 0 15px rgba(5, 255, 255, 0.2)';
  110. });
  111. overlayToggleButton.addEventListener('mouseout', () => {
  112. overlayToggleButton.style.backgroundColor = 'rgba(0, 58, 58, 0.3)';
  113. overlayToggleButton.style.transform = 'scale(1)';
  114. overlayToggleButton.style.boxShadow = 'none';
  115. });
  116.  
  117. infoDiv = document.createElement('div');
  118. infoDiv.id = 'spectate-info';
  119. infoDiv.style.cssText = `
  120. position: fixed;
  121. bottom: 200px;
  122. right: 20px;
  123. z-index: 1000;
  124. background-color: rgba(0, 58, 58, 0.3);
  125. border: 2px solid rgba(5, 255, 255, 0.8);
  126. border-radius: 10px;
  127. padding: 0 20px 20px 20px;
  128. font-family: 'Arial', sans-serif;
  129. font-size: 14px;
  130. color: #05ffff;
  131. backdrop-filter: blur(10px);
  132. -webkit-backdrop-filter: blur(10px);
  133. box-shadow: 0 0 20px rgba(5, 255, 255, 0.5);
  134. transition: opacity 0.3s ease, box-shadow 1s ease;
  135. max-width: 400px;
  136. word-wrap: break-word;
  137. opacity: 0;
  138. `;
  139.  
  140. container.appendChild(switcherButton);
  141. container.appendChild(overlayToggleButton);
  142. document.body.appendChild(container);
  143. document.body.appendChild(infoDiv);
  144.  
  145. setInterval(updateButtonVisibility, 100);
  146. setInterval(updateInfoBoxGlow, 50);
  147.  
  148. document.addEventListener('keydown', function (event) {
  149. if (event.code === 'Space') {
  150. if (isSpectating || localPlayerID === 0) {
  151. isSpectating = false;
  152. clearInterval(updateInterval);
  153. infoDiv.style.opacity = '0';
  154. }
  155. updateButtonVisibility();
  156. }
  157. });
  158.  
  159. setOverlayVisibility(true);
  160. }
  161.  
  162. function updateButtonVisibility() {
  163. const container = document.getElementById('spectate-switcher-container');
  164.  
  165. if (localPlayerID !== 0 && lastKnownLocalPlayerID === 0) {
  166. isSpectating = false;
  167. clearInterval(updateInterval);
  168. infoDiv.style.opacity = '0';
  169. forceOverlayVisible = false;
  170. }
  171.  
  172. if (localPlayerID === 0 && lastKnownLocalPlayerID !== 0) {
  173. forceOverlayVisible = true;
  174. setOverlayVisibility(true);
  175. }
  176.  
  177. lastKnownLocalPlayerID = localPlayerID;
  178.  
  179. if (localPlayerID === 0 || isSpectating) {
  180. container.style.opacity = '1';
  181. infoDiv.style.opacity = isSpectating ? '1' : '0';
  182. } else {
  183. container.style.opacity = '0';
  184. infoDiv.style.opacity = '0';
  185. }
  186. }
  187.  
  188. function setOverlayVisibility(visible) {
  189. const overlay = document.getElementById('overlay');
  190. if (overlay) {
  191. overlayVisible = visible;
  192. overlay.style.opacity = visible ? '1' : '0';
  193. }
  194. }
  195.  
  196. function toggleOverlay() {
  197. if (forceOverlayVisible) {
  198. forceOverlayVisible = false;
  199. }
  200. setOverlayVisibility(!overlayVisible);
  201. }
  202.  
  203. function updateInfoBoxGlow() {
  204. const glowIntensity = Math.sin(Date.now() * 0.0005) * 5 + 15;
  205. infoDiv.style.boxShadow = `0 0 ${glowIntensity}px rgba(5, 255, 255, 0.7)`;
  206. }
  207.  
  208. function switchSpectatePlayer() {
  209. const keys = Object.keys(entities);
  210. if (keys.length === 0) return;
  211.  
  212. let found = false;
  213. for (let i = 0; i < keys.length; i++) {
  214. currentSpectateIndex = (currentSpectateIndex + 1) % keys.length;
  215. const entity = entities[keys[currentSpectateIndex]];
  216. if (entity instanceof Snake && entity !== localPlayer) {
  217. localPlayer = entity;
  218. camera.target = localPlayer;
  219. updateInfoDiv(entity);
  220. clearInterval(updateInterval);
  221. updateInterval = setInterval(() => updateInfoDiv(entity), 100);
  222. isSpectating = true;
  223. found = true;
  224. break;
  225. }
  226. }
  227.  
  228. if (!found) {
  229. hud.addSpecialMessage("No other snakes are close enough to spectate.");
  230. }
  231.  
  232. updateButtonVisibility();
  233. }
  234.  
  235. function updateInfoDiv(entity) {
  236. if (!entity || entity.id === 0) {
  237. infoDiv.style.opacity = '0';
  238. return;
  239. }
  240.  
  241. const entityInfo = `
  242. <h3>Spectating: ${entity.nick || '<unnamed>'}</h3>
  243. <b>ID:</b> ${entity.id}<br>
  244. <b>Current Length:</b> ${entity.curLength.toFixed(2)}<br>
  245. <b>Total Length:</b> ${entity.curLengthDst.toFixed(2)}<br>
  246. <b>Last Speed:</b> ${entity.lastSpeed.toFixed(2)}<br>
  247. <b>Client X:</b> ${entity.x.toFixed(2)}<br>
  248. <b>Client Y:</b> ${entity.y.toFixed(2)}<br>
  249. <b>Server X:</b> ${entity.lastServerX.toFixed(2)}<br>
  250. <b>Server Y:</b> ${entity.lastServerY.toFixed(2)}<br>
  251. `;
  252. infoDiv.innerHTML = entityInfo;
  253. infoDiv.style.opacity = '1';
  254. }
  255.  
  256. waitForGame(createSpectateSwitcherUI);
  257. })();