Better Player Info 2

The best info Script!

  1. // ==UserScript==
  2. // @name Better Player Info 2
  3. // @namespace http://tampermonkey.net/
  4. // @version 2.05
  5. // @description The best info Script!
  6. // @author Dikinx(Diamondkingx)
  7. // @match https://zombs.io/*
  8. // @match http://zombs.io/*
  9. // @icon https://www.google.com/s2/favicons?sz=64&domain=vscode.dev
  10. // @grant none
  11. // ==/UserScript==
  12.  
  13. 'use strict'
  14.  
  15. // Helper Functions----->
  16.  
  17. const log = console.log
  18.  
  19. function element(selector) {
  20. return document.querySelector(selector)
  21. }
  22.  
  23. // Simplifies creation of new elements
  24. function createElement(type, attributes = {}, properties = {}, creationOptions = {}) {
  25. const element = document.createElement(type, creationOptions)
  26.  
  27. // Add all the attributes
  28. for (let attribute in attributes)
  29. element.setAttribute(attribute, attributes[attribute])
  30.  
  31. // Add all the js properties
  32. for (let [property, value] of Object.entries(properties))
  33. element[property] = value
  34.  
  35. element.appendTo = function (parent) {
  36. let parentElement
  37.  
  38. if (typeof parent == "string")
  39. parentElement = element(parent)
  40. else if (parent instanceof HTMLElement)
  41. parentElement = parent
  42. else throw new TypeError("Unknown parent type.")
  43.  
  44. if (!parentElement) throw new ReferenceError("Undefined Parent.")
  45.  
  46. parentElement.append(this)
  47. return this
  48. }
  49.  
  50. return element
  51. }
  52.  
  53. // Elements created for the script to use
  54. function defineScriptElement(name, type, attributes = {}, properties = {}, creationOptions = {}) {
  55. // Create the element and define it in the scriptElements object
  56. return main.scriptElements[name] = createElement(type, attributes, { name, ...properties }, creationOptions)
  57. }
  58.  
  59. // A function that only fires once after a transition ends on an element
  60. HTMLElement.prototype.onceontransitionend = function (callback) {
  61. if (typeof callback !== "function") throw new TypeError("'callback' must be a function.")
  62.  
  63. const transitionEndHandler = () => {
  64. callback.bind(this)()
  65. this.removeEventListener("transitionend", transitionEndHandler)
  66. }
  67.  
  68. this.addEventListener("transitionend", transitionEndHandler)
  69. }
  70.  
  71. Math.lerp = function (a, b, c) {
  72. return a + (b - a) * c
  73. }
  74.  
  75. Math.lerpAngles = function (a1, a2, c, returnUnitVec = false) {
  76. let x2 = Math.lerp(Math.cos(a1), Math.cos(a2), c),
  77. y2 = Math.lerp(Math.sin(a1), Math.sin(a2), c),
  78. mag
  79.  
  80. if (returnUnitVec) {
  81. mag = Math.sqrt(x2 ** 2 + y2 ** 2)
  82. return { x: x2 / mag, y: y2 / mag, angle: Math.atan2(x2, y2) }
  83. }
  84.  
  85. return Math.atan2(y2, x2)
  86. }
  87.  
  88. // function toIndianNumberSystem(string = '0') {
  89. // if (string.length <= 3) return string;
  90.  
  91. // const firstPartLength = (string.length - 3) % 2 === 0 ? 2 : 1
  92. // const firstPart = string.slice(0, firstPartLength)
  93. // const rest = string.slice(firstPartLength, -3)
  94. // const lastThree = string.slice(-3)
  95.  
  96. // const formattedRest = rest.match(/.{2}/g)?.join(',') || ''
  97.  
  98. // return firstPart + (formattedRest ? ',' + formattedRest : '') + ',' + lastThree
  99. // }
  100.  
  101.  
  102. function toInternationalNumberSystem(number = '0') {
  103. if (typeof number == "string") return number
  104. if (number.length <= 3) return number
  105. let rest = number.slice(0, number.length % 3)
  106. let groups = number.slice(rest.length).match(/.{3}/g) || []
  107. return (rest ? rest + ',' : '') + groups.join(',')
  108. }
  109.  
  110. function toLargeUnits(number = 0) {
  111. if (typeof number == "string") return number
  112. if (number < 1_000) return number.toString()
  113.  
  114. const units = ['k', 'mil', 'bil', 'tri']
  115. const unitIndex = Math.floor(Math.log10(number) / 3)
  116.  
  117. if (unitIndex >= units.length) return number.toLocaleString()
  118.  
  119. const scaledNumber = number / Math.pow(10, unitIndex * 3)
  120.  
  121. return `${scaledNumber.toFixed(2) / 1}${units[unitIndex - 1]}`
  122. }
  123.  
  124.  
  125. // Main css----->
  126.  
  127. const css = `
  128. :root {
  129. --background-dark: rgb(0 0 0 / .6);
  130. --background-light: rgb(0 0 0 / .4);
  131. --background-verylight: rgb(0 0 0 / .2);
  132. --background-purple: rgb(132 115 212 / .9);
  133. --background-yellow: rgb(214 171 53 / .9);
  134. --background-green: rgb(118 189 47 / .9);
  135. --background-healthgreen: rgb(100 161 10);
  136. --background-orange: rgb(214 120 32 / .9);
  137. --background-red: rgb(203 87 91 / .9);
  138. --text-light: rgb(255 255 255 / .6);
  139. --text-verylight: #eee;
  140. }
  141.  
  142. #mainMenuWrapper {
  143. display: grid;
  144. grid-template-rows: repeat(2, auto);
  145. grid-template-columns: repeat(3, auto);
  146. width: max-content;
  147. height: max-content;
  148. position: absolute;
  149. padding: .625rem;
  150. scale: 0;
  151. opacity: 0;
  152. inset: 0;
  153. z-index: 11;
  154. border-radius: .25rem;
  155. pointer-events: none;
  156. transition: opacity .35s ease-in-out, scale .55s ease-in-out;
  157. }
  158.  
  159. #mainMenuWrapper.open {
  160. scale: 1;
  161. opacity: 1;
  162. }
  163.  
  164. #mainMenuWrapper.moveTransition {
  165. transition: all .35s ease-in-out, opacity .35s ease-in-out, scale .55s ease-in-out;
  166. }
  167.  
  168. #mainMenuWrapper.pinned {
  169. z-index: 16;
  170. }
  171.  
  172. #mainMenuWrapper #mainMenuTagsContainer {
  173. display: grid;
  174. grid-template-columns: auto auto;
  175. align-items: center;
  176. justify-items: center;
  177. gap: .25rem;
  178. width: max-content;
  179. height: max-content;
  180. padding: inherit;
  181. position: relative;
  182. grid-area: 1 / 2;
  183. inset: 0;
  184. margin-bottom: .5rem;
  185. background: var(--background-verylight);
  186. border-radius: inherit;
  187. transition: all .35s ease-in-out;
  188. }
  189.  
  190. #mainMenuWrapper :is(#mainMenuTagsContainer[data-tagscount="1"], #mainMenuTagsContainer:empty) {
  191. gap: 0;
  192. }
  193.  
  194. #mainMenuWrapper #mainMenuTagsContainer:empty {
  195. padding: 0;
  196. margin-bottom: 0;
  197. background: transparent;
  198. transition-delay: 2s;
  199. }
  200.  
  201. #mainMenuWrapper .tag {
  202. width: max-content;
  203. height: max-content;
  204. padding: 0rem 0rem;
  205. position: relative;
  206. opacity: 0;
  207. font-family: 'Hammersmith One', sans-serif;
  208. font-size: 0px;
  209. color: var(--text-verylight);
  210. background: var(--background-verylight);
  211. border-radius: inherit;
  212. transition: all .55s cubic-bezier(.65, .05, .19, 1.02), opacity .65s cubic-bezier(.65, .05, .19, 1.02);
  213. }
  214.  
  215. #mainMenuWrapper .tag.neutral {
  216. color: color-mix(in srgb, var(--background-green) 10%, #eee);
  217. background: var(--background-green);
  218. }
  219.  
  220. #mainMenuWrapper .tag.warning {
  221. color: color-mix(in srgb, var(--background-yellow) 10%, #eee);
  222. background: var(--background-yellow);
  223. }
  224.  
  225. #mainMenuWrapper .tag.error {
  226. margin: 0;
  227. color: color-mix(in srgb, var(--background-red) 10%, #eee);
  228. background: var(--background-red);
  229. }
  230.  
  231. #mainMenuWrapper .tag.active {
  232. padding: .2rem .4rem;
  233. opacity: 1;
  234. font-size: 18px;
  235. }
  236.  
  237. #mainMenuWrapper #mainMenuFeatureContainer {
  238. display: flex;
  239. flex-direction: column;
  240. gap: .25rem;
  241. height: max-content;
  242. position: relative;
  243. grid-area: 2 / 1;
  244. margin-right: .5rem;
  245. border-radius: inherit;
  246. pointer-events: all;
  247. }
  248.  
  249. #mainMenuWrapper #mainMenuFeatureContainer .featureButton {
  250. width: 2.25rem;
  251. height: 2.25rem;
  252. padding: .625rem;
  253. scale: 1;
  254. font-size: 17px;
  255. color: var(--background-light);
  256. background: var(--background-verylight);
  257. outline: none;
  258. border: none;
  259. border-radius: inherit;
  260. cursor: pointer;
  261. transition: background .45s ease-in-out, all .35s ease-in-out, scale .25s cubic-bezier(0, .16, .79, 1.66);
  262. }
  263.  
  264. #mainMenuWrapper #mainMenuFeatureContainer button:hover {
  265. background: var(--background-light);
  266. }
  267.  
  268. #mainMenuWrapper #mainMenuFeatureContainer button.light {
  269. scale: .922;
  270. font-size: 14px;
  271. color: var(--text-verylight);
  272. background: var(--background-light);
  273. }
  274.  
  275. #mainMenuWrapper #otherMenuTogglesContainer {
  276. display: flex;
  277. flex-direction: column;
  278. align-self: flex-end;
  279. gap: 0rem;
  280. width: max-content;
  281. height: max-content;
  282. padding: 0;
  283. position: relative;
  284. grid-area: 2 / 3;
  285. inset: 0 0 0 -350%;
  286. margin-left: 0rem;
  287. opacity: 0;
  288. background: var(--background-verylight);
  289. border-radius: .25rem;
  290. pointer-events: none;
  291. transition: all .35s ease-out, left .4s cubic-bezier(.5, 0, .5, 0), padding .35s ease-in;
  292. }
  293.  
  294. #mainMenuWrapper #otherMenuTogglesContainer.open {
  295. gap: .25rem;
  296. left: 0%;
  297. padding: .625rem;
  298. margin-left: .5rem;
  299. opacity: 1;
  300. pointer-events: all;
  301. transition: all .35s ease-in, left .4s cubic-bezier(.5, 1, .5, 1), padding .35s ease-out, pointer-events 1ms linear .5s;
  302. }
  303.  
  304. #mainMenuWrapper #otherMenuButton {
  305. display: flex;
  306. align-items: center;
  307. justify-content: center;
  308. flex-shrink: 0;
  309. width: 2rem;
  310. height: 2rem;
  311. position: relative;
  312. left: 100%;
  313. translate: -100% 0;
  314. z-index: 1;
  315. font-family: 'Hammersmith One', sans-serif;
  316. font-size: 15px;
  317. color: var(--text-light);
  318. background: var(--background-verylight);
  319. border: none;
  320. border-radius: .25rem;
  321. outline: none;
  322. transition: color .15s ease-in-out, width .35s ease-in-out, background .35s ease-in-out, transform .35s ease-in-out, translate .45s ease-in-out, left .45s ease-in-out;
  323. cursor: pointer;
  324. }
  325. #mainMenuWrapper .toggleButton {
  326. width: 0rem;
  327. height: 0rem;
  328. padding: 0;
  329. position: relative;
  330. opacity: 1;
  331. font-size: 0;
  332. color: var(--text-light);
  333. background: var(--background-verylight);
  334. border: none;
  335. border-radius: inherit;
  336. outline: none;
  337. transition: all .35s ease-in-out, color .15s ease-in-out;
  338. cursor: pointer;
  339. }
  340.  
  341. #mainMenuWrapper :is(#otherMenuButton, .toggleButton):is(:hover, .dark):not(.disabled) {
  342. color: var(--text-verylight);
  343. background: var(--background-light);
  344. }
  345.  
  346. #mainMenuWrapper #otherMenuButton.rotated {
  347. transform: rotateY(180deg);
  348. }
  349.  
  350. #mainMenuWrapper #otherMenuButton.moved {
  351. left: 0%;
  352. translate: 0%;
  353. }
  354.  
  355. #mainMenuWrapper .toggleButton.dark {
  356. color: var(--text-verylight);
  357. background: var(--background-light);
  358. }
  359.  
  360. #mainMenuWrapper .toggleButton.disabled {
  361. opacity: .65;
  362. cursor: not-allowed;
  363. }
  364.  
  365. #mainMenuWrapper #otherMenuTogglesContainer.open > .toggleButton {
  366. width: 2.25rem;
  367. height: 2.25rem;
  368. font-size: 15px;
  369. }
  370.  
  371. #mainMenu {
  372. display: flex;
  373. flex-direction: column;
  374. gap: 1rem;
  375. width: 22.5rem;
  376. height: 14rem;
  377. position: relative;
  378. grid-area: 2 / 2;
  379. inset: 0;
  380. background: var(--background-light);
  381. border-radius: .25rem;
  382. padding: .625rem;
  383. pointer-events: all;
  384. transition: width .35s ease-in-out, height .35s ease-in-out;
  385. }
  386.  
  387. #mainMenu :is(span, p) {
  388. display: inline-block;
  389. height: max-content;
  390. line-height: 100%;
  391. }
  392.  
  393. #mainMenu #mainMenuBody {
  394. width: 100%;
  395. height: 100%;
  396. position: relative;
  397. border-radius: inherit;
  398. }
  399.  
  400. #mainMenuBody .contentHolder {
  401. width: 100%;
  402. height: 100%;
  403. position: absolute;
  404. translate: -100% 0;
  405. z-index: 0;
  406. opacity: 0;
  407. border-radius: inherit;
  408. transition: opacity .45s cubic-bezier(.03, .02, .21, .78), translate .55s cubic-bezier(0, 1, 1, 1);
  409. pointer-events: none;
  410. }
  411.  
  412. #mainMenuBody .contentHolder.opaque {
  413. opacity: 1;
  414. transition: opacity .45s cubic-bezier(.03, .02, .78, .21), translate .55s cubic-bezier(0, 1, 1, 1)
  415. pointer-events: all;
  416. }
  417.  
  418. #mainMenuBody .contentHolder.moved {
  419. translate: 0% 0%;
  420. pointer-events: all;
  421. }
  422.  
  423. #mainMenuBody .contentHolder.moved.opaque {
  424. z-index: 100;
  425. }
  426.  
  427. #mainMenu #header {
  428. display: grid;
  429. grid-template-columns: 1fr 1fr;
  430. }
  431.  
  432. #mainMenu #entityName {
  433. display: block;
  434. margin: 0;
  435. color: #eee;
  436. font-size: 24px;
  437. }
  438.  
  439. #mainMenu #entityUID {
  440. display: block;
  441. margin: 0;
  442. color: var(--text-light);
  443. font-size: 18px;
  444. letter-spacing: .1rem;
  445. }
  446.  
  447. #mainMenu #entityHealthBarsContainer {
  448. display: flex;
  449. justify-self: end;
  450. align-items: center;
  451. justify-content: flex-end;
  452. gap: .25rem;
  453. width: 100%;
  454. height: 100%;
  455. grid-area: 1 / 2 / 3;
  456. }
  457.  
  458. #mainMenu .entityHealth {
  459. width: 0rem;
  460. height: 2.125rem;
  461. padding: .25rem 0 .25rem 0;
  462. position: relative;
  463. opacity: 1;
  464. background: var(--background-verylight);
  465. border-radius: .25rem;
  466. transition: all .35s ease-in-out, opacity .35s ease-in;
  467. }
  468.  
  469. #mainMenu .entityHealth::before {
  470. content: attr(data-name);
  471. display: block;
  472. width: max-content;
  473. height: max-content;
  474. position: absolute;
  475. inset: 50% .5rem;
  476. translate: 0% -50%;
  477. opacity: 0;
  478. font-family: 'Hammersmith One', sans-serif;
  479. color: var(--text-verylight);
  480. font-size: 12px;
  481. text-shadow: 0 0 1px rgb(0 0 0 / .8);
  482. transition: all .15s ease-in;
  483. }
  484.  
  485. #mainMenu .entityHealth.visible {
  486. width: min(6.25rem, 100%);
  487. padding-inline: .25rem;
  488. opacity: 1;
  489. }
  490.  
  491. #mainMenu .entityHealth.visible::before {
  492. opacity: 1;
  493. }
  494.  
  495. #mainMenu .entityHealthBar {
  496. width: 0%;
  497. height: 100%;
  498. background: var(--background-healthgreen);
  499. border-radius: inherit;
  500. transition: width .35s ease-in-out;
  501. }
  502.  
  503. #mainMenu #body {
  504. width: 100%;
  505. height: max-content;
  506. padding: .625rem;
  507. border-radius: inherit;
  508. background: var(--background-verylight);
  509. }
  510.  
  511. #mainMenu #body.infoMenuBody {
  512. display: grid;
  513. grid-template-columns: 1fr 1fr;
  514. align-items: center;
  515. row-gap: .3rem;
  516. margin-top: 1rem;
  517. }
  518.  
  519. #mainMenu .entityInfo {
  520. margin: 0;
  521. opacity: .33;
  522. font-family: 'Open Sans', sans-serif;
  523. font-size: 14px;
  524. color: var(--text-light);
  525. transition: all .35s ease-in-out;
  526. }
  527.  
  528. #mainMenu .entityInfo.visible {
  529. opacity: 1;
  530. }
  531.  
  532. #mainMenu .entityInfo strong {
  533. color: var(--text-verylight);
  534. }
  535.  
  536. #mainMenu .entityInfo span {
  537. display: inline-block;
  538. }
  539.  
  540. #mainMenu #body.spectateMenuBody {
  541. width: 100%;
  542. height: 400%;
  543. max-height: 100%;
  544. overflow: hidden;
  545. border-radius: inherit;
  546. }
  547.  
  548. #mainMenu .spectateButton {
  549. width: 100%;
  550. height: 100%;
  551. padding-bottom: 0rem;
  552. border-radius: inherit;
  553. opacity: 0;
  554. transition: opacity .35s ease-in-out, height .45s ease-in-out, padding .45s ease-in-out;
  555. }
  556.  
  557. #mainMenu .spectateButton button {
  558. display: flex;
  559. flex-direction: column;
  560. align-items: flex-start;
  561. justify-content: center;
  562. width: 100%;
  563. height: 100%;
  564. padding: .625rem;
  565. scale: 1;
  566. font-size: 2rem;
  567. font-family: 'Hammersmith One';
  568. color: var(--text-verylight);
  569. background: var(--background-verylight);
  570. border: none;
  571. border-radius: inherit;
  572. outline: none;
  573. overflow: hidden;
  574. pointer-events: inherit;
  575. cursor: pointer;
  576. transition: background .35s ease-in-out, scale .35s cubic-bezier(0, .16, .79, 1.66);
  577. }
  578.  
  579. #mainMenu .spectateButton button:hover {
  580. background: var(--background-light);
  581. }
  582.  
  583. #mainMenu .spectateButton button:active {
  584. scale: .95;
  585. }
  586.  
  587. #mainMenu #body.spectateMenuBody .spectateButton.visible{
  588. opacity: 1
  589. }
  590.  
  591. #mainMenu #body.spectateMenuBody[data-playercount="1"] .spectateButton.visible{
  592. height: 100%;
  593. }
  594.  
  595. #mainMenu #body.spectateMenuBody[data-playercount="2"] .spectateButton.visible{
  596. height: 50%;
  597. }
  598.  
  599. #mainMenu #body.spectateMenuBody[data-playercount="3"] .spectateButton.visible{
  600. height: 33.33%;
  601. }
  602.  
  603. #mainMenu .spectateMenuBody .wrapper {
  604. display: grid;
  605. grid-template-columns: 1fr;
  606. grid-template-rows: 1fr auto auto;
  607. gap: .4rem 0rem;
  608. width: 100%;
  609. height: 90%;
  610. border-radius: inherit;
  611. transition: all .45s ease-in-out;
  612. }
  613.  
  614. #mainMenu .spectateButton span.name {
  615. justify-self: flex-start;
  616. align-self: center;
  617. max-width: min(18ch, 100%);
  618. text-overflow: ellipsis;
  619. overflow: hidden;
  620. transition: all .35s ease-in-out;
  621. }
  622.  
  623. #mainMenu .spectateButton[data-partyposition="0"] .tag.position {
  624. background: var(--background-purple);
  625. color: color-mix(in srgb, var(--background-purple) 10%, #eee);
  626. }
  627.  
  628. #mainMenu .spectateButton[data-partyposition="1"] .tag.position {
  629. background: var(--background-yellow);
  630. color: color-mix(in srgb, var(--background-yellow) 10%, #eee)
  631. }
  632.  
  633. #mainMenu .spectateButton[data-partyposition="2"] .tag.position {
  634. background: var(--background-green);
  635. color: color-mix(in srgb, var(--background-green) 10%, #eee)
  636. }
  637.  
  638. #mainMenu .spectateButton[data-partyposition="3"] .tag.position {
  639. background: var(--background-orange);
  640. color: color-mix(in srgb, var(--background-orange) 10%, #eee)
  641. }
  642.  
  643. #mainMenu :is(#body.spectateMenuBody[data-playercount="2"]) .wrapper {
  644. grid-template-columns: repeat(2, max-content);
  645. grid-template-rows: 1fr 1fr;
  646. gap: 0rem .2rem;
  647. height: 100%;
  648. }
  649.  
  650. #mainMenu :is(#body.spectateMenuBody[data-playercount="2"], #body.spectateMenuBody[data-playercount="3"]) .wrapper span.name {
  651. justify-self: flex-start;
  652. grid-area: 1 / 4 / 1 / 1;
  653. font-size: 1.25rem;
  654. }
  655.  
  656. #mainMenu :is(#body.spectateMenuBody[data-playercount="2"], #body.spectateMenuBody[data-playercount="3"]) .wrapper .tag {
  657. font-size: 12px;
  658. }
  659.  
  660. #mainMenu #body.spectateMenuBody[data-playercount="3"] .wrapper {
  661. grid-template-columns: 1fr repeat(2, max-content);
  662. grid-template-rows: 1fr;
  663. gap: 0rem .2rem;
  664. height: 100%;
  665. }
  666.  
  667. #mainMenu #body.spectateMenuBody[data-playercount="3"] .wrapper span.name{
  668. grid-area: 1 / 1;
  669. }
  670.  
  671. #entityFollower {
  672. position: absolute;
  673. translate: -50% -50%;
  674. opacity: 0;
  675. font-size: 22px;
  676. transition: opacity .35s ease-in-out, font-size .35s ease-in-out;
  677. }
  678.  
  679. #entityFollower.visible {
  680. opacity: 1;
  681. }
  682.  
  683. #entityFollower i {
  684. display: block;
  685. width: max-content;
  686. height: max-content;
  687. position: absolute;
  688. inset: 50%;
  689. translate: -50% -50%;
  690. rotate: 45deg;
  691. color: var(--text-light);
  692. -webkit-text-stroke: .12em rgb(42 42 42 / .9);
  693. }
  694. `
  695.  
  696. // Create the element and append it on script's initialization
  697. const style = createElement("style")
  698. style.append(document.createTextNode(css))
  699.  
  700. //Main Constants----->
  701.  
  702. // Main controller constant, and it's functions
  703. const main = {
  704. settings: {
  705. targetableEntityClass: ["PlayerEntity", "Prop", "Npc"],
  706. mouseCollisionCheckFPS: 24,
  707. menuUpdateFPS: 20,
  708. backgroundMenuUpdateFPS: 10,
  709. activationKey: "control",
  710. paused: false,
  711. },
  712. gameElements: {},
  713. scriptElements: {},
  714. cursor: {
  715. x: innerWidth / 2,
  716. y: innerHeight / 2
  717. },
  718. controls: {
  719. listenedKeys: ["control"],
  720. },
  721. menu: {
  722. mainMenuName: "infoMenu",
  723. navigationStack: [],
  724. features: {},
  725. pinned: false,
  726. },
  727. // This data is not in the game but on the wiki
  728. gameData: {
  729. towers: {
  730. SlowTrap: {
  731. slowAmount: [40, 45, 50, 55, 60, 65, 70, 70]
  732. },
  733. Harvester: {
  734. attackSpeed: [1500, 1400, 1300, 1200, 1100, 1000, 900, 800]
  735. },
  736. MeleeTower: {
  737. attackSpeed: [400, 333, 284, 250, 250, 250, 250, 250]
  738. },
  739. BombTower: {
  740. attackSpeed: [1000, 1000, 1000, 1000, 1000, 1000, 900, 900]
  741. },
  742. MagicTower: {
  743. attackSpeed: [800, 800, 704, 602, 500, 400, 300, 300]
  744. }
  745. },
  746. pets: {
  747. PetCARL: {
  748. speed: [15, 16, 17, 17.5, 17.5, 18.5, 18.5, 19],
  749. },
  750. PetMiner: {
  751. speed: [30, 32, 34, 35, 35, 37, 37, 38],
  752. resourceGain: [1, 1, 2, 2, 3, 3, 4, 4]
  753. },
  754. }
  755. },
  756. inGame: false,
  757. }
  758.  
  759. // This proxy allows tracking of value additions or removals from the stack.
  760. const navigationStackProxyHandler = {
  761. get(stack, property) {
  762. const value = stack[property]
  763.  
  764. // If the accessed property is a function, wrap it to track stack modifications
  765. if (typeof value == "function") {
  766. return function (...args) {
  767. const returnValue = value.apply(stack, args)
  768.  
  769. // Invoke the appropriate callback when a value is added or removed
  770. if (property == "push") stack.onAddCallback?.()
  771. else if (property == "pop") stack.onRemoveCallback?.()
  772.  
  773. return returnValue
  774. }
  775. }
  776. // Otherwise, return the accessed value
  777. return value
  778. }
  779. }
  780. main.menu.navigationStack = new Proxy(main.menu.navigationStack, navigationStackProxyHandler)
  781.  
  782. // Define the function to set a new active menu
  783. main.menu.setActiveMenu = function (name, onActivatedArgs = [], forceIntoNavigationStack = false) {
  784. if (name == this.activeMenu)
  785. return
  786.  
  787. // Push it to the navigationStack to enable navigation back if necessary
  788. if ((name !== this.mainMenuName && name !== this.navigationStack[this.navigationStack.length - 1]) || forceIntoNavigationStack)
  789. this.navigationStack.push(name)
  790.  
  791. const prevMenuObject = this.activeMenuObject,
  792. foundMenu = this.activeMenuObject = Menu.getActiveMenuObject(name)
  793.  
  794. if (!foundMenu)
  795. throw new SyntaxError(`Cannot find Menu ${name}.\nAvailable Menus: ${Menu.getAvailableMenuNames().join(', ')}`)
  796.  
  797. // Activate the new menu and pass any arguments for when a menu is activated
  798. foundMenu.activate(...onActivatedArgs)
  799.  
  800. foundMenu.toggleButton?.setState(1, 0)
  801. this.activeMenu = name
  802.  
  803. if (!prevMenuObject)
  804. return
  805.  
  806. prevMenuObject.hideAllTags()
  807. prevMenuObject.toggleButton?.setState(0, 0)
  808. }
  809.  
  810. // Define the function to add a new fature button
  811. main.menu.defineFeature = function (name, icon, activationType, ...callbacks) {
  812. const buttonElement = createElement("button", { class: `featureButton ${activationType}` }, {
  813. active: false,
  814. innerHTML: `<i class="${icon}"></i>`,
  815. setState(light) {
  816. this.classList.toggle("light", light)
  817. },
  818. }).appendTo(main.scriptElements.mainMenuFeatureContainer)
  819.  
  820. // Bind all calbacks to refer to the buttonElement
  821. callbacks = callbacks.map(callback => callback.bind(buttonElement))
  822.  
  823. switch (activationType) {
  824. case "toggle": {
  825. buttonElement.onclick = function (event) {
  826. this.setState(this.active = !this.active)
  827. callbacks[0]?.(event)
  828. }
  829. // This is to prevent the player from attacking
  830. buttonElement.onmousedown = function (event) {
  831. event.stopImmediatePropagation()
  832. }
  833. }
  834. break
  835. case "hold": {
  836. buttonElement.onmousedown = function (event) {
  837. event.stopImmediatePropagation()
  838. this.setState(this.active = true)
  839. callbacks[0]?.(event)
  840. }
  841. addEventListener("mouseup", function (event) {
  842. buttonElement.setState(buttonElement.active = false)
  843. callbacks[1]?.(event)
  844. })
  845. }
  846. break
  847. case "click": {
  848. buttonElement.onmouseenter = function (event) {
  849. this.setState(this.active = true)
  850. callbacks[1]?.(event)
  851. }
  852. buttonElement.onmouseleave = function (event) {
  853. this.setState(this.active = false)
  854. callbacks[2]?.(event)
  855. }
  856. buttonElement.onclick = callbacks[0]
  857. // This is to prevent the player from attacking
  858. buttonElement.onmousedown = function (event) {
  859. event.stopImmediatePropagation()
  860. }
  861. }
  862. }
  863.  
  864. main.menu.features[name] = {
  865. name,
  866. activationType,
  867. icon,
  868. element: buttonElement,
  869. callbacks,
  870. }
  871. }
  872.  
  873. // Define the 'main' variable in global scope for accessibility from the console
  874. window.bpi2 = main
  875.  
  876. // Classes----->
  877.  
  878. // Define the menu class
  879. class Menu {
  880. // Store all defined menus
  881. static DefinedMenus = []
  882.  
  883. static getActiveMenuObject(activeMenuName) {
  884. return this.DefinedMenus.find(menu => menu.name === activeMenuName)
  885. }
  886.  
  887. static getAvailableMenuNames() {
  888. return Menu.DefinedMenus.map((menu) => {
  889. return menu.name
  890. })
  891. }
  892.  
  893. static createContentHolder(template) {
  894. const contentHolder = createElement("div",
  895. {
  896. class: "contentHolder"
  897. },
  898. {
  899. innerHTML: template,
  900. setState(moved, opaque) {
  901. this.classList.toggle("moved", moved)
  902. this.classList.toggle("opaque", opaque)
  903. },
  904. }).appendTo(main.scriptElements.mainMenuBody)
  905. return contentHolder
  906. }
  907.  
  908. constructor(name, template, hasToggleButton = true, toggleButtonIcon, toggleButtonIndex = 0, isState = false) {
  909. if (Menu.getAvailableMenuNames().includes(name)) throw new SyntaxError(`Duplicate Menu name, ${name}.`)
  910. this.name = name
  911. this.template = template
  912. this.hasToggleButton = hasToggleButton
  913. this.isState = isState
  914. this.active = false
  915. this.type = "empty"
  916.  
  917. if (!isState) {
  918. this.activeState = "none"
  919. this.hasStates = false
  920. this.tags = []
  921. this.canUpdateTags = false
  922. }
  923.  
  924. this.defineMainElements(template)
  925.  
  926. if (hasToggleButton && !isState)
  927. this.defineToggleButton(toggleButtonIcon, toggleButtonIndex)
  928.  
  929. Menu.DefinedMenus.push(this)
  930. }
  931. // Defines the main header, body and footer elements of a given menu
  932. defineMainElements(template) {
  933. this.contentHolder = Menu.createContentHolder(template)
  934. this.header = this.contentHolder.querySelector("#header")
  935. this.body = this.contentHolder.querySelector("#body")
  936. this.footer = this.contentHolder.querySelector("#footer")
  937. }
  938. // Defines the toggle button for the menu
  939. defineToggleButton(icon, index = 0) {
  940. const toggleButtonContainer = main.scriptElements.otherMenuTogglesContainer
  941. this.toggleButton = toggleButtonContainer.addToggleButton(this.name,
  942. () => {
  943. if (main.menu.activeMenu == this.name)
  944. return
  945. main.menu.setActiveMenu(this.toggleButton.dataset.menuname)
  946. //Wait a bit so the user is ready for the change
  947. this.contentHolder.onceontransitionend(() => toggleButtonContainer.setState(0))
  948. }, icon, index)
  949. }
  950.  
  951. initStates() {
  952. this.definedStates = []
  953. this.stateObject = this.stateMenu = this.stateContentHolder = null
  954.  
  955. this.defineState = function (name, menu, callback = () => { }) {
  956. if (!menu.isState) throw new TypeError("A stateMenu should be a state. Define the menu with isState true.")
  957. this.definedStates.push({ name, menu, callback })
  958. }
  959.  
  960. this.showState = function (stateObject) {
  961. if (stateObject) this.contentHolder.setState(0, 0)
  962. else return this.hideAllStates()
  963.  
  964. this.definedStates.forEach(state => {
  965. if (state.menu.active = state.name == stateObject.name)
  966. state.menu.contentHolder.setState(1, 1)
  967. else state.menu.contentHolder.setState(0, 0)
  968. })
  969.  
  970. this.resizeMenuCallback?.()
  971. this.onStateChangeCallback?.()
  972. }
  973.  
  974. this.hideAllStates = function () {
  975. this.definedStates.forEach(state => state.menu.contentHolder.setState(0, 0))
  976. this.contentHolder.setState(1, 1)
  977.  
  978. this.resizeMenuCallback?.()
  979. this.onStateChangeCallback?.()
  980. }
  981.  
  982. this.setState = function (value) {
  983. // Check if the state is already set
  984. if (value == this.activeState)
  985. return
  986.  
  987. // Find the new state
  988. const foundState = this.definedStates.find(state => state.name == value)
  989.  
  990. // If no state is found then return back to the empty state
  991. if (!foundState)
  992. return this.hideAllStates()
  993.  
  994. // Update the info about current state
  995. this.stateObject = foundState
  996. this.stateMenu = this.stateObject.menu
  997. this.stateContentHolder = this.stateMenu.contentHolder
  998.  
  999. // Update current state name only after everything is done properly
  1000. this.activeState = value
  1001. // Show the state
  1002. return this.showState(foundState)
  1003. }
  1004.  
  1005. this.hasStates = true
  1006. return this
  1007. }
  1008.  
  1009. activate() {
  1010. if (this.active) return
  1011. setTimeout(() => this.canUpdateTags = true, 1200);
  1012.  
  1013. // If there is an active state, display its content; otherwise, display the default content.
  1014. // We can't rely on the showState function because states might not be initialized.
  1015. (this.activeState != "none" ? this.stateContentHolder : this.contentHolder).setState(1, 1)
  1016.  
  1017. // Iterate through defined menus to update their states
  1018. Menu.DefinedMenus.forEach(menu => {
  1019. // Deactivate the other menus
  1020. if (menu.name != this.name && menu.name != this.stateMenu?.name)
  1021. menu.deactivate()
  1022. })
  1023.  
  1024. // Execute the callback function when the menu is activated, passing any arguments from setActiveMenu
  1025. this.onActivatedCallback?.(...arguments)
  1026. this.resizeMenuCallback?.()
  1027. this.active = true
  1028. }
  1029.  
  1030. deactivate() {
  1031. (this.hasStates && this.activeState != "none" ? this.stateContentHolder : this.contentHolder).setState(0, 0)
  1032. this.active = this.canUpdateTags = false
  1033. }
  1034.  
  1035. defineTag(name, type, callback, removalFrequency = 0) {
  1036. const tagElement = createElement("span",
  1037. {
  1038. class: `tag ${type}`,
  1039. "data-name": name.replaceAll(" ", ""),
  1040. "data-removalfrequency": removalFrequency
  1041. },
  1042. {
  1043. textContent: name
  1044. })
  1045.  
  1046. callback = callback.bind(this)
  1047.  
  1048. const tagObject = {
  1049. name,
  1050. type,
  1051. callback,
  1052. removalFrequency,
  1053. element: tagElement,
  1054. state: "closed",
  1055. show() {
  1056. // return if tag is already open
  1057. if (this.state === "open") return
  1058.  
  1059. const container = main.scriptElements.mainMenuTagsContainer
  1060. let inserted = false
  1061.  
  1062. // This code arranges tags according to their likelihood of being 'shown'.
  1063. if (container.childElementCount) {
  1064. for (let element of Array.from(container.children)) {
  1065. if (this.removalFrequency <= element.dataset.removalfrequency || !element.classList.contains("active")) {
  1066. element.insertAdjacentElement("beforebegin", this.element)
  1067. inserted = true
  1068. break
  1069. }
  1070. }
  1071. }
  1072.  
  1073. // 'inserted' will only be false when no tag is in the tagsContainer, hence we can just append this tag
  1074. if (!inserted)
  1075. container.append(this.element)
  1076.  
  1077. // Update this attribute to get the attribute from css and change the styling
  1078. container.dataset.tagscount = parseInt(container.dataset.tagscount) + 1
  1079.  
  1080. // Use requestAnimationFrame for optimal DOM update timing, and to be stop any visual glitches
  1081. requestAnimationFrame(() => this.element.classList.add("active"))
  1082.  
  1083. this.state = "open"
  1084. },
  1085. hide() {
  1086. // Return if tag is closed or the element doesn't exist
  1087. if (this.state === "closed" || this.state === "closing" || !this.element)
  1088. return
  1089.  
  1090. const container = main.scriptElements.mainMenuTagsContainer
  1091.  
  1092. // Remove the 'active' class to hide the tag
  1093. this.element.classList.remove("active")
  1094. // Update the tag count attribute in the container
  1095. container.dataset.tagscount = parseInt(container.dataset.tagscount) - 1
  1096.  
  1097. // Remove the element from DOM after transition ends
  1098. this.element.onceontransitionend(() => {
  1099. this.element.remove()
  1100. this.state = "closed"
  1101. })
  1102.  
  1103. this.state = "closing"
  1104. }
  1105. }
  1106.  
  1107. this.tags.push(tagObject)
  1108. }
  1109.  
  1110. removeTag(name) {
  1111. // Find the tag with the specified display name
  1112. const tag = this.tags.find(tag => tag.name == name)
  1113.  
  1114. if (!tag) return
  1115.  
  1116. tag.hide()
  1117.  
  1118. // Filter out the tag from the tags array
  1119. this.tags = this.tags.filter(tag => tag.name != name)
  1120. }
  1121.  
  1122. hideAllTags() {
  1123. this.tags.forEach(tag => tag.hide())
  1124. }
  1125.  
  1126. setUpdateCallback(callback) {
  1127. this.updateCallback = callback
  1128. }
  1129.  
  1130. setBackgroundUpdateCallback(callback) {
  1131. this.backgroundUpdateCallback = callback
  1132. }
  1133.  
  1134. setOnActivatedCallback(callback) {
  1135. this.onActivatedCallback = callback
  1136. }
  1137.  
  1138. setOnStateChangeCallback(callback) {
  1139. this.onStateChangeCallback = callback
  1140. }
  1141.  
  1142. setResizeMenuCallback(callback) {
  1143. this.resizeMenuCallback = callback
  1144. }
  1145.  
  1146. updateTags(forceUpdate) {
  1147. // Exit early if tag updates are not allowed and no force update is requested
  1148. if (!this.canUpdateTags && !forceUpdate) return
  1149.  
  1150. // Iterate through all tags
  1151. this.tags.forEach(tag => {
  1152. // Execute the callback function of the tag
  1153. if (tag.callback())
  1154. return tag.show() // Show the tag if callback returns true
  1155.  
  1156. // Hide the tag if callback returns false
  1157. tag.hide()
  1158. })
  1159. }
  1160.  
  1161. updateStates(forceUpdate) {
  1162. // Exit early if there are no states defined and no force update is requested
  1163. if (!this.hasStates && !forceUpdate) return
  1164.  
  1165. // Iterate through defined states
  1166. for (let state of this.definedStates) {
  1167. // Execute the callback function of the state
  1168. if (state.callback()) {
  1169. this.setState(state.name)
  1170. return this.stateMenu.update()
  1171. }
  1172. }
  1173.  
  1174. // Hide all states if no state is set
  1175. if (this.activeState != "none") {
  1176. this.activeState = "none"
  1177. this.hideAllStates()
  1178. }
  1179. }
  1180.  
  1181. update() {
  1182. this.updateTags()
  1183.  
  1184. // Check if any state is updated
  1185. if (this.updateStates())
  1186. return
  1187.  
  1188. // Execute the updateCallback function if no state is updated
  1189. this.updateCallback?.()
  1190. }
  1191. }
  1192.  
  1193. class InfoMenu extends Menu {
  1194. static Template = `
  1195. <section id="header">
  1196. <h2 id="entityName" data-forattr="name">Entity</h2>
  1197. <h3 id="entityUID" data-forattr="uid">UID: <span>9817265</span></h3>
  1198. <div id="entityHealthBarsContainer"></div>
  1199. </div>
  1200. </section>
  1201. <section id="body" class="infoMenuBody"></section>`
  1202.  
  1203. constructor(name, updatedAttributes = [], hasToggleButton = true, toggleButtonIcon, isState = false) {
  1204. super(name, InfoMenu.Template, hasToggleButton, toggleButtonIcon, 0, isState)
  1205. this.type = "info"
  1206. // Make sure the place or the order in which attributes are presented can be controlled
  1207. this.updatedAttributes = updatedAttributes.sort((a, b) => { return (a.index || 0) < (b.index || 0) ? -1 : 1 })
  1208. this.infoElements = {}
  1209. this.healthBars = []
  1210. this.header.healthBarsContainer = this.header.querySelector("#entityHealthBarsContainer")
  1211. this.parseHeaderElements()
  1212. this.parseUpdatedAttributes()
  1213. }
  1214.  
  1215. createInfoElement(name, referenceName, activationCondition = () => { return true }, value, isVisible = true, index = 0, type = "text") {
  1216. const infoElement = createElement("p",
  1217. {
  1218. class: "entityInfo" + (isVisible ? " visible" : "")
  1219. },
  1220. {
  1221. innerHTML: `<span>${name}:</span> <strong>${0}</strong>`,
  1222. name,
  1223. referenceName,
  1224. activationCondition,
  1225. value,
  1226. type,
  1227. isVisible,
  1228. index,
  1229. setValue(value) {
  1230. let toSetValue = value ?? null
  1231. // if (this.type == "number" && toSetValue != null) {
  1232. // switch (this.displayNumberSystem) {
  1233. // case 0:
  1234. // toSetValue = toLargeUnits(toSetValue)
  1235. // break;
  1236. // case 1:
  1237. // toSetValue = toInternationalNumberSystem(new String(Math.floor(toSetValue)))
  1238. // break
  1239. // }
  1240. // }
  1241. return this.querySelector("strong").textContent = toSetValue
  1242. }
  1243. }).appendTo(this.body)
  1244.  
  1245. if (type == "number") {
  1246. infoElement.displayNumberSystem = 0
  1247. infoElement.addEventListener("mousedown", () => infoElement.displayNumberSystem = (infoElement.displayNumberSystem + 1) % 3)
  1248. }
  1249.  
  1250. this.infoElements[name] = infoElement
  1251. }
  1252.  
  1253. createHealthBar(name, currentValRef, maxValRef, activationCondition = () => { return true }, barColor = getComputedStyle(document.body).getPropertyValue("--background-healthgreen"), isVisible = true) {
  1254. const healthBar = createElement("div",
  1255. {
  1256. class: `entityHealth` + (isVisible ? " visible" : ""),
  1257. 'data-name': name,
  1258. },
  1259. {
  1260. innerHTML: `<div class="entityHealthBar"></div>`,
  1261. currentValRef,
  1262. maxValRef,
  1263. activationCondition,
  1264. barColor,
  1265. isVisible
  1266. }).appendTo(this.header.healthBarsContainer)
  1267.  
  1268. this.healthBars.push(healthBar)
  1269.  
  1270. healthBar.bar = healthBar.querySelector("div")
  1271. healthBar.bar.style.background = barColor
  1272. }
  1273.  
  1274. parseHeaderElements() {
  1275. for (let child of this.header.children)
  1276. this.infoElements[child.dataset.forattr] = child
  1277. }
  1278.  
  1279. parseUpdatedAttributes() {
  1280. this.updatedAttributes.forEach(attribute => {
  1281. if (attribute.isBar === true)
  1282. return this.createHealthBar(attribute.name, attribute.currentValRef, attribute.maxValRef, attribute.activationCondition, attribute.barColor, attribute.isVisible)
  1283. this.createInfoElement(attribute.name, attribute.referenceName, attribute.activationCondition, attribute.value, attribute.isVisible, attribute.index, attribute.type)
  1284. })
  1285. }
  1286.  
  1287. updateInfo(entity) {
  1288. let element
  1289. for (let property in this.infoElements) {
  1290. element = this.infoElements[property]
  1291.  
  1292. // Set value if the element has a setValue function and call it with the corresponding entity property
  1293. if (element.classList.contains("entityInfo")) {
  1294. element.classList.toggle("visible", element.activationCondition(entity))
  1295.  
  1296. if (element.value)
  1297. element.setValue(typeof element.value == "function" ? element.value(entity) : element.value)
  1298. else element.setValue(entity.targetTick[typeof element.referenceName == "function" ? element.referenceName() : element.referenceName])
  1299.  
  1300. continue
  1301. }
  1302.  
  1303.  
  1304. switch (element.dataset.forattr) {
  1305. // Set element's text content to entity's name or model if name is not available
  1306. case "name":
  1307. element.textContent = entity.targetTick.name || entity.targetTick.model
  1308. break
  1309. // Set the text content of the span element to the entity's UID
  1310. case "uid":
  1311. element.querySelector("span").textContent = entity.targetTick.uid
  1312. break
  1313. }
  1314. }
  1315.  
  1316. let percentage
  1317. for (let healthBar of this.healthBars) {
  1318. percentage = entity.targetTick[healthBar.currentValRef] / entity.targetTick[healthBar.maxValRef] * 100
  1319. healthBar.classList.toggle("visible", healthBar.activationCondition(entity))
  1320. healthBar.bar.style.width = `${percentage}%`
  1321. }
  1322. }
  1323.  
  1324. updateCallback() {
  1325. const activeEntity = main.menu.activeEntity
  1326.  
  1327. if (activeEntity)
  1328. this.updateInfo(activeEntity)
  1329. }
  1330.  
  1331. resizeMenuCallback() {
  1332. // Hardcoded min size(.9) for now
  1333. main.scriptElements.mainMenu.resize(.9 + (this.activeState != "none" && this.stateMenu.updatedAttributes ? this.stateMenu : this).updatedAttributes.length / 36)
  1334. }
  1335. }
  1336.  
  1337. // Other Handler Functions----->
  1338. function checkMouseCollisionWithEntity() {
  1339. // Check if the activation key is pressed down
  1340. if (main.controls[main.settings.activationKey + "Down"] !== true || (main.menu.activeMenu != main.menu.mainMenuName && main.scriptElements.mainMenuWrapper.open))
  1341. return
  1342.  
  1343. let entityObj, entityWidth, entityHeight, entityTick, posScreen, distX, distY, circleRadius, dist
  1344.  
  1345. // Iterate through each entity in the world
  1346. Object.entries(game.world.entities).forEach(entity => {
  1347. entityObj = entity[1]
  1348.  
  1349. // Check if the entity belongs to a targetable class
  1350. if (!main.settings.targetableEntityClass.includes(entityObj.entityClass))
  1351. return
  1352.  
  1353. // Get the entity's width and height
  1354. entityWidth = entityObj.currentModel.base.sprite.width
  1355. entityHeight = entityObj.currentModel.base.sprite.height
  1356.  
  1357. // Throw an error if width or height is missing
  1358. if (!entityWidth || !entityHeight) throw new ReferenceError("Cannot check collision without width and height.")
  1359.  
  1360. entityTick = entityObj.targetTick
  1361. posScreen = game.renderer.worldToScreen(entityTick.position.x, entityTick.position.y)
  1362. distX = main.cursor.x - posScreen.x
  1363. distY = main.cursor.y - posScreen.y
  1364. circleRadius = Math.max(entityHeight, entityWidth) / 3
  1365. dist = Math.sqrt(distX ** 2 + distY ** 2)
  1366.  
  1367. // Check if the mouse is within the circle radius of the entity
  1368. if (dist > circleRadius)
  1369. return
  1370.  
  1371. // Set the active entity's UID and update the menu state
  1372. main.menu.activeEntityUID = entityTick.uid
  1373.  
  1374. main.scriptElements.mainMenuWrapper.setState(1)
  1375. main.scriptElements.mainMenuWrapper.moveToEntity(entityObj)
  1376. main.scriptElements.entityFollower.followEntity(entityObj.targetTick.uid)
  1377. })
  1378. }
  1379.  
  1380. function updateActiveEntity() {
  1381. // Check if the active entity still exists in the world
  1382. if (!game.world.entities[main.menu.activeEntityUID]) {
  1383. // If the active entity exists, update the last active entity
  1384. if (main.menu.activeEntity)
  1385. main.menu.lastActiveEntity = main.menu.activeEntity
  1386. main.menu.activeEntity = undefined
  1387. return
  1388. }
  1389.  
  1390. // Update the active entity with the current entity data
  1391. // Had to do this because objects are passed by reference
  1392. const entity = game.world.entities[main.menu.activeEntityUID]
  1393. main.menu.activeEntity = { entity, targetTick: entity.targetTick }
  1394. }
  1395.  
  1396. // Function responsible for initializing the main script, triggered on DOMContentLoaded event----->
  1397.  
  1398. function script() {
  1399. // DOM Manipulation----->
  1400.  
  1401. // Return if hud is undefined
  1402. if (!document.querySelector(".hud"))
  1403. return
  1404.  
  1405. // Append style element to the head of the document
  1406. document.head.append(style)
  1407.  
  1408. // Append style element for font-awesome to the head of the document
  1409. document.head.append(createElement("link", { rel: "stylesheet", href: "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" }))
  1410.  
  1411. // Define and append main menu wrapper element
  1412. defineScriptElement("mainMenuWrapper", "div", { id: "mainMenuWrapper" }, {
  1413. open: false,
  1414.  
  1415. x: 0,
  1416. y: 0,
  1417. width: 0,
  1418. height: 0,
  1419.  
  1420. // Set the state of the main menu wrapper
  1421. setState(open) {
  1422. // After transitionEnds wait for a delay before updates start
  1423. this.onceontransitionend(() => setTimeout(() => this.open = open, 150))
  1424. this.classList.toggle("open", open)
  1425. main.scriptElements.entityFollower.setState(open)
  1426. },
  1427.  
  1428. // Move the main menu wrapper to the specified coordinates
  1429. moveTo(x = this.x, y = this.y, pivotX = 0, pivotY = 0, transition = false) {
  1430. const boundingBox = {
  1431. width: this.scrollWidth,
  1432. height: this.scrollHeight
  1433. }
  1434.  
  1435. let xPercent, yPercent, xTranslate = 0, yTranslate = 0
  1436.  
  1437. this.classList.toggle("moveTransition", transition)
  1438.  
  1439. // Move the menu in relation to the pivot
  1440. x -= boundingBox.width * pivotX
  1441. y -= boundingBox.height * pivotY
  1442.  
  1443. // Using min-max instead of ifs to add bounds for the menu
  1444. this.x = x = Math.round(Math.max(0, Math.min(x, innerWidth)))
  1445. this.y = y = Math.round(Math.max(0, Math.min(y, innerHeight)))
  1446.  
  1447. if (x > innerWidth - boundingBox.width)
  1448. xTranslate = xPercent = 100
  1449. // ^ This stops a bug where the otherMenuTogglesContainer is out of the screen
  1450. else xPercent = (x / innerWidth) * 100
  1451.  
  1452. if (y > innerHeight - boundingBox.height)
  1453. yTranslate = yPercent = 100
  1454. else yPercent = (y / innerHeight) * 100
  1455.  
  1456. this.style.left = `${xPercent}%`
  1457. this.style.top = `${yPercent}%`
  1458. this.style.translate = `${-xTranslate}% ${-yTranslate}%`
  1459.  
  1460. this.width = boundingBox.width
  1461. this.height = boundingBox.height
  1462. },
  1463.  
  1464. moveToEntity(entity) {
  1465. const menu = main.scriptElements.mainMenu,
  1466. posScreen = game.renderer.worldToScreen(entity.targetTick.position.x, entity.targetTick.position.y)
  1467.  
  1468. this.moveTo(posScreen.x, posScreen.y, .5 + (1 - menu.scrollWidth / this.width) / 2, 1.22, true)
  1469. }
  1470. }).appendTo(element(".hud"))
  1471.  
  1472. // Define and append main menu element
  1473. defineScriptElement("mainMenu", "div", { id: "mainMenu" }, {
  1474. resize(ratio) {
  1475. this.style.width = `${22.5 * ratio}rem`
  1476. this.style.height = `${14 * ratio}rem`
  1477. }
  1478. }).appendTo(main.scriptElements.mainMenuWrapper)
  1479.  
  1480. // Define and append main menu tags container element
  1481. defineScriptElement("mainMenuTagsContainer", "div", { id: "mainMenuTagsContainer", "data-tagscount": 0 }).appendTo(main.scriptElements.mainMenuWrapper)
  1482.  
  1483. // Define and append main menu feature buttons container
  1484. defineScriptElement("mainMenuFeatureContainer", "div", { id: "mainMenuFeatureContainer" }).appendTo(main.scriptElements.mainMenuWrapper)
  1485.  
  1486. // Define the feature for pinning the menu
  1487. main.menu.defineFeature("pin", "fa-solid fa-location-pin", "toggle", () => main.scriptElements.mainMenuWrapper.classList.toggle("pinned", main.menu.pinned = !main.menu.pinned))
  1488.  
  1489. // Function to handle the movement of the menu
  1490. function moveFeatureHandler() {
  1491. const eleBoundingBox = main.menu.features.move.element.getBoundingClientRect(),
  1492. wrapperBoundingBox = main.scriptElements.mainMenuWrapper.getBoundingClientRect()
  1493.  
  1494. const pivotX = ((eleBoundingBox.x - wrapperBoundingBox.x) + eleBoundingBox.width / 2) / wrapperBoundingBox.width,
  1495. pivotY = ((eleBoundingBox.y - wrapperBoundingBox.y) + eleBoundingBox.height / 2) / wrapperBoundingBox.height
  1496.  
  1497. main.scriptElements.mainMenuWrapper.moveTo(main.cursor.x, main.cursor.y, pivotX, pivotY, false)
  1498. }
  1499.  
  1500. let oldWidth = innerWidth,
  1501. oldHeight = innerHeight
  1502.  
  1503. // Make the menu responsive when screen resizes
  1504. addEventListener("resize", () => {
  1505. const wrapper = main.scriptElements.mainMenuWrapper
  1506.  
  1507. wrapper.moveTo(wrapper.x / oldWidth * innerWidth, wrapper.y / oldHeight * innerHeight, 0, 0)
  1508.  
  1509. oldWidth = innerWidth
  1510. oldHeight = innerHeight
  1511. })
  1512.  
  1513. // Define the feature for moving the menu
  1514. main.menu.defineFeature("move", "fa-solid fa-up-down-left-right", "hold",
  1515. () => addEventListener("mousemove", moveFeatureHandler),
  1516. () => removeEventListener("mousemove", moveFeatureHandler)
  1517. )
  1518.  
  1519. // document.addEventListener("visibilitychange", () => {
  1520. // if (main.scriptElements.mainMenuWrapper.open)
  1521. // main.scriptElements.mainMenuWrapper.setState(0)
  1522. // })
  1523.  
  1524. main.menu.defineFeature("forceClose", "fa-solid fa-xmark", "click", () => main.scriptElements.mainMenuWrapper.setState(0))
  1525.  
  1526. // Define and append main menu body element
  1527. defineScriptElement("mainMenuBody", "section", { id: "mainMenuBody" }).appendTo(main.scriptElements.mainMenu)
  1528.  
  1529. // Define and append other menu button element
  1530. defineScriptElement("otherMenuButton", "button", { id: "otherMenuButton" }, {
  1531. innerHTML: `<i class="fa-solid fa-caret-right"></i>`,
  1532.  
  1533. // Set the state of the other menu button
  1534. setState(moved, rotated, dark) {
  1535. this.classList.toggle("moved", moved)
  1536. this.classList.toggle("rotated", rotated)
  1537. this.classList.toggle("dark", dark)
  1538. },
  1539.  
  1540. // Return to the previous menu
  1541. return() {
  1542. const navigationStack = main.menu.navigationStack
  1543. navigationStack.pop()
  1544. main.menu.setActiveMenu(navigationStack[navigationStack.length - 1] ?? main.menu.mainMenuName)
  1545. }
  1546. }).appendTo(main.scriptElements.mainMenu)
  1547.  
  1548. // Set callback functions for when a menu is added or removed from the navigation stack
  1549. main.menu.navigationStack.onAddCallback = function () {
  1550. main.scriptElements.otherMenuButton.setState(1, 1, 0)
  1551. }
  1552.  
  1553. main.menu.navigationStack.onRemoveCallback = function () {
  1554. if (!this.length)
  1555. main.scriptElements.otherMenuButton.setState(0, 0, 0)
  1556. }
  1557.  
  1558. // Define and append other menu container element
  1559. defineScriptElement("otherMenuTogglesContainer", "div", { id: "otherMenuTogglesContainer" }, {
  1560. open: false,
  1561. toggleButtons: [],
  1562.  
  1563. // Set the state of the other menu container
  1564. setState(open) {
  1565. this.classList.toggle("open", this.open = open)
  1566. },
  1567.  
  1568. // Add a toggle button for a specific menu
  1569. addToggleButton(forMenuName, onClickCallback, icon, index = 0) {
  1570. const toggleButton = createElement("button",
  1571. {
  1572. class: "toggleButton",
  1573. "data-menuname": forMenuName,
  1574. "data-index": index
  1575. },
  1576. {
  1577. disabled: false,
  1578. textContent: forMenuName[0],
  1579. innerHTML: `<i class="${icon}"></i>`,
  1580. setState(dark, disabled) {
  1581. this.classList.toggle("dark", dark)
  1582. this.classList.toggle("disabled", this.disabled = disabled)
  1583. },
  1584. })
  1585.  
  1586. // Add event listener to the toggle button
  1587. toggleButton.addEventListener("mousedown", function (event) {
  1588. event.stopImmediatePropagation()
  1589. if (!this.disabled)
  1590. onClickCallback()
  1591. })
  1592.  
  1593. // Allows control over what the order of buttons will be in the container
  1594. switch (true) {
  1595. case index == 0: this.append(toggleButton)
  1596. break
  1597. case index >= this.childElementCount: this.prepend(toggleButton)
  1598. break
  1599. default: for (let child of this.children) {
  1600. if (index > child.dataset.index)
  1601. return child.insertAdjacentElement("beforebegin", this)
  1602. }
  1603. }
  1604.  
  1605. this.toggleButtons.push(toggleButton)
  1606. return toggleButton
  1607. }
  1608. }).appendTo(main.scriptElements.mainMenuWrapper)
  1609.  
  1610. defineScriptElement("entityFollower", "div", { id: "entityFollower" }, {
  1611. innerHTML: `<i class="fa-solid fa-location-arrow"></i>`,
  1612. _x: 0,
  1613. _y: 0,
  1614. _rotation: 0,
  1615. velocity: { x: 0, y: 0 },
  1616. acceleration: { x: 0, y: 0 },
  1617. destination: { x: innerWidth / 2, y: innerHeight / 2, direction: 0, reached: false, outOfReach: false },
  1618. friction: .05,
  1619. speed: .4,
  1620. idleSpeed: .1,
  1621. normalSpeed: .4,
  1622. delta: 0,
  1623. lastFrameTime: 0,
  1624. activeEntityUID: undefined,
  1625. activeEntity: undefined,
  1626.  
  1627. setState(visible) {
  1628. this.classList.toggle("visible", visible)
  1629. },
  1630.  
  1631. followEntity(uid) {
  1632. this.activeEntityUID = uid
  1633. },
  1634.  
  1635. setSize(size) {
  1636. this.style.fontSize = `${size}px`
  1637. },
  1638.  
  1639. setDestination(x, y) {
  1640. this.destination = { x, y }
  1641. },
  1642.  
  1643. update(time) {
  1644. this.delta = (time - this.lastFrameTime) / (1000 / 60) // :)
  1645. this.lastFrameTime = time
  1646. this.activeEntity = game.world.entities[this.activeEntityUID]
  1647.  
  1648. const subSteps = 8,
  1649. divDT = this.delta / subSteps;
  1650.  
  1651. // If there is an activeEntity, follow it by updating the destination and set the speed to normal
  1652. if (this.activeEntity) {
  1653. var targetTick = this.activeEntity.targetTick,
  1654. posScreen = game.renderer.worldToScreen(targetTick.position.x, targetTick.position.y),
  1655. entityWidth = this.activeEntity.currentModel.base.sprite.width,
  1656. entityHeight = this.activeEntity.currentModel.base.sprite.height,
  1657. radius = Math.sqrt(entityWidth ** 2 + entityHeight ** 2)
  1658.  
  1659. this.speed = this.normalSpeed
  1660. this.setDestination(posScreen.x, posScreen.y)
  1661. }
  1662.  
  1663. // Otherwise idle with slower speed while moving around to random destinations from the center of the screen <- removed
  1664. else if (this.destination.reached || this.destination.outOfReach || main.menu.activeMenu != main.menu.mainMenuName)
  1665. this.speed = this.idleSpeed
  1666.  
  1667. this.setSize(Math.min(Math.max(22, (radius ?? 0) * .122), 36))
  1668.  
  1669. for (let i = 0; i < subSteps; i++) {
  1670. // Make the arrow bigger for bigger entities
  1671.  
  1672. // Calculate the distance from the destination
  1673. let distX = this.destination.x - this.x,
  1674. distY = this.destination.y - this.y,
  1675. dist = Math.sqrt(distX ** 2 + distY ** 2)
  1676.  
  1677. // Calculate the direction of the destination
  1678. this.destination.direction = Math.atan2(distY, distX)
  1679. // Check if destination is reached
  1680. this.destination.reached = dist < this.arrowSize
  1681.  
  1682. // Compressed way of setting acceleration to 0 when the destination is reached
  1683. // And to have a normal acceleartion when moving to a new destination
  1684. this.acceleration.x = this.speed * Math.cos(this.destination.direction) * (!this.destination.reached)
  1685. this.acceleration.y = this.speed * Math.sin(this.destination.direction) * (!this.destination.reached)
  1686.  
  1687. this.x += this.velocity.x * divDT
  1688. this.y += this.velocity.y * divDT
  1689.  
  1690. // Smoothly rotate towards the destination
  1691. this.rotation = Math.lerpAngles(this.rotation, Math.atan2(this.velocity.y, this.velocity.x), .22 * divDT)
  1692.  
  1693. this.velocity.x *= 1 - this.friction
  1694. this.velocity.y *= 1 - this.friction
  1695.  
  1696. this.velocity.x += this.acceleration.x
  1697. this.velocity.y += this.acceleration.y
  1698.  
  1699. // Add bounds
  1700. this.x = Math.max(Math.min(innerWidth - this.arrowSize, this.x), this.arrowSize)
  1701. this.y = Math.max(Math.min(innerHeight - this.arrowSize, this.y), this.arrowSize)
  1702. }
  1703.  
  1704. // Define when a destination is out of reach
  1705. this.destination.outOfReach = this.destination.x < 0 || this.destination.x > innerWidth || this.destination.y < 0 || this.destination.y > innerHeight
  1706. },
  1707.  
  1708. updateVisiblity() {
  1709. if (this.activeEntity) this.setState(main.scriptElements.mainMenuWrapper.open)
  1710. else if (this.destination.outOfReach || main.menu.activeMenu != main.menu.mainMenuName) this.setState(0)
  1711. }
  1712. }).appendTo(element(".hud"))
  1713.  
  1714. Object.defineProperties(main.scriptElements.entityFollower, {
  1715. x: {
  1716. set(value) {
  1717. this._x = value
  1718. this.style.left = `${value}px`
  1719. },
  1720.  
  1721. get() {
  1722. return this._x
  1723. },
  1724. },
  1725. y: {
  1726. set(value) {
  1727. this._y = value
  1728. this.style.top = `${value}px`
  1729. },
  1730.  
  1731. get() {
  1732. return this._y
  1733. },
  1734. },
  1735. rotation: {
  1736. set(rad) {
  1737. this._rotation = rad
  1738. this.style.rotate = `${rad}rad`
  1739. },
  1740. get() {
  1741. return this._rotation
  1742. },
  1743. },
  1744. arrowSize: {
  1745. get() {
  1746. return this.querySelector("i").scrollWidth
  1747. }
  1748. }
  1749. })
  1750.  
  1751. // Event Listeners----->
  1752.  
  1753. // Listen for mouse movement events and update the cursor position accordingly
  1754. addEventListener("mousemove", (event) => {
  1755. main.cursor = event
  1756. })
  1757.  
  1758. // Listen for keydown events and update controls accordingly
  1759. addEventListener("keydown", (event) => {
  1760. const key = event.key.toLocaleLowerCase()
  1761. if (!main.controls.listenedKeys.includes(key)) return
  1762. main.controls[`${key}Up`] = false
  1763. main.controls[`${key}Down`] = true
  1764. })
  1765.  
  1766. // Listen for keyup events and update controls accordingly
  1767. addEventListener("keyup", (event) => {
  1768. const key = event.key.toLocaleLowerCase()
  1769. if (!main.controls.listenedKeys.includes(key)) return
  1770. main.controls[`${key}Up`] = true
  1771. main.controls[`${key}Down`] = false
  1772. })
  1773.  
  1774. // Listen for mousedown events and close the main menu if it's open and the click is outside of it
  1775. addEventListener("mousedown", (event) => {
  1776. if (main.inGame && (main.scriptElements.mainMenuWrapper.contains(event.target) || !main.scriptElements.mainMenuWrapper.open || main.menu.pinned)) {
  1777. event.preventDefault()
  1778. event.stopImmediatePropagation()
  1779. return
  1780. }
  1781. main.scriptElements.mainMenuWrapper.setState(0)
  1782. })
  1783.  
  1784. // Listen for mousedown events on the other menu button and toggle the other menu container's state
  1785. main.scriptElements.otherMenuButton.addEventListener("mousedown", function (event) {
  1786. event.stopImmediatePropagation()
  1787.  
  1788. // If navigation stack is not empty, execute return function, else toggle the other menu container's state
  1789. if (main.menu.navigationStack.length) return this.return()
  1790.  
  1791. main.scriptElements.otherMenuTogglesContainer.setState(!main.scriptElements.otherMenuTogglesContainer.open)
  1792.  
  1793. // Toggle button state based on the other menu container's state
  1794. if (main.scriptElements.otherMenuTogglesContainer.open) this.setState(0, 1, 1)
  1795. else this.setState(0, 0, 0)
  1796. })
  1797.  
  1798. var lastMenuUpdateTime = 0,
  1799. lastMouseCollisionCheckTime = 0,
  1800. lastBackgroundMenuUpdateTime = 0;
  1801.  
  1802. // Self-invoking function to update ASAP
  1803. (function update(time) {
  1804. requestAnimationFrame(update)
  1805.  
  1806. // Check if the player is in the game world
  1807. if (!(main.inGame = window.game != undefined && game.network?.connected != undefined && game.world?.inWorld != undefined && game.ui?.playerTick != undefined) || main.settings.paused)
  1808. return
  1809.  
  1810. // Set the active menu to the main menu if no menu is currently active
  1811. if (!main.menu.activeMenu)
  1812. main.menu.setActiveMenu(main.menu.mainMenuName)
  1813.  
  1814. // Perform mouse collision check if enough time has elapsed
  1815. if (time - lastMouseCollisionCheckTime >= 1000 / main.settings.mouseCollisionCheckFPS) {
  1816. checkMouseCollisionWithEntity()
  1817. lastMouseCollisionCheckTime = time
  1818. }
  1819.  
  1820. main.scriptElements.entityFollower.updateVisiblity()
  1821.  
  1822. if (!main.scriptElements.mainMenuWrapper.open)
  1823. return
  1824.  
  1825. // Update the active entity and active menu if enough time has elapsed
  1826. if (time - lastMenuUpdateTime >= 1000 / main.settings.menuUpdateFPS) {
  1827. updateActiveEntity()
  1828. main.menu.activeMenuObject?.update()
  1829. lastMenuUpdateTime = time
  1830. }
  1831.  
  1832. // Update background elements if enough time has elapsed
  1833. if (time - lastBackgroundMenuUpdateTime >= 1000 / main.settings.backgroundMenuUpdateFPS) {
  1834. // Call the background update callback for all menus and toggle buttons
  1835. [...Menu.DefinedMenus, ...main.scriptElements.otherMenuTogglesContainer.toggleButtons].forEach(element => element.backgroundUpdateCallback?.())
  1836. lastBackgroundMenuUpdateTime = time
  1837. }
  1838.  
  1839. main.scriptElements.entityFollower.update(time)
  1840. })(0)
  1841.  
  1842. // Defining Menus----->
  1843.  
  1844. // Main Menu--->
  1845.  
  1846. const mainInfoMenu = new InfoMenu("infoMenu", [
  1847. { name: "Model", referenceName: "model" },
  1848. { name: "Wood", referenceName: "wood", type: "number" },
  1849. { name: "Token", referenceName: "token", type: "number" },
  1850. { name: "Stone", referenceName: "stone", type: "number" },
  1851. {
  1852. name: "Wave", value: (player) => {
  1853. return player.targetTick.wave ?? "Not Found"
  1854. },
  1855. type: "number"
  1856. },
  1857. { name: "Gold", referenceName: "gold", type: "number" },
  1858. { name: "Score", referenceName: "score", type: "number" },
  1859. { name: "Health", referenceName: "health" },
  1860. { name: "MaxHealth", referenceName: "maxHealth" },
  1861. {
  1862. name: "ShieldHealth",
  1863. referenceName: "zombieShieldHealth",
  1864. activationCondition: (entity) => {
  1865. return entity.targetTick.zombieShieldMaxHealth != 0
  1866. },
  1867. index: 1,
  1868. type: "number"
  1869. },
  1870. {
  1871. name: "MaxShieldHealth",
  1872. referenceName: "zombieShieldMaxHealth",
  1873. activationCondition: (entity) => {
  1874. return entity.targetTick.zombieShieldMaxHealth != 0
  1875. },
  1876. index: 2,
  1877. type: "number"
  1878. },
  1879. {
  1880. isBar: true,
  1881. name: "HP",
  1882. currentValRef: "health",
  1883. maxValRef: "maxHealth",
  1884. },
  1885. {
  1886. isBar: true,
  1887. name: "SH",
  1888. currentValRef: "zombieShieldHealth",
  1889. maxValRef: "zombieShieldMaxHealth",
  1890. isVisible: false,
  1891. activationCondition: (entity) => {
  1892. return entity.targetTick.zombieShieldMaxHealth != 0
  1893. },
  1894. barColor: "#3da1d9"
  1895. },
  1896. ], false).initStates()
  1897.  
  1898. mainInfoMenu.defineState(
  1899. "ResourceProp",
  1900. new InfoMenu(
  1901. `infoMenuStateResource`,
  1902. [
  1903. { name: "Model", referenceName: "model" },
  1904. { name: "CollisionRadius", referenceName: "collisionRadius" },
  1905. ],
  1906. false,
  1907. "",
  1908. true
  1909. ),
  1910. () => {
  1911. return (main.menu.activeEntity || main.menu.lastActiveEntity)?.targetTick.model.match(/(tree)|(stone)|(neutralcamp)/gi)
  1912. }
  1913. )
  1914.  
  1915. mainInfoMenu.defineState(
  1916. "TowerProp",
  1917. new InfoMenu(
  1918. `infoMenuStateTower`,
  1919. [
  1920. { name: "Model", referenceName: "model" },
  1921. { name: "Tier", referenceName: "tier", },
  1922. { name: "Health", referenceName: "health", type: "number" },
  1923. { name: "MaxHealth", referenceName: "maxHealth", type: "number" },
  1924. // A lot of error handling
  1925. {
  1926. name: "Damage",
  1927. activationCondition: (tower) => {
  1928. return game.ui.buildingSchema[tower.targetTick.model]?.damageTiers != undefined
  1929. },
  1930. value: (tower) => {
  1931. return game.ui.buildingSchema[tower.targetTick.model]?.damageTiers?.[tower.targetTick.tier - 1] ?? "Not Found"
  1932. },
  1933. type: "number"
  1934. },
  1935. {
  1936. name: "AttackSpeed",
  1937. activationCondition: (tower) => {
  1938. return main.gameData.towers[tower.targetTick.model]?.attackSpeed != undefined
  1939. },
  1940. value: (tower) => {
  1941. return main.gameData.towers[tower.targetTick.model]?.attackSpeed?.[tower.targetTick.tier - 1] ?? "Not Found"
  1942. }
  1943. },
  1944. {
  1945. name: "Range",
  1946. activationCondition: (tower) => {
  1947. return game.ui.buildingSchema[tower.targetTick.model]?.rangeTiers != undefined
  1948. },
  1949. value: (tower) => {
  1950. return game.ui.buildingSchema[tower.targetTick.model]?.rangeTiers?.[tower.targetTick.tier - 1] ?? "Not Found"
  1951. },
  1952. },
  1953. {
  1954. name: "Gold/Sec",
  1955. activationCondition: (tower) => {
  1956. return tower.targetTick.model.match(/goldmine/gi)
  1957. },
  1958. value: (tower) => {
  1959. return game.ui.buildingSchema[tower.targetTick.model]?.gpsTiers?.[tower.targetTick.tier - 1] ?? "Not Found"
  1960. },
  1961. type: "number"
  1962. },
  1963. {
  1964. name: "HarvestAmount",
  1965. activationCondition: (tower) => {
  1966. return tower.targetTick.model.match(/harvester/gi)
  1967. },
  1968. value: (tower) => {
  1969. return game.ui.buildingSchema[tower.targetTick.model]?.harvestTiers?.[tower.targetTick.tier - 1] ?? "Not Found"
  1970. }
  1971. },
  1972. {
  1973. name: "HarvestCapacity",
  1974. activationCondition: (tower) => {
  1975. return tower.targetTick.model.match(/harvester/gi)
  1976. },
  1977. value: (tower) => {
  1978. return game.ui.buildingSchema[tower.targetTick.model]?.harvestCapacityTiers?.[tower.targetTick.tier - 1] ?? "Not Found"
  1979. },
  1980. type: "number"
  1981. },
  1982. {
  1983. name: "SlowAmount",
  1984. activationCondition: (tower) => {
  1985. return tower.targetTick.model.match(/slowtrap/gi)
  1986. },
  1987. value: (tower) => {
  1988. return main.gameData.towers[tower.targetTick.model]?.slowAmount?.[tower.targetTick.tier - 1] ?? "Not Found"
  1989. }
  1990. },
  1991. {
  1992. isBar: true,
  1993. name: "HP",
  1994. currentValRef: "health",
  1995. maxValRef: "maxHealth",
  1996. },
  1997. ],
  1998. false,
  1999. "",
  2000. true
  2001. ),
  2002. () => {
  2003. return Object.entries(game.ui.buildingSchema).find(building => building[0] == (main.menu.activeEntity || main.menu.lastActiveEntity)?.targetTick.model)
  2004. }
  2005. )
  2006.  
  2007.  
  2008. mainInfoMenu.defineState(
  2009. "Npc",
  2010. new InfoMenu(
  2011. `infoMenuStateNPC`,
  2012. [
  2013. { name: "Model", referenceName: "model" },
  2014. {
  2015. name: "Damage",
  2016. activationCondition: (npc) => {
  2017. return npc.targetTick.damage
  2018. },
  2019. value: (npc) => {
  2020. return npc.targetTick.damage ?? "Not Found"
  2021. },
  2022. type: "number"
  2023. },
  2024. { name: "Yaw", referenceName: "yaw" },
  2025. { name: "Health", referenceName: "health", type: "number" },
  2026. { name: "MaxHealth", referenceName: "maxHealth", type: "number" },
  2027. {
  2028. isBar: true,
  2029. name: "HP",
  2030. currentValRef: "health",
  2031. maxValRef: "maxHealth",
  2032. }
  2033. ],
  2034. false,
  2035. "",
  2036. true
  2037. ),
  2038. () => {
  2039. return (main.menu.activeEntity || main.menu.lastActiveEntity)?.targetTick.entityClass == "Npc"
  2040. }
  2041. )
  2042.  
  2043. mainInfoMenu.defineState(
  2044. "PetProp",
  2045. new InfoMenu("infoMenuStatePet", [
  2046. { name: "Model", referenceName: "model" },
  2047. {
  2048. name: "Name",
  2049. value: (pet) => {
  2050. return game.ui.itemSchema[pet.targetTick.model]?.name ?? "Not Found"
  2051. }
  2052. },
  2053. { name: "Experience", referenceName: "experience", type: "number" },
  2054. { name: "Tier", referenceName: "tier" },
  2055. {
  2056. name: "Damage",
  2057. activationCondition: (pet) => {
  2058. return game.ui.itemSchema[pet.targetTick.model]?.damageTiers != undefined
  2059. },
  2060. value: (pet) => {
  2061. return game.ui.itemSchema[pet.targetTick.model]?.damageTiers?.[pet.targetTick.tier - 1] ?? "Not Found"
  2062. },
  2063. type: "number"
  2064. },
  2065. {
  2066. name: "MovementSpeed",
  2067. value: (pet) => {
  2068. return main.gameData.pets[pet.targetTick.model]?.speed?.[pet.targetTick.tier - 1] ?? "Not Found"
  2069. }
  2070. },
  2071. {
  2072. name: "AttackSpeed",
  2073. value: (pet) => {
  2074. return (1000 / game.ui.itemSchema[pet.targetTick.model]?.attackSpeedTiers?.[pet.targetTick.tier - 1]) ?? "Not Found"
  2075. }
  2076. },
  2077. {
  2078. name: "ResourceGain",
  2079. activationCondition: (pet) => {
  2080. return pet.targetTick.model.match(/petminer/gi)
  2081. },
  2082. value: (pet) => {
  2083. return main.gameData.pets[pet.targetTick.model]?.resourceGain?.[pet.targetTick.tier - 1] ?? "Not Found"
  2084. },
  2085. isVisible: false,
  2086. },
  2087. { name: "Health", referenceName: "health", type: "number" },
  2088. { name: "MaxHealth", referenceName: "maxHealth", type: "number" },
  2089. {
  2090. isBar: true,
  2091. name: "HP",
  2092. currentValRef: "health",
  2093. maxValRef: "maxHealth",
  2094. },
  2095. ],
  2096. false,
  2097. "",
  2098. true),
  2099. () => {
  2100. return (main.menu.activeEntity || main.menu.lastActiveEntity)?.targetTick.model.match(/pet/gi)
  2101. })
  2102.  
  2103. mainInfoMenu.defineTag(
  2104. "Entity Not Found",
  2105. "error",
  2106. () => {
  2107. return (main.menu.activeMenu == main.menu.mainMenuName && !main.menu.activeEntity)
  2108. },
  2109. 1)
  2110.  
  2111. // Party Spectate Menu--->
  2112. // The most annoying menu
  2113.  
  2114. const partySpectateMenu = new Menu("partySpectateMenu", `<div id="body" class="spectateMenuBody"></div>`, true, "fas fa-users", 1)
  2115.  
  2116. partySpectateMenu.spectateButtons = []
  2117.  
  2118. partySpectateMenu.createSpectateButton = function () {
  2119. const spectateButton = createElement("div",
  2120. {
  2121. class: "spectateButton",
  2122. "data-partyposition": "",
  2123. "data-playeruid": ""
  2124. },
  2125. {
  2126. innerHTML: `
  2127. <button>
  2128. <div class="wrapper">
  2129. <span class="name">Undef</span>
  2130. <section class="tag active uid">UID: <span></span></section>
  2131. <section class="tag active position"></section>
  2132. </div>
  2133. </button>
  2134. `
  2135. })
  2136.  
  2137. spectateButton.nameTag = spectateButton.querySelector("span.name")
  2138. spectateButton.positionTag = spectateButton.querySelector("section.position")
  2139. spectateButton.uidTag = spectateButton.querySelector("section.uid span")
  2140.  
  2141. spectateButton.addEventListener("mousedown", function (event) {
  2142. event.stopImmediatePropagation()
  2143. this.onceontransitionend(() => {
  2144. spectateInfoMenu.spectateEntityUID = parseInt(this.dataset.playeruid)
  2145. main.menu.setActiveMenu(spectateInfoMenu.name)
  2146. })
  2147. })
  2148.  
  2149. this.spectateButtons.push(spectateButton)
  2150. this.body.append(spectateButton)
  2151. }
  2152.  
  2153. // I don't know how to do this in css :(
  2154. partySpectateMenu.styleButtons = function (gap) {
  2155. const visibleElements = Array.from(partySpectateMenu.body.children).filter(element => element.classList.contains("visible"))
  2156.  
  2157. this.body.dataset.playercount = visibleElements.length
  2158.  
  2159. if (visibleElements.length == 1) return
  2160.  
  2161. gap = `${gap / 2}px`
  2162.  
  2163. let eleCurrent
  2164. for (let index = 0; index < visibleElements.length; index++) {
  2165. eleCurrent = visibleElements[index]
  2166. if (visibleElements[index - 1]) eleCurrent.style.paddingTop = gap
  2167. if (visibleElements[index + 1]) eleCurrent.style.paddingBottom = gap
  2168. }
  2169. }
  2170.  
  2171. partySpectateMenu.updateCallback = function () {
  2172. if (game.ui.playerPartyMembers.length <= 1)
  2173. main.scriptElements.otherMenuButton.return()
  2174. }
  2175.  
  2176. partySpectateMenu.backgroundUpdateCallback = function () {
  2177. // Filter out the player and add index to each party member
  2178. const partyMembers = game.ui.playerPartyMembers.map((member, index) => {
  2179. if (member.playerUid == game.ui.playerTick.uid) return undefined
  2180. return { member, index }
  2181. }).filter(element => element)
  2182.  
  2183. this.spectateButtons.forEach((button, index) => {
  2184. button.classList.toggle("visible", false)
  2185. button.dataset.playeruid = button.dataset.partyposition = ""
  2186.  
  2187. if (!partyMembers[index]) return
  2188.  
  2189. const nameTag = button.nameTag,
  2190. { member, index: memberIndex } = partyMembers[index]
  2191.  
  2192. nameTag.textContent = member.displayName
  2193.  
  2194. switch (button.dataset.partyposition = memberIndex) {
  2195. case 0: button.positionTag.textContent = "Leader"
  2196. break
  2197. case 1: button.positionTag.textContent = "Co-Leader"
  2198. break
  2199. case 2: case 3: button.positionTag.textContent = "Member"
  2200. break
  2201. }
  2202.  
  2203. button.uidTag.textContent = button.dataset.playeruid = member.playerUid
  2204. button.classList.toggle("visible")
  2205. })
  2206. this.styleButtons(8)
  2207. }
  2208.  
  2209. partySpectateMenu.toggleButton.backgroundUpdateCallback = function () {
  2210. this.setState(0, game.ui.playerPartyMembers.length <= 1)
  2211. }
  2212.  
  2213. for (let i = 0; i < 4; i++)
  2214. partySpectateMenu.createSpectateButton()
  2215.  
  2216. // Spectate Info Menu--->
  2217.  
  2218. const spectateInfoMenu = new InfoMenu("spectateInfoMenu",
  2219. [
  2220. ...mainInfoMenu.updatedAttributes,
  2221. {
  2222. name: "CanSell", value: (player) => {
  2223. return new Boolean(game.ui.getPlayerPartyMembers()?.find(member => member.playerUid == player.targetTick.uid)?.canSell)
  2224. },
  2225. index: 0,
  2226. },
  2227. ], false)
  2228.  
  2229. spectateInfoMenu.spectateEntityUID = undefined
  2230.  
  2231. spectateInfoMenu.updateCallback = function () {
  2232. this.spectateEntity = game.world.entities[this.spectateEntityUID]
  2233.  
  2234. if (this.spectateEntity)
  2235. this.updateInfo(this.spectateEntity)
  2236. }
  2237.  
  2238. spectateInfoMenu.defineTag("Spectating", "neutral", function () {
  2239. return true
  2240. }, 0)
  2241.  
  2242. spectateInfoMenu.defineTag("Entity Not Found", "error", function () {
  2243. return !this.spectateEntity
  2244. }, 1)
  2245.  
  2246. // Pet Spectate Menu--->
  2247.  
  2248. const petSpectateMenu = new InfoMenu("PetSpectateMenu", mainInfoMenu.definedStates.find(state => state.name == "PetProp").menu.updatedAttributes, true, "fa-solid fa-paw")
  2249.  
  2250. petSpectateMenu.updateCallback = function () {
  2251. const petTick = game.ui.getPlayerPetTick()
  2252.  
  2253. if (petTick)
  2254. this.updateInfo({ targetTick: petTick })
  2255. }
  2256.  
  2257. petSpectateMenu.toggleButton.backgroundUpdateCallback = function () {
  2258. this.setState(0, main.menu.activeEntityUID != game.ui.playerTick.uid || !game.ui.getPlayerPetTick())
  2259. }
  2260.  
  2261. petSpectateMenu.defineTag("Pet Died", "warning", () => {
  2262. return game.ui.getPlayerPetTick()?.health <= 0
  2263. })
  2264. }
  2265.  
  2266. // If the document is already loaded, call the scriptInit function immediately
  2267. if (document.readyState != "loading") script()
  2268. else document.addEventListener("DOMContentLoaded", script)
  2269. // If the document is still loading, add an event listener for the DOMContentLoaded event

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址