9mm [MooMoo.io] [v1.0.1]

Это хороший скрипт для moomoo.io, не самый лучший, но я постараюсь сделать его как можно лучше. M - Автоматические мельницы. Q/F/V/N/H - Макросы. Всё остальное работает автоматически! Если какие-то шапки не купились сами, то вы можете купить их в магазине.

// ==UserScript==
// @name            9mm [MooMoo.io] [v1.0.1]
// @name:ru         9mm [MooMoo.io] [v1.0.1]
// @namespace       https://github.com/Nudo-o
// @version         1.0.1
// @description     It's a good script for moomoo.io, it's not the best, but I'll try to make it as good as possible. M - Auto mills. Q/F/V/N/H - Macro placers. Everything else works automatically! If some hats were not bought by yourself, then you can buy them in the store.
// @description:ru  Это хороший скрипт для moomoo.io, не самый лучший, но я постараюсь сделать его как можно лучше. M - Автоматические мельницы. Q/F/V/N/H - Макросы. Всё остальное работает автоматически! Если какие-то шапки не купились сами, то вы можете купить их в магазине.
// @author          @nudoo
// @match           *://moomoo.io/*
// @match           *://*.moomoo.io/*
// @icon            https://www.google.com/s2/favicons?sz=64&domain=moomoo.io
// @require         https://greasyfork.org/scripts/423602-msgpack/code/msgpack.js
// @require         https://update.greasyfork.org/scripts/480301/1322984/CowJS.js
// @license         MIT
// @grant           none
// @run-at          document-start
// ==/UserScript==

// LICENSE (MIT): https://www.tldrlegal.com/license/mit-license

// The creation uses my library designed to simplify the work in creating scripts on MooMoo.io. You can get acquainted with it by following the link.
// Cow.js - https://update.greasyfork.org/scripts/480301/1322984/CowJS.js

