n-Nurbo client

Bot = P() Shift=Insta, AutoBiomehat Autoheal, F=Trap, V=Spike, C=4Spikes, B=4Traps, N=Mill./'

Du musst eine Erweiterung wie Tampermonkey, Greasemonkey oder Violentmonkey installieren, um dieses Skript zu installieren.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

Sie müssten eine Skript Manager Erweiterung installieren damit sie dieses Skript installieren können

(Ich habe schon ein Skript Manager, Lass mich es installieren!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         n-Nurbo client
// @namespace    http://youtube.com
// @version      1.6.0.3
// @description  Bot  =  P()     Shift=Insta, AutoBiomehat Autoheal, F=Trap, V=Spike, C=4Spikes, B=4Traps, N=Mill./'
// @icon         https://static.wikia.nocookie.net/moom/images/7/70/Cookie.png/revision/latest?cb=20190223141839
// @author       Nurbo Mod
// @match        *://moomoo.io/*
// @match        *://*.moomoo.io/*
// @match        *://sandbox.moomoo.io/*
// @match        *://dev.moomoo.io/*
// @grant        none
// @require      https://update.greasyfork.org/scripts/423602/1005014/msgpack.js
// @require      https://update.greasyfork.org/scripts/480301/1322984/CowJS.js
// @require      https://cdn.jsdelivr.net/npm/[email protected]/fontfaceobserver.standalone.min.js
// @license      MIT
// ==/UserScript==

let multiboxAlts = [];
const mousePosition = {x: 0, y: 0};
const FOLLOW_DISTANCE = 100; // расстояние следования бота за игроком в пикселях

const upgradeOptions = {}; // когда основной игрок улучшается в определенном возрасте, ID предмета, на который он улучшается, будет сохранен здесь.

let placingSpikes = false;
let placingTraps = false;
let repellingAlts = false;
let automill = false;

// Храним позицию игрока для быстрого доступа
let mainPlayerPosition = {x: 0, y: 0};
let mainPlayerUpdated = false;

const updateAltsCounter = () => {
    document.getElementById('altsCounter').innerText = String(multiboxAlts.length);
};

class PowSolver {
    constructor() {
        console.log('PowSolver initialized');
    };

    createToken(json, solution) {
        return 'alt:' + btoa(JSON.stringify({
            algorithm: "SHA-256",
            challenge: json.challenge,
            number: solution,
            salt: json.salt,
            signature: json.signature || null,
            took: 15439
        }));
    };

    async getCaptcha() {
        const resp = await fetch('https://api.moomoo.io/verify');
        const json = await resp.json();
        return json;
    };

    async hash(string) {
        const hash = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(string));
        return new Uint8Array(hash).toHex();
    };

    async solveCaptcha(json) {
        for (let i = 0; i < json.maxnumber; i++) {
            if (await this.hash(json.salt + i) == json.challenge) {
                return i;
            };
        };
    };

    async generateAltchaToken() {
        const json = await this.getCaptcha();
        const solution = await this.solveCaptcha(json);
        return this.createToken(json, solution);
    };
};

class Input {
    constructor(ws) {
        this.msgpack = msgpack;
        this.ws = ws;
    };

    sendMsg(data) {
        this.ws.send(this.msgpack.encode(data));
    };

    sendChatMessage(message) {
        this.sendMsg(['6', [message]]);
    };

    useItem(id) {
        this.sendMsg(['z', [id, null]]);
        this.sendMsg(['F', [1, null]]);
        this.sendMsg(['F', [0, null]]);
        this.sendMsg(['z', [document.ws.player.entity.weapon, true]]);
    };

    healPlayer(currentHealth) {
        let timeout = 115;
        if (currentHealth <= 60) {
            timeout = 1;
        };
        setTimeout(() => {
            this.useItem(0); // heal with apple
            this.useItem(1); // heal with cookie
        }, timeout);
    };

    moveTowardsDirection(angle) {
        this.sendMsg(['9', [angle]]);
    };

    stopMoving() {
        this.sendMsg(['9', [null]]); // null останавливает движение
    };

    sendEnterWorld(name) {
        this.sendMsg(['M', [{
            name: name,
            moofoll: true,
            skin: 0
        }]]);
    };

    joinTribe(name) {
        this.sendMsg(['b', [name]]);
    };

    placeTrap() {
        return this.useItem(15);
    };

    placeBoost() {
        return this.useItem(16);
    };

    placeSpike(spikeType) {
        switch (spikeType) {
            case 'regular':
                this.useItem(6);
                break;
            case 'greater':
                this.useItem(7);
                break;
            case 'poison':
                this.useItem(8);
                break;
            case 'spinning':
                this.useItem(9);
                break;
        };
    };
};

class Player {
    constructor(ws) {
        this.ws = ws;
        this.input = new Input(this.ws);
        this.autoheal = true;
        this.entity = {
            id: null,
            health: 100,
            knownPlayers: [],
            position: {
                x: 0,
                y: 0
            },
            aimingYaw: 0,
            object: -1,
            weapon: 0,
            clan: null,
            isLeader: 0,
            hat: 0,
            accessory: 0
        };
        this.fullyUpgraded = true;

        this.ws.addEventListener('message', this.handleMessage.bind(this));
    };

