Building Health Bars

Shows the health of buildings.

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Building Health Bars
// @namespace    https://github.com/Nudo-o
// @version      1
// @description  Shows the health of buildings.
// @author       @nudoo
// @match        *://moomoo.io/*
// @match        *://*.moomoo.io/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=moomoo.io
// @require      https://update.greasyfork.org/scripts/480301/1283571/CowJS.js
// @require      https://update.greasyfork.org/scripts/480303/1282926/MooUI.js
// @license      MIT
// @grant        none
// @run-at       document-start
// ==/UserScript==

(function() {
    "use strict"

    const { Cow, CowUtils, MooUI } = window

    let settings = {
        "health-bars": true,
        "circle-bars": false,
        "in-look-dir": false,
        "in-weapon-range": false,
        "weapon-range-mult": "1",
        "bars-color": "#933db8",
        "hit-counter": false
    }

    const settingsMap = Object.entries(settings)
    const storageName = "building-health-settings"

    function setVisualSetting(key, value) {
        settings[key] = value

        localStorage.setItem(storageName, JSON.stringify(settings))
    }

    for (let i = 0; i < settingsMap.length; i++) {
        const visualSettings = JSON.parse(localStorage.getItem(storageName) || null)

        if (!visualSettings) {
            localStorage.setItem(storageName, JSON.stringify(settings))

            break
        }

        if (!visualSettings.hasOwnProperty(settingsMap[i][0])) {
            setVisualSetting(settingsMap[i][0], settingsMap[i][1])
        }
    }

    settings = JSON.parse(localStorage.getItem(storageName))

    const columnsSettings = {
        settings: {
            targetColumn: "settings",
            togglers: [{
                key: "health-bars",
                name: "Health bars",
                description: "Shows the health of buildings.",
                isActive: settings["health-bars"],
                options: [
                    new MooUI.OptionCheckbox({
                        key: "circle-bars",
                        name: "Circle bars",
                        description: "If enabled, the bars will be displayed as circles",
                        isActive: settings["circle-bars"]
                    }),
                    new MooUI.OptionCheckbox({
                        key: "in-look-dir",
                        name: "In look dir",
                        description: "Bars will be drawn only when you look in their direction.",
                        isActive: settings["in-look-dir"]
                    }),
                    new MooUI.OptionCheckbox({
                        key: "in-weapon-range",
                        name: "In weapon range",
                        description: "Bars will only be drawn when your weapon can reach them.",
                        isActive: settings["in-weapon-range"]
                    }),
                    new window.MooUI.OptionIRange({
                        key: "weapon-range-mult",
                        name: "Weapon range mult",
                        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.",
                        min: 1,
                        max: 3,
                        step: "any",
                        fixValue: 1,
                        value: settings["weapon-range-mult"]
                    }),
                    new window.MooUI.OptionIColor({
                        key: "bars-color",
                        name: "Color",
                        description: "Color of bars",
                        value: settings["bars-color"]
                    })
                ]
            }, {
                key: "hit-counter",
                name: "Hit counter",
                description: "Shows how many hits you need to hit the building.",
                isActive: settings["hit-counter"]
            }]
        }
    }

    class MenuBuilder {
        constructor() {
            this.menu = void 0

            this.settings = new MooUI.Column()
        }

        buildTogglers() {
            for (const columnSettings of Object.values(columnsSettings)) {
                const column = this[columnSettings.targetColumn]

                for (const toggler of columnSettings.togglers) {
                    column.add(new MooUI.Checkbox(toggler))
                }
            }
        }

        build() {
            this.menu = MooUI.createMenu({
                toggleKey: {
                    code: "Escape"
                },
                appendNode: document.getElementById("gameUI")
            })

            document.head.insertAdjacentHTML("beforeend", `<style>
            .column-container {
                border-radius: 0 0 6px 6px !important;
            }

            .ui-model {
                border-radius: 4px !important;
            }

            .ui-model.show-options {
                border-radius: 4px 4px 0px 0px !important;
            }

            .options-container {
                border-radius: 0px 0px 4px 4px !important;
            }

            .ui-option-input-color {
                border-radius: 4px !important;
            }
            </style>`)

            this.settings.setHeaderText("Settings")

            this.settings.collisionWidth = -999999

            this.buildTogglers()

            this.menu.add(this.settings)
            this.menu.onModelsAction(setVisualSetting)

            this.menu.columns.forEach((column) => {
                column.header.element.style.borderRadius = "6px"

                column.header.element.addEventListener("mousedown", (event) => {
                    if (event.button !== 2) return

                    column.header.isOpen ??= false
                    column.header.isOpen = !column.header.isOpen

                    column.header.element.style.borderRadius = column.header.isOpen ? "6px 6px 0 0" : "6px"
                })
            })
        }
    }

    const menuBuilder = new MenuBuilder()

    let menu = void 0
    let lastWeaponRangeMultChange = null

    window.addEventListener("DOMContentLoaded", () => {
        menuBuilder.build()

        menu = menuBuilder.menu

        menu.getModel("weapon-range-mult").on("input", () => {
            lastWeaponRangeMultChange = Date.now()
        })
    })

    function drawCircleBar(color, width, scale, endAngle) {
        const { context } = Cow.renderer

        context.strokeStyle = color
        context.lineWidth = width
        context.lineCap = "round"
        context.beginPath()
        context.arc(0, 0, scale, 0, endAngle)
        context.stroke()
        context.closePath()
    }

    Cow.addRender("building-health-bars", () => {
        if (!Cow.player) return

        const { context } = Cow.renderer
        const weaponRange = (Cow.player.weapon.range + Cow.player.scale / 2) * parseFloat(menu.getModelValue("weapon-range-mult"))

        if ((Date.now() - lastWeaponRangeMultChange) <= 1500) {
            const color = menu.getModelValue("bars-color")

            context.save()
            context.fillStyle = color
            context.strokeStyle = color
            context.globalAlpha = .3
            context.lineWidth = 4

            context.translate(Cow.player.renderX, Cow.player.renderY)
            context.beginPath()
            context.arc(0, 0, weaponRange, 0, Math.PI * 2)
            context.fill()
            context.globalAlpha = .7
            context.stroke()
            context.closePath()
            context.restore()
        } else {
            lastWeaponRangeMultChange = null
        }

        Cow.objectsManager.eachVisible((object) => {
            if (!object.isItem) return

            const distance = CowUtils.getDistance(Cow.player, object) - object.scale
            const angle = CowUtils.getDirection(object, Cow.player)

            if (menu.getModelActive("in-weapon-range") && distance > weaponRange) return
            if (menu.getModelActive("in-look-dir") && CowUtils.getAngleDist(angle, Cow.player.lookAngle) > Cow.config.gatherAngle) return

            if (menu.getModelActive("hit-counter")) {
                const damage = Cow.player.weapon.dmg * Cow.items.variants[Cow.player.weaponVariant].val
                const damageAmount = damage * (Cow.player.weapon.sDmg || 1) * (Cow.player.skin?.id === 40 ? 3.3 : 1)
                const hits = Math.ceil(object.health / damageAmount)
                const offsetY = menu.getModelActive("circle-bars") ? 2 : 22

                context.save()
                context.font = `18px Hammersmith One`
                context.fillStyle = "#fff"
                context.textBaseline = "middle"
                context.textAlign = "center"
                context.lineWidth = 8
                context.lineJoin = "round"

                context.translate(object.renderX, object.renderY)
                context.strokeText(hits, 0, offsetY)
                context.fillText(hits, 0, offsetY)
                context.restore()
            }

            if (!menu.getModelActive("health-bars")) return

            if (menu.getModelActive("circle-bars")) {
                const endAngle = ((object.health / object.maxHealth) * 360) * (Math.PI / 180)
                const width = 14
                const scale = 22

                context.save()
                context.translate(object.renderX, object.renderY)
                context.rotate(object.dir ?? object.dir2)
                drawCircleBar("#3d3f42", width, scale, endAngle)
                drawCircleBar(menu.getModelValue("bars-color"), width / 2.5, scale, endAngle)
                context.restore()

                return
            }

            const { healthBarWidth, healthBarPad } = window.config
            const width = healthBarWidth / 2 - healthBarPad / 2
            const height = 17
            const radius = 8

            context.save()
            context.translate(object.renderX, object.renderY)

            context.fillStyle = "#3d3f42"
            context.roundRect(-width - healthBarPad, -height / 2, 2 * width + 2 * healthBarPad, height, radius)
            context.fill()

            context.fillStyle = menu.getModelValue("bars-color")
            context.roundRect(-width, -height / 2 + healthBarPad, 2 * width * (object.health / object.maxHealth), height - 2 * healthBarPad, radius - 1)
            context.fill()
            context.restore()
        })
    })
})()