Greasy Fork is available in English.

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");