    handleMessage(msg) {
        const data = msgpack.decode(msg.data);

        switch (data[0]) {
            case 'C': // C means id. it is a message received from the server that tells you what your entity ID is.
                this.entity.id = data[1][0];
                break;
            case 'O': // O means health change. it follows this format: ['O', [entityId, health]]
                // autoheal causes u to get the clown hat very quickly, but who cares when you have 30 alts as well as op insta?
                if (data[1][0] == this.entity.id) this.entity.health = data[1][1];
                if (data[1][0] == this.entity.id && this.autoheal && data[1][1] < 100) this.input.healPlayer(this.entity.health);
                break;
            case 'a':
                // credits to the creator of x-RedDragon client for most of this
                this.entity.knownPlayers = [];
                var playerInfos = data[1][0];
                for (let j = 0; j < playerInfos.length; j += 13) {
                    const playerInfo = playerInfos.slice(j, j + 13);
                    if (playerInfo[0] == this.entity.id) {
                        this.entity.position.x = playerInfo[1];
                        this.entity.position.y = playerInfo[2];
                        this.entity.aimingYaw = playerInfo[3];
                        this.entity.object = playerInfo[4];
                        this.entity.weapon = playerInfo[5];
                        this.entity.clan = playerInfo[7];
                        this.entity.isLeader = playerInfo[8];
                        this.entity.hat = playerInfo[9];
                        this.entity.accessory = playerInfo[10];

                        // Обновляем глобальную позицию главного игрока
                        if (this.ws === document.ws) {
                            mainPlayerPosition.x = playerInfo[1];
                            mainPlayerPosition.y = playerInfo[2];
                            mainPlayerUpdated = true;
                        }
                    } else {
                        this.entity.knownPlayers.push({
                            id: playerInfo[0],
                            position: {
                                x: playerInfo[1],
                                y: playerInfo[2],
                            },
                            aimingYaw: playerInfo[3],
                            object: playerInfo[4],
                            weapon: playerInfo[5],
                            clan: playerInfo[7],
                            isLeader: playerInfo[8],
                            hat: playerInfo[9],
                            accessory: playerInfo[10]
                        });
                    };
                };
                break;
            case 'U':
                this.upgradeAge = ((data[1][0] + data[1][1]) - data[1][0]);
                if (data[1][0] == 0) {
                    this.fullyUpgraded = true;
                } else {
                    this.fullyUpgraded = false;
                };
                break;
        };
    };
};

class Bot {
    constructor(name, serverUrl) {
        this.powSolver = new PowSolver();
        this.name = name;
        this.age = 1;
        this.lastFollowTime = 0;
        this.followInterval = 100; // обновляем движение каждые 100мс

        this.powSolver.generateAltchaToken().then((token) => {
            this.ws = new WebSocket(document.ws.url.split('?token=')[0] + `?token=${token}`);
            this.ws.binaryType = 'arraybuffer';
            this.ws.player = new Player(this.ws);
            this.ws.addEventListener('message', this.handleMessage.bind(this));

            // Запускаем интервал для плавного следования
            setInterval(() => {
                this.followMainPlayer();
            }, this.followInterval);

            setInterval(() => {
                this.ws.player.input.sendChatMessage('Nurbo mode Age: ' + this.age);
                if (!this.ws.player.fullyUpgraded) {
                    try {
                        this.ws.player.input.sendMsg(['H', [upgradeOptions[this.ws.player.upgradeAge]]]);
                    } catch(e) {
                        ; // do nothing
                    };
                };
            }, 30000);
        });
    };

    followMainPlayer() {
        const now = Date.now();
        if (now - this.lastFollowTime < this.followInterval) return;

        this.lastFollowTime = now;

        // Используем глобальные координаты игрока
        if (!mainPlayerUpdated) return;

        // Вычисляем направление к курсору (для атаки/цели)
        const mouseXWorld = (mainPlayerPosition.x - this.ws.player.entity.position.x) + (mousePosition.x - (window.innerWidth / 2)) * (1+(1/3));
        const mouseYWorld = (mainPlayerPosition.y - this.ws.player.entity.position.y) + (mousePosition.y - (window.innerHeight / 2)) * (1+(1/3));
        const aimDir = Math.atan2(mouseYWorld, mouseXWorld);

        // Всегда поворачиваемся к курсору (целимся)
        this.ws.player.input.sendMsg(['D', [aimDir]]);

        // Вычисляем расстояние до игрока
        const distanceX = mainPlayerPosition.x - this.ws.player.entity.position.x;
        const distanceY = mainPlayerPosition.y - this.ws.player.entity.position.y;
        const distanceToPlayer = Math.sqrt(distanceX * distanceX + distanceY * distanceY);

        // Вычисляем направление к игроку (для следования)
        const followDir = Math.atan2(distanceY, distanceX);

        // Логика следования: двигаемся к игроку если далеко, останавливаемся если близко
        if (distanceToPlayer > FOLLOW_DISTANCE) {
            // Двигаемся к игроку
            if (repellingAlts) {
                this.ws.player.input.moveTowardsDirection(followDir - 2.35619);
            } else {
                this.ws.player.input.moveTowardsDirection(followDir);
            }
        } else {
            // Останавливаемся если достаточно близко
            this.ws.player.input.stopMoving();

            // Если очень близко и включен repel, отталкиваемся немного
            if (repellingAlts && distanceToPlayer < 20) {
                this.ws.player.input.moveTowardsDirection(followDir - Math.PI);
            }
        }
    };

    handleMessage(msg) {
        const data = this.ws.player.input.msgpack.decode(msg.data);
        switch (data[0]) {
            case 'io-init':
                multiboxAlts.push(this.ws);
                this.ws.player.input.sendEnterWorld(this.name);
                setInterval(() => { // attempt to respawn every second and join clan
                    this.ws.player.input.sendEnterWorld(this.name);
                    this.ws.player.input.joinTribe(document.ws.player.entity.clan);
                }, 1000);
                updateAltsCounter();
                console.log(multiboxAlts.length);
                break;
            case 'U':
                this.age = (data[1][0] + data[1][1]) - 1;
        };
    };
};

