Bonk Host

Makes hosting rooms in bonk.io better

  1. // ==UserScript==
  2. // @name Bonk Host
  3. // @version 4.0
  4. // @author Salama
  5. // @description Makes hosting rooms in bonk.io better
  6. // @match https://bonk.io/gameframe-release.html
  7. // @match https://bonkisback.io/gameframe-release.html
  8. // @run-at document-start
  9. // @grant none
  10. // @supportURL https://discord.gg/Dj6usq7ww3
  11. // @namespace https://greatest.deepsurf.us/users/824888
  12. // ==/UserScript==
  13.  
  14. // for use as a userscript ensure you have Excigma's code injector userscript
  15. // https://greatest.deepsurf.us/en/scripts/433861-code-injector-bonk-io
  16.  
  17. let injector = (str) => {
  18. let newStr = str;
  19. window.bonkHost = {};
  20. window.bonkHost.playerManagement = {};
  21. window.bonkHost.freejoin = false;
  22. window.bonkHost.bans = [];
  23. window.bonkHost.inGame = false;
  24. window.bonkHost.playerManagement.canBeVisible = false;
  25. window.bonkHost.bonkCallbacks = {};
  26. window.bonkHost.playerHistory = {};
  27. window.bonkHost.playerSus = [];
  28. window.bonkHost.fig = 0;
  29. window.bonkHost.cheatDetection = false;
  30. let mapHistory = [];
  31. let mapHistoryIndex = 0;
  32.  
  33. window.bonkCommands = window.bonkCommands.concat(["/kick", "/mute", "/unmute", "/lock", "/unlock", "/balance", "/fav", "/unfav", "/curate", "/curateyes", "/curateno", "/roomname", "/roompass", "/clearroompass", "/hhelp", "/balanceall", "/start", "/freejoin", "/host", "/ban", "/bans", "/unban", "/scoreboard", "/resetpos"]);
  34.  
  35. if(!localStorage.getItem("bonkHost")) {
  36. localStorage.setItem("bonkHost", "{}");
  37. }
  38.  
  39. let hostPlayerMenuCSS = document.createElement('style');
  40. hostPlayerMenuCSS.innerHTML = `
  41. #hostPlayerMenu {
  42. background-color: #cfd8cd;
  43. width: calc(35.2vw - 400px);
  44. min-width: 154px;
  45. max-width: 200px;
  46. height: 576px;
  47. position: absolute;
  48. left: 10px;
  49. top: 60px;
  50. bottom: unset;
  51. border-radius: 7px;
  52. display: none;
  53. transition: ease-in-out 100ms;
  54. z-index: 100;
  55. overflow: visible;
  56. }
  57.  
  58. #hostPlayerMenuBox {
  59. top: 32px;
  60. height: calc(47px * 8);
  61. }
  62.  
  63. #hostPlayerMenuCollapse {
  64. position: absolute;
  65. left: 3px;
  66. top: 3px;
  67. width: 26px;
  68. height: 26px;
  69. border-radius: 2px;
  70. visibility: visible;
  71. }
  72.  
  73. #hostPlayerMenuGrab {
  74. position: absolute;
  75. right: 3px;
  76. top: 3px;
  77. width: 26px;
  78. height: 26px;
  79. border-radius: 2px;
  80. visibility: visible;
  81. cursor: grab;
  82. }
  83.  
  84. #hostPlayerMenuControls {
  85. position: absolute;
  86. bottom: 0;
  87. width: 100%;
  88. }
  89.  
  90. #hostPlayerCheatDetection {
  91. position: absolute;
  92. left: 0;
  93. top: 0;
  94. z-index: -1;
  95. background-color: #cfd8cd;
  96. width: inherit;
  97. min-width: inherit;
  98. max-width: inherit;
  99. height: inherit;
  100. border-radius: 7px;
  101. transition: ease-in-out 100ms;
  102. opacity: 70%;
  103. visibility: hidden;
  104. }
  105.  
  106. #hostPlayerCheatDetection canvas {
  107. background-color: rgb(58, 58, 58);
  108. margin-left: 5%;
  109. height: 45px;
  110. width: 95%;
  111. margin-top: 1px;
  112. margin-bottom: 1px;
  113. }
  114. #selectionWheel {
  115. width: 150px;
  116. height: 150px;
  117. position: absolute;
  118. left: 588px;
  119. top: 181px;
  120. pointer-events: none;
  121. display: none;
  122. z-index: 150;
  123. }
  124. #selectionWheelTeams {
  125. width: 150px;
  126. height: 150px;
  127. position: absolute;
  128. left: 1061px;
  129. top: 169px;
  130. pointer-events: none;
  131. display: none;
  132. z-index: 150;
  133. }
  134.  
  135. #hostPlayerMenuRestartButton {
  136. width: 100%;
  137. border-width: 0 !important;
  138. }
  139.  
  140. #newbonklobby_hostprevmap {
  141. width: 27px;
  142. height: 27px;
  143. position: absolute;
  144. top: 106px;
  145. left: 15px;
  146. }
  147.  
  148. #newbonklobby_hostnextmap {
  149. width: 27px;
  150. height: 27px;
  151. position: absolute;
  152. top: 106px;
  153. left: 48px;
  154. }
  155.  
  156. #newbonklobby_roundsinput {
  157. height: 50px !important;
  158. text-align: center !important;
  159. }
  160. `;
  161. document.getElementsByTagName('head')[0].appendChild(hostPlayerMenuCSS);
  162.  
  163. let hostPlayerMenu = document.createElement('div');
  164. document.getElementById('pagecontainer').appendChild(hostPlayerMenu);
  165. hostPlayerMenu.outerHTML = `
  166. <div class="windowShadow newbonklobby_elementcontainer" id="hostPlayerMenu">
  167. <div class="newbonklobby_boxtop newbonklobby_boxtop_classic" style="background-color: #009688;">
  168. <div onclick="window.bonkHost.playerManagement.collapse();" id="hostPlayerMenuCollapse" class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow">-</div>
  169. <span>Player List</span>
  170. <div id="hostPlayerMenuGrab" class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow">:::</div>
  171. </div>
  172. <div id="hostPlayerMenuBox" class="newbonklobby_elementcontainer"></div>
  173. <div id="hostPlayerMenuControls">
  174. <table>
  175. <tbody>
  176. <tr style="background-color: rgba(58, 58, 58, 0.07);">
  177. <td style="padding-left: 10px;" class="mapeditor_rightbox_table">
  178. Teamlock
  179. </td>
  180. <td>
  181. <input type="checkbox" id="hostPlayerMenuTeamlock">
  182. </td>
  183. </tr>
  184. <tr style="background-color: rgba(58, 58, 58, 0.07);">
  185. <td style="padding-left: 10px;" class="mapeditor_rightbox_table">
  186. Freejoin
  187. </td>
  188. <td>
  189. <input type="checkbox" id="hostPlayerMenuFreejoin">
  190. </td>
  191. </tr>
  192. <tr style="background-color: rgba(58, 58, 58, 0.07);">
  193. <td style="padding-left: 10px;" class="mapeditor_rightbox_table">
  194. Keep scores
  195. </td>
  196. <td>
  197. <input type="checkbox" id="hostPlayerMenuKeepScores">
  198. </td>
  199. </tr>
  200. <tr style="background-color: rgba(58, 58, 58, 0.07);">
  201. <td style="padding-left: 10px;" class="mapeditor_rightbox_table">
  202. Cheat detection
  203. </td>
  204. <td>
  205. <input type="checkbox" id="hostPlayerMenuCheatDetectionCheckbox">
  206. </td>
  207. </tr>
  208. <tr style="background-color: rgba(58, 58, 58, 0.07);">
  209. <td>
  210. <div style="padding-left: 10px;" class="mapeditor_rightbox_table">
  211. Keep positions
  212. </div>
  213. <div style="padding-left: 10px; font-size: 10px; display:none" class="mapeditor_rightbox_table" id="hostPlayerMenuRespawningRequiredWarning">
  214. Respawning required
  215. </div>
  216. </td>
  217. <td>
  218. <input type="checkbox" id="hostPlayerMenuKeepPositions">
  219. </td>
  220. </tr>
  221. </tbody>
  222. </table>
  223. <div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow" id="hostPlayerMenuRestartButton">
  224. RESTART GAME
  225. </div>
  226. </div>
  227. <div class="windowShadow" id="hostPlayerCheatDetection">
  228. <div class="newbonklobby_boxtop newbonklobby_boxtop_classic" style="background-color: #009688;"></div>
  229. <div id="hostPlayerMenuCheatBox" class="newbonklobby_elementcontainer">
  230. <canvas width="1" height="45" class="mapeditor_rightbox_table"></canvas>
  231. <canvas width="1" height="45" class="mapeditor_rightbox_table"></canvas>
  232. <canvas width="1" height="45" class="mapeditor_rightbox_table"></canvas>
  233. <canvas width="1" height="45" class="mapeditor_rightbox_table"></canvas>
  234. <canvas width="1" height="45" class="mapeditor_rightbox_table"></canvas>
  235. <canvas width="1" height="45" class="mapeditor_rightbox_table"></canvas>
  236. <canvas width="1" height="45" class="mapeditor_rightbox_table"></canvas>
  237. <canvas width="1" height="45" class="mapeditor_rightbox_table"></canvas>
  238. </div>
  239. <div class="mapeditor_rightbox_table" style="position:absolute;bottom:0;width:95%;margin-left:5%;text-align:justify;font-size:15;height:170px;overflow-y:scroll;">
  240. INSTRUCTIONS: The graph shows how laggy someone is compared to how laggy they actually should be. This can be used to detect lag cheats. Being over the first red line is OK. Consistently being under it can happen but it's suspicious. Anything consistent under that is usually just cheats. Random lag spikes can happen. ⚠️ indicates that the player is using a mod that is known to have related cheats.
  241. </div>
  242. </div>
  243. </div>
  244. <div id="selectionWheel">
  245. <svg width="150" height="150" viewBox="0 0 26.458 26.458"><g><path d="M22.009-13.227a8.78 8.78 0 0 1-13.17 7.605 8.78 8.78 0 0 1-4.39-7.605" transform="rotate(90)" stroke="#fff" style="stroke-linejoin: round; stroke-width: 9; fill: none; opacity: 0.5; stroke: rgb(121, 85, 72);"></path><text class="brownButton_classic" style="
  246. text-anchor: middle;
  247. font-family: 'futurept_b1';
  248. font-size: 3.17496;
  249. transform: scale(1, 1);
  250. fill: #fff;
  251. "><tspan x="4.5" y="14.4">FFA</tspan></text>
  252. </g><g style="transform: translateX(50%);transform-origin: 100% 0px;"><path d="M22.009-13.227a8.78 8.78 0 0 1-13.17 7.605 8.78 8.78 0 0 1-4.39-7.605" transform="rotate(90)" stroke="#fff" style="stroke-linejoin: round; stroke-width: 9; fill: none; opacity: 0.5; transform: rotate(-90deg); transform-origin: 75% 25%; stroke: rgb(121, 85, 72);"></path><text style="
  253. text-anchor: middle;
  254. font-family: 'futurept_b1';
  255. font-size: 3.17496;
  256. transform: scale(1, 1);
  257. fill: #fff;
  258. "><tspan x="8.8" y="14.4">SPEC</tspan></text></g></svg>
  259. </div>
  260.  
  261. <div id="selectionWheelTeams">
  262. <svg width="150" height="150" viewBox="0 0 26.458 26.458" style="
  263. "><g stroke-linejoin="round" stroke-width="9" stroke="#fff" style="transform: rotate(180deg);transform-origin: 50% 0%;"><path d="M6.126-8.066c-2.236-3.078-2.236-7.246 0-10.323" style="opacity: 0.5; stroke: rgb(121, 85, 72);"></path><path d="M6.126-18.39a8.78 8.78 0 0 1 9.816-3.19" style="stroke: rgb(255, 235, 59); opacity: 1;"></path><path d="M15.942-21.58a8.78 8.78 0 0 1 6.067 8.352" style="stroke: rgb(76, 175, 80); opacity: 0.5;"></path><path d="M22.009-13.228a8.78 8.78 0 0 1-6.067 8.352" style="stroke: rgb(33, 150, 243); opacity: 0.5;"></path><path d="M15.942-4.876a8.78 8.78 0 0 1-9.816-3.19" style="stroke: rgb(244, 67, 54); opacity: 0.5;"></path></g><text font-size="6" fill="#000" style="
  264. text-anchor: middle;
  265. font-family: 'futurept_b1';
  266. font-size: 3.17496;
  267. fill: #fff;
  268. "><tspan x="22" y="14.4">SPEC</tspan></text><text font-size="6" fill="#000" style="
  269. text-anchor: middle;
  270. font-family: 'futurept_b1';
  271. font-size: 3.17496;
  272. fill: #fff;
  273. "><tspan x="16.75" y="22.5">YELLOW</tspan></text><text font-size="6" fill="#000" style="
  274. text-anchor: middle;
  275. font-family: 'futurept_b1';
  276. font-size: 3.17496;
  277. fill: #fff;
  278. "><tspan x="16.5" y="6">RED</tspan></text><text font-size="6" fill="#000" style="
  279. text-anchor: middle;
  280. font-family: 'futurept_b1';
  281. font-size: 3.17496;
  282. fill: #fff;
  283. "><tspan x="6" y="9.5">BLUE</tspan></text><text font-size="6" fill="#000" style="
  284. text-anchor: middle;
  285. font-family: 'futurept_b1';
  286. font-size: 3.17496;
  287. fill: #fff;
  288. "><tspan x="6" y="19.5">GREEN</tspan></text></svg>
  289. </div>
  290. <div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow" id="newbonklobby_hostprevmap">&lt;</div>
  291. <div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow" id="newbonklobby_hostnextmap">&gt;</div>
  292. <!--
  293. <div id="teamshufflebutton" class="brownButton brownButton_classic buttonShadow" style="
  294. width: 16px;
  295. height: 35px;
  296. border-radius: 2px;
  297. position: absolute;
  298. top: calc(17% - 40px);
  299. right: calc(33.5% - 8px);
  300. font-family: monospace;
  301. ">
  302. <svg width="16" height="16" fill="currentColor" viewBox="0 0 16 16" style="height: 100%;transform:scaleX(-0.9);">
  303. <path fill-rule="evenodd" d="M0 3.5A.5.5 0 0 1 .5 3H1c2.202 0 3.827 1.24 4.874 2.418.49.552.865 1.102 1.126 1.532.26-.43.636-.98 1.126-1.532C9.173 4.24 10.798 3 13 3v1c-1.798 0-3.173 1.01-4.126 2.082A9.624 9.624 0 0 0 7.556 8a9.624 9.624 0 0 0 1.317 1.918C9.828 10.99 11.204 12 13 12v1c-2.202 0-3.827-1.24-4.874-2.418A10.595 10.595 0 0 1 7 9.05c-.26.43-.636.98-1.126 1.532C4.827 11.76 3.202 13 1 13H.5a.5.5 0 0 1 0-1H1c1.798 0 3.173-1.01 4.126-2.082A9.624 9.624 0 0 0 6.444 8a9.624 9.624 0 0 0-1.317-1.918C4.172 5.01 2.796 4 1 4H.5a.5.5 0 0 1-.5-.5z"></path>
  304. <path d="M13 5.466V1.534a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384l-2.36 1.966a.25.25 0 0 1-.41-.192zm0 9v-3.932a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384l-2.36 1.966a.25.25 0 0 1-.41-.192z"></path>
  305. </svg>
  306. </div>
  307. <div id="teamshufflebox" class="newbonklobby_elementcontainer" style="
  308. position: absolute;
  309. top: calc(17% - 42px);
  310. right: calc(33.5% - 10px);
  311. width: auto;
  312. height: auto;
  313. bottom: unset;
  314. outline: 3000px solid rgba(0,0,0,0.30);
  315. visibility: hidden;
  316. ">
  317. <div class="newbonklobby_boxtop newbonklobby_boxtop_classic" style="
  318. height: 39px;
  319. line-height: 39px;
  320. ">
  321. Shuffle Teams
  322. <div onclick=window.bonkHost.shuffleTeams class="newbonklobby_votewindow_votebutton brownButton brownButton_classic buttonShadow" id="newbonklobby_votewindow_votebutton_up" style="
  323. margin: 0;
  324. height: 35px;
  325. width: 25px;
  326. position: absolute;
  327. left: calc(100% - 27px);
  328. top: 2px;
  329. "></div>
  330. <div onclick="()=>{document.getElementById('teamshufflebox').style.visibility='hidden';}" class="newbonklobby_votewindow_votebutton brownButton brownButton_classic buttonShadow" id="newbonklobby_votewindow_votebutton_down" style="
  331. margin: 0;
  332. height: 35px;
  333. width: 25px;
  334. position: absolute;
  335. top: 2px;
  336. left: calc(100% - 55px);
  337. "></div>
  338. </div>
  339. <table id="mapeditor_rightbox_table_simple" class="mapeditor_rightbox_table">
  340. <tbody>
  341. <tr>
  342. <td class="mapeditor_rightbox_table_leftcell">Players per team</td>
  343. <td class="mapeditor_rightbox_table_rightcell">
  344. <div class="mapeditor_field_button fieldShadow" style="width:auto;margin:0 3;" onclick="() => {
  345. document.getElementById('teamshufflebox').value = 'AUTO';
  346. }">AUTO</div>
  347. <div class="mapeditor_field_button fieldShadow" style="margin:0 1;">-</div>
  348. <input id="teamshuffleppt" class="mapeditor_field fieldShadow" value="1" autocomplete="off" style="width:44px;">
  349. <div class="mapeditor_field_button fieldShadow" style="margin 0 1;" onclick="() => {
  350. document.getElementById('teamshuffleppt').value = parseInt(document.getElementById('teamshuffleppt').value) + 1;
  351. }">+</div>
  352. </td>
  353. </tr>
  354. <tr>
  355. <td class="mapeditor_rightbox_table_leftcell">Teams</td>
  356. <td class="mapeditor_rightbox_table_rightcell" style="
  357. display: grid;
  358. align-content: center;
  359. justify-content: center;
  360. align-items: center;
  361. grid-template-columns: 20px 25px 20px 35px;
  362. ">
  363. <input type="checkbox" id="team_shuffle_blue"><label>Blue</label>
  364. <input type="checkbox" id="team_shuffle_green"><label>Green</label>
  365. <input type="checkbox" id="team_shuffle_red"><label>Red</label>
  366. <input type="checkbox" id="team_shuffle_yellow"><label>Yellow</label>
  367. </td>
  368. </tr>
  369. <tr>
  370. <td class="mapeditor_rightbox_table_leftcell">Autoshuffle</td>
  371. <td class="mapeditor_rightbox_table_rightcell">
  372. <input type="checkbox" id="mapeditor_rightbox_table_bullet">
  373. </td>
  374. </tr>
  375. </tbody>
  376. </table>
  377. </div>
  378. `;
  379. document.getElementById("hostPlayerMenuBox").addEventListener("click", (e) => {
  380. if(e.target === document.getElementById("hostPlayerMenuBox")) {
  381. document.getElementById('newbonklobby').click();
  382. }
  383. });
  384.  
  385. document.getElementById("hostPlayerMenuRestartButton").addEventListener("click", () => {
  386. window.bonkHost.startGame();
  387. });
  388.  
  389. /*document.getElementById('newbonklobby').appendChild(document.getElementById('teamshufflebutton'));
  390. document.getElementById('newbonklobby').appendChild(document.getElementById('teamshufflebox'));
  391. document.getElementById('teamshufflebutton').addEventListener('click', () => {
  392. document.getElementById('teamshufflebox').style.visibility = document.getElementById('teamshufflebox').style.visibility == "hidden" ? "visible" : "hidden";
  393. });
  394. */
  395. document.getElementById("newbonklobby_specbutton").addEventListener("dblclick", () => {moveEveryone(0)});
  396. document.getElementById("newbonklobby_ffabutton").addEventListener("dblclick", () => {moveEveryone(1)});
  397. document.getElementById("newbonklobby_bluebutton").addEventListener("dblclick", () => {moveEveryone(3)});
  398. document.getElementById("newbonklobby_redbutton").addEventListener("dblclick", () => {moveEveryone(2)});
  399. document.getElementById("newbonklobby_greenbutton").addEventListener("dblclick", () => {moveEveryone(4)});
  400. document.getElementById("newbonklobby_yellowbutton").addEventListener("dblclick", () => {moveEveryone(5)});
  401.  
  402. document.getElementById("newbonklobby_settingsbox").appendChild(document.getElementById("newbonklobby_hostprevmap"));
  403. document.getElementById("newbonklobby_settingsbox").appendChild(document.getElementById("newbonklobby_hostnextmap"));
  404.  
  405. const updateMapHistoryButtons = () => {
  406. if(window.bonkHost.toolFunctions.getGameSettings().ga === "b") {
  407. document.getElementById("newbonklobby_hostprevmap").style.display = "";
  408. document.getElementById("newbonklobby_hostnextmap").style.display = "";
  409. if(isHost()) {
  410. document.getElementById("newbonklobby_hostprevmap").classList.toggle("brownButtonDisabled", mapHistoryIndex >= mapHistory.length - 1);
  411. document.getElementById("newbonklobby_hostnextmap").classList.toggle("brownButtonDisabled", mapHistoryIndex === 0);
  412.  
  413. // Make sure they are the topmost elements
  414. document.getElementById("newbonklobby_settingsbox").appendChild(document.getElementById("newbonklobby_hostprevmap"));
  415. document.getElementById("newbonklobby_settingsbox").appendChild(document.getElementById("newbonklobby_hostnextmap"));
  416. }
  417. else {
  418. document.getElementById("newbonklobby_hostprevmap").classList.add("brownButtonDisabled");
  419. document.getElementById("newbonklobby_hostnextmap").classList.add("brownButtonDisabled");
  420. }
  421. }
  422. else {
  423. document.getElementById("newbonklobby_hostprevmap").style.display = "none";
  424. document.getElementById("newbonklobby_hostnextmap").style.display = "none";
  425. }
  426. }
  427.  
  428. document.getElementById("newbonklobby_hostprevmap").addEventListener("click", () => {
  429. if(!isHost() || mapHistoryIndex >= mapHistory.length - 1) return;
  430. mapHistoryIndex++;
  431. let gs = window.bonkHost.toolFunctions.getGameSettings();
  432. gs.map = mapHistory[mapHistoryIndex];
  433. window.bonkHost.menuFunctions.setGameSettings(gs);
  434. window.bonkHost.menuFunctions.updateGameSettings();
  435. window.bonkHost.toolFunctions.networkEngine.sendMapAdd(gs.map);
  436. updateMapHistoryButtons();
  437. });
  438.  
  439. document.getElementById("newbonklobby_hostnextmap").addEventListener("click", () => {
  440. if(!isHost() || mapHistoryIndex == 0) return;
  441. mapHistoryIndex--;
  442. let gs = window.bonkHost.toolFunctions.getGameSettings();
  443. gs.map = mapHistory[mapHistoryIndex];
  444. window.bonkHost.menuFunctions.setGameSettings(gs);
  445. window.bonkHost.menuFunctions.updateGameSettings();
  446. window.bonkHost.toolFunctions.networkEngine.sendMapAdd(gs.map);
  447. updateMapHistoryButtons();
  448. });
  449.  
  450. document.getElementById("newbonklobby_roundsinput").addEventListener("focus", e => {
  451. e.target.value = "";
  452. });
  453. document.getElementById("newbonklobby_roundsinput").addEventListener("blur", e => {
  454. if(e.target.value == "") {
  455. e.target.value = window.bonkHost.toolFunctions.getGameSettings().wl;
  456. }
  457. });
  458.  
  459. const moveEveryone = (team) => {
  460. if(!isHost()) return;
  461. for(let id of window.bonkHost.toolFunctions.networkEngine.getConnectedPlayers()) {
  462. window.bonkHost.toolFunctions.networkEngine.changeOtherTeam(id, team);
  463. }
  464. }
  465.  
  466. const BIGVAR = newStr.match(/[A-Za-z0-9$_]+\[[0-9]{6}\]/)[0].split('[')[0];
  467.  
  468. const isHost = () => {
  469. return window.bonkHost.toolFunctions.networkEngine.getLSID() === window.bonkHost.toolFunctions.networkEngine.hostID && !window.bonkHost.toolFunctions.getGameSettings().q;
  470. }
  471.  
  472. window.bonkHost.wrap = () => {
  473. // Event for when game finishes loading. Using a setInterval isn't optimal and we might miss some, but it's good enough for now
  474. const gameLoadedWaiter = setInterval(() => {
  475. // I hope a better way of doing this exists
  476. if(window.bonkHost.menuFunctions !== undefined && Object.keys(window.bonkHost.menuFunctions).length >= 27) {
  477. clearInterval(gameLoadedWaiter);
  478. }
  479. else return;
  480.  
  481. // Wrap menuFunctions
  482. for(const i of Object.keys(window.bonkHost.menuFunctions)) {
  483. if(typeof window.bonkHost.menuFunctions[i] !== "function") continue;
  484. const ogFunc = window.bonkHost.menuFunctions[i];
  485. window.bonkHost.menuFunctions[i] = function() {
  486. let response = ogFunc.apply(window.bonkHost.menuFunctions, arguments);
  487. switch(i) {
  488. case "returnFromTesting":
  489. case "show":
  490. window.bonkHost.playerManagement.canBeVisible = false;
  491. window.bonkHost.playerManagement.hide();
  492. if(window[BIGVAR].bonkHost.state) {
  493. if(Math.max(...window[BIGVAR].bonkHost.state.scores) >= window.bonkHost.toolFunctions.getGameSettings().wl) {
  494. for(let i = 0; i < window[BIGVAR].bonkHost.state.scores.length; i++) {
  495. if(window[BIGVAR].bonkHost.state.scores === null) continue;
  496. window[BIGVAR].bonkHost.state.scores[i] = 0;
  497. }
  498. }
  499. }
  500. break;
  501. case "hide":
  502. window.bonkHost.menuFunctions.visible = true;
  503. window.bonkHost.playerManagement.canBeVisible = true;
  504. window.bonkHost.playerManagement.show();
  505. //Intentional fallthrough to update host status
  506.  
  507. case "updateGameSettings":
  508. window.bonkHost.handleHostChange(window.bonkHost.toolFunctions.networkEngine.hostID === window.bonkHost.toolFunctions.networkEngine.getLSID());
  509. break;
  510. case "handleHostChange":
  511. if(isHost()) {
  512. window.bonkHost.stateFunctions.chatStatus("* You are now the host of this game")
  513. }
  514. //Intentional fallthrough to update host status
  515. case "handleHostLeft":
  516. window.bonkHost.handleHostChange(arguments[1] === window.bonkHost.toolFunctions.networkEngine.getLSID());
  517. break;
  518. case "handlePlayerJoin":
  519. window.bonkHost.handlePlayerJoin(arguments[1]);
  520. break;
  521. case "updatePlayers":
  522. while(document.getElementById("hostPlayerMenuBox").firstChild) {
  523. document.getElementById("hostPlayerMenuBox").removeChild(document.getElementById("hostPlayerMenuBox").firstChild);
  524. }
  525. [...document.getElementsByClassName("newbonklobby_playerentry")].sort((a, b) => {
  526. return window.bonkHost.players.filter(p=>p).findIndex(p => a.childNodes[1].textContent === p.userName) - window.bonkHost.players.filter(p=>p).findIndex(p => b.childNodes[1].textContent == p.userName);
  527. }).filter(p => {
  528. // Idk if this is needed but it doesn't hurt to have it
  529. return window.bonkHost.players.filter(pp=>pp).findIndex(pp => p.childNodes[1].textContent === pp.userName) !== -1;
  530. }).forEach(p => {
  531. if(p.classList.contains("bonkhost_playerentry")) return;
  532. window.bonkHost.playerManagement.addPlayer(p);
  533. })
  534. window.bonkHost.updatePlayers();
  535. break;
  536. case "updatePings":
  537. let pings = window.bonkHost.players.filter(e=>e).map(e=>e.ping);
  538. for(let i = 0; i < [...document.getElementById("hostPlayerMenuBox").children].length; i++) {
  539. document.getElementById("hostPlayerMenuBox").children[i].getElementsByClassName("newbonklobby_playerentry_pingtext")[0].textContent = pings[i] + "ms";
  540. }
  541. break;
  542. }
  543. return response;
  544. }
  545. }
  546.  
  547. for(const i of Object.keys(window.bonkHost.toolFunctions.networkEngine)) {
  548. if(typeof window.bonkHost.toolFunctions.networkEngine[i] !== "function") continue;
  549. const ogFunc = window.bonkHost.toolFunctions.networkEngine[i];
  550. window.bonkHost.toolFunctions.networkEngine[i] = function() {
  551. let response = ogFunc.apply(window.bonkHost.toolFunctions.networkEngine, arguments);
  552. switch(i) {
  553. case "changeOtherTeam":
  554. window.bonkHost.playerManagement.movePlayer(arguments[1], arguments[0]);
  555. break;
  556. case "changeOwnTeam":
  557. window.bonkHost.playerManagement.movePlayer(arguments[0]);
  558. break;
  559. case "sendMapAdd":
  560. if(arguments[0].m.dbid !== mapHistory[mapHistoryIndex]?.m.dbid || arguments[0].m.dbv !== mapHistory[mapHistoryIndex]?.m.dbv) {
  561. mapHistory.splice(0, mapHistoryIndex);
  562. mapHistoryIndex = 0;
  563. mapHistory.unshift(arguments[0]);
  564. }
  565. updateMapHistoryButtons();
  566. break;
  567. case "destroy":
  568. window.bonkHost.playerManagement.hide();
  569. window.bonkHost.playerSus = [];
  570. break;
  571. }
  572. return response;
  573. }
  574. }
  575.  
  576. window.bonkHost.toolFunctions.networkEngine.on("teamLockChange", lock => {
  577. document.getElementById("hostPlayerMenuTeamlock").checked = lock;
  578. });
  579.  
  580. let mapUpdateTimeout;
  581. window.bonkHost.toolFunctions.networkEngine.on("status", code => {
  582. if(code === "rate_limit_sma") {
  583. // A second of delay is hopefully enough. This is needed because map history can easily get ratelimited, so we want to update players with the current map before the game begins.
  584. clearTimeout(mapUpdateTimeout);
  585. mapUpdateTimeout = setTimeout(() => {
  586. if(!isHost()) return;
  587. window.bonkHost.toolFunctions.networkEngine.sendMapAdd(window.bonkHost.toolFunctions.getGameSettings().map);
  588. }, 1000);
  589. }
  590. });
  591.  
  592. for(const i of Object.keys(window.bonkHost.toolFunctions)) {
  593. if(typeof window.bonkHost.toolFunctions[i] !== "function") continue;
  594. const ogFunc = window.bonkHost.toolFunctions[i];
  595. window.bonkHost.toolFunctions[i] = function() {
  596. let response = ogFunc.apply(window.bonkHost.toolFunctions, arguments);
  597. switch(i) {
  598. case "recvInputs":
  599. if(arguments[2] !== "node") break;
  600. let playerId = arguments[0];
  601. let packet = arguments[1];
  602.  
  603. if(!window.bonkHost.playerSus[playerId]) {
  604. window.bonkHost.playerSus[playerId] = {
  605. lagHistory: [],
  606. lagginessHistory: [],
  607. cheatWarning: false
  608. }
  609. }
  610.  
  611. if(packet.type === "commands") {
  612. window.bonkHost.playerSus[playerId].cheatWarning = true;
  613. }
  614.  
  615. if(window.bonkHost.cheatDetection) {
  616. let ping = (window.bonkHost.players[playerId].ping + window.bonkHost.players[window.bonkHost.toolFunctions.networkEngine.getLSID()].ping) / 2;
  617.  
  618. if(isNaN(packet.f - window.bonkHost.fig)) {
  619. return;
  620. }
  621.  
  622. window.bonkHost.playerSus[playerId].lagHistory.push(Math.floor(1000*(1/30)*(packet.f - window.bonkHost.fig)) + ping);
  623. if(window.bonkHost.playerSus[playerId].lagHistory.length > 20) {
  624. window.bonkHost.playerSus[playerId].lagHistory.shift();
  625. }
  626.  
  627. let avgPingDiff = (window.bonkHost.playerSus[playerId].lagHistory.length > 2) ?
  628. ((
  629. window.bonkHost.playerSus[playerId].lagHistory.reduce((a, b) => a+b) -
  630. Math.max.apply(Math, window.bonkHost.playerSus[playerId].lagHistory) -
  631. Math.min.apply(Math, window.bonkHost.playerSus[playerId].lagHistory)
  632. ) / window.bonkHost.playerSus[playerId].lagHistory.length - 2) : 0
  633.  
  634. if(isNaN(avgPingDiff)) return;
  635. window.bonkHost.playerSus[playerId].lagginessHistory.push(avgPingDiff);
  636.  
  637. window.bonkHost.drawLagginess(playerId);
  638. }
  639. break;
  640. }
  641. return response;
  642. }
  643. }
  644.  
  645. for(const i of Object.keys(window.bonkHost.stateFunctions)) {
  646. if(typeof window.bonkHost.stateFunctions[i] !== "function") continue;
  647. const ogFunc = window.bonkHost.stateFunctions[i];
  648. window.bonkHost.stateFunctions[i] = function() {
  649. let response = ogFunc.apply(window.bonkHost.stateFunctions, arguments);
  650. switch(i) {
  651. case "go":
  652. case "goInProgress":
  653. window.bonkHost.handleHostChange(false);
  654. window.bonkHost.playerManagement.canBeVisible = true;
  655. window.bonkHost.menuFunctions.visible = true;
  656. window.bonkHost.menuFunctions.updatePlayers();
  657. window.bonkHost.playerManagement.show();
  658. document.getElementById("hostPlayerMenuTeamlock").checked = window.bonkHost.toolFunctions.getGameSettings().tl;
  659. break;
  660. }
  661. return response;
  662. }
  663. }
  664. }, 50);
  665. }
  666.  
  667.  
  668. const chatHandler = e => {
  669. if(e.keyCode === 13) {
  670. if(e.target.value.length > 0) {
  671. if(e.target.value[0] === "/") {
  672. let command = e.target.value.split(" ")[0].substring(1);
  673. let args = e.target.value.split(" ").slice(1);
  674. newArgs = [];
  675. for(let i = 0; i < args.length; i++) {
  676. if(args[i][0] === '"' && args[i][args[i].length - 1] !== '"') {
  677. let str = args[i];
  678. for(let j = i + 1; j < args.length; j++) {
  679. str += " " + args[j];
  680. if(args[j][args[j].length - 1] === '"') {
  681. i = j;
  682. break;
  683. }
  684. }
  685. newArgs.push(str.substring(1, str.length - 1));
  686. }
  687. else if(args[i][0] === '"' && args[i][args[i].length - 1] === '"') {
  688. newArgs.push(args[i].substring(1, args[i].length - 1));
  689. }
  690. else {
  691. newArgs.push(args[i]);
  692. }
  693. }
  694. args = newArgs;
  695. // Save without reference
  696. let oldMsg = e.target.value + "";
  697. e.target.value = "";
  698. if(command == "hhelp") {
  699. window.bonkHost.menuFunctions.showStatusMessage("/balance * -100 to 100 -- Balances everyone","#b53030",false);
  700. window.bonkHost.menuFunctions.showStatusMessage("/balanceall -100 to 100 -- Balances everyone","#b53030",false);
  701. window.bonkHost.menuFunctions.showStatusMessage("/scoreboard -- Shows the scoreboard from the last game","#b53030",false);
  702. window.bonkHost.menuFunctions.showStatusMessage("/start -- Starts the game","#b53030",false);
  703. window.bonkHost.menuFunctions.showStatusMessage("/freejoin on/off -- Lets people join during the game","#b53030",false);
  704. window.bonkHost.menuFunctions.showStatusMessage('/host "user name" -- Gives host to the player',"#b53030",false);
  705. window.bonkHost.menuFunctions.showStatusMessage('/ban "user name" -- Kicks the player and prevents them from joining with the same account',"#b53030",false);
  706. window.bonkHost.menuFunctions.showStatusMessage('/unban "user name" -- Unbans the player but doesn\'t remove kicked status',"#b53030",false);
  707. window.bonkHost.menuFunctions.showStatusMessage('/bans -- Lists banned players',"#b53030",false);
  708. window.bonkHost.menuFunctions.showStatusMessage('/resetpos -- Resets host menu position',"#b53030",false);
  709. }
  710. else if(command == "start") {
  711. if(!isHost()) {
  712. window.bonkHost.menuFunctions.showStatusMessage("* Must be room host to use this command", "#b53030", false);
  713. return;
  714. }
  715. window.bonkHost.startGame();
  716. }
  717. else if(command == "freejoin") {
  718. if(args.length == 0) {
  719. window.bonkHost.freejoin = !window.bonkHost.freejoin;
  720. }
  721. else if(["true", "on", "yes", "enable"].includes(args[0])) {
  722. window.bonkHost.freejoin = true;
  723. }
  724. else if(["false", "off", "no", "disable"].includes(args[0])) {
  725. window.bonkHost.freejoin = false;
  726. }
  727. window.bonkHost.menuFunctions.showStatusMessage("* Freejoin " + (window.bonkHost.freejoin ? "on" : "off"), "#b53030", false);
  728. document.getElementById('hostPlayerMenuFreejoin').checked = window.bonkHost.freejoin;
  729. }
  730. else if(command == "host") {
  731. if(args.length === 0) {
  732. window.bonkHost.menuFunctions.showStatusMessage(`* Usage: /${command} "user name"`, "#b53030", false);
  733. return;
  734. }
  735. if(!isHost()) {
  736. window.bonkHost.menuFunctions.showStatusMessage("* Must be room host to use this command", "#b53030", false);
  737. return;
  738. }
  739. let id = window.bonkHost.players.findIndex(e => {return e && e.userName === args[0]});
  740. if(id !== -1) {
  741. window.bonkHost.toolFunctions.networkEngine.sendHostChange(id);
  742. }
  743. else {
  744. window.bonkHost.menuFunctions.showStatusMessage("* Giving host failed, username " + args[0] + " not found in this room", "#b53030", false);
  745. }
  746. }
  747. else if(command == "ban") {
  748. if(args.length === 0) {
  749. window.bonkHost.menuFunctions.showStatusMessage(`* Usage: /${command} "user name"`, "#b53030", false);
  750. return;
  751. }
  752. window.bonkHost.ban(args[0]);
  753. }
  754. else if(command == "unban") {
  755. if(args.length === 0) {
  756. window.bonkHost.menuFunctions.showStatusMessage(`* Usage: /${command} "user name"`, "#b53030", false);
  757. return;
  758. }
  759. if(!window.bonkHost.bans.includes(args[0])) {
  760. window.bonkHost.menuFunctions.showStatusMessage(`* Username ${args[0]} not found in ban list`, "#b53030", false);
  761. return;
  762. }
  763. window.bonkHost.menuFunctions.showStatusMessage(`* Removing ${args[0]} from ban list`, "#b53030", false);
  764. window.bonkHost.bans.splice(window.bonkHost.bans.indexOf(args[0]), 1);
  765. }
  766. else if(command == "bans") {
  767. window.bonkHost.menuFunctions.showStatusMessage("* List of banned players:\n" + window.bonkHost.bans.join("\n"), "#b53030", false);
  768. }
  769. else if((command == "balance" && args[0] === "*") || command == "balanceall") {
  770. if(args.length === 0) {
  771. window.bonkHost.menuFunctions.showStatusMessage(`* Usage: /${command} "user name"`, "#b53030", false);
  772. return;
  773. }
  774. if(!isHost()) {
  775. window.bonkHost.menuFunctions.showStatusMessage("* Must be room host to use this command", "#b53030", false);
  776. return;
  777. }
  778. let amount = parseInt(args[args.length - 1]);
  779. if(amount < -100 || amount > 100 || isNaN(amount)) {
  780. window.bonkHost.menuFunctions.showStatusMessage("* Balance must be between -100 and 100", "#b53030", false);
  781. return;
  782. }
  783. for(let i = 0; i < window.bonkHost.players.length; i++) {
  784. window.bonkHost.gameInfo[2].bal[i] = amount;
  785. window.bonkHost.toolFunctions.networkEngine.sendBalance(e, amount);
  786. };
  787. if(amount != 0) {
  788. window.bonkHost.menuFunctions.showStatusMessage("* Buff/nerf changed for all players", "#b53030", false);
  789. }
  790. else {
  791. window.bonkHost.menuFunctions.showStatusMessage("* Buff/nerf reset for all players", "#b53030", false);
  792. }
  793. if (window.bonkHost.menuFunctions) {
  794. window.bonkHost.menuFunctions.updateGameSettings();
  795. window.bonkHost.menuFunctions.updatePlayers();
  796. }
  797. }
  798. else if(command == "scoreboard") {
  799. if(window.bonkHost.toolFunctions.getGameSettings().ga === "b") {
  800. const players = document.getElementById("ingamewinner_scores_left").textContent.slice(0, -1).split(":\r\n");
  801. const scores = document.getElementById("ingamewinner_scores_right").textContent.split("\r\n");
  802. const longestScore = scores.reduce((a, b) => {return a.length > b.length ? a : b}).length;
  803. window.bonkHost.menuFunctions.showStatusMessage("* Scoreboard from the last game:", "#b53030", true);
  804.  
  805. for(let i = 0; i < players.length; i++) {
  806. // \u2007 is a figure space (witdh of one digit)
  807. window.bonkHost.menuFunctions.showStatusMessage(`${scores[i].padStart((longestScore), "\u2007")}\t${players[i]}`, "#b53030", false);
  808. }
  809. }
  810. else if(window.bonkHost.toolFunctions.getGameSettings().ga === "f" && window[BIGVAR].bonkHost.footballState) {
  811. const scores = window[BIGVAR].bonkHost.footballState.scores.map(s => s.toString());
  812. const longestScore = (scores[2].length > scores[3].length ? scores[2] : scores[3]).length;
  813. window.bonkHost.menuFunctions.showStatusMessage(`${scores[3].padStart((longestScore), "\u2007")}\tBLUE TEAM`, "#b53030", false);
  814. window.bonkHost.menuFunctions.showStatusMessage(`${scores[2].padStart((longestScore), "\u2007")}\tRED TEAM`, "#b53030", false);
  815. }
  816. }
  817. else if(command == "resetpos") {
  818. document.getElementById("hostPlayerMenu").style.removeProperty("top");
  819. document.getElementById("hostPlayerMenu").style.removeProperty("bottom");
  820. document.getElementById("hostPlayerMenu").style.removeProperty("left");
  821. document.getElementById("hostPlayerMenu").style.removeProperty("right");
  822. let ls = JSON.parse(localStorage.getItem("bonkHost"));
  823. ls.position = undefined;
  824. localStorage.setItem("bonkHost", JSON.stringify(ls));
  825. }
  826. else {
  827. e.target.value = oldMsg;
  828. }
  829. }
  830. }
  831. }
  832. }
  833.  
  834. document.getElementById("newbonklobby_chat_input").addEventListener("keydown", chatHandler, true);
  835. document.getElementById("ingamechatinputtext").addEventListener("keydown", chatHandler, true);
  836.  
  837. const chatObserver = new MutationObserver(e => {
  838. for(let mutation of e) {
  839. if(mutation.type == "childList") {
  840. for(let node of mutation.addedNodes) {
  841. if(node.textContent === "* Accepted commands are listed above ") {
  842. window.bonkHost.menuFunctions.showStatusMessage(`/hhelp - commands from host mod`, "#b53030", false);
  843. }
  844. else if(node.textContent[0] === "*" && node.textContent.endsWith((" has left the game "))) {
  845. let userName = node.textContent.slice(2).slice(0, -19);
  846. if(window.bonkHost.playerHistory[userName].guest) return;
  847. let banButton = document.createElement("span");
  848. banButton.className = "newbonklobby_mapsuggest_high newbonklobby_chat_link";
  849. banButton.textContent = "[Click to ban]";
  850. banButton.style = "color: #b53030 !important;";
  851. banButton.onclick = () => {
  852. if(banButton.textContent === "[Click to ban]") {
  853. window.bonkHost.ban(userName);
  854. banButton.textContent = "[Click to unban]";
  855. }
  856. else {
  857. window.bonkHost.bans.splice(window.bonkHost.bans.indexOf(userName), 1);
  858. window.bonkHost.menuFunctions.showStatusMessage(`* Removing ${userName} from ban list`, "#b53030", false);
  859. banButton.textContent = "[Click to ban]";
  860. }
  861. }
  862. node.appendChild(banButton);
  863. }
  864. }
  865. }
  866. }
  867. });
  868.  
  869. chatObserver.observe(document.getElementById("newbonklobby_chat_content"), {attributes: false, childList: true, subtree: false});
  870.  
  871. const mapSuggestionModeRegex = newStr.match(/([A-Za-z0-9\$_]{3}\[[0-9]{1,4}\]\[[A-Za-z0-9\$_]{3}(\[[0-9]{1,4}\]){2}\]\([A-Za-z0-9\$_]{3}\[[0-9]{1,4}\]\);){7}if\([A-Za-z0-9\$_]{3}\[[0-9]{1,4}\]\[[A-Za-z0-9\$_]{3}(\[[0-9]{1,4}\]){2}\] > 250/)[0];
  872.  
  873. let SUGGESTION_MODE_BUTTON = `
  874. let args = arguments;
  875. if(!!window.bonkHost.bonkModesObject[args[0].m.mo]) {
  876. let space = document.createElement("span");
  877. space.classList.add("newbonklobby_mapsuggest_high");
  878. space.appendChild(document.createTextNode(" "));
  879.  
  880.  
  881. let smb = document.createElement("span");
  882. smb.classList.add("newbonklobby_mapsuggest_high");
  883. smb.classList.add("newbonklobby_chat_link");
  884. smb.style.color="#ff0000";
  885. smb.onclick = e => {
  886. smb.parentNode.getElementsByClassName("newbonklobby_mapsuggest_high newbonklobby_chat_link")[0].click();
  887. window.bonkHost.bonkSetMode(args[0].m.mo);
  888. };
  889. ${mapSuggestionModeRegex.split("]")[0] + "]"}.appendChild(space);
  890. smb.appendChild(document.createTextNode("[" + window.bonkHost.bonkModesObject[args[0].m.mo].lobbyName + "]"));
  891. ${mapSuggestionModeRegex.split("]")[0] + "]"}.appendChild(smb);
  892. }
  893. `;
  894.  
  895. let APPEND_SUGGESTION_MODE_BUTTON = `
  896. A7b[31].appendChild(space);
  897. smb.appendChild(document.createTextNode("[" + r6t[47].modes[A7b[4][I$2[12].suggestID].m.mo].lobbyName + "]"));
  898. A7b[31].appendChild(smb);
  899. }
  900. `;
  901.  
  902. let modeStuff = newStr.match(/[A-Za-z0-9\$_]{3}\[[0-9]{1,3}\]=class [A-Za-z0-9\$_]{3}\{constructor.{1,400}this\[[A-Za-z0-9\$_]{3}(\[[0-9]{1,3}\]){2}\]=2;/)[0].split("=")[0];
  903.  
  904. let modesObject =`${modeStuff}.modes`;
  905.  
  906. window.bonkHost.drawLagginess = (playerId) => {
  907. if(!window.bonkHost.playerSus[playerId] || !window.bonkHost.playerSus[playerId].lagginessHistory || !window.bonkHost.players[playerId] || window.bonkHost.toolFunctions.networkEngine.getLSID() === playerId) return false;
  908. let graph = document.getElementById("hostPlayerMenuCheatBox").children[window.bonkHost.players.filter(p=>p).indexOf(window.bonkHost.players[playerId])];
  909. if(graph.width !== graph.clientWidth && document.getElementById('hostPlayerMenu').style.visibility != "hidden") {
  910. graph.width = graph.clientWidth;
  911. }
  912. while (window.bonkHost.playerSus[playerId].lagginessHistory.length > Math.max(graph.clientWidth, 300)) {
  913. window.bonkHost.playerSus[playerId].lagginessHistory.shift();
  914. }
  915. let ctx = graph.getContext("2d");
  916. ctx.scale(1, -1);
  917. ctx.beginPath();
  918. ctx.clearRect(0, 0, graph.width, graph.height);
  919. ctx.strokeStyle = "#0f0";
  920. let lagginessHistory = window.bonkHost.playerSus[playerId].lagginessHistory.slice(-graph.clientWidth);
  921. let max = Math.max(0, Math.max.apply(Math, lagginessHistory));
  922. let min = Math.min(-60, Math.min.apply(Math, lagginessHistory)) - max;
  923. ctx.moveTo(0, (lagginessHistory[0] - max) / min * graph.height);
  924. for(let i = 1; i < lagginessHistory.length; i++) {
  925. ctx.lineTo(i, (lagginessHistory[i] - max) / min * graph.height);
  926. }
  927. ctx.stroke();
  928. for(let i = 1; i < 11; i++) {
  929. if (min - Math.abs(max) < -5 - i * graph.height) {
  930. ctx.beginPath();
  931. ctx.strokeStyle = "#f00";
  932. ctx.moveTo(0, (-5 - i*graph.height - max) / min * graph.height);
  933. ctx.lineTo(300, (-5 - i*graph.height - max) / min * graph.height);
  934. ctx.stroke();
  935. }
  936. else {
  937. break;
  938. }
  939. }
  940. if(window.bonkHost.playerSus[playerId].cheatWarning) {
  941. ctx.font = "16px monospace";
  942. ctx.fillText("⚠️", 0, 16);
  943. }
  944. if(window.bonkHost.playerSus[playerId].lagginessHistory === undefined || !window.bonkHost.players[playerId] || window.bonkHost.toolFunctions.networkEngine.getLSID() === playerId) {
  945. ctx.clearRect(0, 0, graph.width, graph.height);
  946. }
  947. return true;
  948. }
  949.  
  950. window.bonkHost.modeDropdownCreated = false;
  951. window.bonkHost.createModeDropdown = () => {
  952. if (window.bonkHost.modeDropdownCreated) return;
  953. window.bonkHost.modeDropdownCreated = true;
  954. const dropdown = document.createElement("div");
  955. dropdown.classList = "newbonklobby_settings_button brownButton brownButton_classic buttonShadow";
  956. const mds = dropdown.style;
  957. mds.color = "#ffffff";
  958. mds.position = "absolute";
  959. mds.right = "15px";
  960. mds.bottom = "55px";
  961. mds.display = "flex";
  962. mds.textAlign = "center";
  963. mds.flexDirection = "column-reverse";
  964.  
  965. document.getElementById("newbonklobby_modebutton").remove();
  966. let title = document.createElement("div");
  967. title.classList = "dropdown_classic";
  968. title.innerText = "Mode";
  969. title.id = "newbonklobby_modebutton";
  970. title.style.position = "unset";
  971. dropdown.appendChild(title);
  972.  
  973. const options = [];
  974. let dropdownOpen = false;
  975.  
  976. function toggleVisibility(e) {
  977. dropdownOpen = !dropdownOpen;
  978. for (const o of options) {
  979. o.style.visibility = dropdownOpen ? "" : "hidden";
  980. }
  981. e.stopImmediatePropagation();
  982. }
  983.  
  984. for (const mode of Object.keys(window.bonkHost.bonkModesObject)) {
  985. const option = document.createElement("div");
  986. option.classList = "dropdown-option dropdown_classic";
  987. option.style.display = "block";
  988. option.style.visibility = "hidden";
  989. option.style.fontSize = "15px";
  990. option.innerText = window.bonkHost.bonkModesObject[mode].lobbyName;
  991. option.onclick = (e) => {
  992. window.bonkHost.bonkSetMode(mode);
  993. toggleVisibility(e);
  994. };
  995. options.push(option);
  996. dropdown.appendChild(option);
  997. }
  998.  
  999. title.addEventListener("click", toggleVisibility);
  1000.  
  1001. document.getElementById("newbonklobby_settingsbox").appendChild(dropdown);
  1002. };
  1003.  
  1004. let stateCreationString = newStr.match(/[A-Za-z]\[...(\[[0-9]{1,4}\]){2}\]\(\[\{/)[0];
  1005. let stateCreationStringIndex = stateCreationString.match(/[0-9]{1,4}/g);
  1006. stateCreationStringIndex = stateCreationStringIndex[stateCreationStringIndex.length - 1];
  1007. let stateCreation = newStr.match(`[A-Za-z0-9\$_]{3}\[[0-9]{1,3}\]=[A-Za-z0-9\$_]{3}\\[[0-9]{1,4}\\]\\[[A-Za-z0-9\$_]{3}\\[[0-9]{1,4}\\]\\[${stateCreationStringIndex}\\]\\].+?(?=;);`)[0];
  1008. stateCreationString = stateCreation.split(']')[0] + "]";
  1009.  
  1010. const SET_STATE = `
  1011. if (
  1012. ${BIGVAR}.bonkHost.state &&
  1013. window.bonkHost.keepState &&
  1014. window.bonkHost.toolFunctions.getGameSettings().map.s.re &&
  1015. window.bonkHost.toolFunctions.getGameSettings().ga === "b" &&
  1016. ${BIGVAR}.bonkHost.state.mm.dbid == window.bonkHost.toolFunctions.getGameSettings().map.m.dbid &&
  1017. ${BIGVAR}.bonkHost.state.mm.dbv == window.bonkHost.toolFunctions.getGameSettings().map.m.dbv
  1018. ) {
  1019. for(let i = 0; i < ${BIGVAR}.bonkHost.state.discs.length; i++) {
  1020. if(${BIGVAR}.bonkHost.state.discs[i] != undefined) {
  1021. if(${BIGVAR}.bonkHost.state.discs[i].team !== window.bonkHost.players[i].team) {
  1022. let discInfo = JSON.parse(JSON.stringify(${stateCreationString}.discs[i]));
  1023. ${stateCreationString}.discs[i] = ${BIGVAR}.bonkHost.state.discs[i];
  1024. ${stateCreationString}.discs[i].sx = discInfo.sx;
  1025. ${stateCreationString}.discs[i].sy = discInfo.sy;
  1026. ${stateCreationString}.discs[i].sxv = discInfo.svx;
  1027. ${stateCreationString}.discs[i].syv = discInfo.syv;
  1028. ${stateCreationString}.discs[i].spawnTeamInfo = discInfo.spawnTeamInfo;
  1029. ${stateCreationString}.discs[i].team = discInfo.team;
  1030. }
  1031. else {
  1032. ${stateCreationString}.discs[i] = ${BIGVAR}.bonkHost.state.discs[i];
  1033. }
  1034. if(window.bonkHost.toolFunctions.getGameSettings().mo=='sp') {
  1035. ${stateCreationString}.discs[i].a1a -= Math.min(2*30, 2*30 - ${BIGVAR}.bonkHost.state.ftu)*3;
  1036. }
  1037. }
  1038. }
  1039. for(let i = 0; i < ${BIGVAR}.bonkHost.state.discDeaths.length; i++) {
  1040. if(${BIGVAR}.bonkHost.state.discDeaths[i] != undefined) {
  1041. ${stateCreationString}.discDeaths[i] = ${BIGVAR}.bonkHost.state.discDeaths[i];
  1042. }
  1043. }
  1044. ${stateCreationString}.physics=${BIGVAR}.bonkHost.state.physics;
  1045. ${stateCreationString}.seed=${BIGVAR}.bonkHost.state.seed;
  1046. ${stateCreationString}.rc=${BIGVAR}.bonkHost.state.rc;
  1047. ${stateCreationString}.rl=${BIGVAR}.bonkHost.state.rl - Math.min(60, 60 - ${BIGVAR}.bonkHost.state.ftu);
  1048. ${stateCreationString}.ftu=60;
  1049. ${stateCreationString}.shk=${BIGVAR}.bonkHost.state.shk;
  1050. ${stateCreationString}.projectiles=${BIGVAR}.bonkHost.state.projectiles;
  1051. ${stateCreationString}.capZones=${BIGVAR}.bonkHost.state.capZones;
  1052. window.bonkHost.keepState=false;
  1053. };
  1054. if(${stateCreationString}.scores.length > 0 && document.getElementById('hostPlayerMenuKeepScores').checked) {
  1055. if(window.bonkHost.toolFunctions.getGameSettings().ga === "b" && ${BIGVAR}.bonkHost.state !== undefined) {
  1056. ${stateCreationString}.scores = ${BIGVAR}.bonkHost.state.scores;
  1057. }
  1058. else if(window.bonkHost.toolFunctions.getGameSettings().ga === "f" && ${BIGVAR}.bonkHost.footballState !== undefined) {
  1059. ${stateCreationString}.scores = ${BIGVAR}.bonkHost.footballState.scores;
  1060. }
  1061. }
  1062. `;
  1063.  
  1064. // Step function beginning
  1065. // First 2 score indexes are null if teams are on
  1066. const STEP_BEGIN = `
  1067. {
  1068. let state = arguments[0];
  1069. let gs = arguments[4];
  1070. let updatePlayers = false;
  1071. if(${BIGVAR}.bonkHost.state !== undefined) {
  1072. if(gs.tea) {
  1073. let teams = state.players.filter(p=>p).map(p=>p.team);
  1074. for(let i = 2; i < 7; i++) {
  1075. if(state.scores[i] && state.scores[i] === 0 && !teams.includes(i)) {
  1076. state.scores[i] = null;
  1077. }
  1078. }
  1079. state.scores = [null, null, ...state.scores.slice(2, 6)];
  1080. }
  1081. if(document.getElementById("gamerenderer").style.visibility === "inherit" && ${BIGVAR}.bonkHost.state.players.map(p => p?.team).join() != state.players.map(p => p?.team).join()) {
  1082. updatePlayers = true;
  1083. }
  1084. }
  1085. else {
  1086. updatePlayers = true;
  1087. }
  1088. ${BIGVAR}.bonkHost.state = state;
  1089. if(updatePlayers && window.bonkHost.inGame && !window.bonkHost.toolFunctions.getGameSettings().q) {
  1090. window.bonkHost.menuFunctions.updatePlayers();
  1091. }
  1092. }
  1093. `;
  1094.  
  1095. const FOOTBALL_STEP_BEGIN = `
  1096. {
  1097. let state = arguments[0];
  1098. let updatePlayers = false;
  1099. if(${BIGVAR}.bonkHost.footballState !== undefined) {
  1100. if(document.getElementById("gamerenderer").style.visibility === "inherit" && ${BIGVAR}.bonkHost.footballState.players.map(p => p?.team).join() != state.players.map(p => p?.team).join()) {
  1101. updatePlayers = true;
  1102. }
  1103. }
  1104. else {
  1105. updatePlayers = true;
  1106. }
  1107. ${BIGVAR}.bonkHost.footballState = state;
  1108. if(updatePlayers) {
  1109. window.bonkHost.menuFunctions.updatePlayers();
  1110. }
  1111. }
  1112. `;
  1113.  
  1114. document.getElementById('hostPlayerMenuFreejoin').addEventListener('change', (e) => {
  1115. window.bonkHost.freejoin = e.target.checked;
  1116. });
  1117.  
  1118. document.getElementById('hostPlayerMenuTeamlock').addEventListener('change', () => {
  1119. document.getElementById('newbonklobby_teamlockbutton').onclick();
  1120. });
  1121.  
  1122. document.getElementById('hostPlayerMenuCheatDetectionCheckbox').addEventListener('change', (e) => {
  1123. window.bonkHost.cheatDetection = e.target.checked;
  1124. if(window.bonkHost.cheatDetection) {
  1125. document.getElementById('hostPlayerCheatDetection').style.left = "95%";
  1126. document.getElementById('hostPlayerCheatDetection').style.visibility = "visible";
  1127. }
  1128. else {
  1129. document.getElementById('hostPlayerCheatDetection').style.left = "0";
  1130. document.getElementById('hostPlayerCheatDetection').style.visibility = "hidden";
  1131. }
  1132. window.bonkHost.menuFunctions.updatePlayers();
  1133. });
  1134.  
  1135. window.bonkHost.handlePlayerJoined = (playerID, playerName, guest) => {
  1136. if(!isHost()) return;
  1137. if(!guest && window.bonkHost.bans.includes(playerName)) {
  1138. window.bonkHost.toolFunctions.networkEngine.banPlayer(playerID);
  1139. return;
  1140. }
  1141. if(window.bonkHost.freejoin) {
  1142. let team = 1;
  1143. if(window.bonkHost.toolFunctions.getGameSettings().tea) {
  1144. let teams = window.bonkHost.players.slice(0, -1).filter(p=>p && p.team > 1).map(p=>p.team);
  1145. if(teams.every(t=>t==teams[0])) {
  1146. team = teams[0];
  1147. }
  1148. else return;
  1149. }
  1150. window.bonkHost.stateFunctions.hostHandlePlayerJoined(playerID, window.bonkHost.players.length, team);
  1151. }
  1152. }
  1153.  
  1154. window.bonkHost.ban = (playerName) => {
  1155. if(window.bonkHost.bans.includes(playerName)) {
  1156. window.bonkHost.menuFunctions.showStatusMessage("* " + playerName + " is already banned", "#b53030", false);
  1157. return;
  1158. }
  1159. let id = window.bonkHost.players.findIndex(e => {return e && e.userName === playerName});
  1160. if(id !== -1 && isHost()) {
  1161. if(window.bonkHost.players[id].guest) {
  1162. window.bonkHost.menuFunctions.showStatusMessage("* Banning guests doesn't work, so they'll get kicked instead", "#b53030", false);
  1163. }
  1164. else {
  1165. window.bonkHost.bans.push(playerName);
  1166. }
  1167. window.bonkHost.toolFunctions.networkEngine.banPlayer(id);
  1168. }
  1169. else if(id !== -1) {
  1170. window.bonkHost.menuFunctions.showStatusMessage(`* You're not room host, but ${playerName} will be added to ban list`, "#b53030", false);
  1171. window.bonkHost.bans.push(playerName);
  1172. }
  1173. else {
  1174. window.bonkHost.menuFunctions.showStatusMessage(`* Username ${playerName} not found in this room, but they'll be added to ban list`, "#b53030", false);
  1175. window.bonkHost.bans.push(playerName);
  1176. }
  1177. }
  1178.  
  1179.  
  1180. window.bonkHost.playerManagement.addPlayer = (playerEntry) => {
  1181. for(let player of window.bonkHost.players.filter(p=>p)) {
  1182. window.bonkHost.playerHistory[player.userName] = player;
  1183. }
  1184. while(window.bonkHost.playerManagement.getPlayer(playerEntry)) {
  1185. window.bonkHost.playerManagement.removePlayer(playerEntry);
  1186. }
  1187. let newPlayerEntry = playerEntry.cloneNode(true);
  1188. newPlayerEntry.classList.remove('newbonklobby_playerentry_half');
  1189. newPlayerEntry.getElementsByClassName("newbonklobby_playerentry_ping")[0].remove();
  1190. newPlayerEntry.getElementsByClassName("newbonklobby_playerentry_host")[0].remove();
  1191. if(isHost()) {
  1192. playerEntry.addEventListener('click', (e) => {
  1193. let menu = document.getElementsByClassName("newbonklobby_playerentry_menu")[0];
  1194. banButton = document.createElement("div");
  1195. banButton.classList = "newbonklobby_playerentry_menu_button brownButton brownButton_classic buttonShadow";
  1196. banButton.innerHTML = "BAN";
  1197. banButton.onclick = () => {
  1198. if(banButton.innerHTML == "BAN") {
  1199. banButton.innerHTML = "SURE?";
  1200. return;
  1201. }
  1202. window.bonkHost.ban(playerEntry.children[1].textContent);
  1203. menu.style.display = "none";
  1204. };
  1205. for(let child of menu.childNodes) {
  1206. if(child.textContent == "KICK") {
  1207. menu.insertBefore(banButton, child.nextSibling);
  1208. break;
  1209. }
  1210. }
  1211. });
  1212. }
  1213. newPlayerEntry.onclick = e => {
  1214. playerEntry.click();
  1215. let menu = document.getElementsByClassName("newbonklobby_playerentry_menu")[0];
  1216. document.getElementById("hostPlayerMenuBox").parentNode.appendChild(menu);
  1217. newPlayerEntry.playerElement=playerEntry;
  1218. menu.style.removeProperty("left");
  1219. menu.style.right=0;
  1220. menu.style.top=([...playerEntry.parentNode.children].indexOf(playerEntry))*43+"px";
  1221. }
  1222. newPlayerEntry.onmouseenter = playerEntry.onmouseenter;
  1223.  
  1224. // Make spectators transparent
  1225. let playerId = window.bonkHost.players.findIndex(p => p && (p.userName === newPlayerEntry.childNodes[1].textContent));
  1226. if(
  1227. !(window.bonkHost.toolFunctions.getGameSettings().ga === "b" && window[BIGVAR].bonkHost.state?.players[playerId]) &&
  1228. !(window.bonkHost.toolFunctions.getGameSettings().ga === "f" && window[BIGVAR].bonkHost.footballState?.players[playerId])
  1229. ) {
  1230. newPlayerEntry.style.filter = "opacity(0.4)";
  1231. }
  1232.  
  1233. // Listen for skin render
  1234. let observer = new MutationObserver((mutations) => {
  1235. mutations.forEach((mutation) => {
  1236. // New child
  1237. mutation.addedNodes.forEach((node) => {
  1238. newPlayerEntry.firstChild.appendChild(node.cloneNode(true));
  1239. observer.disconnect();
  1240. });
  1241. });
  1242. });
  1243. observer.observe(playerEntry.children[0], { childList: true });
  1244. hostPlayerMenuBox.appendChild(newPlayerEntry);
  1245. for(let i = 0; i < [...document.getElementById("hostPlayerMenuCheatBox").children].length; i++) {
  1246. let graph = document.getElementById("hostPlayerMenuCheatBox").children[i];
  1247. let playerId = window.bonkHost.players.indexOf(window.bonkHost.players.filter(p=>p)[i]);
  1248. if(!window.bonkHost.drawLagginess(playerId)) {
  1249. let ctx = graph.getContext("2d");
  1250. ctx.scale(1, -1);
  1251. ctx.clearRect(0, 0, graph.width, graph.height);
  1252. }
  1253. }
  1254. }
  1255.  
  1256. window.bonkHost.playerManagement.removePlayer = (playerEntry) => {
  1257. let foundPlayerEntry = window.bonkHost.playerManagement.getPlayer(playerEntry);
  1258. if(foundPlayerEntry) {
  1259. hostPlayerMenuBox.removeChild(foundPlayerEntry);
  1260. }
  1261. }
  1262.  
  1263. window.bonkHost.playerManagement.show = () => {
  1264. if(!window.bonkHost.playerManagement.canBeVisible || document.getElementById("gamerenderer").style.visibility != "inherit") return;
  1265. if(parent.document.getElementById('adboxverticalleftCurse') != null)
  1266. parent.document.getElementById('adboxverticalleftCurse').style.display = "none";
  1267. document.getElementById("hostPlayerMenuKeepPositions").parentNode.parentNode.style.filter = (window.bonkHost.toolFunctions.getGameSettings().map.s.re ? "" : "brightness(0.5)");
  1268. document.getElementById("hostPlayerMenuKeepPositions").style.pointerEvents = (window.bonkHost.toolFunctions.getGameSettings().map.s.re ? "" : "none");
  1269. document.getElementById("hostPlayerMenuRespawningRequiredWarning").style.display = (window.bonkHost.toolFunctions.getGameSettings().map.s.re ? "none" : "");
  1270. document.getElementById('hostPlayerMenu').style.display = "unset";
  1271. window.bonkHost.menuFunctions.updatePlayers();
  1272. window.bonkHost.inGame = true;
  1273. for(let graph of [...document.getElementById("hostPlayerMenuCheatBox").children]) {
  1274. let ctx = graph.getContext("2d");
  1275. ctx.scale(1, -1);
  1276. ctx.clearRect(0, 0, graph.width, graph.height);
  1277. }
  1278. }
  1279.  
  1280. window.bonkHost.playerManagement.hide = () => {
  1281. window.bonkHost.inGame = false;
  1282. document.getElementById('hostPlayerMenu').style.display = "none";
  1283. if(parent.document.getElementById('adboxverticalleftCurse') != null)
  1284. parent.document.getElementById('adboxverticalleftCurse').style.removeProperty("display");
  1285. }
  1286.  
  1287. // Can be called in other situations too
  1288. window.bonkHost.handleHostChange = (host) => {
  1289. document.getElementById("hostPlayerMenuRestartButton").classList.toggle("brownButtonDisabled", !host);
  1290. document.getElementById("hostPlayerMenuTeamlock").classList.toggle("brownButtonDisabled", !host);
  1291. updateMapHistoryButtons();
  1292. if(!host) {
  1293. mapHistory = [];
  1294. mapHistoryIndex = 0;
  1295. }
  1296. }
  1297.  
  1298. window.bonkHost.playerManagement.collapse = (saveToLocalStorage = true) => {
  1299. if(document.getElementById('hostPlayerMenu').style.visibility != "hidden") {
  1300. document.getElementById('hostPlayerMenuControls').style.display = "none";
  1301. document.getElementById('hostPlayerMenuControls').visibility = "hidden";
  1302. document.getElementById('hostPlayerMenu').style.minWidth = 0;
  1303. document.getElementById('hostPlayerMenu').style.minHeight = 0;
  1304. document.getElementById('hostPlayerMenu').style.width = 0;
  1305. document.getElementById('hostPlayerMenu').style.height = 0;
  1306. document.getElementById('hostPlayerMenu').style.visibility = "hidden";
  1307. document.getElementById('hostPlayerMenuCollapse').textContent = "+";
  1308. }
  1309. else {
  1310. document.getElementById('hostPlayerMenu').style.visibility = "visible";
  1311. document.getElementById('hostPlayerMenu').style.removeProperty("min-width");
  1312. document.getElementById('hostPlayerMenu').style.removeProperty("min-height");
  1313. document.getElementById('hostPlayerMenu').style.removeProperty("width");
  1314. document.getElementById('hostPlayerMenu').style.removeProperty("height");
  1315. document.getElementById('hostPlayerMenu').style.visibility = "visible";
  1316. document.getElementById('hostPlayerMenuCollapse').textContent = "-";
  1317. setTimeout(() => {document.getElementById('hostPlayerMenuControls').style.removeProperty("display");}, 100);
  1318. }
  1319. if(saveToLocalStorage) {
  1320. let ls = JSON.parse(localStorage.getItem("bonkHost"));
  1321. ls.collapse = (document.getElementById('hostPlayerMenu').style.visibility == "hidden");
  1322. localStorage.setItem("bonkHost", JSON.stringify(ls));
  1323. }
  1324. }
  1325.  
  1326. if(JSON.parse(localStorage.getItem("bonkHost")).collapse) {
  1327. window.bonkHost.playerManagement.collapse(false);
  1328. }
  1329.  
  1330. {
  1331. let position = JSON.parse(localStorage.getItem("bonkHost")).position;
  1332. if(position !== undefined) {
  1333. document.getElementById("hostPlayerMenu").style.left = position.left;
  1334. document.getElementById("hostPlayerMenu").style.right = position.right;
  1335. document.getElementById("hostPlayerMenu").style.top = position.top;
  1336. document.getElementById("hostPlayerMenu").style.bottom = position.bottom;
  1337. }
  1338. }
  1339.  
  1340. // Host menu mover
  1341.  
  1342. let startGrabPoint = {x:0,y:0};
  1343. let grabbing = false;
  1344. const hostMenuMover = e => {
  1345. if(e.x - startGrabPoint.x < document.body.clientWidth / 2) {
  1346. document.getElementById("hostPlayerMenu").style.left = e.x + startGrabPoint.x - document.getElementById("hostPlayerMenu").clientWidth;
  1347. document.getElementById("hostPlayerMenu").style.right = "unset";
  1348. }
  1349. else {
  1350. document.getElementById("hostPlayerMenu").style.right = document.body.clientWidth - e.x - startGrabPoint.x;
  1351. document.getElementById("hostPlayerMenu").style.left = "unset";
  1352. }
  1353.  
  1354. if(e.y - startGrabPoint.y < document.body.clientHeight / 2) {
  1355. document.getElementById("hostPlayerMenu").style.top = e.y - startGrabPoint.y;
  1356. document.getElementById("hostPlayerMenu").style.bottom = "unset";
  1357. }
  1358. else {
  1359. document.getElementById("hostPlayerMenu").style.bottom = document.body.clientHeight - e.y + startGrabPoint.y - document.getElementById("hostPlayerMenu").clientHeight;
  1360. document.getElementById("hostPlayerMenu").style.top = "unset";
  1361. }
  1362. }
  1363.  
  1364. window.bonkHost.playerManagement.startGrab = e => {
  1365. grabbing = true;
  1366. document.getElementById("hostPlayerMenuGrab").style.cursor = "grabbing"
  1367. document.getElementById("hostPlayerMenu").style.transition = "unset";
  1368. startGrabPoint.x = e.offsetX;
  1369. startGrabPoint.y = e.offsetY;
  1370. document.body.addEventListener("pointermove", hostMenuMover);
  1371. }
  1372.  
  1373. window.bonkHost.playerManagement.endGrab = e => {
  1374. if(!grabbing) return;
  1375. grabbing = false;
  1376. document.getElementById("hostPlayerMenuGrab").style.cursor = "grab";
  1377. document.getElementById("hostPlayerMenu").style.removeProperty("transition");
  1378. document.body.removeEventListener("pointermove", hostMenuMover);
  1379. let ls = JSON.parse(localStorage.getItem("bonkHost"));
  1380. ls.position = {
  1381. left: document.getElementById("hostPlayerMenu").style.left,
  1382. right: document.getElementById("hostPlayerMenu").style.right,
  1383. top: document.getElementById("hostPlayerMenu").style.top,
  1384. bottom: document.getElementById("hostPlayerMenu").style.bottom
  1385. };
  1386. localStorage.setItem("bonkHost", JSON.stringify(ls));
  1387. }
  1388.  
  1389. document.getElementById("hostPlayerMenuGrab").addEventListener("pointerdown", window.bonkHost.playerManagement.startGrab);
  1390. document.body.addEventListener("pointerup", window.bonkHost.playerManagement.endGrab);
  1391.  
  1392. // Helper functions
  1393.  
  1394. window.bonkHost.playerManagement.getPlayer = (playerEntry, exact = false) => {
  1395. if (exact) {
  1396. let child = [...hostPlayerMenuBox.children].indexOf(playerEntry);
  1397. if(child) return hostPlayerMenuBox.children[child];
  1398. }
  1399. for(let child of hostPlayerMenuBox.children) {
  1400. if(child.children[1].textContent == playerEntry.children[1].textContent) {
  1401. return child;
  1402. }
  1403. }
  1404. return false;
  1405. }
  1406.  
  1407. window.bonkHost.playerManagement.movePlayer = (team, playerID = window.bonkHost.toolFunctions.networkEngine.getLSID()) => {
  1408. if(!window.bonkHost.players[playerID] || !window.bonkHost.inGame || !isHost() || window.bonkHost.toolFunctions.getGameSettings().q) return;
  1409. window.bonkHost.menuFunctions.visible = true;
  1410. if(team > 0)
  1411. window.bonkHost.stateFunctions.hostHandlePlayerJoined(playerID, window.bonkHost.players.length, team);
  1412. else
  1413. window.bonkHost.stateFunctions.hostHandlePlayerLeft(playerID);
  1414. window.bonkHost.menuFunctions.updatePlayers();
  1415. }
  1416.  
  1417. window.bonkHost.startGame = () => {
  1418. window.bonkHost.keepState = document.getElementById("hostPlayerMenuKeepPositions").checked;
  1419. for(let callback of Object.keys(window.bonkHost.bonkCallbacks)) {
  1420. window.bonkHost.bonkCallbacks[callback]("startGame");
  1421. }
  1422. }
  1423.  
  1424. // Fisher–Yates shuffle
  1425. const shuffle = (array) => {
  1426. let currentIndex = array.length, randomIndex;
  1427.  
  1428. // While there remain elements to shuffle.
  1429. while (currentIndex != 0) {
  1430. // Pick a remaining element.
  1431. randomIndex = Math.floor(Math.random() * currentIndex);
  1432. currentIndex--;
  1433.  
  1434. // And swap it with the current element.
  1435. [array[currentIndex], array[randomIndex]] = [
  1436. array[randomIndex], array[currentIndex]];
  1437. }
  1438. return array;
  1439. }
  1440.  
  1441. window.bonkHost.shufflePlayers = () => {
  1442. document.getElementById("teamshufflebox").style.visibility = "hidden";
  1443.  
  1444. //Players per team
  1445. let ppt = document.getElementById("teamshuffleppt").value;
  1446. let teams = 0;
  1447. for(let team of ["red", "blue", "yellow", "green"]) {
  1448. if(document.getElementById("team_shuffle_"+team).checked) {
  1449. teams++;
  1450. }
  1451. }
  1452. if(ppt === "AUTO") {
  1453. ppt = Math.ceil(window.bonkHost.players.length / teams);
  1454. }
  1455. let shuffledPlayers = shuffle(window.bonkHost.players);
  1456. for(let team of ["red", "blue", "yellow", "green"]) {
  1457. if(document.getElementById("team_shuffle_"+team).checked) {
  1458. for(let i = 0; i < ppt; i++) {
  1459. window.bonkHost.toolFunctions.networkEngine.changeOtherTeam(window.bonkHost.players.indexOf(shuffledPlayers.pop()), ["red", "blue", "yellow", "green"].indexOf(team)+2);
  1460. }
  1461. }
  1462. }
  1463. }
  1464.  
  1465. let mapSelectionObserver = new MutationObserver(mutations => {
  1466. for(let mutation of mutations) {
  1467. for(let e of mutation.addedNodes) {
  1468. let mode = e.getElementsByClassName('maploadwindowtextmode')[0];
  1469. if(mode === undefined) mode = e.getElementsByClassName('maploadwindowtextmode_picks')[0];
  1470. if(mode === undefined) return;
  1471. if(mode.textContent !== "Any Mode") {
  1472. mode.classList.add('brownButton');
  1473. mode.classList.add('brownButton_classic');
  1474. mode.classList.add('buttonShadow');
  1475. mode.style.padding = "2px";
  1476. mode.style.width = "90px";
  1477. }
  1478. mode.addEventListener("click", e => {
  1479. if(!document.getElementById('newbonklobby_modebutton').classList.contains("brownButtonDisabled")) {
  1480. window.bonkHost.bonkSetMode(Object.entries(window.bonkHost.bonkModesObject).filter(e => {return e[1].lobbyName === mode.textContent})[0][0]);
  1481. }
  1482. });
  1483. }
  1484. }
  1485. });
  1486. mapSelectionObserver.observe(document.getElementById('maploadwindowmapscontainer'), {attributes: false, childList: true, subtree: false});
  1487.  
  1488. /*Autocomplete*/
  1489.  
  1490. const autocomplete = e => {
  1491. if (e.keyCode === 9) {
  1492. e.preventDefault();
  1493. e.stopPropagation();
  1494. let chatText = e.target.value.split(' ');
  1495. let length = 0;
  1496. for (let i = 0; i < chatText.length; i++) {
  1497. length += chatText[i].length + 1;
  1498. if (length <= e.target.selectionStart || chatText[i] === "")
  1499. continue;
  1500. let foundAutocompletes = [];
  1501. let foundAutocompletesOffsets = [];
  1502. for (let j = 0; j < window.bonkCommands.length; j++) {
  1503. if (window.bonkCommands[j].toLowerCase().replace(/[.*+?^${}()|[\]\\]/g, '\\$&').match("^" + chatText[i].toLowerCase().replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))) {
  1504. foundAutocompletes.push(window.bonkCommands[j]);
  1505. foundAutocompletesOffsets.push(0);
  1506. }
  1507. }
  1508. if (foundAutocompletes.length === 0) {
  1509. if(chatText[0] !== "/unban") {
  1510. for (let j = 0; j < window.bonkHost.players.filter(p=>p).length; j++) {
  1511. for (let k = i; k >= 0; k--) {
  1512. if (window.bonkHost.players.filter(p=>p).map(p=>p.userName)[j].toLowerCase().replace(/[.*+?^${}()|[\]\\]/g, '\\$&').match("^" + chatText.slice(k, i + 1).join(" ").toLowerCase().replace(/"/g, "").replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))) {
  1513. foundAutocompletes.push(window.bonkHost.players.filter(p=>p).map(p=>p.userName)[j]);
  1514. foundAutocompletesOffsets.push(k);
  1515. }
  1516. }
  1517. }
  1518. }
  1519. else {
  1520. for (let j = 0; j < window.bonkHost.bans.length; j++) {
  1521. for (let k = i; k >= 0; k--) {
  1522. if (window.bonkHost.bans[j].toLowerCase().replace(/[.*+?^${}()|[\]\\]/g, '\\$&').match("^" + chatText.slice(k, i + 1).join(" ").toLowerCase().replace(/"/g, "").replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))) {
  1523. foundAutocompletes.push(window.bonkHost.bans[j]);
  1524. foundAutocompletesOffsets.push(k);
  1525. }
  1526. }
  1527. }
  1528. }
  1529. }
  1530. if (foundAutocompletes.length === 1) {
  1531. let oldlen = chatText.slice(foundAutocompletesOffsets[0], i + 1).join(" ").length;
  1532. for (let j = i; j > foundAutocompletesOffsets[0]; j--) {
  1533. chatText.splice(j, 1);
  1534. }
  1535. if (chatText[0][0] === "/" && i > 0 && foundAutocompletes[0].includes(" ")) {
  1536. chatText[foundAutocompletesOffsets[0]] = `"${foundAutocompletes[0]}" `;
  1537. } else {
  1538. chatText[foundAutocompletesOffsets[0]] = foundAutocompletes[0] + (foundAutocompletesOffsets[0] === (chatText.length - 1) && (chatText[0][0] === "/") && (chatText[foundAutocompletesOffsets[0] + 1] !== "") ? " " : "");
  1539. }
  1540. if(chatText[foundAutocompletesOffsets[0] + 1] === "") {
  1541. chatText.splice(foundAutocompletesOffsets[0] + 1, 1);
  1542. }
  1543. e.target.value = chatText.join(' ');
  1544. e.target.selectionStart = length - oldlen + chatText[foundAutocompletesOffsets[0]].length + ((foundAutocompletesOffsets[0] === chatText.length - 1) && (chatText[0][0] !== "/") ? 0 : 1);
  1545. e.target.selectionEnd = length - oldlen + chatText[foundAutocompletesOffsets[0]].length + ((foundAutocompletesOffsets[0] === (chatText.length - 1)) && (chatText[0][0] !== "/") ? 1 : 0);
  1546. return;
  1547. } else if (foundAutocompletes.length > 1) {
  1548. let maxAutocomplete = "";
  1549. let char = "";
  1550. for (let j = 0; j >= 0; j++) {
  1551. if(char === undefined) break;
  1552. maxAutocomplete += char;
  1553. char = "";
  1554. for (let k = 0; k < foundAutocompletes.length; k++) {
  1555. if (char === "") char = foundAutocompletes[k][j];
  1556. else if (foundAutocompletes[k][j] !== char) {
  1557. j = -Infinity;
  1558. break;
  1559. }
  1560. }
  1561. }
  1562. if(maxAutocomplete === "") return;
  1563. let oldlen = chatText[i].length;
  1564. let quotes = (chatText[0][0] === "/" && foundAutocompletes.some(r => r.includes(" ")));
  1565. if (quotes) {
  1566. chatText[i] = `"${maxAutocomplete}"`;
  1567. } else {
  1568. chatText[i] = maxAutocomplete;
  1569. }
  1570. e.target.value = chatText.join(' ');
  1571. e.target.selectionStart = length - oldlen + chatText[i].length - quotes * 2;
  1572. e.target.selectionEnd = length - oldlen + chatText[i].length - quotes * 2;
  1573. return;
  1574. }
  1575. }
  1576. }
  1577. };
  1578. document.getElementById("newbonklobby_chat_input").addEventListener("keydown", autocomplete, true);
  1579. document.getElementById("ingamechatinputtext").addEventListener("keydown", autocomplete, true);
  1580.  
  1581. (() => {
  1582. let selectedPlayer = null;
  1583. let start = [], end = [];
  1584. const wheelSize = 150;
  1585. let oldAngle = null;
  1586. const innerWheelRadius = (wheelSize/26.458)/2*8.5;
  1587. window.bonkHost.updatePlayers = () => {
  1588. [...document.getElementsByClassName("newbonklobby_playerentry")].filter(e => {
  1589. return ["newbonklobby_playerbox_leftelementcontainer",
  1590. "newbonklobby_playerbox_rightelementcontainer",
  1591. "newbonklobby_playerbox_elementcontainer",
  1592. "newbonklobby_specbox_elementcontainer",
  1593. "hostPlayerMenuBox"].includes(e.parentNode.id);
  1594. }).forEach(e => {
  1595. e.addEventListener("mousedown", mouse => {
  1596. if(isHost() || (e.children[1].textContent === window.bonkHost.players[window.bonkHost.toolFunctions.networkEngine.getLSID()].userName && !window.bonkHost.inGame)) {
  1597. selectedPlayer = e;
  1598. start = [mouse.clientY, mouse.clientX];
  1599. }
  1600. });
  1601. e.addEventListener("click", mouse => {
  1602. if(!isHost() && e.children[1].textContent === window.bonkHost.players[window.bonkHost.toolFunctions.networkEngine.getLSID()].userName && window.bonkHost.inGame) {
  1603. mouse.stopImmediatePropagation();
  1604. }
  1605. }, true);
  1606. });
  1607. }
  1608. document.addEventListener("mousemove", mouse => {
  1609. if(selectedPlayer) {
  1610. document.body.style.pointerEvents = "none";
  1611. let wheelType = (!window.bonkHost.toolFunctions.getGameSettings().tea) ? "selectionWheel" : "selectionWheelTeams";
  1612. document.getElementById(wheelType).style.display = "block";
  1613. document.getElementById(wheelType).style.top = start[0]-document.getElementById(wheelType).getBoundingClientRect().height/2+"px";
  1614. document.getElementById(wheelType).style.left = start[1]-document.getElementById(wheelType).getBoundingClientRect().width/2+"px";
  1615. end = [mouse.clientY, mouse.clientX];
  1616. if(Math.sqrt((end[0]-start[0])**2 + (end[1]-start[1])**2) >= innerWheelRadius) {
  1617. if(!window.bonkHost.toolFunctions.getGameSettings().tea) {
  1618. let angle = end[1] < start[1];
  1619. if(angle) {
  1620. document.getElementById("selectionWheel").children[0].children[0].children[0].style.opacity = 1;
  1621. document.getElementById("selectionWheel").children[0].children[1].children[0].style.opacity = 0.5;
  1622. }
  1623. else {
  1624. document.getElementById("selectionWheel").children[0].children[0].children[0].style.opacity = 0.5;
  1625. document.getElementById("selectionWheel").children[0].children[1].children[0].style.opacity = 1;
  1626. }
  1627. selectedPlayer.onmouseenter(angle);
  1628. oldAngle = angle;
  1629. }
  1630. else {
  1631. let angle = Math.atan((end[0]-start[0])/(end[1]-start[1]))/Math.PI*180+360/5/2;
  1632. if(end[1] < start[1]) {
  1633. angle += 180;
  1634. }
  1635. else if(end[0] < start[0]) {
  1636. angle += 360;
  1637. }
  1638. angle = Math.floor((angle%360)/(360/5));
  1639. if(oldAngle !== angle) {
  1640. for(let child of [...document.getElementById("selectionWheelTeams").children[0].children[0].children]) {
  1641. child.style.opacity = 0.5;
  1642. }
  1643. }
  1644. document.getElementById("selectionWheelTeams").children[0].children[0].children[angle].style.opacity = 1;
  1645. selectedPlayer.onmouseenter(angle);
  1646. oldAngle = angle;
  1647. }
  1648. }
  1649. else {
  1650. if(oldAngle !== null) {
  1651. for(let child of [...document.getElementById("selectionWheelTeams").children[0].children[0].children]) {
  1652. child.style.opacity = 0.5;
  1653. }
  1654. for(let child of [...document.getElementById("selectionWheel").children[0].children]) {
  1655. child.children[0].style.opacity = 0.5;
  1656. }
  1657. }
  1658. selectedPlayer.onmouseenter(null);
  1659. oldAngle = null;
  1660. }
  1661. }
  1662. });
  1663. const changeTeam = mouse => {
  1664. document.getElementById("selectionWheel").style.display = "none";
  1665. document.getElementById("selectionWheelTeams").style.display = "none";
  1666. if(!selectedPlayer) return;
  1667. document.body.style.removeProperty("pointer-events");
  1668. end = [mouse.clientY, mouse.clientX];
  1669. const sendTeamChange = (i, team) => {
  1670. if(i == window.bonkHost.toolFunctions.networkEngine.getLSID()) {
  1671. window.bonkHost.toolFunctions.networkEngine.changeOwnTeam(team);
  1672. }
  1673. else {
  1674. window.bonkHost.toolFunctions.networkEngine.changeOtherTeam(i, team);
  1675. }
  1676. }
  1677. if(Math.sqrt((end[0]-start[0])**2 + (end[1]-start[1])**2) >= innerWheelRadius) {
  1678. if(!window.bonkHost.toolFunctions.getGameSettings().tea) {
  1679. for(let child of [...document.getElementById("selectionWheel").children[0].children]) {
  1680. child.children[0].style.opacity = 0.5;
  1681. }
  1682. sendTeamChange(window.bonkHost.players.findIndex(i => {return i && i.userName === selectedPlayer.children[1].textContent}), end[1]<start[1] ? 1 : 0);
  1683. }
  1684. else {
  1685. let angle = Math.atan((end[0]-start[0])/(end[1]-start[1]))/Math.PI*180+360/5/2;
  1686. if(end[1] < start[1]) {
  1687. angle += 180;
  1688. }
  1689. else if(end[0] < start[0]) {
  1690. angle += 360;
  1691. }
  1692. angle = Math.floor((angle%360)/(360/5));
  1693. sendTeamChange(window.bonkHost.players.findIndex(i => {return i && i.userName === selectedPlayer.children[1].textContent}), [0, 5, 4, 3, 2][angle]);
  1694. }
  1695. }
  1696. else {
  1697. selectedPlayer.click();
  1698. }
  1699. selectedPlayer = null;
  1700. }
  1701. document.addEventListener("mouseup", changeTeam);
  1702. document.addEventListener("mouseenter", mouse => {
  1703. let selectedPlayer = (mouse.buttons !== 0 ? selectedPlayer : null);
  1704. if(!selectedPlayer) {
  1705. changeTeam(mouse);
  1706. }
  1707. });
  1708. })();
  1709. let patchCounter = 0;
  1710. const patch = (a, b) => {
  1711. let c = newStr;
  1712. newStr = newStr.replace(a, b);
  1713. /*console.log(`Patch ${patchCounter++}: ${c === newStr ? 'fail' : 'success'}`);
  1714. if(c === newStr) {
  1715. console.log(`Failed to patch ${a} with ${b}`);
  1716. }*/
  1717. return c !== newStr;
  1718. }
  1719.  
  1720. //Remove round limit
  1721. document.getElementById('newbonklobby_roundsinput').removeAttribute("maxlength");
  1722. patch(/[A-Za-z0-9\$_]{3}\[[0-9]{1,3}\]\[[0-9]{1,3}\]\[[A-Za-z0-9\$_]{3}\[[0-9]{1,3}\]\[[0-9]{1,3}\]\]=Math\[[A-Za-z0-9\$_]{3}\[[0-9]{1,3}\]\[[0-9]{1,3}\]\]\(Math\[[A-Za-z0-9\$_]{3}\[[0-9]{1,3}\]\[[0-9]{1,3}\]\]\(1,[A-Za-z0-9\$_]{3}\[[0-9]{1,3}\]\[[0-9]{1,3}\]\[[A-Za-z0-9\$_]{3}\[[0-9]{1,3}\]\[[0-9]{1,3}\]\]\),9\);/, '');
  1723. const roundVal = newStr.match(/[A-Za-z0-9\$_]{3}\[[0-9]{1,4}\]=parseInt\([A-Za-z0-9\$_]{3}(\[0\]){2}\[[A-Za-z0-9\$_]{3}(\[[0-9]{1,4}\]){2}\]\)\;/)[0];
  1724. const roundValVar = roundVal.split('=')[0];
  1725. patch(roundVal, `${roundValVar}=parseInt(document.getElementById('newbonklobby_roundsinput').value);if(isNaN(${roundValVar}) || ${roundValVar} <= 0){return;}`);
  1726.  
  1727. //Mode selection menu. Custom patch
  1728. let lastTarget = newStr.match(/editorCanTarget:(true|false)\};/g)
  1729. lastTarget = lastTarget[lastTarget.length - 1];
  1730. newStr = newStr.split(lastTarget);
  1731.  
  1732. newStr[newStr.length - 1] = `
  1733. window.bonkHost.bonkModesObject=${modesObject};
  1734. window.bonkHost.bonkSetMode = m => {
  1735. if(m === "f") {
  1736. window.bonkHost.gameInfo[2].ga = "f";
  1737. window.bonkHost.gameInfo[2].tea=true;
  1738. window.bonkHost.toolFunctions.networkEngine.sendTeamSettingsChange(window.bonkHost.gameInfo[2].tea);
  1739. }
  1740. else {
  1741. window.bonkHost.gameInfo[2].ga = "b";
  1742. }
  1743. window.bonkHost.gameInfo[2].mo = m;
  1744. window.bonkHost.menuFunctions.updatePlayers();
  1745. window.bonkHost.toolFunctions.networkEngine.sendGAMO(window.bonkHost.gameInfo[2].ga, window.bonkHost.gameInfo[2].mo);
  1746. window.bonkHost.menuFunctions.updateGameSettings();
  1747. }
  1748. window.bonkHost.createModeDropdown();
  1749. ` + newStr[newStr.length - 1];
  1750.  
  1751. newStr = newStr.join(lastTarget);
  1752.  
  1753. //Add mode button to map suggestion message
  1754. patch(mapSuggestionModeRegex, mapSuggestionModeRegex.split("if")[0] + SUGGESTION_MODE_BUTTON + "if" + mapSuggestionModeRegex.split("if")[1]);
  1755.  
  1756. //Internal var
  1757. patch(`function ${BIGVAR}(){}`, `function ${BIGVAR}(){}${BIGVAR}.bonkHost={};`);
  1758.  
  1759. /////////////
  1760. //Host menu//
  1761. /////////////
  1762.  
  1763. //Save latest state
  1764. const stateRegex = newStr.match(/[A-Za-z]\[[A-Za-z0-9\$_]{3}(\[[0-9]{1,3}\]){2}\]={discs/)[0];
  1765. patch(stateRegex, STEP_BEGIN + stateRegex);
  1766.  
  1767. //Football state
  1768. let footballStateRegex = newStr.match(/=\[\];if\(\![A-Za-z0-9\$_]\[[A-Za-z0-9\$_]{3}\[[0-9]{1,3}\]\[[0-9]{1,3}\]\]\)\{/)[0];
  1769. footballStateRegex = footballStateRegex.split(";");
  1770. patch(footballStateRegex.join(";"), footballStateRegex[0] + ";" + FOOTBALL_STEP_BEGIN + footballStateRegex[1]);
  1771.  
  1772. //Apply latest state
  1773. const stateSetRegex = newStr.match(/\* 999\),[A-Za-z0-9\$_]{3}\[[0-9]{1,3}\],null,[A-Za-z0-9\$_]{3}\[[0-9]{1,3}\],true\);/)[0];
  1774. patch(stateSetRegex, stateSetRegex + SET_STATE);
  1775.  
  1776. //Handle player joins and rebans
  1777. let playerJoinedCustom = newStr.match(/\[\d\],ping:105\};/)[0];
  1778. patch(playerJoinedCustom, playerJoinedCustom+`window.bonkHost.handlePlayerJoined(...arguments);`);
  1779.  
  1780. //Joined room
  1781. let connectionMatch = newStr.match(/reconnection:false\}\);/)[0];
  1782. patch(connectionMatch, connectionMatch + `window.bonkHost.bans=[];`);
  1783.  
  1784.  
  1785. //Get some useful objects
  1786. let menuRegex = newStr.match(/== 13\){...\(\);}}/)[0];
  1787. patch(menuRegex, menuRegex + "window.bonkHost.menuFunctions = this; window.bonkHost.wrap();");
  1788. let toolRegex = newStr.match(/=new [A-Za-z0-9\$_]{1,3}\(this,[A-Za-z0-9\$_]{1,3}\[0\]\[0\],[A-Za-z0-9\$_]{1,3}\[0\]\[1\]\);/);
  1789. patch(toolRegex, toolRegex + "window.bonkHost.toolFunctions = this;");
  1790. let infoRegex = newStr.match(/[A-Za-z0-9\$_]{3}\[[0-9]{1,3}\]=\{id:-1,element:null\};/)[0];
  1791. patch(infoRegex, infoRegex + "window.bonkHost.gameInfo = arguments;");
  1792. patch("newbonklobby_votewindow_close", "window.bonkHost.players = arguments[1];" + "newbonklobby_votewindow_close");
  1793. // state handling
  1794. patch("{a:0.0};", "{a:0.0};" + `window.bonkHost.stateFunctions = this;`);
  1795.  
  1796. //Big class
  1797. let bigClass = newStr.match(/[A-Z]\[[A-Za-z0-9\$_]{1,3}\[[0-9]+\]\[[0-9]+\]\]\([A-Za-z0-9\$_]{1,3}\[0\]\[0\]\);[A-Za-z0-9\$_]{1,3}\[[0-9]+\]\[[A-Za-z0-9\$_]{1,3}\[[0-9]+\]\[[0-9]+\]\]\([A-Za-z0-9\$_]{1,3}\[[0-9]+\],{m:/)[0][0];
  1798. patch(`function ${bigClass}(){}`, `function ${bigClass}(){};window.bonkHost.bigClass=${bigClass};`);
  1799.  
  1800. //Function for all callbacks
  1801. let callbacks = [...newStr.match(/[A-Za-z0-9\$_]{3}\(\.\.\./g)];
  1802. for(let callback of callbacks) {
  1803. patch(`function ${callback}`, `window.bonkHost.bonkCallbacks["${callback.split("(")[0]}"] = ${callback.split("(")[0]};` + `function ${callback}`);
  1804. }
  1805.  
  1806. //Frames in game
  1807. let fig = newStr.match(/\[[A-Za-z0-9\$_]{3}\[[0-9]{1,3}\] \- 30\]/)[0].split(' ')[0].slice(1);
  1808. patch(`${fig}++;`, `${fig}++;window.bonkHost.fig=${fig};`);
  1809. console.log("Bonk Host injector run");
  1810. return newStr;
  1811. }
  1812.  
  1813. if(!window.bonkCommands) window.bonkCommands = [];
  1814.  
  1815. if(!window.bonkCodeInjectors) window.bonkCodeInjectors = [];
  1816. window.bonkCodeInjectors.push(bonkCode => {
  1817. try {
  1818. return injector(bonkCode);
  1819. } catch (error) {
  1820. alert(
  1821. `Whoops! Bonk Host was unable to load.
  1822. This may be due to an update to Bonk.io. If so, please report this error!
  1823. This could also be because you have an extension that is incompatible with \
  1824. Bonk Host, such as the Bonk Leagues Client. You would have to disable it to use \
  1825. Bonk Host.`);
  1826. throw error;
  1827. }
  1828. });
  1829.  
  1830. console.log("Bonk Host injector loaded");