(function() {
    "use strict"

    const { Cow, CowUtils, msgpack } = window
    const { packets, items } = Cow.config.designations
    const _placeItem = Cow.placeItem
    const _roundRect = CanvasRenderingContext2D.prototype.roundRect

    Cow.setCodec(msgpack)

    let nearEnemy = null
    let preplaceObjects = []
    let lastPreplaceClear = 0

    class AutoHeal {
        constructor() {
            this.checkIsHealed = false

            this.lastHeal = 0
        }

        doFullHeal() {
            const { player } = Cow

            if (player.health === player.maxHealth) return

            const amount = player.items[0] === 0 ? 20 : player.items[0] === 1 ? 30 : 25

            for (let i = player.health; i < player.maxHealth; i += amount) {
                this.doHeal()
            }
        }

        doHeal() {
            const { player } = Cow

            if (player.health === player.maxHealth) return

            const timeSinceHeal = Cow.ticker.ticks - (this.lastHeal || 0)

            if (timeSinceHeal >= 1) {
                /*Cow.delayedPlaceItem(() => Cow.placeItem(items.FOOD))*/

                Cow.placeItem(items.FOOD)

                this.lastHeal = Cow.ticker.ticks
            }
        }

        update() {
            const { player } = Cow

            if (!player?.alive || player?.skinIndex === 45) return
            if (player.health === player.maxHealth) {
                if (player.shameCount >= 2) {
                    tailor.autoBullTick = true
                }

                return
            }

            if (player.health <= 85 && nearEnemy) {
                const distance = CowUtils.getDistance(nearEnemy, player)

                if (distance <= 500) {
                    tailor.autoEmpHat = true
                }
            }

            const timeSinceHit = Cow.ticker.ticks - (player.hitTime || 0)
            const timeSinceHeal = Cow.ticker.ticks - (this.lastHeal || 0)

            if (player.shameCount < 5) {
                if (timeSinceHeal >= 8) {
                    this.doHeal()
                } else {
                    if (timeSinceHit >= ((player.health <= 30) ? 0 : 2)) {
                        if (player.health <= 40) {
                            this.doFullHeal()
                        } else if (timeSinceHit >= 1 && timeSinceHit >= 1) {
                            this.doHeal()
                        }

                        if (player.health >= 65) {
                            this.doHeal()
                        } else if (player.health > 40 && player.health < 65) {
                            this.doFullHeal()
                        }
                    } else if (timeSinceHit === 1) {
                        if (player.health <= 30) {
                            this.doHeal()
                            this.doFullHeal()
                        }
                    }
                }
            } else {
                if (player.health < 40 && timeSinceHit >= 2) {
                    this.doHeal()
                    this.doHeal()
                } else if (player.health >= 40 && timeSinceHit >= 3 && timeSinceHeal >= 2) {
                    this.doFullHeal()
                    this.doHeal()
                }
            }
        }
    }

    class AntiInsta {
        constructor() {
            this.targetHealth = 35

            this.lastHeal = 0
        }

        doFullHeal() {
            const { player } = Cow

            if (player.health === player.maxHealth) return

            const amount = player.items[0] === 0 ? 20 : player.items[0] === 1 ? 30 : 25

            for (let i = player.health; i < player.maxHealth; i += amount) {
                this.doHeal()
            }
        }

        doHeal() {
            const { player } = Cow

            if (player.health === player.maxHealth) return

            const timeSinceHeal = Cow.ticker.ticks - (this.lastHeal || 0)

            if (timeSinceHeal >= 0) {
                Cow.placeItem(items.FOOD)

                this.lastHeal = Cow.ticker.ticks
            }
        }

        update() {
            const { player } = Cow

            if (!player?.alive || player?.skinIndex === 45) return
            if (player.health > this.targetHealth || !nearEnemy) return

            const timeSinceHit = Cow.ticker.ticks - (player.hitTime || 0)
            const timeSinceHeal = Cow.ticker.ticks - (this.lastHeal || 0)

            if (player.shameCount <= 4) {
                if (timeSinceHeal >= 8) {
                    this.doHeal()

                    if (player.health <= 20) {
                        this.doFullHeal()
                    }

                    this.doHeal()
                } else {
                    if (timeSinceHit >= ((player.health <= 20) ? 0 : 1)) {
                        if (player.health <= (this.targetHealth - 10)) {
                            this.doHeal()
                            this.doHeal()
                        } else if (timeSinceHit >= 1 && timeSinceHit >= 1) {
                            this.doHeal()
                            this.doFullHeal()
                        }

                        if (player.health >= (this.targetHealth - 10)) {
                            this.doHeal()
                            this.doHeal()
                        } else if (player.health > 5 && player.health < 15) {
                            this.doFullHeal()
                            this.doHeal()
                        }
                    } else if (timeSinceHit === 1) {
                        this.doHeal()
                        this.doHeal()
                    }
                }

                if (player.health <= Math.max(this.targetHealth - 15, 10)) {
                    this.doFullHeal()
                } else if (timeSinceHit >= 1 && timeSinceHeal >= 1) {
                    this.doHeal()
                    this.doHeal()
                } else {
                    this.doHeal()
                }
            } else {
                if (player.health < (this.targetHealth - 5) && timeSinceHit >= 1) {
                    this.doHeal()
                    this.doHeal()
                } else if (player.health >= (this.targetHealth - 5) && timeSinceHit >= 2 && timeSinceHeal >= 1) {
                    this.doHeal()
                }
            }
        }
    }

    class Tailor {
        constructor() {
            this.hatTicks = []
            this.hasHats = [ 0 ]
            this.hasAccs = [ 0 ]

            this.lastHatTick = 0

            this.autoEmpCD = null

            this.autoEmpSoldier = false
            this.autoSoldierEmp = false
            this.autoEmpHat = false
            this.autoBullTick = false
            this.autoTankHat = false
        }

        get canEquip() {
            return !autoSpikeSync.isActive && !autoClickHats.isActive
        }

        isHasTick(key) {
            return Boolean(this.hatTicks.filter((tick) => tick.key == key).length)
        }

        reset() {
            this.autoEmpSoldier = false
            this.autoSoldierEmp = false
            this.autoEmpHat = false
            this.autoBullTick = false
            this.autoTankHat = false
        }

        equipHat(id, onlyBuy) {
            const { player } = Cow

            if (!this.hasHats.includes(id) && id !== 0) {
                if (player.points >= Cow.items.hats.searchById(id).price) {
                    Cow.sendPacket(packets.STORE_EQUIP, 1, id, 0, "by 9mm")

                    return this.hasHats.push(id)
                }
            }

            if (onlyBuy) return

            if (this.hasHats.includes(id) && player.skinIndex !== id) {
                Cow.sendPacket(packets.STORE_EQUIP, 0, id, 0)

                this.lastHatID = id
            }
        }

        equipAcc(id, onlyBuy) {
            const { player } = Cow

            if (!this.hasAccs.includes(id) && id !== 0) {
                if (player.points >= Cow.items.accessories.searchById(id).price) {
                    Cow.sendPacket(packets.STORE_EQUIP, 1, id, 1, "by 9mm")

                    return this.hasAccs.push(id)
                }
            }

            if (onlyBuy) return

            if (this.hasAccs.includes(id) && player.tailIndex !== id) {
                Cow.sendPacket(packets.STORE_EQUIP, 0, id, 1)

                this.lastAccID = id
            }
        }

        equipBiomeHat() {
            if (!this.canEquip) return

            const { player } = Cow

            let hatID = 0

            if (player.y2 > 6850 && player.y2 < 7550) {
                hatID = 31
            } else if (player.y2 < 2400) {
                hatID = 15
            } else {
                hatID = 12
            }

            this.equipHat(hatID)

            if (!this.isHasTick("uneqip-tail")) {
                this.hatTicks.push({
                    key: "unknown",
                    callback: () => {
                        this.equipAcc(11)
                    }
                })
            }
        }

        unknownTicks(amount) {
            while (amount--) {
                this.hatTicks.push({
                    key: "unknown",
                    callback: () => {}
                })
            }
        }

        autoHats() {
            const { player } = Cow
            const dangerBuildings = calculator.getDangerBuildings(player)

            if (nearEnemy && player.health < player.maxHealth && this.canEquip) {
                const isPolearm = nearEnemy.weaponIndex === 4
                const isSword = nearEnemy.weaponIndex === 3
                const isKatana = nearEnemy.weaponIndex === 4

                if (isPolearm || isSword || isKatana) {
                    if (nearEnemy.weapons[1] === 10) {
                        if (this.autoEmpSoldier) return

                        this.autoEmpSoldier = true

                        this.hatTicks = []

                        this.equipHat(22)

                        setTimeout(() => {
                            this.equipHat(6)

                            setTimeout(() => {
                                this.autoEmpSoldier = false
                            }, 25)
                        }, 90)
                    } else {
                        if (this.autoSoldierEmp) return

                        this.autoSoldierEmp = true

                        this.hatTicks = []

                        this.equipHat(6)

                        setTimeout(() => {
                            this.equipHat(22)

                            setTimeout(() => {
                                this.autoEmpSoldier = false
                            }, 25)
                        }, 90)
                    }

                    return
                }
            }

            if (this.autoEmpHat && (!this.autoEmpCD || Date.now() - this.autoEmpCD >= 500) && this.canEquip) {
                this.equipHat(22)

                if (!this.isHasTick("uneqip-hats")) {
                    this.unknownTicks(1)

                    this.hatTicks.push({
                        key: "uneqip-hats",
                        callback: () => {
                            this.autoEmpHat = false

                            this.autoEmpCD = null
                        }
                    })
                }

                this.autoEmpCD = Date.now()
            } else if (this.autoTankHat) {
                this.equipHat(40)

                if (!this.isHasTick("uneqip-hats")) {
                    this.unknownTicks(3)

                    this.hatTicks.push({
                        key: "uneqip-hats",
                        callback: () => {
                            this.autoTankHat = false
                        }
                    })
                }
            } else if (dangerBuildings.length || nearEnemy && this.canEquip) {
                if (dangerBuildings.length) {
                    this.equipHat(6)
                } else if (nearEnemy) {
                    const weaponIndex = nearEnemy.weaponIndex
                    const angle = CowUtils.getDirection(nearEnemy, player)
                    const distance = CowUtils.getDistance(nearEnemy, player) - player.scale
                    const isMeInAngle = true//getAngleDist(angle, tmpValues.nearEnemy.dir) <= Math.PI / 1.25
                    const weapon = Cow.items.weapons[weaponIndex]

                    if (!weapon) return

                    const isMeInRange = distance <= weapon.range * 3.25

                    if (isMeInRange && isMeInAngle) {
                        if (nearEnemy.skinIndex === 7 && nearEnemy.tailIndex !== 11) {
                            if (!this.isHasTick("auto-spike")) {
                                this.equipHat(6)

                                this.unknownTicks(2)

                                this.hatTicks.push({
                                    key: "auto-spike",
                                    callback: () => {
                                        this.equipHat(11)
                                    }
                                })
                            }

                            return
                        } else {
                            this.equipHat(6)

                            return
                        }
                    } else {
                        if (this.canEquip) return this.equipBiomeHat()
                    }
                }
            } else {
                if (this.canEquip) this.equipBiomeHat()
            }
        }

        update() {
            this.autoHats()

            const { player } = Cow

            const timeSinceHatTick = Cow.ticker.ticks - (this.lastHatTick || 0)

            if (timeSinceHatTick >= 20 && !this.hatTicks.length && this.canEquip) {
                this.equipAcc(11, true)

                this.equipHat(6, true)
                this.equipHat(7, true)
                this.equipHat(22, true)
                this.equipHat(40, true)
                this.equipHat(53, true)

                this.lastHatTick = Cow.ticker.ticks
            }

            if (!this.canEquip) return (this.hatTicks = [])

            const hatTick = this.hatTicks[0]

            typeof hatTick?.callback === 'function' && hatTick.callback()

            this.hatTicks.shift()
        }
    }

    class AutoPlacer {
        constructor() {
            this.delay = 10
            this.lastUpdate = null
        }

        update() {
            const { player } = Cow

            if (!player?.alive || !nearEnemy?.visible || autoSpikeSync.isActive) return
            if (Date.now() - this.lastUpdate < this.delay) return

            const trapConfig = Cow.items.list[player.items[items.TRAP]]
            const spikeConfig = Cow.items.list[player.items[items.SPIKE]]

            if (!trapConfig || !spikeConfig) return

            const visibleObjects = Cow.objectsManager.list.filter((gameObject) => gameObject.visible && gameObject.active && CowUtils.getDistance(gameObject, player) <= 300)
            const angle = CowUtils.getDistance(nearEnemy, player)
            const placeSpikeDistance = player.scale + spikeConfig.scale * 1.4
            const distance = CowUtils.getDistance(player, nearEnemy)

            let distanceToPlace = distance

            nearEnemy.inTrap = false

            for (let i = 0; i < visibleObjects.length; i++) {
                const gameObject = visibleObjects[i]

                if (!gameObject.isItem || gameObject.id !== 15 || gameObject.owner?.sid === nearEnemy.sid) continue

                const scale = gameObject.scale || gameObject.getScale()
                const enemyDistanceToTrap = CowUtils.getDistance(nearEnemy, gameObject) - scale + window.config.collisionDepth
                const angleTrapToEnemy = CowUtils.getDirection(nearEnemy, gameObject)

                if (enemyDistanceToTrap > 0) continue

                nearEnemy.inTrap = true

                const offset = scale - Math.abs(enemyDistanceToTrap) + nearEnemy.scale / 2 + spikeConfig.scale
                const placeX = gameObject.x + offset * Math.cos(angleTrapToEnemy)
                const placeY = gameObject.y + offset * Math.sin(angleTrapToEnemy)

                distanceToPlace = CowUtils.getDistance(placeX, placeY, player.x, player.y)

                if (distanceToPlace <= placeSpikeDistance) {
                    const angleToPlace = CowUtils.getDirection(placeX, placeY, player.x, player.y)

                    Cow.placeItem(items.SPIKE, {
                        angle: angleToPlace
                    })
                }

                break
            }

            const distanceToEnemy = CowUtils.getDistance(player, nearEnemy) - nearEnemy.scale
            const placeDistance = trapConfig.scale * 1.2 + player.scale

            if (!nearEnemy.inTrap) {
                if (trapConfig) {
                    if (distanceToEnemy <= placeDistance && player.items[items.TRAP] === 15) {
                        const angle = CowUtils.getDirection(nearEnemy, player)

                        Cow.placeItem(items.TRAP, { angle })
                    }
                }
            } else if (distanceToPlace > placeSpikeDistance && distanceToEnemy <= 275) {
                if (player.items[items.TRAP] === 15) {
                    Cow.placeItem(items.TRAP, {
                        angle: angle
                    })
                }

                Cow.placeItem(items.SPIKE, {
                    angle: -angle
                })
            }

            this.lastUpdate = Date.now()
        }
    }

    class Macro {
        constructor() {
            this.assistPlaceX = null
            this.assistPlaceY = null
            this.lastAssistActive = null

            this.keys = {
                FOOD: 81,
                TRAP: 70,
                SPIKE: 86,
                MILL: 78,
                TURRET: 72
            }
        }

        resetAssist() {
            this.assistPlaceX = null
            this.assistPlaceY = null
            this.lastAssistActive = Date.now()
        }

        update() {
            const { player } = Cow

            if (!player?.alive || isInputFocused()) return
            if (Date.now() - this.lastAssistActive >= 500) this.resetAssist()

            for (const key in this.keys) {
                if (!Cow.input.keyboard.activeKeys.get(this.keys[key])) continue

                const placeItemIndex = items[key.replace(/wind/, "").toUpperCase()]
                const placeItem = Cow.items.list[player.items[placeItemIndex]]

                if (!placeItem) continue

                let placeAngle = player.lookAngle

                if (placeItem?.scale) {
                    const generalScale = (player.scale + placeItem.scale + (placeItem.placeOffset || 0))
                    const placeX = player.x + (generalScale * Math.cos(placeAngle))
                    const placeY = player.y + (generalScale * Math.sin(placeAngle))
                    const placePosition = { x: placeX, y: placeY }
                    const interferingObject = Cow.objectsManager.checkItemLocation(placeX, placeY, placeItem.scale, 0.6, placeItem.id, false, true)

                    if (interferingObject) {
                        let nearObjects = Cow.objectsManager.list.filter((gameObject) => {
                            const generalScale = placeItem.scale + (gameObject.isItem ? gameObject.scale : gameObject.getScale(0.6, false))
                            const inPlace = CowUtils.getDistance(gameObject, player) <= generalScale + player.scale * 2 + (placeItem.placeOffset || 0)

                            return gameObject.visible && gameObject.active && inPlace
                        })

                        nearObjects = nearObjects.sort((a, b) => {
                            a = CowUtils.getDistance(a, placePosition)
                            b = CowUtils.getDistance(b, placePosition)

                            return a - b
                        })

                        if (nearObjects.length) {
                            let newPlaceX = placeX
                            let newPlaceY = placeY

                            for (const nearObject of nearObjects) {
                                const angle = CowUtils.getDirection(player, nearObject)
                                const scale = nearObject.isItem ? nearObject.scale : nearObject.getScale(.6, false)
                                const offsetScale = scale / 2 + placeItem.scale / 2 * 1.25

                                const _x = newPlaceX + offsetScale
                                const _y = newPlaceY + offsetScale

                                newPlaceX = _x * Math.cos(angle - Math.atan(player.x - _x))
                                newPlaceY = _y * Math.sin(angle - Math.atan(player.y - _y))

                                const isCanPlace = Cow.objectsManager.checkItemLocation(newPlaceX, newPlaceY, placeItem.scale, 0.6, placeItem.id, false)

                                if (isCanPlace) break

                                nearObjects = nearObjects.sort((a, b) => {
                                    const newPlacePosition = { x: newPlaceX, y: newPlaceY }

                                    a = CowUtils.getDistance(a, newPlacePosition)
                                    b = CowUtils.getDistance(b, newPlacePosition)

                                    return a - b
                                })
                            }

                            this.assistPlaceX = newPlaceX
                            this.assistPlaceY = newPlaceY
                            this.lastAssistActive = Date.now()

                            placeAngle = CowUtils.getDirection(player.x, player.y, newPlaceX, newPlaceY)
                        }
                    }
                }

                //Cow.delayedPlaceItem(() => {
                Cow.placeItem(placeItemIndex, {
                    angle: placeAngle
                })
                //})
            }
        }
    }

    class AutoMills {
        constructor() {
            this.gaps = [ 1.115820407, 1.141422642 ]
            this.lastPlace = null

            this.isActive = false

            this.expectedMills = 0
            this.autoResetTime = null

            const onKeyboard = () => {
                if (isInputFocused()) return

                this.isActive = !this.isActive
                this.expectedMills = 0

                if (!this.isActive) this.lastPlace = null
            }

            Cow.onKeyboard(77, onKeyboard, {
                repeat: false
            })
        }

        get gap() {
            const { player } = Cow

            return this.gaps[Number(player.items[items.MILL] !== 10)]
        }

        update() {
            const { player } = Cow

            if (!player?.alive || !this.isActive) return

            if (Date.now() - this.autoResetTime >= 500) {
                this.autoResetTime = null
            } else if (this.autoResetTime) {
                return
            }

            if (this.lastPlace && Date.now() - this.lastPlace < 100) return

            const millConfig = Cow.items.list[player.items[items.MILL]]
            const checkCanBuild = (angle) => {
                const scale = player.scale + millConfig.scale + (millConfig.placeOffset || 0)
                const placeX = player.x2 + scale * Math.cos(angle)
                const placeY = player.y2 + scale * Math.sin(angle)

                return Cow.objectsManager.checkItemLocation(placeX, placeY, millConfig.scale, .6, millConfig.id, false)
            }

            if (checkCanBuild(player.moveDir + this.gap)) {
                Cow.placeItem(items.MILL, {
                    angle: player.moveDir + this.gap
                })

                this.expectedMills += 1
            }

            if (checkCanBuild(player.moveDir)) {
                Cow.placeItem(items.MILL, {
                    angle: player.moveDir
                })

                this.expectedMills += 1
            }

            if (checkCanBuild(player.moveDir)) {
                Cow.placeItem(items.MILL, {
                    angle: player.moveDir - this.gap
                })

                this.expectedMills += 1
            }

            if (this.expectedMills === 1) {
                this.autoResetTime = Date.now()
            }

            this.lastPlace = Date.now()
        }
    }

    class ReloadBars {
        constructor() {
            this.colors = {
                1: [ "#cc5151", "#8ecc51" ],
                2: "#accd51",
                3: "#c4cd51",
                4: "#cdae51",
                5: "#cd8251",
                6: "#cd5d51"
            }
        }

        getColor(reloadValue, isAlly) {
            let color = ""

            if (reloadValue >= 0.8 && reloadValue < 1) {
                color = this.colors[2]
            } else if (reloadValue >= 0.6 && reloadValue < 0.8) {
                color = this.colors[3]
            } else if (reloadValue >= 0.4 && reloadValue < 0.6) {
                color = this.colors[4]
            } else if (reloadValue >= 0.2 && reloadValue < 0.4) {
                color = this.colors[5]
            } else if (reloadValue < 0.2) {
                color = this.colors[6]
            } else {
                color = this.colors[1][Number(isAlly)]
            }

            return color
        }

        drawBar(widthMult, color, object, offsetX, offsetY, _width, radii) {
            const { healthBarWidth, healthBarPad } = window.config
            const { context } = Cow.renderer
            const width = _width || (healthBarWidth / 2 - healthBarPad / 2)
            const height = 17

            context._roundRect = _roundRect

            context.save()
            context.fillStyle = "#3d3f42"

            context.translate(object.renderX + offsetX, object.renderY + offsetY)
            context.beginPath()
            context._roundRect(-width - healthBarPad, -height / 2, 2 * width + 2 * healthBarPad, height, Array.isArray(radii) ? radii[0] : radii)
            context.fill()
            context.restore()

            context.save()
            context.fillStyle = color

            context.translate(object.renderX + offsetX, object.renderY + offsetY)
            context.beginPath()
            context._roundRect(-width, -height / 2 + healthBarPad, 2 * width * widthMult, height - 2 * healthBarPad, Array.isArray(radii) ? radii[1] : radii - 1)
            context.fill()
            context.restore()
        }

        drawPrimaryBar(entity) {
            const primaryReload = Math.min(Math.max(entity.reloads.primary.count / entity.reloads.primary.max, 0), 1)
            const isAlly = entity.isMe || entity.isAlly
            const { healthBarWidth, healthBarPad } = window.config
            const width = (healthBarWidth / 2 - healthBarPad / 2)
            const addWidth = 0
            const color = this.getColor(primaryReload, isAlly)
            const offset = -width * 1.19 + addWidth
            const radius = 8
            const radii = [[ radius, 0, 0, radius ], [ radius - 1, 0, 0, radius - 1 ]]

            this.drawBar(primaryReload, color, entity, offset, entity.scale + window.config.nameY - 5, width + addWidth, radii)
        }

        drawSecondaryBar(entity) {
            const secondaryReload = Math.min(Math.max(entity.reloads.secondary.count / entity.reloads.secondary.max, 0), 1)
            const isAlly = entity.isMe || entity.isAlly
            const { healthBarWidth, healthBarPad } = window.config
            const width = (healthBarWidth / 2 - healthBarPad / 2)
            const addWidth = 0
            const color = this.getColor(secondaryReload, isAlly)
            const offset = width * 1.19 - addWidth
            const radius = 8
            const radii = [[ 0, radius, radius, 0 ], [ 0, radius - 1, radius - 1, 0 ]]

            this.drawBar(secondaryReload, color, entity, offset, entity.scale + window.config.nameY - 5, width + addWidth, radii)
        }

        drawTurretHatBar(entity) {
            const turretReload = Math.min(Math.max(entity.reloads.turret.count / entity.reloads.turret.max, 0), 1)
            const isAlly = entity.isMe || entity.isAlly
            const { healthBarWidth } = window.config
            const color = this.getColor(turretReload, isAlly)
            const radius = 8

            this.drawBar(turretReload, color, entity, 0, entity.scale + window.config.nameY * 1.75 - 3, healthBarWidth, radius)
        }

        update() {
            const { player } = Cow

            if (!player?.alive) return

            Cow.playersManager.eachVisible((player) => {
                this.drawPrimaryBar(player)
                this.drawSecondaryBar(player)
                this.drawTurretHatBar(player)
            })
        }
    }

    class Calculator {
        constructor() {}

        getLength(x, y) {
            const math = (Math.pow, Math.sqrt)

            return math(x * x + y * y)
        }

        findBuildingOnPosition(target, other) {
            const dx = target.x - other.x
            const dy = target.y - other.y
            const scale = target.scale + (other.getScale ? other.getScale() : other.scale)
            const length = this.getLength(dx, dy)

            return length - scale < 0
        }

        getPredictor(target) {
            return {
                x: target.x2 + target.speed * Math.cos(target.moveDir - Math.PI),
                y: target.y2 + target.speed * Math.sin(target.moveDir - Math.PI),
                scale: target.scale
            }
        }

        getDangerBuildings(target) {
            const { player } = Cow

            if (!player?.alive || !target?.visible) return []

            const predictor = this.getPredictor(target)

            return Cow.objectsManager.list.filter((gameObject) => {
                if (!gameObject.visible || !gameObject.isItem || !gameObject.visible) return

                const isSpike = [6, 7, 8, 9].includes(gameObject.id)

                return isSpike && !Cow.isAllianceMember(gameObject.owner?.sid) && this.findBuildingOnPosition(predictor, gameObject)
            })
        }
    }

    class AutoBreak {
        constructor() {
            this.isBreaking = false

            this.weaponBeforeStart = null
        }

        stopBreaking() {
            if (!this.isBreaking) return

            const { player } = Cow

            this.isBreaking = false
            tailor.autoTankHat = false

            aimControl.stopAiming()

            Cow.sendPacket(packets.SELECT_BUILD, player.weapons[this.weaponBeforeStart], true)

            this.weaponBeforeStart = null
        }

        async update() {
            const { player } = Cow

            player.inTrap = false

            const nearTrap = Cow.objectsManager.list
            .filter((gameObject) => gameObject.visible && gameObject.active && gameObject.id === 15 && !Cow.isAllianceMember(gameObject.owner?.sid))
            .sort((a, b) => {
                a = CowUtils.getDistance(a, player)
                b = CowUtils.getDistance(b, player)

                return a - b
            })[0]

            if (!nearTrap) return this.stopBreaking()

            const distance = CowUtils.getDistance(nearTrap, player) - nearTrap.scale + window.config.collisionDepth

            player.inTrap = distance <= 10

            if (!player.inTrap) return this.stopBreaking()

            this.isBreaking = true
            tailor.autoTankHat = true

            if (!this.weaponBeforeStart) {
                this.weaponBeforeStart = Number(player.weaponIndex > 8)
            }

            const breakWeapon = player.weapons[1] === 10 ? player.weapons[1] : player.weapons[0]
            const equipWeapon = (id) => {
                if (player.weaponIndex !== breakWeapon) Cow.sendPacket(packets.SELECT_BUILD, breakWeapon, true)
            }

            if (nearEnemy) {
                const weapon = Cow.items.weapons[player.weaponIndex]
                const angle = CowUtils.getDirection(nearEnemy, player)
                const distance = CowUtils.getDistance(nearEnemy, player) - player.scale * 2
                const isInAngle = CowUtils.getAngleDist(angle, player.dir) <= window.config.gatherAngle
                const isInRange = distance <= weapon.range

                if (isInAngle && isInRange && player.weapons[0] !== 8) {
                    equipWeapon(player.weapons[0])
                } else {
                    equipWeapon(breakWeapon)
                }
            } else {
                equipWeapon(breakWeapon)
            }

            aimControl.startAiming(nearTrap)
            Cow.sendPacket(packets.ATTACK_STATE, 1, aimControl.aimAngle)
            Cow.sendPacket(packets.ATTACK_STATE, 0, aimControl.aimAngle)
        }
    }

    class AntiTrap extends AutoBreak {
        constructor() {
            super()
        }

        update() {
            super.update()
        }
    }

    class AimControl {
        constructor() {
            this.aimTarget = null
            this.isAiming = false
            this._aimAngle = null
            this.isSent = false
        }

        get aimAngle() {
            this.updateAimToTarget()

            return this._aimAngle
        }

        set aimAngle(_angle) {
            this._aimAngle = _angle
        }

        onSent() {
            this.isSent = true
        }

        updateAimToTarget() {
            if (!this.isAiming) return

            const { player } = Cow
            const angle = typeof this.aimTarget === 'number' ? this.aimTarget : CowUtils.getDirection(this.aimTarget, player)

            this.aimAngle = angle
        }

        startAiming(point) {
            this.aimTarget = point
            this.isAiming = true
            this.isSent = false

            this.updateAimToTarget()
        }

        stopAiming() {
            this.aimTarget = null
            this.isAiming = false
            this.aimAngle = null
        }
    }

    class AutoClickHats {
        constructor() {
            this.isActive = false
            this.isGathering = false
            this.isAutoAttacking = false

            this.timeout = null
        }

        onStartGather(isAutoAttack) {
            if (isAutoAttack) {
                this.isAutoAttacking = true
            }

            this.isGathering = true

            this.reset()
        }

        onStopGather(isAutoAttack) {
            if (isAutoAttack) {
                this.isAutoAttacking = false
            }

            if (!isAutoAttack && this.isAutoAttacking) return

            this.isGathering = false

            this.reset()
        }

        fullReset() {
            this.isGathering = false
            this.isAutoAttacking = false

            this.reset()
        }

        reset() {
            this.isActive = false

            this.clearTimeout()
        }

        clearTimeout() {
            clearTimeout(this.timeout)

            this.timeout = null
        }

        update() {
            if (autoSpikeSync.isActive) return this.reset()

            if (!this.isGathering || this.isActive || this.timeout) return

            const { player } = Cow
            const weapon = Cow.items.weapons[player.weaponIndex]

            let isTargetEnemy = false

            if (nearEnemy) {
                const angle = CowUtils.getDirection(nearEnemy, player)
                const distance = CowUtils.getDistance(nearEnemy, player) - player.scale * 2
                const isInAngle = CowUtils.getAngleDist(angle, player.dir) <= window.config.gatherAngle
                const isInRange = distance <= weapon.range

                if (isInRange && isInAngle) {
                    isTargetEnemy = nearEnemy

                    this.isActive = true

                    this.clearTimeout()
                    player.tailIndex === 11 && tailor.equipAcc(0)
                    tailor.equipHat(7)

                    this.timeout = setTimeout(() => {
                        tailor.equipHat(6)
                        this.reset()
                    }, weapon.speed / 1.5)
                }
            }

            if (isTargetEnemy) return

            const gameObjects = Cow.objectsManager.list.filter((gameObject) => gameObject.isItem && gameObject.visible && gameObject.active && CowUtils.getDistance(player, gameObject) <= 300)
            const nearGameObject = gameObjects.sort((a, b) => {
                a = CowUtils.getDistance(player, a)
                b = CowUtils.getDistance(player, b)

                return a - b
            })[0]

            if (nearGameObject) {
                const angle = CowUtils.getDirection(nearGameObject, player)
                const distance = CowUtils.getDistance(nearGameObject, player) - nearGameObject.scale - weapon.range

                if (distance > 0) return

                this.isActive = true

                this.clearTimeout()
                tailor.equipHat(40)

                this.timeout = setTimeout(() => {
                    tailor.equipHat(6)
                    this.reset()
                }, weapon.speed / 1.5)
            }
        }
    }

    class AutoSpikeSync {
        constructor() {
            this.isActive = false

            this.lastActive = null
        }

        getSpikes() {
            return Cow.objectsManager.list.filter((gameObject) => gameObject.visible && gameObject.active && gameObject.group?.name === "spikes")
        }

        async doSpikeSync(angle) {
            if (this.lastActive && Date.now() - this.lastActive < 1000) return

            const { player } = Cow

            this.isActive = true

            if (player.weapons[0] === 7) {
                this.isActive = false

                return Cow.placeItem(items.SPIKE, { angle })
            }

            aimControl.stopAiming()
            aimControl.startAiming(angle)

            await CowUtils.delay(10)

            player.weaponIndex !== player.weapons[0] && Cow.sendPacket(packets.SELECT_BUILD, player.weapons[0], true)

            await CowUtils.delay(10)

            tailor.equipAcc(0)

            await CowUtils.delay(10)

            tailor.equipHat(7)

            await CowUtils.delay(25)

            Cow.sendPacket(packets.ATTACK_STATE, 1, angle, "by 9mm")
            Cow.sendPacket(packets.ATTACK_STATE, 0, null, "by 9mm")

            await CowUtils.delay(25)

            Cow.placeItem(items.SPIKE, { angle })
            aimControl.stopAiming()

            this.isActive = false
            this.lastActive = Date.now()
        }

        update() {
            const { player, camera } = Cow
            const { context } = Cow.renderer

            if (!player?.alive || this.isActive || !nearEnemy) return

            const spikeObjects = this.getSpikes()

            if (!spikeObjects.length) return

            const nearSpikesToEnemy = spikeObjects.filter((spikeObject) => spikeObject.owner.sid !== nearEnemy.sid && CowUtils.getDistance(nearEnemy, spikeObject) <= 90 + nearEnemy.scale)

            if (!nearSpikesToEnemy.length || nearSpikesToEnemy.length > 3) return

            const midX = nearSpikesToEnemy.reduce((acc, spikeObject) => acc + spikeObject.x, 0) / nearSpikesToEnemy.length
            const midY = nearSpikesToEnemy.reduce((acc, spikeObject) => acc + spikeObject.y, 0) / nearSpikesToEnemy.length
            const angleMeToMid = CowUtils.getDirection(midX, midY, player.x, player.y)
            const angleEnemyToMid = CowUtils.getDirection(midX, midY, nearEnemy.x, nearEnemy.y)
            const angleMeToEnemy = CowUtils.getDirection(nearEnemy, player)
            const targetAngle = window.config.gatherAngle * (nearSpikesToEnemy.length === 2 ? 2 : 1)
            const distanceToEnemy = CowUtils.getDistance(nearEnemy, player) - player.scale * 2

            if (CowUtils.getAngleDist(-angleEnemyToMid, angleMeToEnemy) > targetAngle) return

            renderGameObjectMark({
                renderX: midX - camera.xOffset,
                renderY: midY - camera.yOffset,
            }, context, 1, distanceToEnemy > player.weapon.range ? "#941492" : "#941414", true)

            if (distanceToEnemy > player.weapon.range) return

            this.doSpikeSync(angleMeToMid)
        }
    }

    CowUtils.delay = function(ms) {
        return new Promise((resolve) => setTimeout(resolve, ms))
    }

    Cow.placeItem = function() {
        const lastWeapon = Number(this.player.weaponIndex > 8)

        if (arguments[0] !== 0) {
            preplaceObjects.push({
                id: arguments[0],
                angle: arguments[1]?.angle || this.player.dir
            })
        }

        _placeItem.apply(this, arguments)

        const weaponId = this.player.weapons[lastWeapon]

        if (this.player.weaponIndex !== weaponId) this.sendPacket(packets.SELECT_BUILD, weaponId, true)
    }

    Cow.delayedPlaceItem = function(callback) {
        this.lastPlaceItem ??= 0

        if (this.lastPlaceItem && (this.ticker.ticks - this.lastPlaceItem) < 1) return

        callback()

        this.lastPlaceItem = this.ticker.ticks
    }

    Cow.isAllianceMember = function(sid) {
        const { player } = Cow

        if (player && player.sid == sid) return true
        if (!player.team || sid < 0) return false

        for (var i = 0; i < Cow.alliancePlayers.length; i += 2) {
            if (sid !== Cow.alliancePlayers[i]) continue

            return true
        }

        return false
    }

    Cow.isCanGather = function(doer, other) {
        const distance = CowUtils.getDistance(doer, other) - other.scale
        const angle = CowUtils.getDirection(other, doer)
        const angleDistance = CowUtils.getAngleDist(angle, doer.dir2)
        const isInAngle = angleDistance <= window.config.gatherAngle
        const isInRange = distance <= doer.weapon.range

        return {
            range: isInRange,
            angle: isInAngle,
            both: isInRange && isInAngle
        }
    }

    Cow.onPacket(packets.INIT_DATA, (initData) => {
        Cow.alliances = initData.teams
    })

    Cow.onPacket(packets.ADD_ALLIANCE, (alliance) => {
        Cow.alliances.push(alliance)
    })

    Cow.onPacket(packets.DELETE_ALLIANCE, (sid) => {
        for (let i = Cow.alliances.length - 1; i >= 0; i--) {
            if (Cow.alliances[i].sid !== sid) continue

            Cow.alliances.splice(i, 1)
        }
    })

    Cow.onPacket(packets.SET_ALLIANCE_PLAYERS, (players) => {
        Cow.alliancePlayers = players
    })

    Cow.onPacket(packets.UPDATE_PLAYERS, () => {
        const { player } = Cow

        if (Cow.ticker.ticks - lastPreplaceClear >= 1) {
            preplaceObjects = []

            lastPreplaceClear = Cow.ticker.ticks
        }
    })

    Cow.onPacket(packets.ADD_PLAYER, (_, isYou) => {
        if (!isYou) return

        tailor.reset()
        autoClickHats.fullReset()
        toggleLoadingMenu(false)

        setTimeout(() => {
            updateItemsCount(true)
        }, 500)

        if (!Cow.isFirstEnterGame) {
            const firstClanInput = document.querySelector("#first_clan_input")

            Cow.isFirstEnterGame = true

            firstClanInput.value && Cow.sendPacket(packets.CREATE_ALLIANCE, firstClanInput.value)
        }
    })

    Cow.onPacket(packets.UPDATE_ITEM_COUNTS, (index, value) => {
        const { player } = Cow

        player.itemCounts[index] = value

        updateItemsCount(false, index)
    })

    Cow.onPacket(packets.UPDATE_UPGRADES, (index, value) => {
        updateItemsCount()
    })

    const macro = new Macro()
    const calculator = new Calculator()
    const autoPlacer = new AutoPlacer()
    const tailor = new Tailor()
    const autoHeal = new AutoHeal()
    const antiInsta = new AntiInsta()
    const autoMills = new AutoMills()
    const reloadBars = new ReloadBars()
    const antiTrap = new AntiTrap()
    const aimControl = new AimControl()
    const autoClickHats = new AutoClickHats()
    const autoSpikeSync = new AutoSpikeSync()

    let lastRenderUpdate = 0
    let fps = 0
    let lastPingUpdate = null

    Cow.addRender("global", () => {
        fps += (1000 / Math.max(Date.now() - lastRenderUpdate, 1) - fps) / 10

        lastRenderUpdate = Date.now()

        const { context } = Cow.renderer
        const { player } = Cow

        nearEnemy = Cow.getNearEnemy()

        for (const preplaceObject of preplaceObjects) {
            renderPreplace(preplaceObject, context)
        }

        Cow.objectsManager.eachVisible((gameObject) => {
            if (!gameObject.isItem || !gameObject.active) return

            const distance = CowUtils.getDistance(player, gameObject) - player.scale

            if (distance > 600) return

            const alpha = Math.min(1, Math.max(0, 1 - (distance / 600)))

            renderGameObjectMark(gameObject, context, alpha)
        })

        macro.update()
        autoPlacer.update()
        autoClickHats.update()
        tailor.update()
        autoHeal.update()
        antiInsta.update()
        autoMills.update()
        reloadBars.update()
        antiTrap.update()
        autoSpikeSync.update()

        const pingDisplay = document.querySelector("#pingDisplay")

        if (pingDisplay && player?.alive) {
            if (!lastPingUpdate || (Date.now() - lastPingUpdate) >= 750 || !/FPS/.test(pingDisplay.innerHTML)) {
                pingDisplay.innerHTML = `Ping: ${window.pingTime}ms, FPS: ${fps.toFixed(0) || 0}`

                lastPingUpdate = Date.now()
            }
        }
    })

    const oldSend = WebSocket.prototype.send

    WebSocket.prototype.send = function(data) {
        const binary = new Uint8Array(data)
        const decoded = msgpack.decode(binary)
        const { player } = Cow

        if (decoded[0] === packets.STORE_EQUIP && decoded[1][0] === 1 && decoded[1][1] !== 0) {
            if (decoded[1][3] !== "by 9mm" && player.points >= Cow.items.hats.searchById(decoded[1][1])?.price) {
                if (decoded[1][2] === 0) {
                    tailor.hasHats.push(decoded[1][1])
                } else {
                    tailor.hasAccs.push(decoded[1][1])
                }
            }
        }

        if (![9, 12, 13, 15].includes(player?.weaponIndex)) {
            if (decoded[0] === packets.ATTACK_STATE) {
                if (decoded[1][2] !== "by cowjs" && decoded[1][2] !== "by 9mm") {
                    if (decoded[1][0] === 1) {
                        autoClickHats.onStartGather()
                    } else {
                        autoClickHats.onStopGather()
                    }
                }
            }

            if (decoded[0] === packets.AUTO_ATTACK) {
                if (!autoClickHats.isAutoAttacking) {
                    autoClickHats.onStartGather(true)
                } else {
                    autoClickHats.onStopGather(true)
                }
            }
        }

        if (decoded[0] === packets.SPAWN) {
            const nicknameInput = document.querySelector("#nickname_input")

            decoded[1][0] = {
                name: "9-".concat(nicknameInput.value.slice(0, 13)),
                moofoll: true,
                skin: decoded[1][0].skin
            }

            return oldSend.call(this, msgpack.encode(decoded))
        }

        if (decoded[0] === packets.LOOK_DIR && aimControl.isAiming) {
            aimControl.updateAimToTarget()

            decoded[1][0] = aimControl.aimAngle

            oldSend.call(this, msgpack.encode(decoded))

            return aimControl.onSent()
        }

        oldSend.apply(this, arguments)
    }

    function isInputFocused() {
        return document.activeElement.tagName === "INPUT"
    }

    function renderGameObjectMark(gameObject, context, alpha, color, isDebug) {
        const { player } = Cow
        const radius = 12
        const innerRadius = !gameObject.maxHealth ? radius : (gameObject.health / gameObject.maxHealth) * radius

        color = color ? color : gameObject.owner.sid === player.sid ? "#8ecc51" : Cow.isAllianceMember(gameObject.owner.sid) ? "#cdaa51" : "#cc5151"

        context.save()
        context.globalAlpha = alpha
        context.fillStyle = color

        context.translate(gameObject.renderX, gameObject.renderY)
        context.beginPath()
        context.arc(0, 0, isDebug ? radius : Math.min(radius, Math.max(0, innerRadius)), 0, Math.PI * 2)
        context.fill()
        context.closePath()
        context.restore()

        context.save()
        context.globalAlpha = alpha
        context.strokeStyle = "#3d3f42"
        context.lineWidth = 5.5

        context.translate(gameObject.renderX, gameObject.renderY)
        context.beginPath()
        context.arc(0, 0, radius, 0, Math.PI * 2)
        context.stroke()
        context.closePath()
        context.restore()
    }

    function renderPreplace(preplaceObject, context) {
        const { player } = Cow
        const item = Cow.items.list[Cow.player.items[preplaceObject.id]]
        const sprite = getItemSprite(item)
        const x = (player.scale + item.scale) * Math.cos(preplaceObject.angle)
        const y = (player.scale + item.scale) * Math.sin(preplaceObject.angle)
        const isCanPlace = Cow.objectsManager.checkItemLocation(player.x + x, player.y + y, item.scale, 0.6, item.id, false)

        if (!isCanPlace) return

        context.save()
        context.globalAlpha = .3
        context.translate(player.renderX + x, player.renderY + y)
        context.rotate(preplaceObject.angle)
        context.drawImage(sprite, -(sprite.width / 2), -(sprite.height / 2))
        context.restore()
    }

    function updateItemsCount(isFirst, itemIndex) {
        const { player } = Cow
        const allActionBarItems = [ ...document.querySelectorAll(".actionBarItem") ]

        allActionBarItems.forEach((actionBarItem) => {
            const id = parseInt(actionBarItem.id.replace(/\D/g, "")) - 16
            const itemConfig = Cow.items.list[id]

            if (!itemConfig?.group) return

            const { group } = itemConfig

            if (!group.place) return

            actionBarItem.innerHTML = `<span class="item-count${!isFirst && group.id === itemIndex ? " scale-anim" : ""}">${player.itemCounts[group.id] || 0}</span>`
        })
    }

    const itemSprites = {}

    function renderStar(ctxt, spikes, outer, inner) {
        const step = Math.PI / spikes

        let rot = Math.PI / 2 * 3
        let x = 0
        let y = 0

        ctxt.beginPath()
        ctxt.moveTo(0, -outer)

        for (let i = 0; i < spikes; i++) {
            x = Math.cos(rot) * outer
            y = Math.sin(rot) * outer

            ctxt.lineTo(x, y)

            rot += step
            x = Math.cos(rot) * inner
            y = Math.sin(rot) * inner

            ctxt.lineTo(x, y)

            rot += step
        }

        ctxt.lineTo(0, -outer)
        ctxt.closePath()
    }

    function renderCircle(x, y, scale, tmpContext, dontStroke, dontFill) {
        tmpContext = tmpContext || Cow.renderer.context

        tmpContext.beginPath()
        tmpContext.arc(x, y, scale, 0, 2 * Math.PI)

        if (!dontFill) tmpContext.fill()
        if (!dontStroke) tmpContext.stroke()
    }

    function renderRect(x, y, w, h, ctxt, stroke) {
        ctxt.fillRect(x - (w / 2), y - (h / 2), w, h)

        if (!stroke) ctxt.strokeRect(x - (w / 2), y - (h / 2), w, h)
    }

    function renderRectCircle(x, y, s, sw, seg, ctxt, stroke) {
        ctxt.save()
        ctxt.translate(x, y)
        seg = Math.ceil(seg / 2)

        for (var i = 0; i < seg; i++) {
            renderRect(0, 0, s * 2, sw, ctxt, stroke)
            ctxt.rotate(Math.PI / seg)
        }

        ctxt.restore()
    }

    function renderTriangle(s, ctx) {
        ctx = ctx || Cow.renderer.context

        const h = s * (Math.sqrt(3) / 2)

        ctx.beginPath()
        ctx.moveTo(0, -h / 2)
        ctx.lineTo(-s / 2, h / 2)
        ctx.lineTo(s / 2, h / 2)
        ctx.lineTo(0, -h / 2)
        ctx.fill()
        ctx.closePath()
    }

    function getItemSprite(obj) {
        let tmpSprite = itemSprites[obj.id];

        if (tmpSprite) return tmpSprite

        const tmpCanvas = document.createElement("canvas")
        const tmpContext = tmpCanvas.getContext("2d")
        const outlineWidth = 5.5
        const outlineColor = "#525252"

        tmpCanvas.width = tmpCanvas.height = (obj.scale * 2.5) + outlineWidth + (Cow.items.list[obj.id].spritePadding || 0)

        tmpContext.strokeStyle = outlineColor
        tmpContext.lineWidth = outlineWidth

        tmpContext.translate((tmpCanvas.width / 2), (tmpCanvas.height / 2))
        tmpContext.rotate(Math.PI / 2)

        if (/wall/.test(obj.name)) {
            const sides = (obj.name == "castle wall") ? 4 : 3

            tmpContext.fillStyle = obj.name == "castle wall" ? "#83898e" : obj.name == "wood wall" ? "#a5974c" : "#939393"

            renderStar(tmpContext, sides, obj.scale * 1.1, obj.scale * 1.1)
            tmpContext.fill()
            tmpContext.stroke()

            tmpContext.fillStyle = obj.name == "castle wall" ? "#9da4aa" : obj.name == "wood wall" ? "#c9b758" : "#bcbcbc"

            renderStar(tmpContext, sides, obj.scale * 0.65, obj.scale * 0.65)
            tmpContext.fill()
        } else if (/spikes/.test(obj.name)) {
            const tmpScale = (obj.scale * 0.6)

            tmpContext.fillStyle = obj.name == "poison spikes" ? "#7b935d" : "#939393"

            renderStar(tmpContext, (obj.name == "spikes") ? 5 : 6, obj.scale, tmpScale)
            tmpContext.fill()
            tmpContext.stroke()

            tmpContext.fillStyle = "#a5974c"

            renderCircle(0, 0, tmpScale, tmpContext)

            tmpContext.fillStyle = "#c9b758"

            renderCircle(0, 0, tmpScale / 2, tmpContext, true)
        } else if (/mill/.test(obj.name)) {
            tmpContext.fillStyle = "#a5974c"

            renderCircle(0, 0, obj.scale, tmpContext)

            tmpContext.fillStyle = "#c9b758"

            renderRectCircle(0, 0, obj.scale * 1.5, 29, 4, tmpContext)

            tmpContext.fillStyle = "#a5974c"

            renderCircle(0, 0, obj.scale * 0.5, tmpContext)
        } else if (/mine/.test(obj.name)) {
            tmpContext.fillStyle = "#939393"

            renderStar(tmpContext, 3, obj.scale, obj.scale)
            tmpContext.fill()
            tmpContext.stroke()

            tmpContext.fillStyle = "#bcbcbc"

            renderStar(tmpContext, 3, obj.scale * 0.55, obj.scale * 0.65)
            tmpContext.fill()
        } else if (/sapling/.test(obj.name)) {
            for (let i = 0; i < 2; ++i) {
                const tmpScale = obj.scale * (!i ? 1 : 0.5)

                renderStar(tmpContext, 7, tmpScale, tmpScale * 0.7)

                tmpContext.fillStyle = (!i ? "#9ebf57" : "#b4db62")

                tmpContext.fill()
                !i && tmpContext.stroke()
            }
        } else if (/trap/.test(obj.name)) {
            tmpContext.fillStyle = "#a5974c"

            renderStar(tmpContext, 3, obj.scale * 1.1, obj.scale * 1.1)
            tmpContext.fill()
            tmpContext.stroke()

            tmpContext.fillStyle = outlineColor

            renderStar(tmpContext, 3, obj.scale * 0.65, obj.scale * 0.65)
            tmpContext.fill()
        } else if (/boost/.test(obj.name)) {
            tmpContext.fillStyle = "#7e7f82"

            renderRect(0, 0, obj.scale * 2, obj.scale * 2, tmpContext)
            tmpContext.fill()
            tmpContext.stroke()

            tmpContext.fillStyle = "#dbd97d"

            renderTriangle(obj.scale * 1, tmpContext)
        } else if (/turret/.test(obj.name)) {
            const tmpLen = 50

            tmpContext.fillStyle = "#a5974c"

            renderCircle(0, 0, obj.scale, tmpContext)
            tmpContext.fill()
            tmpContext.stroke()

            tmpContext.fillStyle = "#939393"

            renderRect(0, -tmpLen / 2, obj.scale * 0.9, tmpLen, tmpContext)
            renderCircle(0, 0, obj.scale * 0.6, tmpContext)
            tmpContext.fill()
            tmpContext.stroke()
        } else if (/platform/.test(obj.name)) {
            const tmpCount = 4;
            const tmpS = obj.scale * 2
            const tmpW = tmpS / tmpCount

            let tmpX = -(obj.scale / 2)

            tmpContext.fillStyle = "#cebd5f"

            for (let i = 0; i < tmpCount; ++i) {
                renderRect(tmpX - (tmpW / 2), 0, tmpW, obj.scale * 2, tmpContext)
                tmpContext.fill()
                tmpContext.stroke()

                tmpX += tmpS / tmpCount
            }
        } else if (/spawn/.test(obj.name)) {
            tmpContext.fillStyle = "#7e7f82"

            renderRect(0, 0, obj.scale * 2, obj.scale * 2, tmpContext)
            tmpContext.fill()
            tmpContext.stroke()

            tmpContext.fillStyle = "#71aad6"

            renderCircle(0, 0, obj.scale * 0.6, tmpContext)
        } else if (/blocker/.test(obj.name)) {
            tmpContext.fillStyle = "#7e7f82"

            renderCircle(0, 0, obj.scale, tmpContext)
            tmpContext.fill()
            tmpContext.stroke()
            tmpContext.rotate(Math.PI / 4)

            tmpContext.fillStyle = "#db6e6e"

            renderRectCircle(0, 0, obj.scale * 0.65, 20, 4, tmpContext, true)
        } else if (/teleport/.test(obj.name)) {
            tmpContext.fillStyle = "#7e7f82"

            renderCircle(0, 0, obj.scale, tmpContext)
            tmpContext.fill()
            tmpContext.stroke()
            tmpContext.rotate(Math.PI / 4)

            tmpContext.fillStyle = "#d76edb"

            renderCircle(0, 0, obj.scale * 0.5, tmpContext, true)
        }

        tmpSprite = tmpCanvas
        itemSprites[obj.id] = tmpSprite

        return tmpSprite
    }

    function waitForInterval(selector, callback) {
        const checker = setInterval(() => {
            const node = document.querySelector(selector)

            if (!node?.style) return

            callback()
            clearInterval(checker)
        })

        setTimeout(() => {
            clearInterval(checker)
        }, 30000)

        return checker
    }

    waitForInterval("#gameUI", () => {
        createCustomHtmlAndCss()
    })

    waitForInterval("#mainMenu", createCustomMainMenu)

    waitForInterval("#enterGame", () => {
        const enterGameBtn = document.querySelector("#enterGame")

        enterGameBtn.addEventListener = new Proxy(enterGameBtn.addEventListener, {
            apply(target, _this, args) {
                _this = document.querySelector("#play_button")

                return target.apply(_this, args)
            }
        })
    })

    Object.defineProperty(HTMLElement.prototype, "onclick", {
        set(callback) {
            this.addEventListener("click", arguments[0])

            if (!/enterGame/.test(this.id)) return

            const playButton = document.querySelector("#play_button")

            playButton.addEventListener("click", arguments[0])
        }
    })

    function createCustomMainMenu() {
        const mainMenu = document.querySelector("#mainMenu")
        const style = document.createElement("style")

        style.insertAdjacentHTML("beforeend", `
        .better-mm-holder {
            display: block;
            position: absolute;
            top: 0;
            z-index: 999999999;
            width: 100%;
            height: 100%;
            background: rgba(0, 0, 0, .75);
            overflow: hidden;
            pointer-events: all;
        }

        .better-mm-holder * {
            box-sizing: border-box;
        }

        .better-mm-holder ul, .better-mm-holder li {
            margin: 0;
            padding: 0;
            list-style: none;
            text-decoration: none;
        }

        .better-mm-wrapper {
            display: flex;
            flex-direction: column;
            justify-content: space-between;
            height: 100%;
        }

        .better-mm-header {
            display: flex;
            align-items: center;
            justify-content: center;
            width: 100%;
            height: 105px;
            min-height: 105px;
            background: #101010;
            border-bottom: 4px solid #1f0f29;
        }

        .mod-title {
            background: #1f0f29;
            color: #7575757a;
            font-size: 50px;
            transform: skewX(10deg);
            padding: 5px 30px;
            border-radius: 50% 20% 35% / 70%;
            box-shadow: 0px 0px 3px 1px #020002;
            animation: blob-anim 20s infinite ease-in-out;
        }

        @keyframes blob-anim {
            0% {
                border-radius: 50% 20% 35% / 70%;
            }

            25% {
                border-radius: 50% 20% 35% / 30% 30% 20%;
            }

            50% {
                border-radius: 50% 20% 35% / 50% 50% 40%;
            }

            100% {
                border-radius: 50% 20% 35% / 70%;
            }
        }

        .better-mm-container {
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 20px;
            width: 100%;
            height: 100%;
            padding: 40px;
        }

        .bmm-container-box {
            display: flex;
            flex-direction: column;
            background: #101010;
            border: 4px solid #1f0f29;
            border-radius: 12px;
            padding: 5px;
            overflow-y: auto;
        }

        .bmm-container-box::-webkit-scrollbar {
            width: 8px;
        }

        .bmm-container-box::-webkit-scrollbar-track {
            width: 8px;
        }

        .bmm-container-box::-webkit-scrollbar-thumb {
            background: rgba(0, 0, 0, .35);
            border-radius: 4px 30px 30px 4px;
        }

        .game-servers-box, .mod-changelog-box, .game-settings-box {
            min-height: 375px;
            max-height: 375px;
            width: 325px;
        }

        .items-list {
            display: flex;
            flex-direction: column;
            align-items: center;
            gap: 5px;
            padding: 5px !important;
        }

        .items-list .item {
            display: flex;
            flex-direction: column;
            gap: 4px;
            background: #252525b8;
            width: 100%;
            min-height: max-content;
            border-radius: 6px;
            padding: 5px !important;
        }

        .items-list .item.light-background {
            background: #474747bd;
        }

        .changelog-item-header, .server-data-header {
            font-size: 16px;
            color: #d0d0d0;
        }

        .changelog-updates {
            display: flex;
            flex-direction: column;
            gap: 4px;
            padding: 0 4px;
        }

        .changelog-update-value {
            font-size: 14px;
            color: #a3a2a2;
        }

        .changelog-version-info {
            display: flex;
            flex-direction: column;
            gap: 2px;
            padding-left: 4px;
            border-left: 2px solid #a3a2a2;
        }

        .player-body-figure {
            stroke-width: 4;
            stroke: #3d3f42;
            transition: .3s fill;
        }

        .game-settings-box, .game-servers-box {
            overflow-y: hidden;
        }

        .game-servers-box .items-list {
            overflow-y: auto;
        }

        .game-servers-box .items-list::-webkit-scrollbar {
            width: 8px;
        }

        .game-servers-box .items-list::-webkit-scrollbar-track {
            width: 8px;
        }

        .game-servers-box .items-list::-webkit-scrollbar-thumb {
            background-color: rgba(0, 0, 0, .35);
            border-radius: 20px;
        }

        .game-settings-box {
            display: flex;
            align-items: center;
            justify-content: space-between;
        }

        .player-settings {
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 5px;
            padding: 10px;
        }

        .player-preview-wrapper {
            min-width: 50px;
            min-height: 50px;
            border-radius: 12px;
            background: #252525b8;
            cursor: pointer;
        }

        .player-data-wrapper {
            display: flex;
            flex-direction: column;
            gap: 8px;
        }

        .player-data-input {
            background: none;
            outline: 0;
            border: none;
            color: #d0d0d0;
            font-size: 14px;
            border-bottom: 2px solid #1f0f29;
            transition: .3s border-bottom;
        }

        .player-data-input:hover, .player-data-input:focus {
            border-bottom: 2px solid #2d143d;
        }

        .game-servers-update {
            display: flex;
            align-items: center;
            justify-content: center;
            color: #a3a2a2;
            font-size: 16px;
            width: 100%;
            min-height: 30px;
            background: #252525b8;
            border-radius: 6px;
            cursor: pointer;
            margin-bottom: 5px;
            transition: .3s color;
        }

        .game-servers-update:hover {
            color: #d0d0d0;
        }

        .server-data-wrapper {
            display: flex;
            align-items: center;
            justify-content: space-between;
        }

        .server-data-header, .server-data-ping {
            font-size: 14px;
            user-select: text;
            cursor: default;
        }

        .server-data-ping.red {
            color: #750d0d;
        }

        .server-data-ping.low-red {
            color: #852323;
        }

        .server-data-ping.yellow {
            color: #b3af0c;
        }

        .server-data-ping.green {
            color: #4bb30c;
        }

        .server-data-ping.low-green {
            color: #6c9f2b;
        }

        .server-data-actions {
            display: flex;
            align-items: center;
            gap: 4px;
        }

        .server-data-players {
            display: inline-block;
            user-select: none;
            color: #a3a2a2;
            width: 55px;
        }

        .server-open-btn {
            display: flex;
            align-items: center;
            justify-content: center;
            color: #d0d0d0;
            font-size: 14px;
            cursor: pointer;
            padding: 0 4px;
            background: #1f0f29;
            border-radius: 4px;
        }

        .loading-text, .disconnect-text {
            color: #d0d0d0;
            font-size: 35px;
        }

        .info-link {
            cursor: pointer;
            text-decoration: underline;
        }

        .link-logo {
            width: 25px;
            height: 25px;
        }

        .info-footer {
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 10px;
            padding: 8px;
            border-radius: 8px;
        }

        .info-footer-item {
            display: flex;
            align-items: center;
        }

        .info-footer-item.discord-item {
            user-select: text;
        }

        .info-footer-item.discord-item img {
            transform: scale(1.35);
        }

        .info-footer-item.discord-item::after {
            content: "nudoo";
            position: fixed;
            color: #5a75ce;
            font-size: 14px;
            width: 0px;
            transform: scaleX(0) translateX(30px);
            text-decoration: none;
            opacity: 0;
            user-select: text;
            transition: transform .3s, width .3s, opacity .3s;
        }

        .info-footer-item.discord-item:hover::after {
            margin-left: 4px;
            transform: scaleX(1) translateX(30px);
            width: 40px;
            opacity: 1;
        }

        .game-settings {
            display: flex;
            flex-direction: column;
        }

        .player-settings-wrapper {
            display: flex;
            flex-direction: column;
            align-items: center;
        }

        .play-button, .game-setting-btn {
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 18px;
            padding: 0 4px;
            width: 100%;
            height: 30px;
            color: #7575757a;
            background: #1f0f29;
            border-radius: 6px;
            letter-spacing: 4px;
            cursor: pointer;
            transition: .3s background, .3s color;
        }

        .play-button:hover {
            background: #2d143d;
            color: #75757599;
        }

        .play-button:active, .game-setting-btn:active {
            transform: scale(.975);
        }

        .game-settings {
            display: grid;
            grid-template-columns: repeat(2, max-content);
            justify-items: center;
            justify-content: center;
            grid-gap: 5px;
            width: 100%;
            height: 100%;
        }

        .game-setting-btn {
            font-size: 14px;
            width: 140px;
            padding: 0 6px;
            letter-spacing: 0px;
            background: #1f0f29;
        }

        .game-setting-btn.enabled {
            background: #2d143d;
            color: #d0d0d0;
        }

        .select-skin-panel {
            position: absolute;
            padding: 4px;
            display: grid;
            grid-template-columns: repeat(5, max-content);
            gap: 4px;
            background: #252525b8;
            border: 4px solid #1f0f29;
            border-radius: 6px;
            z-index: 99999999999;
        }

        .skin-circle {
            cursor: pointer;
            width: 20px;
            height: 20px;
            border-radius: 6px;
            border: 3px solid #525252;
            transition: .3s border-radius;
        }

        .skin-circle:hover {
            border-radius: 50%;
        }

        .skin-circle.selected {
            border-radius: 50% !important;
        }

        .global-notification {
            position: absolute;
            top: 0;
            width: 100%;
            height: 100%;
            z-index: 9999999999999999999;
            display: flex;
            align-items: center;
            justify-content: center;
        }

        .global-notification-box {
            width: 400px;
            height: 400px;
            overflow: hidden;
        }
        `)

        document.head.appendChild(style)

        mainMenu.insertAdjacentHTML("beforeend", `
        <div class="better-mm-holder" id="menuCardHolder">
            <main class="better-mm-wrapper">
                <header class="better-mm-header">
                    <span class="mod-title">9mm</span>
                </header>

                <container class="better-mm-container" id="better_mm_loading">
                    <span class="loading-text">Loading...</span>
                </container>

                <container class="better-mm-container hidden" id="better_mm_disconnect">
                    <span class="disconnect-text">Disconnected...</span>
                </container>

                <container class="better-mm-container hidden" id="better_mm_container">
                    <box class="bmm-container-box game-servers-box">
                        <div class="game-servers-update" id="game_servers_update">UPDATE</div>

                        <ul class="items-list" id="game_servers"></ul>
                    </box>

                    <box class="bmm-container-box game-settings-box">
                        <div class="player-settings-wrapper">
                            <div class="player-settings">
                                <div class="player-preview-wrapper" id="player_preview">
                                    <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="eIZvJ0eqgt61" viewBox="0 0 100 100" shape-rendering="geometricPrecision" text-rendering="geometricPrecision">
                                        <ellipse class="player-body-figure" rx="10" ry="10" transform="translate(80 32.942071)" fill="#bf8f54"/>
                                        <ellipse class="player-body-figure" rx="10" ry="10" transform="translate(20 32.942071)" fill="#bf8f54"/>
                                        <ellipse class="player-body-figure" rx="30" ry="30" transform="translate(50 57)" fill="#bf8f54"/>
                                    </svg>
                                </div>

                                <div class="player-data-wrapper">
                                    <input class="player-data-input" id="nickname_input" placeholder="Enter nickname..." maxlength="13">
                                    <input class="player-data-input" id="first_clan_input" placeholder="Enter clan name..." maxlength="7">
                                </div>
                            </div>

                            <div class="play-button" id="play_button">PLAY</div>
                        </div>

                        <div class="game-settings">
                            <div class="game-setting-btn${localStorage.show_ping == "true" ? " enabled" : ""}" id="show_ping">Show ping/fps</div>
                            <div class="game-setting-btn${localStorage.native_resolution == "true" ? " enabled" : ""}" id="native_resolution">Native resolution</div>
                            <div class="game-setting-btn${localStorage.mill_rotate == "true" ? " enabled" : ""}" id="mill_rotate">Mill rotate</div>
                            <div class="game-setting-btn${localStorage.remove_grid == "true" ? " enabled" : ""}" id="remove_grid">Remove grid</div>
                        </div>

                        <footer class="info-footer">
                            <a class="info-footer-item info-link" href="https://www.youtube.com/channel/UCpBgMEb1vFQnMcSz-kJ6i0Q" target="_blank">
                                <img class="link-logo" src="https://cdn.glitch.global/26ec0d7f-01b9-40b1-aff5-8aa4412693c4/youtube.png?v=1700800235511">
                            </a>

                            <a class="info-footer-item info-link" href="https://github.com/Nudo-o/" target="_blank">
                                <img class="link-logo" src="https://cdn.glitch.global/26ec0d7f-01b9-40b1-aff5-8aa4412693c4/github-mark-white.png?v=1700800770200">
                            </a>

                            <a class="info-footer-item info-link" href="https://greasyfork.org/ru/users/759782-nudo" target="_blank">
                                <img class="link-logo" src="https://cdn.glitch.global/26ec0d7f-01b9-40b1-aff5-8aa4412693c4/greasyfork.png?v=1700800851140">
                            </a>

                            <div class="info-footer-item discord-item">
                                <img class="link-logo" src="https://cdn.glitch.global/26ec0d7f-01b9-40b1-aff5-8aa4412693c4/discord.png?v=1700801396017">
                            </div>
                        </footer>
                    </box>

                    <box class="bmm-container-box mod-changelog-box">
                        <ul class="items-list" id="mod_changelog"></ul>
                    </box>
                </container>
            </main>
        </div>

        <div class="select-skin-panel hidden" id="select_skin_panel">
            <div class="skin-circle selected" activeSkin" style="background: #bf8f54;" skin_index="0"></div>
            <div class="skin-circle" style="background: #cbb091;" skin_index="1"></div>
            <div class="skin-circle" style="background: #896c4b;" skin_index="2"></div>
            <div class="skin-circle" style="background: #fadadc;" skin_index="3"></div>
            <div class="skin-circle" style="background: #ececec;" skin_index="4"></div>
            <div class="skin-circle" style="background: #c37373;" skin_index="5"></div>
            <div class="skin-circle" style="background: #4c4c4c;" skin_index="6"></div>
            <div class="skin-circle" style="background: #ecaff7;" skin_index="7"></div>
            <div class="skin-circle" style="background: #738cc3;" skin_index="8"></div>
            <div class="skin-circle" style="background: #8bc373;" skin_index="9"></div>
        </div>
        `)

        updateChangelog(getModVersions(), "#mod_changelog")

        const gameServersUpdateBtn = document.querySelector("#game_servers_update")
        const nicknameInput = document.querySelector("#nickname_input")
        const firstClanInput = document.querySelector("#first_clan_input")
        const playButton = document.querySelector("#play_button")
        const showPing = document.querySelector("#show_ping")
        const millRotate = document.querySelector("#mill_rotate")
        const nativeResolution = document.querySelector("#native_resolution")
        const removeGrid = document.querySelector("#remove_grid")
        const playerPreview = document.querySelector("#player_preview")
        const allSelectSkinElements = [ ...document.querySelectorAll(".skin-circle") ]
        const setNodeVisibility = (selector, key) => {
            const node = document.querySelector(selector)

            if (!node) return

            const state = JSON.parse(localStorage.getItem(key))

            if (!node.hiddenInterval) {
                node.hiddenInterval = setInterval(() => {
                    node.hidden = false
                })
            }

            if (state) return node.classList.remove("hidden")

            node.classList.add("hidden")
        }
        const toggleGameSettingBtn = (toggler, key) => {
            if (!toggler) return

            toggler.classList.toggle("enabled")

            const state = toggler.classList.contains("enabled")

            localStorage.setItem(key, state.toString())

            if (key === "show_ping") {
                setNodeVisibility("#pingDisplay", key)
            }
        }
        const setGameSettingBtnState = (toggler, key) => {
            if (!toggler) return

            const state = JSON.parse(localStorage.getItem(key))

            if (key === "show_ping") {
                setNodeVisibility("#pingDisplay", key)
            }

            if (state) return toggler.classList.add("enabled")

            toggler.classList.remove("enabled")
        }

        setGameSettingBtnState(showPing, "show_ping")
        setGameSettingBtnState(millRotate, "mill_rotate")
        setGameSettingBtnState(nativeResolution, "native_resolution")
        setGameSettingBtnState(removeGrid, "remove_grid")

        nicknameInput.value = localStorage.getItem("9mm_name") || ""
        firstClanInput.value = localStorage.getItem("moo_first_clan") || ""

        nicknameInput.addEventListener("input", () => {
            localStorage.setItem("9mm_name", nicknameInput.value)
        })

        firstClanInput.addEventListener("input", () => {
            localStorage.setItem("moo_first_clan", firstClanInput.value)
        })

        showPing.addEventListener("click", toggleGameSettingBtn.bind(null, showPing, "show_ping"))
        millRotate.addEventListener("click", toggleGameSettingBtn.bind(null, millRotate, "mill_rotate"))
        nativeResolution.addEventListener("click", toggleGameSettingBtn.bind(null, nativeResolution, "native_resolution"))
        removeGrid.addEventListener("click", toggleGameSettingBtn.bind(null, removeGrid, "remove_grid"))
        playButton.addEventListener("click", enterGame)
        gameServersUpdateBtn.addEventListener("click", updateGameServers)
        playerPreview.addEventListener("click", toggleSelectSkin)

        window.addEventListener("resize", () => {
            toggleSelectSkin(null, true)
        })

        allSelectSkinElements.forEach((selectSkinElement) => {
            selectSkinElement.addEventListener("mousedown", selectSkin)
        })

        const checkGameLoading = setInterval(() => {
            const loadingText = document.querySelector("#loadingText")

            if (loadingText?.style.display !== "none") return

            if (localStorage.moo_skin) {
                selectSkin({ target: allSelectSkinElements[+localStorage.moo_skin] })
            }

            toggleLoadingMenu(false)
            clearInterval(checkGameLoading)
        })

        const checkGameDisconnect = setInterval(() => {
            const loadingText = document.querySelector("#loadingText")

            if (loadingText?.style.display === "none" || !/disconnect/.test(loadingText?.innerHTML)) return

            toggleDisconnectMenu(true)
            clearInterval(checkGameLoading)
        })

        Cow.socket.onEvent("close", toggleDisconnectMenu.bind(null, true))
    }

    function selectSkin(event) {
        const allSelectSkinElements = [ ...document.querySelectorAll(".skin-circle") ]

        allSelectSkinElements.forEach((selectSkinElement) => {
            selectSkinElement.classList.remove("selected")
        })

        const skinIndex = parseInt(event.target.getAttribute("skin_index"))
        const playerBodyFigures = [ ...document.querySelectorAll(".player-body-figure") ]

        playerBodyFigures.forEach((playerBodyFigure) => {
            playerBodyFigure.style.fill = window.config.skinColors[skinIndex]
        })

        event.target.classList.add("selected")
        window.selectSkinColor(skinIndex)

        localStorage.setItem("moo_skin", skinIndex)
    }

    function toggleSelectSkin(_, isResize, forceHide) {
        const playerPreview = document.querySelector("#player_preview")
        const selectSkinPanel = document.querySelector("#select_skin_panel")
        const boundings = playerPreview.getBoundingClientRect()
        const width = 162
        const height = 72

        if (forceHide) return selectSkinPanel.classList.add("hidden")

        !isResize && selectSkinPanel.classList.toggle("hidden")

        selectSkinPanel.style.left = `${boundings.x - width / 2 + boundings.width / 2}px`
        selectSkinPanel.style.top = `${boundings.y - height - 5}px`
    }

    function enterGame() {
        const enterGameBtn = document.querySelector("#enterGame")

        toggleSelectSkin(null, false, true)
        toggleLoadingMenu(true)
        setLoadingText("Connecting...")
    }

    function setLoadingText(text) {
        const bettermmLoading = document.querySelector("#better_mm_loading")
        const loadingText = bettermmLoading.querySelector(".loading-text")

        loadingText.innerHTML = text
    }

    function toggleDisconnectMenu(visibility) {
        const bettermmDisconnect = document.querySelector("#better_mm_disconnect")

        if (visibility) {
            bettermmDisconnect.classList.remove("hidden")
            toggleLoadingMenu(false)
            toggleSelectSkin(null, false, true)

            return toggleBettermmContainer(false)
        }

        bettermmDisconnect.classList.add("hidden")
        toggleBettermmContainer(true)
        toggleLoadingMenu(false)
    }

    function toggleLoadingMenu(visibility) {
        const bettermmLoading = document.querySelector("#better_mm_loading")

        if (visibility) {
            bettermmLoading.classList.remove("hidden")
            toggleSelectSkin(null, false, true)

            return toggleBettermmContainer(false)
        }

        bettermmLoading.classList.add("hidden")
        toggleBettermmContainer(true)
    }

    function toggleBettermmContainer(visibility) {
        const bettermmContainer = document.querySelector("#better_mm_container")

        if (visibility) {
            return bettermmContainer.classList.remove("hidden")
        }

        bettermmContainer.classList.add("hidden")
    }

    function getGameServers() {
        const currentMode = location.host.replace(/\.moomoo\.io/, "")
        const getRequestUrl = () => {
            if (/(sandbox|dev)/.test(currentMode)) {
                return `https://api-${currentMode}.moomoo.io/servers?v=1.22`
            }

            return "https://api.moomoo.io/servers"
        }

        return new Promise((resolve) => {
            fetch(getRequestUrl()).then((res) => res.text()).then((servers) => resolve(JSON.parse(servers)))
        })
    }

    async function updateGameServers() {
        let servers = await getGameServers()

        const [ currentServerRegion, currentServerName ] = location.href.replace(/.+\=/, "").split(":")
        const gameServers = document.querySelector("#game_servers")
        const serversByRegions = {}

        gameServers.innerHTML = ""

        for (const server of servers) {
            if (!serversByRegions[server.region]) {
                serversByRegions[server.region] = []
            }

            serversByRegions[server.region].push(server)
        }

        servers = Object.values(serversByRegions)

        for (let serversRegion of servers) {
            serversRegion = serversRegion.sort((a, b) => b.playerCount - a.playerCount)

            for (let i = 0; i < serversRegion.length; i++) {
                const server = serversRegion[i]
                const requestPingUrl = `https://${server.key}.${server.region}.moomoo.io/ping/9mm`
                const sentTime = Date.now()
                const currentMode = location.host.replace(/\.moomoo\.io/, "")
                const id = `${server.region}_${server.name}`
                const isCurrentServer = server.region === currentServerRegion && server.name === currentServerName

                gameServers.insertAdjacentHTML(isCurrentServer ? "afterbegin" : "beforeend", `
                <li class="item${isCurrentServer ? " light-background" : ""}">
                    <div class="server-data-wrapper">
                        <header class="server-data-header">
                            <span class="server-data-players">
                                (${server.playerCount}/${server.playerCapacity})
                            </span>${window.regionsName[server.region]} ${server.name}
                        </header>

                        <div class="server-data-actions">
                            <span class="server-data-ping" id="${id}_ping"></span>
                            ${!isCurrentServer ? `<div class="server-open-btn" id="${id}_open">GO!</div>` : ""}
                        </div>
                    </div>
                </li>
                `)

                const serverOpenBtn = document.querySelector(`#${id}_open`)

                if (serverOpenBtn) {
                    serverOpenBtn.addEventListener("click", () => {
                        window.open(`https://${currentMode !== "" ? currentMode + "." : ""}moomoo.io/?server=${server.region}:${server.name}`)
                    })
                }

                fetch(requestPingUrl).then(() => {
                    const ping = Date.now() - sentTime
                    const serverDataPing = document.querySelector(`#${server.region}_${server.name}_ping`)

                    if (ping >= 500) {
                        serverDataPing.classList.add("red")
                    } else if (ping >= 350 && ping < 500) {
                        serverDataPing.classList.add("low-red")
                    } else if (ping >= 200 && ping < 350) {
                        serverDataPing.classList.add("yellow")
                    } else if (ping >= 100 && ping < 200) {
                        serverDataPing.classList.add("low-green")
                    } else {
                        serverDataPing.classList.add("green")
                    }

                    serverDataPing.innerHTML = ping
                })
            }
        }
    }

    function getModVersions() {
        return `
1.0.1 (5/02/2024):
> Added auto spike sync.
> Added distance check for building markers.
> Optimization for any keyboard.
> Fixed a bug with infinite loading.

1.0.0 (4/02/2024):
> Release.
> Mini update auto mills.
> Added custom main menu.
> Added auto tank and bull hats.
> Added building markers/hp.
> Added items count.
> Store was returned.

1.0.0b (2/02/2024):
> Beta release.
        `
    }

    async function updateChangelog(versions, appendNodeSelector, linesSlice = 0) {
        const changelog = document.querySelector(appendNodeSelector)
        const versionsList = versions.split(/\n/).slice(linesSlice).filter((line) => line !== "" && !/\s\s\-\s/.test(line))

        for (let i = 0; i < versionsList.length; i++) {
            const versionsLine = versionsList[i]

            if (!/^\d/.test(versionsLine)) continue

            const currentVersion = versionsLine.replace(/\:.+$/, ":").replace(/\)\s.+\:/, ":")
            const parsedVersion = currentVersion.replace(/\(.+/, "").replace(/\s\-/, "")
            const versionDate = currentVersion.replace(/^.+\(/, "(").replace(/(\(|\)|\:)/g, "").split("/")
            const date = `${versionDate[1]}/${versionDate[0]}/${versionDate[2]}`
            const month = new Date(date).toLocaleString('en-GB', { month: 'long' })
            const nextGameVersionIndex = versionsList.slice(i + 1).findIndex((line) => /^\d+\./.test(line))
            const updatesList = versionsList.slice(i + 1, (i + 1) + nextGameVersionIndex)

            if (/0\.10/.test(parsedVersion) && linesSlice !== 0) {
                updatesList.push("> Initial Release")
            }

            if (/1\.0\.0b/.test(parsedVersion) && linesSlice === 0) {
                updatesList.push("> Beta release.")
            }

            changelog.insertAdjacentHTML("beforeend", `
            <li class="item">
                <header class="changelog-item-header">${parsedVersion} (${month} ${versionDate[0]}, ${versionDate[2]})</header>

                <div class="changelog-updates">
                ${updatesList.map((update) => `
                <div class="changelog-version-info">
                    <span class="changelog-update-value">${update.replace(/\>\s/, "")}</span>
                </div>
                `).join("")}
                </div>
            </li>
            `)
        }
    }

    function createCustomHtmlAndCss() {
        const style = document.createElement("style")

        style.insertAdjacentHTML("beforeend", `
        .hidden {
            display: none !important;
        }

        .item-count {
            position: absolute;
            display: block;
            color: #fff;
            font-size: 16px;
            margin: 2px 5px;
        }

        .item-count.scale-anim {
            transform: scale(1);
            animation: item-count-scale-anim 1s;
        }

        @keyframes item-count-scale-anim {
            0% {
                transform: scale(1);
            }

            50% {
                transform: scale(1.1);
            }

            100% {
                transform: scale(1);
            }
        }

        .actionBarItem {
            text-align: end;
        }

        #actionBar {
            display: flex !important;
            justify-content: center;
            margin-bottom: 5px;
        }

        #menuContainer, #settingsButton, #partyButton, #linksContainer2, #joinPartyButton {
            display: none !important;
        }
        `)

        document.head.appendChild(style)
    }

    const maxScreenWidth = 1920
    const maxScreenHeight = 1080
    const { lineTo, moveTo } = CanvasRenderingContext2D.prototype
    const gridAlpha = 0.06

    CanvasRenderingContext2D.prototype.moveTo = function(x, y) {
        if (localStorage.remove_grid == "false") return moveTo.apply(this, arguments)
        if (this.globalAlpha === gridAlpha) return

        return moveTo.apply(this, arguments)
    }

    CanvasRenderingContext2D.prototype.lineTo = function(x, y) {
        if (localStorage.remove_grid == "false") return lineTo.apply(this, arguments)
        if (this.globalAlpha === gridAlpha && (y === maxScreenHeight || x === maxScreenWidth)) return

        return lineTo.apply(this, arguments)
    }

    const turnSpeeds = {
        9: .003,
        10: .0016,
        11: .0025,
        12: .005,
    }

    Object.defineProperty(Object.prototype, "turnSpeed", {
        get() {
            if (![10, 11, 12].includes(this.id)) return turnSpeeds[this.id]

            return localStorage.mill_rotate == "true" ? turnSpeeds[this.id] : 0
        },
        set(value) {
            this[Symbol("turnSpeed")] = value
        }
    })

    window.fetch = new Proxy(fetch, {
        apply(target, _this, args) {
            if (/\/ping/.test(args[0]) && !/\/9mm/.test(args[0])) {
                return target.apply(_this)
            }

            if (/\/9mm/.test(args[0])) {
                args[0] = args[0].replace(/\/9mm/, "")
            }

            return target.apply(_this, args)
        }
    })

    console._error = console.error
    console.error = function(error) {
        // FK THIS FKING ERROR!!! FK U FAILED TO LOAD. I'VE BEEN FIXING YOU FOR 4 FUCKING HOURS.
        if (error === "Failed to load.") {
            setInterval(() => {
                setLoadingText("Failed to load -___-")
                toggleBettermmContainer(false)
                toggleLoadingMenu(true)
            })
        }

        this._error(error)
    }

    window.regionsName = {
        "us-east": "Miami",
        "us-west": "Silicon Valley",
        "gb": "London",
        "eu-west": "Frankfurt",
        "au": "Sydney",
        "sg": "Singapore"
    }
})()