const init = () => {
    document.getElementById('promoImgHolder').remove(); // remove the promo
    for (let i = 0; i < document.getElementsByClassName('adsbygoogle').length; i++) {
        document.getElementsByClassName('adsbygoogle')[0].remove();
    };


    const altCounter = document.createElement('h2');
    altCounter.style = 'text-align: center; font-size: 18x; position: fixed; top: 10px; left: 50%; transform: translateX(-50%);';
    altCounter.innerHTML = 'Alts: <span id="altsCounter">0</span>';
    document.getElementById('gameUI').appendChild(altCounter);

    document.getElementById('touch-controls-fullscreen').addEventListener('mousemove', (e) => {
        mousePosition.x = e.clientX;
        mousePosition.y = e.clientY;
    });

    // store the main player's websocket in the document
    const originalWebSocket = WebSocket;

    const wsInterceptor = {
        construct(target, args) {
            const ws = new originalWebSocket(...args);
            document.ws = ws;
            console.log('captured ws');
            window.WebSocket = originalWebSocket; // this sets the websocket constructor back to normal

            document.ws.player = new Player(document.ws);
            window.addEventListener('keyup', (e) => {
                if (e.target.tagName === 'INPUT') {
                    return;
                };

                if (e.key === 'p') {
            new Bot(`n-${Math.floor(Math.random() * 100) + 1}`);
        }

                if (e.key === ',') {
                    multiboxAlts.forEach((sock) => {
                        sock.close();
                    })
                    multiboxAlts = [];
                    updateAltsCounter();
                };

                if (e.key === 'm') {
                    automill = !automill;
                };

                if (e.key === 'v') {
                    placingSpikes = false;
                };

                if (e.key === 'f') {
                    placingTraps = false;
                };

                if (e.key === 'z') {
                    repellingAlts = false;
                };
            });

            window.addEventListener('keydown', (e) => {
                if (e.target.tagName === 'INPUT') {
                    return;
                };

                if (e.key === 'f') {
                    placingTraps = true;
                };

                if (e.key === 'v') {
                    placingSpikes = true;
                };

                if (e.key === 'z') {
                    repellingAlts = true;
                };
            });

            setInterval(() => {
                if (placingSpikes) {
                    if (upgradeOptions[5] == 23) {
                        document.ws.player.input.placeSpike('greater');
                    } else {
                        document.ws.player.input.placeSpike('regular');
                    };

                    if (upgradeOptions[9] == 24) {
                        document.ws.player.input.placeSpike('poison');
                    };
                    if (upgradeOptions[9] == 25) {
                        document.ws.player.input.placeSpike('spinning');
                    };
                };
                if (placingTraps) {
                    if (upgradeOptions[4] == 31) {
                        document.ws.player.input.placeTrap();
                    } else {
                        document.ws.player.input.placeBoost();
                    };
                };
                if (automill) {
                    if (upgradeOptions[5] == 27) {
                        if (upgradeOptions[8] == 28) {
                            document.ws.player.input.useItem(12);
                        } else {
                            document.ws.player.input.useItem(11);
                        };
                    } else {
                        document.ws.player.input.useItem(10);
                    };
                };
            }, 50);

            let originalSend = document.ws.send.bind(document.ws);
            document.ws.send = (msg) => {
                if (msgpack.decode(msg)[0] === 'F' || msgpack.decode(msg)[0] === 'z' || msgpack.decode(msg)[0] === 'c') {
                    multiboxAlts.forEach((sock) => {
                        sock.send(msg);
                    });
                };

                if (msgpack.decode(msg)[0] === 'H') {
                    upgradeOptions[document.ws.player.upgradeAge] = msgpack.decode(msg)[1][0];
                };

                console.log(msgpack.decode(msg));
                originalSend(msg);
            };

            return ws;
        }
    };

    window.WebSocket = new Proxy(originalWebSocket, wsInterceptor);
};

let waitForGameName = setInterval(() => {
    if (document.getElementById('gameName')) {
        clearInterval(waitForGameName);
        return init();
    };
}, 100);

