Building Health Bars

Shows the health of buildings.

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 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()
        })
    })
})()