Greasy Fork

Greasy Fork is available in English.

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

It's a good script for the game, 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! You don't need to buy anything in the store, the script will buy everything by itself!

当前为 2024-02-04 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name            9mm [MooMoo.io] [v1.0.0]
// @name:ru         9mm [MooMoo.io] [v1.0.0]
// @namespace       https://github.com/Nudo-o
// @version         1.0.0
// @description     It's a good script for the game, 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! You don't need to buy anything in the store, the script will buy everything by itself!
// @description:ru  Это хороший сценарий для игры, не самый лучший, но я постараюсь сделать его как можно лучше. 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://update.greasyfork.icu/scripts/480301/1322139/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.icu/scripts/480301/1322139/CowJS.js

(function() {
    "use strict"

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

    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
        }

        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")

                    setTimeout(() => {
                        Cow.sendPacket(packets.STORE_EQUIP, 1, id, 0, "by 9mm")
                    }, 222)

                    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")

                    setTimeout(() => {
                        Cow.sendPacket(packets.STORE_EQUIP, 1, id, 1, "by 9mm")
                    }, 222)

                    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 (autoClickHats.isActive) 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 && !autoClickHats.isActive) {
                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) && !autoClickHats.isActive) {
                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 && !autoClickHats.isActive) {
                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 (!autoClickHats.isActive) return this.equipBiomeHat()
                    }
                }
            } else {
                if (!autoClickHats.isActive) this.equipBiomeHat()
            }
        }

        update() {
            this.autoHats()

            const { player } = Cow

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

            if (timeSinceHatTick >= 3 && this.hatTicks.length) {
                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
            }

            const hatTick = this.hatTicks[0]

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

            this.hatTicks.shift()
        }
    }

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

        update() {
            const { player } = Cow

            if (!player?.alive || !nearEnemy?.visible) 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)
            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: "KeyQ",
                TRAP: "KeyF",
                SPIKE: "KeyV",
                MILL: "KeyN",
                TURRET: "KeyH"
            }
        }

        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

            Cow.onKeyboard("KeyM", () => {
                if (isInputFocused()) return

                this.isActive = !this.isActive

                if (!this.isActive) this.lastPlace = null
            }, {
                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 (this.lastPlace) {
                const millConfig = Cow.items.list[player.items[items.MILL]]
                const distance = CowUtils.getDistance(player, this.lastPlace)
                const placeDistance = millConfig.scale

                if (distance < placeDistance) return
            }

            Cow.delayedPlaceItem(() => {
                Cow.placeItem(items.MILL, {
                    angle: player.moveDir + this.gap
                })

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

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

                this.lastPlace = {
                    x: player.x,
                    y: player.y
                }
            })
        }
    }

    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
        }

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

            this.isBreaking = false
            tailor.autoTankHat = false

            aimControl.stopAiming()
        }

        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 <= 0

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

            this.isBreaking = true
            tailor.autoTankHat = true

            const breakWeapon = player.weapons[1] === 10 ? player.weapons[1] : player.weapons[0]

            if (player.weaponIndex !== breakWeapon) Cow.sendPacket(packets.SELECT_BUILD, breakWeapon, true)

            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 (!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)
            }
        }
    }

    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)
        }, 750)

        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 = window.autoClickhats = new AutoClickHats()

    let lastRenderUpdate = 0
    let fps = 0

    let h = 100

    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

            renderGameObjectMark(gameObject, context)
        })

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

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

        if (pingDisplay) {
            pingDisplay.innerHTML = `Ping: ${window.pingTime}ms, FPS: ${fps.toFixed(0) || 0}`
        }
    })

    const oldSend = WebSocket.prototype.send

    WebSocket.prototype.send = function(data) {
        const binary = new Uint8Array(data)
        const decoded = Cow.codec.decoder.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") {
                    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, Cow.codec.encoder.encode(decoded))
        }

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

            decoded[1][0] = aimControl.aimAngle

            oldSend.call(this, Cow.codec.encoder.encode(decoded))

            return aimControl.onSent()
        }

        oldSend.apply(this, arguments)
    }

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

    function renderGameObjectMark(gameObject, context) {
        const { player } = Cow
        const color = gameObject.owner.sid === player.sid ? "#8ecc51" : Cow.isAllianceMember(gameObject.owner.sid) ? "#cdaa51" : "#cc5151"
        const radius = 12
        const innerRadius = !gameObject.maxHealth ? radius : (gameObject.health / gameObject.maxHealth) * radius

        context.save()
        context.fillStyle = color

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

        context.save()
        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 removeButton(buttonId) {
        const button = document.querySelector(buttonId)

        button.style.visibility = "hidden"
    }

    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("#storeButton", () => {
        // removeButton("#storeButton")
        removeButton("#chatButton")
    })

    waitForInterval("#gameUI", () => {
        createCustomHtmlAndCss()
        // menu.append()
    })

    waitForInterval("#mainMenu", createCustomMainMenu)

    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;
        }
        `)

        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="http://greasyfork.icu/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>
        `)

        window.addEventListener("load", updateGameServers)
        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("moo_name") || ""
        firstClanInput.value = localStorage.getItem("moo_first_clan") || ""

        nicknameInput.addEventListener("input", () => {
            localStorage.setItem("moo_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() {
        toggleSelectSkin(null, false, true)
        toggleLoadingMenu(true)
    }

    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`
            }

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

        return new Promise((resolve) => {
            const xhr = new XMLHttpRequest()

            xhr.open("GET", getRequestUrl())
            xhr.addEventListener("load", (event) => resolve(JSON.parse(event.target.responseText)))
            xhr.send()
        })
    }

    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 = `${server.key}.${server.region}.moomoo.io/ping`
                const xhr = new XMLHttpRequest()
                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}`)
                    })
                }

                xhr.open("GET", requestPingUrl)

                xhr.onload = () => {
                    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
                }

                xhr.send()
            }
        }
    }

    function getModVersions() {
        return `
1.0.0 (3/02/2024):
> Release.
> Mini update auto mills.
> Added custom main menu.
> Added auto tank and bull hats.
> Added building markers/hp bar.
> 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;
        }

        #gameName::after {
            content: "9mm";
            display: inline-block;
            position: absolute;
            font-size: 40px;
            transform: rotate(45deg) translate(-10px, 60px) scale(1);
            text-shadow: 0 1px 0 #c4c4c4, 0 2px 0 #c4c4c4, 0 3px 0 #c4c4c4;
            animation: scale-anim 3s infinite ease-in-out;
        }

        @keyframes scale-anim {
            0% {
                transform: rotate(45deg) translate(-10px, 60px) scale(1);
            }

            50% {
                transform: rotate(45deg) translate(-10px, 60px) scale(1.1);
            }

            100% {
                transform: rotate(45deg) translate(-10px, 60px) scale(1);
            }
        }
        `)

        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
        }
    })

    document._getElementById = document.getElementById
    document.getElementById = function(selector) {
        if (selector === "enterGame") {
            selector = "play_button"
        }

        return this._getElementById(selector)
    }

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