(function() {
    'use strict';

    // Global variables
    let ws;
    const msgpack5 = window.msgpack;

    let boostType, spikeType, turretType = null, windmillType = null, foodType;
    let width, height, mouseX, mouseY;
    let myPlayer = {
        id: null,
        x: null,
        y: null,
        dir: null,
        object: null,
        weapon: null,
        clan: null,
        isLeader: null,
        hat: null,
        accessory: null,
        isSkull: null,
        health: 100,
        secondaryWeapon: null
    };

    const keysPressed = {};
    const enemiesNear = [];

    let nearestEnemy = null, nearestEnemyAngle = 0;
    const mousePosition = {x: 0, y: 0};
    const upgradeOptions = {};
    let placingSpikes = false;
    let placingTraps = false;
    let automill = false;

    // Menu variables
    let menuOpen = false;
    let autoHealEnabled = true;
    let autoBiomeHatEnabled = true;
    let autoClickEnabled = true;

    // ===================== MENU INTERFACE =====================
    function createMenu() {
        // Create menu container
        const menuContainer = document.createElement('div');
        menuContainer.id = 'nurbo-menu';
        menuContainer.style.cssText = `
            position: fixed;
            top: 10px;
            left: 10px;
            background: rgba(0, 0, 0, 0.9);
            border: 1px solid #555;
            border-radius: 8px;
            color: #fff;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            z-index: 9999;
            min-width: 280px;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5);
            backdrop-filter: blur(8px);
        `;

        // Menu header
        const header = document.createElement('div');
        header.style.cssText = `
            background: #222;
            padding: 12px 15px;
            border-radius: 8px 8px 0 0;
            display: flex;
            justify-content: space-between;
            align-items: center;
            cursor: move;
            user-select: none;
            border-bottom: 1px solid #444;
        `;

        const title = document.createElement('span');
        title.textContent = 'NURBO MOD v1.6.0.3';
        title.style.cssText = 'font-weight: 600; font-size: 14px; color: #4CAF50;';

        const closeBtn = document.createElement('span');
        closeBtn.textContent = '×';
        closeBtn.style.cssText = 'cursor: pointer; font-size: 20px; color: #888;';
        closeBtn.onclick = () => {
            menuOpen = false;
            menuContainer.style.display = 'none';
        };

        header.appendChild(title);
        header.appendChild(closeBtn);

        // Menu content
        const content = document.createElement('div');
        content.style.cssText = 'padding: 15px;';

        // Information section
        const infoSection = document.createElement('div');
        infoSection.innerHTML = `
            <div style="margin-bottom: 20px;">
                <h3 style="color: #4CAF50; margin: 0 0 12px 0; border-bottom: 1px solid #333; padding-bottom: 6px; font-size: 13px; font-weight: 600;">📊 INFORMATION</h3>
                <div style="display: flex; justify-content: space-between; margin: 8px 0; font-size: 13px;">
                    <span style="color: #aaa;">Health:</span>
                    <span id="menu-health" style="color: #FF6B6B; font-weight: 500;">100%</span>
                </div>
                <div style="display: flex; justify-content: space-between; margin: 8px 0; font-size: 13px;">
                    <span style="color: #aaa;">Weapon:</span>
                    <span id="menu-weapon" style="color: #FFD166; font-weight: 500;">-</span>
                </div>
                <div style="display: flex; justify-content: space-between; margin: 8px 0; font-size: 13px;">
                    <span style="color: #aaa;">Biome:</span>
                    <span id="menu-biome" style="color: #06D6A0; font-weight: 500;">-</span>
                </div>
                <div style="display: flex; justify-content: space-between; margin: 8px 0; font-size: 13px;">
                    <span style="color: #aaa;">Enemies:</span>
                    <span id="menu-enemies" style="color: #EF476F; font-weight: 500;">0</span>
                </div>
            </div>
        `;

        // Hotkeys section
        const hotkeysSection = document.createElement('div');
        hotkeysSection.innerHTML = `
            <h3 style="color: #4CAF50; margin: 0 0 12px 0; border-bottom: 1px solid #333; padding-bottom: 6px; font-size: 13px; font-weight: 600;">🎮 CONTROLS</h3>
            <div style="margin: 8px 0;">
                <div style="display: flex; justify-content: space-between; margin: 6px 0; font-size: 12px;">
                    <span style="color: #bbb;">Shift:</span>
                    <span style="color: #06D6A0; font-weight: 500;">Insta Attack</span>
                </div>
                <div style="display: flex; justify-content: space-between; margin: 6px 0; font-size: 12px;">
                    <span style="color: #bbb;">F (Hold):</span>
                    <span style="color: #06D6A0; font-weight: 500;">Place Trap</span>
                </div>
                <div style="display: flex; justify-content: space-between; margin: 6px 0; font-size: 12px;">
                    <span style="color: #bbb;">V (Hold):</span>
                    <span style="color: #06D6A0; font-weight: 500;">Place Spike</span>
                </div>
                <div style="display: flex; justify-content: space-between; margin: 6px 0; font-size: 12px;">
                    <span style="color: #bbb;">C:</span>
                    <span style="color: #06D6A0; font-weight: 500;">4 Spikes around</span>
                </div>
                <div style="display: flex; justify-content: space-between; margin: 6px 0; font-size: 12px;">
                    <span style="color: #bbb;">B:</span>
                    <span style="color: #06D6A0; font-weight: 500;">4 Traps around</span>
                </div>
                <div style="display: flex; justify-content: space-between; margin: 6px 0; font-size: 12px;">
                    <span style="color: #bbb;">N:</span>
                    <span style="color: #06D6A0; font-weight: 500;">Auto Mill</span>
                </div>
                <div style="display: flex; justify-content: space-between; margin: 6px 0; font-size: 12px;">
                    <span style="color: #bbb;">RMB:</span>
                    <span style="color: #06D6A0; font-weight: 500;">Auto Attack</span>
                </div>
                <div style="display: flex; justify-content: space-between; margin: 6px 0; font-size: 12px;">
                    <span style="color: #bbb;">Space:</span>
                    <span style="color: #06D6A0; font-weight: 500;">Attack + Spikes</span>
                </div>
                <div style="display: flex; justify-content: space-between; margin: 6px 0; font-size: 12px;">
                    <span style="color: #bbb;">ESC:</span>
                    <span style="color: #FFD166; font-weight: 500;">Menu</span>
                </div>
            </div>
        `;

        // Settings section
        const settingsSection = document.createElement('div');
        settingsSection.innerHTML = `
            <h3 style="color: #4CAF50; margin: 15px 0 12px 0; border-bottom: 1px solid #333; padding-bottom: 6px; font-size: 13px; font-weight: 600;">⚙️ SETTINGS</h3>
            <div style="margin: 8px 0;">
                <label style="display: flex; align-items: center; margin: 10px 0; cursor: pointer; font-size: 13px;">
                    <input type="checkbox" id="autoheal-toggle" checked style="margin-right: 10px; accent-color: #4CAF50;">
                    <span style="color: #ccc;">Auto Heal</span>
                </label>
                <label style="display: flex; align-items: center; margin: 10px 0; cursor: pointer; font-size: 13px;">
                    <input type="checkbox" id="biomehat-toggle" checked style="margin-right: 10px; accent-color: #4CAF50;">
                    <span style="color: #ccc;">Auto Biome Hat</span>
                </label>
                <label style="display: flex; align-items: center; margin: 10px 0; cursor: pointer; font-size: 13px;">
                    <input type="checkbox" id="autoclick-toggle" checked style="margin-right: 10px; accent-color: #4CAF50;">
                    <span style="color: #ccc;">Auto Click (RMB)</span>
                </label>
            </div>
        `;

        // Status bar
        const statusBar = document.createElement('div');
        statusBar.id = 'menu-status';
        statusBar.style.cssText = `
            background: #1a1a1a;
            padding: 10px;
            border-radius: 6px;
            margin-top: 15px;
            text-align: center;
            font-size: 12px;
            color: #06D6A0;
            border: 1px solid #333;
            font-weight: 500;
        `;
        statusBar.textContent = '✅ Active';

        content.appendChild(infoSection);
        content.appendChild(hotkeysSection);
        content.appendChild(settingsSection);
        content.appendChild(statusBar);

        menuContainer.appendChild(header);
        menuContainer.appendChild(content);

        document.body.appendChild(menuContainer);

        // Add dragging
        let isDragging = false;
        let dragOffset = {x: 0, y: 0};

        header.addEventListener('mousedown', startDrag);
        document.addEventListener('mousemove', doDrag);
        document.addEventListener('mouseup', stopDrag);

        function startDrag(e) {
            if (e.target === closeBtn) return;
            isDragging = true;
            const rect = menuContainer.getBoundingClientRect();
            dragOffset.x = e.clientX - rect.left;
            dragOffset.y = e.clientY - rect.top;
            menuContainer.style.cursor = 'grabbing';
        }

        function doDrag(e) {
            if (!isDragging) return;
            e.preventDefault();
            menuContainer.style.left = (e.clientX - dragOffset.x) + 'px';
            menuContainer.style.top = (e.clientY - dragOffset.y) + 'px';
            menuContainer.style.right = 'auto';
        }

        function stopDrag() {
            isDragging = false;
            menuContainer.style.cursor = '';
        }

        // Switch handlers
        document.getElementById('autoheal-toggle').onchange = function() {
            autoHealEnabled = this.checked;
            updateStatus();
        };

        document.getElementById('biomehat-toggle').onchange = function() {
            autoBiomeHatEnabled = this.checked;
            updateStatus();
        };

        document.getElementById('autoclick-toggle').onchange = function() {
            autoClickEnabled = this.checked;
            updateStatus();
        };

        // Menu toggle button
        const toggleBtn = document.createElement('div');
        toggleBtn.id = 'menu-toggle';
        toggleBtn.style.cssText = `
            position: fixed;
            top: 10px;
            left: 10px;
            width: 36px;
            height: 36px;
            background: #222;
            border: 1px solid #444;
            border-radius: 6px;
            display: flex;
            align-items: center;
            justify-content: center;
            color: #4CAF50;
            font-weight: bold;
            font-size: 16px;
            cursor: pointer;
            z-index: 9998;
            box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
            user-select: none;
            transition: all 0.2s ease;
        `;
        toggleBtn.textContent = 'N';
        toggleBtn.onmouseenter = () => {
            toggleBtn.style.background = '#2a2a2a';
            toggleBtn.style.borderColor = '#4CAF50';
        };
        toggleBtn.onmouseleave = () => {
            toggleBtn.style.background = '#222';
            toggleBtn.style.borderColor = '#444';
        };
        toggleBtn.onclick = () => {
            menuOpen = !menuOpen;
            menuContainer.style.display = menuOpen ? 'block' : 'none';
        };

        document.body.appendChild(toggleBtn);

        // Status update function
        function updateStatus() {
            const status = statusBar;
            const health = document.getElementById('menu-health');
            const weapon = document.getElementById('menu-weapon');
            const biome = document.getElementById('menu-biome');
            const enemies = document.getElementById('menu-enemies');

            // Update statistics
            if (health) health.textContent = myPlayer.health ? `${myPlayer.health}%` : '100%';
            if (enemies) enemies.textContent = enemiesNear.length;

            // Determine biome
            if (biome && myPlayer.y !== null) {
                if (myPlayer.y < 2400) {
                    biome.textContent = 'Desert';
                } else if (myPlayer.y > 6850 && myPlayer.y < 7550) {
                    biome.textContent = 'Winter';
                } else if (myPlayer.y > 6200 && myPlayer.y < 6800) {
                    biome.textContent = 'Water';
                } else {
                    biome.textContent = 'Normal';
                }
            }

            // Update mod status
            let statusText = '✅ Active ';
            statusText += autoHealEnabled ? '💊' : '';
            statusText += autoBiomeHatEnabled ? '🎩' : '';

            status.textContent = statusText;
        }

        // Update info every second
        setInterval(updateStatus, 1000);

        // Hide menu by default
        menuContainer.style.display = 'none';

        return menuContainer;
    }

    // ===================== MAIN FUNCTIONS =====================

    function storeEquip(hatId = null, accessoryId = null) {
        const hat = hatId !== null ? hatId : myPlayer.hat || 0;
        const acc = accessoryId !== null ? accessoryId : myPlayer.accessory || 0;
        doNewSend(["c", [0, hat, acc]]);
    }

    function doNewSend(sender) {
        if (ws && msgpack5) ws.send(new Uint8Array(Array.from(msgpack5.encode(sender))));
    }

    function place(id, angle = Math.atan2(mouseY - height / 2, mouseX - width / 2)) {
        if (id == null) return;
        doNewSend(["z", [id, null]]);
        doNewSend(["F", [1, angle]]);
        doNewSend(["F", [0, angle]]);
        doNewSend(["z", [myPlayer.weapon, true]]);
    }

    function isVisible(el) {
        return el && el.offsetParent !== null;
    }

    function updateItems() {
        for (let i = 31; i < 33; i++) if (isVisible(document.getElementById("actionBarItem" + i))) boostType = i - 16;
        for (let i = 22; i < 26; i++) if (isVisible(document.getElementById("actionBarItem" + i))) spikeType = i - 16;
        for (let i = 26; i <= 28; i++) if (isVisible(document.getElementById("actionBarItem" + i))) windmillType = i - 16;
        for (let i = 33; i <= 38; i++) if (i !== 36 && isVisible(document.getElementById("actionBarItem" + i))) turretType = i - 16;
        for (let i = 16; i <= 18; i++) if (isVisible(document.getElementById("actionBarItem" + i))) foodType = i - 16;
    }

    function toRad(degrees) {
        return degrees * 0.01745329251;
    }

    function getSecondaryWeaponIndex() {
        for (let i = 9; i <= 15; i++) {
            if (isVisible(document.getElementById("actionBarItem" + i)) && i !== myPlayer.weapon) {
                return i;
            }
        }
        return myPlayer.weapon;
    }

    function performAttackWithSpikes() {
        if (!nearestEnemy) return;

        const dx = myPlayer.x - nearestEnemy[1];
        const dy = myPlayer.y - nearestEnemy[2];
        const distance = Math.sqrt(dx * dx + dy * dy);

        doNewSend(["9", [nearestEnemyAngle]]);

        place(spikeType, nearestEnemyAngle + Math.PI / 2);
        place(spikeType, nearestEnemyAngle - Math.PI / 2);

        if (distance <= 150) {
            place(spikeType, nearestEnemyAngle - Math.PI / 4);
            place(spikeType, nearestEnemyAngle + Math.PI / 4);
        }

        place(boostType, nearestEnemyAngle);
    }

    function performFourTraps() {
        const base = myPlayer.dir;
        place(boostType, base);
        place(boostType, base + Math.PI / 2);
        place(boostType, base - Math.PI / 2);
        place(boostType, base + Math.PI);
    }

    function placeFourSpikes() {
        const firstAngle = -Math.PI / 2;
        place(spikeType, firstAngle);
        place(spikeType, firstAngle + toRad(90));
        place(spikeType, firstAngle + toRad(180));
        place(spikeType, firstAngle + toRad(270));
    }

    function placeSingleSpike() {
        place(spikeType);
    }

    function placeSingleTrap() {
        place(boostType);
    }

    // ===================== AUTOBIOME HAT =====================
    function autoBiomeHatController() {
        let normalHat = 12;
        let currentHat = null;
        let overridePause = false;
        let resumeTimeout = null;
        const movementKeys = new Set();
        const overrideKeys = new Set(["r", "t", " "]);
        let isHoldingSecondaryWeapon = false;
        let lastWeaponCheckTime = 0;
        const WEAPON_CHECK_INTERVAL = 100;

        if (!autoBiomeHatEnabled) return;

        function setHat(id) {
            if (id !== currentHat && myPlayer && myPlayer.id != null) {
                currentHat = id;
                doNewSend(["c", [0, id, 0]]);

                let accessoryId = null;
                if (id === 6) {
                    accessoryId = 0;
                } else if ([15, 31, 12].includes(id)) {
                    accessoryId = 11;
                }

                if (accessoryId !== null) {
                    [40, 80, 120].forEach(delay => {
                        setTimeout(() => {
                            storeEquip(accessoryId, 1);
                        }, delay);
                    });
                }
            }
        }

        function updateBiomeHat() {
            if (!myPlayer || typeof myPlayer.y !== "number") return;

            const isInWater = myPlayer.y > 6200 && myPlayer.y < 6800;

            if (myPlayer.y < 2400) {
                normalHat = 15;
            } else if (myPlayer.y > 6850 && myPlayer.y < 7550) {
                normalHat = 31;
            } else {
                normalHat = 12;
            }

            if (isHoldingSecondaryWeapon && movementKeys.size === 0) {
                normalHat = 20;
            }
        }

        function updateHatLogic() {
            if (overridePause) return;
            updateBiomeHat();

            if (isHoldingSecondaryWeapon && movementKeys.size === 0) {
                setHat(20);
                storeEquip(0, 1);
            } else {
                if (movementKeys.size > 0) {
                    setHat(normalHat);
                } else {
                    setHat(6);
                }
            }
        }

        function checkSecondaryWeapon() {
            const now = Date.now();
            if (now - lastWeaponCheckTime < WEAPON_CHECK_INTERVAL) return;
            lastWeaponCheckTime = now;

            const secondaryIndex = getSecondaryWeaponIndex();
            isHoldingSecondaryWeapon = (secondaryIndex !== null && secondaryIndex !== myPlayer.weapon);

            if (isHoldingSecondaryWeapon) {
                myPlayer.secondaryWeapon = secondaryIndex;
            } else {
                myPlayer.secondaryWeapon = null;
            }

            if (!overridePause) {
                updateHatLogic();
            }
        }

        function pauseOverride() {
            overridePause = true;
            if (resumeTimeout) clearTimeout(resumeTimeout);
        }

        function resumeOverride() {
            if (resumeTimeout) clearTimeout(resumeTimeout);
            resumeTimeout = setTimeout(() => {
                overridePause = false;
                updateHatLogic();
            }, 360);
        }

        document.addEventListener("keydown", e => {
            const key = e.key.toLowerCase();
            if (["w", "a", "s", "d", "arrowup", "arrowdown", "arrowleft", "arrowright"].includes(key)) {
                if (!movementKeys.has(key)) {
                    movementKeys.add(key);
                    if (!overridePause) updateHatLogic();
                }
            }
            if (overrideKeys.has(key)) pauseOverride();
        });

        document.addEventListener("keyup", e => {
            const key = e.key.toLowerCase();
            if (movementKeys.delete(key) && !overridePause) {
                updateHatLogic();
            }
            if (overrideKeys.has(key)) resumeOverride();
        });

        document.addEventListener("mousedown", e => {
            if (e.button === 0 || e.button === 2) pauseOverride();
        });

        document.addEventListener("mouseup", e => {
            if (e.button === 0 || e.button === 2) resumeOverride();
        });

        setInterval(() => {
            checkSecondaryWeapon();
        }, WEAPON_CHECK_INTERVAL);

        setInterval(() => {
            if (!overridePause) updateHatLogic();
        }, 250);
    }

    // ===================== AUTO CLICK (RMB) =====================
    const WEAPON_SPEEDS = {
        0: 260, 1: 360, 2: 360, 3: 260, 4: 260,
        5: 500, 6: 660, 7: 60, 8: 360,
        9: 560, 10: 360, 12: 660, 13: 170,
        14: 660, 15: 1460
    };

    let rightClickHeld = false;
    let rightLoopRunning = false;
    let lastAttackTime = 0;

    function getWeaponReloadTime(id) {
        return WEAPON_SPEEDS[id] || 400;
    }

    function weaponReady(id) {
        return Date.now() - lastAttackTime >= getWeaponReloadTime(id);
    }

    function equipWeapon(id) {
        doNewSend(["z", [id, true]]);
    }

    function equipHat(id) {
        doNewSend(["c", [0, id, 0]]);
    }

    function swing() {
        lastAttackTime = Date.now();
        doNewSend(["F", [1]]);
        setTimeout(() => doNewSend(["F", [0]]), 12);
    }

    function isInUI(e) {
        const target = e.target;
        return target.closest(
            '#nameInput, .menuButton, .menuCard, #bottomText, #storeHolder,' +
            '#youtuberBtn, #adCard, .setNameContainer, .newsHolder, #gameUI,' +
            '.resourceDisplay, #killCounter, .uiElement, .actionBarItem, #itemInfoHolder'
        );
    }

    function rightAttackStep() {
        if (!rightClickHeld || !autoClickEnabled) return;

        const secondary = getSecondaryWeaponIndex();
        const primary = myPlayer.weapon;
        const weaponToUse = secondary === 10 ? secondary : primary;

        equipWeapon(weaponToUse);
        equipHat(40);

        if (!weaponReady(weaponToUse)) return;

        swing();

        const reload = getWeaponReloadTime(weaponToUse);
        setTimeout(() => {
            if (rightClickHeld) equipHat(6);
        }, reload - 20);
    }

    function rightAttackLoop() {
        if (!rightClickHeld) {
            rightLoopRunning = false;
            return;
        }

        rightAttackStep();

        const secondary = getSecondaryWeaponIndex();
        const currentWeapon = secondary === 10 ? secondary : myPlayer.weapon;
        const delay = getWeaponReloadTime(currentWeapon) * 2;

        setTimeout(rightAttackLoop, delay);
    }

    document.addEventListener("mousedown", e => {
        if (isInUI(e)) return;

        if (e.button === 2 && !rightClickHeld) {
            rightClickHeld = true;
            if (!rightLoopRunning && autoClickEnabled) {
                rightLoopRunning = true;
                rightAttackLoop();
            }
        }
    });

    document.addEventListener("mouseup", e => {
        if (e.button === 2) {
            rightClickHeld = false;
            rightLoopRunning = false;
            setTimeout(() => equipHat(6), 100);
        }
    });

    // ===================== AUTO HEAL =====================
    function healMainPlayer(currentHealth) {
        if (!autoHealEnabled || currentHealth >= 100) return;

        let timeout = 115;
        if (currentHealth <= 60) {
            timeout = 1;
        }

        setTimeout(() => {
            if (!ws || ws.readyState !== WebSocket.OPEN) return;

            doNewSend(["z", [0, null]]);
            doNewSend(["F", [1, null]]);
            doNewSend(["F", [0, null]]);
            doNewSend(["z", [myPlayer.weapon, true]]);

            if (currentHealth <= 60) {
                setTimeout(() => {
                    doNewSend(["z", [1, null]]);
                    doNewSend(["F", [1, null]]);
                    doNewSend(["F", [0, null]]);
                    doNewSend(["z", [myPlayer.weapon, true]]);
                }, 50);
            }
        }, timeout);
    }

    // ===================== INSTA ATTACK =====================
    function performNormalInsta() {
        let attackAngle = Math.atan2(mouseY - height / 2, mouseX - width / 2);
        if (nearestEnemy) {
            const dx = myPlayer.x - nearestEnemy[1];
            const dy = myPlayer.y - nearestEnemy[2];
            const distance = Math.sqrt(dx * dx + dy * dy);




        } else {
            attackAngle = Math.atan2(mouseY - height / 2, mouseX - width / 2);
        }

        storeEquip(0, 1);
        setTimeout(() => {
            doNewSend(["D", [attackAngle]]);

            const primary = myPlayer.weapon;
            const secondary = getSecondaryWeaponIndex();

            doNewSend(["c", [0, 7, 0]]);
            doNewSend(["z", [primary, true]]);
            doNewSend(["F", [1, attackAngle]]);
            setTimeout(() => doNewSend(["F", [0, attackAngle]]), 25);

            setTimeout(() => {
                doNewSend(["c", [0, 53, 0]]);
                doNewSend(["z", [secondary, true]]);
                doNewSend(["F", [1, attackAngle]]);
                setTimeout(() => doNewSend(["F", [0, attackAngle]]), 25);

                setTimeout(() => {
                    doNewSend(["c", [0, 6, 0]]);
                    doNewSend(["z", [primary, true]]);
                    doNewSend(["z", [primary, true]]);

                    setTimeout(() => {
                        storeEquip(11, 1);

                        if (secondary === 15) {
                            doNewSend(["z", [secondary, true]]);
                            setTimeout(() => doNewSend(["z", [primary, true]]), 1900);
                        } else if (secondary === 12) {
                            doNewSend(["z", [secondary, true]]);
                            setTimeout(() => doNewSend(["z", [primary, true]]), 1000);
                        } else if (secondary === 13) {
                            doNewSend(["z", [secondary, true]]);
                            setTimeout(() => doNewSend(["z", [primary, true]]), 400);
                        }
                    }, 170);
                }, 120);
            }, 120);
        }, 120);
    }

    // ===================== TRACK MOUSE POSITION =====================
    const cvs = document.getElementById("gameCanvas");
    if (cvs) {
        cvs.addEventListener("mousemove", e => {
            mouseX = e.clientX;
            mouseY = e.clientY;
            width = e.target.clientWidth;
            height = e.target.clientHeight;
            mousePosition.x = e.clientX;
            mousePosition.y = e.clientY;
        });
    }

    document.addEventListener("mousemove", e => {
        mousePosition.x = e.clientX;
        mousePosition.y = e.clientY;
    });

    // ===================== EVENT HANDLING =====================

    // Key press tracking for hold functionality
    let fKeyHeld = false;
    let vKeyHeld = false;
    let fKeyInterval = null;
    let vKeyInterval = null;

    document.addEventListener("keydown", e => {
        if (document.activeElement && document.activeElement.id.toLowerCase() === 'chatbox') return;

        const key = e.key.toLowerCase();

        // Insta attack on Shift
        if (e.keyCode == 16) {
            performNormalInsta();
        }

        // Handle hotkeys
        if (e.target.tagName === 'INPUT') return;

        // F key - Place trap (hold for continuous)
        if (key === 'f' && !fKeyHeld) {
            fKeyHeld = true;
            placeSingleTrap(); // Place one immediately

            // Start continuous placement
            fKeyInterval = setInterval(() => {
                if (fKeyHeld) {
                    placeSingleTrap();
                }
            }, 100);
        }

        // V key - Place spike (hold for continuous)
        if (key === 'v' && !vKeyHeld) {
            vKeyHeld = true;
            placeSingleSpike(); // Place one immediately

            // Start continuous placement
            vKeyInterval = setInterval(() => {
                if (vKeyHeld) {
                    placeSingleSpike();
                }
            }, 100);
        }

        // C key - 4 spikes around
        if (key === 'c') {
            placeFourSpikes();
        }

        // B key - 4 traps around
        if (key === 'b') {
            performFourTraps();
        }

        // N key - Toggle auto mill
        if (key === 'n') {
            automill = !automill;
        }

        // Space - Attack with spikes
        if (key === ' ') {
            performAttackWithSpikes();
        }

        // Escape - Toggle menu
        if (key === 'escape') {
            const menu = document.getElementById('nurbo-menu');
            const toggleBtn = document.getElementById('menu-toggle');
            if (menu && toggleBtn) {
                menuOpen = !menuOpen;
                menu.style.display = menuOpen ? 'block' : 'none';
            }
        }
    });

    document.addEventListener("keyup", e => {
        const key = e.key.toLowerCase();

        // Stop F key placement
        if (key === 'f' && fKeyHeld) {
            fKeyHeld = false;
            if (fKeyInterval) {
                clearInterval(fKeyInterval);
                fKeyInterval = null;
            }
        }

        // Stop V key placement
        if (key === 'v' && vKeyHeld) {
            vKeyHeld = false;
            if (vKeyInterval) {
                clearInterval(vKeyInterval);
                vKeyInterval = null;
            }
        }
    });

    // WebSocket interceptor
    WebSocket.prototype.nsend = WebSocket.prototype.send;
    WebSocket.prototype.send = function (message) {
        if (!ws) {
            ws = this;
            ws.addEventListener("message", handleMessage);
            ws.addEventListener("close", (event) => {
                if (event.code == 4001) {
                    window.location.reload();
                }
            });
        }

        return this.nsend(message);
    };

    function handleMessage(m) {
        let temp = msgpack5.decode(new Uint8Array(m.data));
        let data = (temp.length > 1) ? [temp[0], ...temp[1]] : temp;
        if (!data) return;

        if (data[0] === "C" && myPlayer.id == null) myPlayer.id = data[1];

        if (data[0] === "a") {
            enemiesNear.length = 0; // Clear array

            for (let i = 0; i < data[1].length / 13; i++) {
                let obj = data[1].slice(13 * i, 13 * i + 13);
                if (obj[0] === myPlayer.id) {
                    [myPlayer.x, myPlayer.y, myPlayer.dir, myPlayer.object, myPlayer.weapon,
                     , myPlayer.clan, myPlayer.isLeader, myPlayer.hat, myPlayer.accessory,
                     myPlayer.isSkull] = [obj[1], obj[2], obj[3], obj[4],
                     obj[5], obj[7], obj[8], obj[9], obj[10], obj[11]];
                } else enemiesNear.push(obj);
            }

            if (enemiesNear.length > 0) {
                enemiesNear.sort((a, b) => {
                    const distA = Math.hypot(a[1] - myPlayer.x, a[2] - myPlayer.y);
                    const distB = Math.hypot(b[1] - myPlayer.x, b[2] - myPlayer.y);
                    return distA - distB;
                });

                nearestEnemy = enemiesNear[0];

                if (nearestEnemy) {
                    const dx = myPlayer.x - nearestEnemy[1];
                    const dy = myPlayer.y - nearestEnemy[2];
                    const distance = Math.sqrt(dx * dx + dy * dy);

                    if (distance > 1000) {
                        nearestEnemy = null;
                    } else {
                        nearestEnemyAngle = Math.atan2(nearestEnemy[2] - myPlayer.y, nearestEnemy[1] - myPlayer.x);
                    }
                }
            } else {
                nearestEnemy = null;
            }
        }

        // Auto heal when damaged
        if (data[0] === "O" && data[1] === myPlayer.id) {
            myPlayer.health = data[2];
            healMainPlayer(myPlayer.health);
        }
    }

    // ===================== INITIALIZATION =====================

    function init() {
        // Remove ads
        const promoImgHolder = document.getElementById('promoImgHolder');
        if (promoImgHolder) promoImgHolder.remove();

        for (let i = 0; i < document.getElementsByClassName('adsbygoogle').length; i++) {
            const ad = document.getElementsByClassName('adsbygoogle')[0];
            if (ad) ad.remove();
        }

        // Change game name
        const gameName = document.getElementById('gameName');
        if (gameName) {
            gameName.innerText = 'Hypbo mode';
            gameName.style = 'color: #4CAF50;';
        }

        // Change menu
        const mainMenu = document.getElementById('mainMenu');
        if (mainMenu) mainMenu.style = 'background-color: #111;';

        const diedText = document.getElementById('diedText');
        if (diedText) {
            diedText.innerText = 'RESPAWN';
            diedText.style = 'color: #4CAF50; background-color: #111;';
        }

        // Create menu
        createMenu();

        // Start auto item update
        setInterval(updateItems, 250);

        // Start auto hats
        autoBiomeHatController();

        // Intervals for automatic actions
        setInterval(() => {
            if (automill) {
                if (upgradeOptions[5] == 27) {
                    if (upgradeOptions[8] == 28) {
                        doNewSend(['z', [12, null]]);
                        doNewSend(['F', [1, null]]);
                        doNewSend(['F', [0, null]]);
                        doNewSend(['z', [myPlayer.weapon, true]]);
                    } else {
                        doNewSend(['z', [11, null]]);
                        doNewSend(['F', [1, null]]);
                        doNewSend(['F', [0, null]]);
                        doNewSend(['z', [myPlayer.weapon, true]]);
                    };
                } else {
                    doNewSend(['z', [10, null]]);
                    doNewSend(['F', [1, null]]);
                    doNewSend(['F', [0, null]]);
                    doNewSend(['z', [myPlayer.weapon, true]]);
                };
            };
        }, 50);
    }

    // Start initialization
    let waitForGameName = setInterval(() => {
        if (document.getElementById('gameName')) {
            clearInterval(waitForGameName);
            init();
        }
    }, 100);

})();