Building Health Bars

Shows the health of buildings.

  1. // ==UserScript==
  2. // @name Building Health Bars
  3. // @namespace https://github.com/Nudo-o
  4. // @version 1
  5. // @description Shows the health of buildings.
  6. // @author @nudoo
  7. // @match *://moomoo.io/*
  8. // @match *://*.moomoo.io/*
  9. // @icon https://www.google.com/s2/favicons?sz=64&domain=moomoo.io
  10. // @require https://update.gf.qytechs.cn/scripts/480301/1283571/CowJS.js
  11. // @require https://update.gf.qytechs.cn/scripts/480303/1282926/MooUI.js
  12. // @license MIT
  13. // @grant none
  14. // @run-at document-start
  15. // ==/UserScript==
  16.  
  17. (function() {
  18. "use strict"
  19.  
  20. const { Cow, CowUtils, MooUI } = window
  21.  
  22. let settings = {
  23. "health-bars": true,
  24. "circle-bars": false,
  25. "in-look-dir": false,
  26. "in-weapon-range": false,
  27. "weapon-range-mult": "1",
  28. "bars-color": "#933db8",
  29. "hit-counter": false
  30. }
  31.  
  32. const settingsMap = Object.entries(settings)
  33. const storageName = "building-health-settings"
  34.  
  35. function setVisualSetting(key, value) {
  36. settings[key] = value
  37.  
  38. localStorage.setItem(storageName, JSON.stringify(settings))
  39. }
  40.  
  41. for (let i = 0; i < settingsMap.length; i++) {
  42. const visualSettings = JSON.parse(localStorage.getItem(storageName) || null)
  43.  
  44. if (!visualSettings) {
  45. localStorage.setItem(storageName, JSON.stringify(settings))
  46.  
  47. break
  48. }
  49.  
  50. if (!visualSettings.hasOwnProperty(settingsMap[i][0])) {
  51. setVisualSetting(settingsMap[i][0], settingsMap[i][1])
  52. }
  53. }
  54.  
  55. settings = JSON.parse(localStorage.getItem(storageName))
  56.  
  57. const columnsSettings = {
  58. settings: {
  59. targetColumn: "settings",
  60. togglers: [{
  61. key: "health-bars",
  62. name: "Health bars",
  63. description: "Shows the health of buildings.",
  64. isActive: settings["health-bars"],
  65. options: [
  66. new MooUI.OptionCheckbox({
  67. key: "circle-bars",
  68. name: "Circle bars",
  69. description: "If enabled, the bars will be displayed as circles",
  70. isActive: settings["circle-bars"]
  71. }),
  72. new MooUI.OptionCheckbox({
  73. key: "in-look-dir",
  74. name: "In look dir",
  75. description: "Bars will be drawn only when you look in their direction.",
  76. isActive: settings["in-look-dir"]
  77. }),
  78. new MooUI.OptionCheckbox({
  79. key: "in-weapon-range",
  80. name: "In weapon range",
  81. description: "Bars will only be drawn when your weapon can reach them.",
  82. isActive: settings["in-weapon-range"]
  83. }),
  84. new window.MooUI.OptionIRange({
  85. key: "weapon-range-mult",
  86. name: "Weapon range mult",
  87. description: "Adds the distance to the range of the weapon so that the drawing of the bars is further than the distance of the weapon.",
  88. min: 1,
  89. max: 3,
  90. step: "any",
  91. fixValue: 1,
  92. value: settings["weapon-range-mult"]
  93. }),
  94. new window.MooUI.OptionIColor({
  95. key: "bars-color",
  96. name: "Color",
  97. description: "Color of bars",
  98. value: settings["bars-color"]
  99. })
  100. ]
  101. }, {
  102. key: "hit-counter",
  103. name: "Hit counter",
  104. description: "Shows how many hits you need to hit the building.",
  105. isActive: settings["hit-counter"]
  106. }]
  107. }
  108. }
  109.  
  110. class MenuBuilder {
  111. constructor() {
  112. this.menu = void 0
  113.  
  114. this.settings = new MooUI.Column()
  115. }
  116.  
  117. buildTogglers() {
  118. for (const columnSettings of Object.values(columnsSettings)) {
  119. const column = this[columnSettings.targetColumn]
  120.  
  121. for (const toggler of columnSettings.togglers) {
  122. column.add(new MooUI.Checkbox(toggler))
  123. }
  124. }
  125. }
  126.  
  127. build() {
  128. this.menu = MooUI.createMenu({
  129. toggleKey: {
  130. code: "Escape"
  131. },
  132. appendNode: document.getElementById("gameUI")
  133. })
  134.  
  135. document.head.insertAdjacentHTML("beforeend", `<style>
  136. .column-container {
  137. border-radius: 0 0 6px 6px !important;
  138. }
  139.  
  140. .ui-model {
  141. border-radius: 4px !important;
  142. }
  143.  
  144. .ui-model.show-options {
  145. border-radius: 4px 4px 0px 0px !important;
  146. }
  147.  
  148. .options-container {
  149. border-radius: 0px 0px 4px 4px !important;
  150. }
  151.  
  152. .ui-option-input-color {
  153. border-radius: 4px !important;
  154. }
  155. </style>`)
  156.  
  157. this.settings.setHeaderText("Settings")
  158.  
  159. this.settings.collisionWidth = -999999
  160.  
  161. this.buildTogglers()
  162.  
  163. this.menu.add(this.settings)
  164. this.menu.onModelsAction(setVisualSetting)
  165.  
  166. this.menu.columns.forEach((column) => {
  167. column.header.element.style.borderRadius = "6px"
  168.  
  169. column.header.element.addEventListener("mousedown", (event) => {
  170. if (event.button !== 2) return
  171.  
  172. column.header.isOpen ??= false
  173. column.header.isOpen = !column.header.isOpen
  174.  
  175. column.header.element.style.borderRadius = column.header.isOpen ? "6px 6px 0 0" : "6px"
  176. })
  177. })
  178. }
  179. }
  180.  
  181. const menuBuilder = new MenuBuilder()
  182.  
  183. let menu = void 0
  184. let lastWeaponRangeMultChange = null
  185.  
  186. window.addEventListener("DOMContentLoaded", () => {
  187. menuBuilder.build()
  188.  
  189. menu = menuBuilder.menu
  190.  
  191. menu.getModel("weapon-range-mult").on("input", () => {
  192. lastWeaponRangeMultChange = Date.now()
  193. })
  194. })
  195.  
  196. function drawCircleBar(color, width, scale, endAngle) {
  197. const { context } = Cow.renderer
  198.  
  199. context.strokeStyle = color
  200. context.lineWidth = width
  201. context.lineCap = "round"
  202. context.beginPath()
  203. context.arc(0, 0, scale, 0, endAngle)
  204. context.stroke()
  205. context.closePath()
  206. }
  207.  
  208. Cow.addRender("building-health-bars", () => {
  209. if (!Cow.player) return
  210.  
  211. const { context } = Cow.renderer
  212. const weaponRange = (Cow.player.weapon.range + Cow.player.scale / 2) * parseFloat(menu.getModelValue("weapon-range-mult"))
  213.  
  214. if ((Date.now() - lastWeaponRangeMultChange) <= 1500) {
  215. const color = menu.getModelValue("bars-color")
  216.  
  217. context.save()
  218. context.fillStyle = color
  219. context.strokeStyle = color
  220. context.globalAlpha = .3
  221. context.lineWidth = 4
  222.  
  223. context.translate(Cow.player.renderX, Cow.player.renderY)
  224. context.beginPath()
  225. context.arc(0, 0, weaponRange, 0, Math.PI * 2)
  226. context.fill()
  227. context.globalAlpha = .7
  228. context.stroke()
  229. context.closePath()
  230. context.restore()
  231. } else {
  232. lastWeaponRangeMultChange = null
  233. }
  234.  
  235. Cow.objectsManager.eachVisible((object) => {
  236. if (!object.isItem) return
  237.  
  238. const distance = CowUtils.getDistance(Cow.player, object) - object.scale
  239. const angle = CowUtils.getDirection(object, Cow.player)
  240.  
  241. if (menu.getModelActive("in-weapon-range") && distance > weaponRange) return
  242. if (menu.getModelActive("in-look-dir") && CowUtils.getAngleDist(angle, Cow.player.lookAngle) > Cow.config.gatherAngle) return
  243.  
  244. if (menu.getModelActive("hit-counter")) {
  245. const damage = Cow.player.weapon.dmg * Cow.items.variants[Cow.player.weaponVariant].val
  246. const damageAmount = damage * (Cow.player.weapon.sDmg || 1) * (Cow.player.skin?.id === 40 ? 3.3 : 1)
  247. const hits = Math.ceil(object.health / damageAmount)
  248. const offsetY = menu.getModelActive("circle-bars") ? 2 : 22
  249.  
  250. context.save()
  251. context.font = `18px Hammersmith One`
  252. context.fillStyle = "#fff"
  253. context.textBaseline = "middle"
  254. context.textAlign = "center"
  255. context.lineWidth = 8
  256. context.lineJoin = "round"
  257.  
  258. context.translate(object.renderX, object.renderY)
  259. context.strokeText(hits, 0, offsetY)
  260. context.fillText(hits, 0, offsetY)
  261. context.restore()
  262. }
  263.  
  264. if (!menu.getModelActive("health-bars")) return
  265.  
  266. if (menu.getModelActive("circle-bars")) {
  267. const endAngle = ((object.health / object.maxHealth) * 360) * (Math.PI / 180)
  268. const width = 14
  269. const scale = 22
  270.  
  271. context.save()
  272. context.translate(object.renderX, object.renderY)
  273. context.rotate(object.dir ?? object.dir2)
  274. drawCircleBar("#3d3f42", width, scale, endAngle)
  275. drawCircleBar(menu.getModelValue("bars-color"), width / 2.5, scale, endAngle)
  276. context.restore()
  277.  
  278. return
  279. }
  280.  
  281. const { healthBarWidth, healthBarPad } = window.config
  282. const width = healthBarWidth / 2 - healthBarPad / 2
  283. const height = 17
  284. const radius = 8
  285.  
  286. context.save()
  287. context.translate(object.renderX, object.renderY)
  288.  
  289. context.fillStyle = "#3d3f42"
  290. context.roundRect(-width - healthBarPad, -height / 2, 2 * width + 2 * healthBarPad, height, radius)
  291. context.fill()
  292.  
  293. context.fillStyle = menu.getModelValue("bars-color")
  294. context.roundRect(-width, -height / 2 + healthBarPad, 2 * width * (object.health / object.maxHealth), height - 2 * healthBarPad, radius - 1)
  295. context.fill()
  296. context.restore()
  297. })
  298. })
  299. })()

QingJ © 2025

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