BounceClient

Enhanced Bloxd.io client with modern UI

คุณจะต้องติดตั้งส่วนขยาย เช่น Tampermonkey, Greasemonkey หรือ Violentmonkey เพื่อติดตั้งสคริปต์นี้

คุณจะต้องติดตั้งส่วนขยาย เช่น Tampermonkey หรือ Violentmonkey เพื่อติดตั้งสคริปต์นี้

คุณจะต้องติดตั้งส่วนขยาย เช่น Tampermonkey หรือ Violentmonkey เพื่อติดตั้งสคริปต์นี้

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.

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

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

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         BounceClient
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Enhanced Bloxd.io client with modern UI
// @author       ninja + CyphrNX + marcus (Modified for BounceClient)
// @match        *://*.bloxd.io/*
// @grant        none
// ==/UserScript==

(function () {
    'use strict';
    if (document.getElementById('bounceclient-root')) return;

    // --- Module definitions ---
    const Modules = {
        Blatant: { "Killaura": false, "Scaffold": false },
        Visual: { "ESP": false, "Coords": false },
        Legit: { "FPS Counter": false, "CPS Counter": false, "Keystrokes": false }
    };

    // Keybinds configuration
    const Keybinds = {
        "Killaura": null,
        "Scaffold": null,
        "ESP": null,
        "Coords": null
    };

    // Module configuration
    const Config = {
        Killaura: {
            delay: 50,
            range: 4.5,
            jitter: 0
        },
        Scaffold: {
            delay: 50,
            tower: false
        },
        ESP: {
            chestUpdateInterval: 3000,
            playerUpdateInterval: 100,
            wireframeColor: [0, 0.7, 1] // Cyan RGB
        },
        Coords: {
            updateInterval: 100
        }
    };

    // Load config from localStorage
    const savedConfig = localStorage.getItem('bounceclient-config');
    if (savedConfig) {
        try {
            Object.assign(Config, JSON.parse(savedConfig));
        } catch(e) {
            console.error("Failed to load config:", e);
        }
    }

    // Save config to localStorage
    function saveConfig() {
        localStorage.setItem('bounceclient-config', JSON.stringify(Config));
    }

    let currentCategory = "Blatant";
    let gameState = {};
    let injectionStatus = { injected: false, failed: false }; // Track injection status
    let injectionRetryInterval = null;
    let objectUtils = {
        keys(e) {
            var t = [], n = 0;
            for (var o in e) null != e && (t[n] = o, n++);
            return t;
        },
        values(e) {
            for (var t = this.keys(e), n = [], o = 0, i = 0; o < t.length;) {
                var a = e[t[o]];
                n[i] = a, i++, o++;
            }
            return n;
        }
    };

    // === GUI Setup ===
    const root = document.createElement("div");
    root.id = "bounceclient-root";
    document.body.appendChild(root);

    const style = document.createElement("style");
    style.textContent = `
    :root {
        --primary: #00d4ff;
        --primary-dark: #0099cc;
        --secondary: #7c4dff;
        --bg: rgba(15, 15, 30, 0.95);
        --card-bg: rgba(25, 25, 45, 0.8);
        --border: rgba(100, 100, 255, 0.3);
        --text: #ffffff;
        --text-secondary: rgba(255, 255, 255, 0.7);
    }
    
    #bounceclient-root * { 
        font-family: 'Segoe UI', system-ui, sans-serif; 
        box-sizing: border-box; 
        transition: all 0.2s ease;
    }

    #bounceclient-gui {
        position: fixed;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        width: 500px;
        max-width: 90vw;
        background: var(--bg);
        color: var(--text);
        border-radius: 16px;
        border: 1px solid var(--border);
        backdrop-filter: blur(10px);
        box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
        display: none;
        flex-direction: column;
        z-index: 999999;
        overflow: hidden;
    }
    
    #bounceclient-header {
        display: flex;
        align-items: center;
        justify-content: space-between;
        padding: 16px 20px;
        background: linear-gradient(135deg, var(--primary), var(--secondary));
        border-bottom: 1px solid var(--border);
    }
    
    .title {
        font-weight: 800;
        font-size: 24px;
        text-shadow: 0 0 10px rgba(0, 212, 255, 0.5);
        letter-spacing: 1px;
    }
    
    .title sup {
        font-size: 14px;
        color: var(--text);
        opacity: 0.8;
    }
    
    #category-buttons {
        display: flex;
        gap: 8px;
        margin: 16px;
        margin-bottom: 0;
    }
    
    .cat-btn {
        flex: 1;
        padding: 10px;
        border-radius: 10px;
        border: 1px solid var(--border);
        background: var(--card-bg);
        color: var(--text-secondary);
        cursor: pointer;
        text-align: center;
        font-weight: 600;
        transition: all 0.2s;
    }
    
    .cat-btn:hover {
        background: rgba(255, 255, 255, 0.1);
        transform: translateY(-2px);
    }
    
    .cat-btn.on {
        background: linear-gradient(135deg, var(--primary), var(--secondary));
        color: white;
        border-color: transparent;
        box-shadow: 0 4px 12px rgba(0, 212, 255, 0.3);
    }
    
    #bounceclient-content {
        display: flex;
        padding: 16px;
        gap: 16px;
    }
    
    #bounceclient-mods {
        flex: 1;
        display: flex;
        flex-direction: column;
        gap: 12px;
    }
    
    .module {
        display: flex;
        align-items: center;
        justify-content: space-between;
        padding: 14px 16px;
        border-radius: 12px;
        background: var(--card-bg);
        border: 1px solid var(--border);
        cursor: pointer;
        transition: all 0.2s;
    }
    
    .module:hover {
        background: rgba(255, 255, 255, 0.08);
        transform: translateY(-2px);
        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
    }
    
    .module-name {
        font-weight: 600;
        font-size: 16px;
    }
    
    .toggle {
        width: 48px;
        height: 24px;
        border-radius: 24px;
        background: rgba(255, 255, 255, 0.15);
        position: relative;
        cursor: pointer;
    }
    
    .knob {
        position: absolute;
        top: 3px;
        left: 3px;
        width: 18px;
        height: 18px;
        background: white;
        border-radius: 50%;
        transition: all 0.2s;
        box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
    }
    
    .toggle.on {
        background: linear-gradient(90deg, var(--primary), var(--secondary));
    }
    
    .toggle.on .knob {
        left: 27px;
    }
    
    #themes {
        width: 160px;
        display: flex;
        flex-direction: column;
        gap: 12px;
    }
    
    #themeLabel {
        font-weight: 700;
        color: var(--primary);
        margin-bottom: 4px;
        text-shadow: 0 0 8px rgba(0, 212, 255, 0.5);
    }
    
    .theme {
        padding: 12px;
        border-radius: 10px;
        border: 1px solid var(--border);
        background: var(--card-bg);
        cursor: pointer;
        transition: all 0.2s;
    }
    
    .theme:hover {
        background: rgba(255, 255, 255, 0.1);
        transform: translateY(-2px);
    }
    
    .keybind-btn {
        width: 36px;
        height: 24px;
        font-size: 10px;
        padding: 0;
        margin-left: 10px;
        cursor: pointer;
        border: 1px solid var(--border);
        background: var(--card-bg);
        color: var(--text-secondary);
        border-radius: 6px;
        transition: all 0.2s;
    }
    
    .keybind-btn:hover {
        background: rgba(255, 255, 255, 0.1);
    }
    
    .module-info {
        display: flex;
        align-items: center;
        flex: 1;
    }
    
    .keybind-label {
        font-size: 11px;
        opacity: 0.6;
        margin-left: 8px;
    }
    
    #inject-status {
        width: 14px;
        height: 14px;
        border-radius: 50%;
        margin-left: 12px;
        transition: background 0.3s;
        box-shadow: 0 0 8px rgba(0, 0, 0, 0.3);
    }
    
    #inject-status.red {
        background: #ff3b49;
        box-shadow: 0 0 8px rgba(255, 59, 73, 0.5);
    }
    
    #inject-status.green {
        background: #34dd55;
        box-shadow: 0 0 8px rgba(52, 221, 85, 0.5);
    }
    
    #inject-status.yellow {
        background: #ffa500;
        box-shadow: 0 0 8px rgba(255, 165, 0, 0.5);
    }
    
    #inject-btn, #settings-btn {
        padding: 10px 16px;
        background: linear-gradient(135deg, var(--primary), var(--secondary));
        border: none;
        border-radius: 10px;
        color: white;
        cursor: pointer;
        font-weight: 700;
        font-size: 13px;
        margin-top: 8px;
        transition: all 0.2s;
        box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
    }
    
    #inject-btn:hover, #settings-btn:hover {
        transform: translateY(-2px);
        box-shadow: 0 6px 12px rgba(0, 0, 0, 0.3);
    }
    
    #settings-panel {
        display: none;
        position: fixed;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        width: 500px;
        max-width: 90vw;
        background: var(--bg);
        color: var(--text);
        border: 1px solid var(--border);
        border-radius: 16px;
        backdrop-filter: blur(10px);
        box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
        z-index: 1000000;
        padding: 24px;
        max-height: 80vh;
        overflow-y: auto;
    }
    
    #settings-panel.show {
        display: block;
    }
    
    .settings-section {
        margin-bottom: 24px;
    }
    
    .settings-section h3 {
        color: var(--primary);
        margin-bottom: 16px;
        font-size: 18px;
        text-shadow: 0 0 8px rgba(0, 212, 255, 0.3);
    }
    
    .setting-item {
        margin-bottom: 16px;
        display: flex;
        align-items: center;
        justify-content: space-between;
    }
    
    .setting-label {
        flex: 1;
        font-size: 14px;
    }
    
    .setting-input {
        width: 100px;
        padding: 8px 12px;
        background: var(--card-bg);
        border: 1px solid var(--border);
        border-radius: 8px;
        color: var(--text);
        font-size: 14px;
        transition: all 0.2s;
    }
    
    .setting-input:focus {
        outline: none;
        border-color: var(--primary);
        box-shadow: 0 0 8px rgba(0, 212, 255, 0.3);
    }
    
    .setting-toggle {
        width: 48px;
        height: 24px;
        border-radius: 24px;
        background: rgba(255, 255, 255, 0.15);
        position: relative;
        cursor: pointer;
    }
    
    .setting-toggle.knob {
        position: absolute;
        top: 3px;
        left: 3px;
        width: 18px;
        height: 18px;
        background: white;
        border-radius: 50%;
        transition: all 0.2s;
        box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
    }
    
    .setting-toggle.on {
        background: linear-gradient(90deg, var(--primary), var(--secondary));
    }
    
    .setting-toggle.on .knob {
        left: 27px;
    }
    
    #settings-close {
        position: absolute;
        top: 16px;
        right: 16px;
        background: none;
        border: none;
        color: var(--text-secondary);
        font-size: 24px;
        cursor: pointer;
        padding: 4px 8px;
        border-radius: 6px;
        transition: all 0.2s;
    }
    
    #settings-close:hover {
        background: rgba(255, 255, 255, 0.1);
        color: var(--text);
    }
    
    #watermark {
        position: fixed;
        top: 15px;
        right: 15px;
        font-weight: 900;
        font-size: 28px;
        background: linear-gradient(135deg, var(--primary), var(--secondary));
        -webkit-background-clip: text;
        -webkit-text-fill-color: transparent;
        text-shadow: 0 0 15px rgba(0, 212, 255, 0.5);
        z-index: 9999999;
        pointer-events: none;
        letter-spacing: 1px;
    }
    
    #arraylist {
        position: fixed;
        right: 15px;
        top: 60px;
        display: flex;
        flex-direction: column;
        gap: 8px;
        z-index: 9999999;
        text-align: right;
    }
    
    .arrItem {
        background: rgba(20, 20, 40, 0.8);
        padding: 8px 14px;
        border-radius: 10px;
        font-weight: 600;
        color: white;
        border: 1px solid var(--border);
        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
        backdrop-filter: blur(5px);
    }
    
    .arrItem::before {
        content: '';
        position: absolute;
        right: 0;
        top: 0;
        height: 100%;
        width: 4px;
        background: linear-gradient(to bottom, var(--primary), var(--secondary));
        border-radius: 4px 0 0 4px;
    }
    `;
    document.head.appendChild(style);

    root.innerHTML = `
    <div id="bounceclient-gui">
        <div id="bounceclient-header">
            <div style="display:flex;align-items:center;">
                <div class="title">Bounce<sup>Client</sup></div>
                <div id="inject-status" class="red"></div>
            </div>
            <div style="font-size:13px;opacity:.8;">RightShift to toggle</div>
        </div>
        <div id="category-buttons"></div>
        <div id="bounceclient-content">
            <div id="bounceclient-mods"></div>
            <div id="themes">
                <div id="themeLabel">Theme</div>
                <div class="theme" data-theme="ocean">Ocean Blue</div>
                <div class="theme" data-theme="purple">Deep Purple</div>
                <div class="theme" data-theme="matrix">Matrix Green</div>
                <div class="theme" data-theme="sunset">Sunset Red</div>
                <button id="inject-btn">Inject/Re-inject</button>
                <button id="settings-btn">Settings</button>
            </div>
        </div>
    </div>
    <div id="settings-panel">
        <button id="settings-close">×</button>
        <h2 style="color:var(--primary);margin-bottom:24px;">Settings</h2>
        <div id="settings-content"></div>
    </div>
    <div id="watermark">BounceClient</div>
    <div id="arraylist"></div>
    `;

    const gui = root.querySelector("#bounceclient-gui");
    const modsDiv = root.querySelector("#bounceclient-mods");
    const arraylist = root.querySelector("#arraylist");
    const watermark = root.querySelector("#watermark");
    const categoryButtonsDiv = root.querySelector("#category-buttons");
    const injectStatus = root.querySelector("#inject-status");
    const injectBtn = root.querySelector("#inject-btn");
    const settingsPanel = root.querySelector("#settings-panel");
    const settingsContent = root.querySelector("#settings-content");
    const settingsBtn = root.querySelector("#settings-btn");
    const settingsClose = root.querySelector("#settings-close");

    // === Category buttons ===
    ["Blatant","Visual","Legit"].forEach(cat=>{
        const btn=document.createElement("div");
        btn.className="cat-btn";
        btn.textContent=cat;
        btn.onclick=()=>{currentCategory=cat; refreshModules(); updateCategoryButtons();}
        categoryButtonsDiv.appendChild(btn);
    });
    function updateCategoryButtons(){
        categoryButtonsDiv.querySelectorAll(".cat-btn").forEach(b=>{
            if(b.textContent===currentCategory) b.classList.add("on"); else b.classList.remove("on");
        });
    }

    // === Module refresh ===
    function refreshModules(){
        modsDiv.innerHTML="";
        const mods=Modules[currentCategory];
        Object.keys(mods).forEach(name=>{
            const div=document.createElement("div");
            div.className="module";
            
            const moduleInfo = document.createElement("div");
            moduleInfo.className = "module-info";
            moduleInfo.innerHTML = `<span class="module-name">${name}</span>`;
            
            const hasKeybind = Keybinds.hasOwnProperty(name);
            if(hasKeybind){
                const keybindBtn = document.createElement("button");
                keybindBtn.className = "keybind-btn";
                keybindBtn.textContent = Keybinds[name] || "NONE";
                keybindBtn.onclick = (e) => {
                    e.stopPropagation();
                    listeningKeybind = name;
                    keybindBtn.textContent = "...";
                    keybindBtn.style.background = "var(--primary)";
                };
                moduleInfo.appendChild(keybindBtn);
            }
            
            const toggle = document.createElement("div");
            toggle.className = "toggle";
            toggle.dataset.mod = name;
            toggle.innerHTML = "<div class='knob'></div>";
            
            div.appendChild(moduleInfo);
            div.appendChild(toggle);
            
            div.onclick = () => {
                mods[name] = !mods[name];
                updateToggles(); updateArray(); toggleModule(name, mods[name]);
            };
            modsDiv.appendChild(div);
        });
        updateToggles();
    }
    
    let listeningKeybind = null;
    window.addEventListener("keydown", e => {
        if(listeningKeybind && listeningKeybind in Keybinds){
            Keybinds[listeningKeybind] = e.code;
            listeningKeybind = null;
            refreshModules();
        }
    });

    function updateToggles(){
        root.querySelectorAll(".toggle").forEach(t=>{
            const m=t.dataset.mod;
            const mods=Modules[currentCategory];
            if(mods[m]) t.classList.add("on"); else t.classList.remove("on");
        });
    }

    function updateArray(){
        arraylist.innerHTML="";
        Object.values(Modules).forEach(group=>{
            Object.keys(group).forEach(m=>{
                if(group[m]){
                    const el=document.createElement("div");
                    el.className="arrItem"; 
                    el.textContent=m;
                    el.style.position = "relative";
                    el.style.paddingRight = "20px";
                    arraylist.appendChild(el);
                }
            });
        });
    }

    // === Themes ===
    const themes = {
        ocean: { 
            primary: "#00d4ff", 
            primaryDark: "#0099cc",
            secondary: "#7c4dff",
            border: "rgba(100, 100, 255, 0.3)"
        },
        purple: { 
            primary: "#9d4edd", 
            primaryDark: "#7b2cbf",
            secondary: "#c77dff",
            border: "rgba(157, 78, 221, 0.3)"
        },
        matrix: { 
            primary: "#00ff41", 
            primaryDark: "#00cc33",
            secondary: "#00ff7f",
            border: "rgba(0, 255, 65, 0.3)"
        },
        sunset: { 
            primary: "#ff5e5b", 
            primaryDark: "#ff3d3a",
            secondary: "#ff9a8b",
            border: "rgba(255, 94, 91, 0.3)"
        }
    };
    root.querySelectorAll(".theme").forEach(t=>{
        t.onclick=()=>{
            const th=themes[t.dataset.theme];
            document.documentElement.style.setProperty("--primary",th.primary);
            document.documentElement.style.setProperty("--primary-dark",th.primaryDark);
            document.documentElement.style.setProperty("--secondary",th.secondary);
            document.documentElement.style.setProperty("--border",th.border);
        };
    });

    // === GUI Toggle ===
    let visible=false;
    function toggleGUI(){visible=!visible; gui.style.display=visible?"flex":"none";}
    window.addEventListener("keydown",e=>{
        if(e.code==="ShiftRight" && !listeningKeybind) toggleGUI();
    });

    // === Vector utilities ===
    const vectorUtils = {
        normalizeVector(e) {
            let t = e[0] * e[0] + e[1] * e[1] + e[2] * e[2];
            if (t > 0) {
                let n = 1 / Math.sqrt(t);
                return [e[0] * n, e[1] * n, e[2] * n];
            }
            return e;
        },
        distanceBetweenSqrt(e, t) {
            let n = t[0] - e[0], o = t[1] - e[1], i = t[2] - e[2];
            return Math.sqrt(n * n + o * o + i * i);
        }
    };

    // === NOA API wrappers ===
    const noaAPI = {
        noa: {
            getPosition(e) {
                return gameState.entities?.getState(e, "position")?.position;
            },
            get getMoveState() {
                return objectUtils.values(gameState.noa?.entities)?.[36];
            },
            get getHeldItem() {
                return objectUtils.values(gameState.noa?.entities).find((e => 1 == e?.length && e?.toString()?.length < 13 && e?.toString().includes(").")));
            },
            safeGetHeldItem(e) {
                let t;
                try {
                    t = this.getHeldItem(e);
                } catch {}
                return t;
            },
            get playerList() {
                try {
                    return objectUtils.values(gameState.bloxd?.getPlayerIds()).filter((e => 1 !== e && this.safeGetHeldItem(e))).map((e => parseInt(e)));
                } catch(e) {
                    return [];
                }
            },
            get doAttack() {
                let e = this.safeGetHeldItem(1);
                return (e?.doAttack || e?.breakingItem?.doAttack)?.bind(e);
            }
        }
    };

    // === Killaura ===
    let killauraInterval = null;

    function tryKill() {
        let myPos = noaAPI.noa.getPosition(1);
        
        noaAPI.noa.playerList.forEach((target => {
            let targetPos = noaAPI.noa.getPosition(target);
            
            if (targetPos && vectorUtils.distanceBetweenSqrt(myPos, targetPos) <= Config.Killaura.range) {
                let direction = vectorUtils.normalizeVector([
                    targetPos[0] - myPos[0], 
                    targetPos[1] - myPos[1], 
                    targetPos[2] - myPos[2]
                ]);
                
                // Apply random jitter if enabled
                if (Config.Killaura.jitter > 0) {
                    const jitterAmount = Config.Killaura.jitter / 100;
                    direction[0] += (Math.random() - 0.5) * jitterAmount;
                    direction[1] += (Math.random() - 0.5) * jitterAmount;
                    direction[2] += (Math.random() - 0.5) * jitterAmount;
                    direction = vectorUtils.normalizeVector(direction);
                }
                
                noaAPI.noa.doAttack(direction, target.toString(), "BodyMesh");
                noaAPI.noa.getHeldItem(1)?.trySwingBlock?.();
                noaAPI.noa.getMoveState(1)?.setArmsAreSwinging?.();
            }
        }));
    }

    function toggleKillaura(on) {
        if (on) {
            if (killauraInterval) clearInterval(killauraInterval);
            let last = 0;
            killauraInterval = setInterval(() => {
                const now = Date.now();
                if (now - last >= Config.Killaura.delay) {
                    last = now;
                    tryKill();
                }
            }, 16); // ~60fps
        } else {
            if (killauraInterval) {
                clearInterval(killauraInterval);
                killauraInterval = null;
            }
        }
    }

    function toggleModule(name,state){
        if(name==="Killaura") toggleKillaura(state);
        if(name==="Scaffold") toggleScaffold(state);
        if(name==="ESP") toggleESP(state);
        if(name==="Coords") toggleCoords(state);
        if(name==="FPS Counter") fpsHUD.style.display=state?"block":"none";
        if(name==="CPS Counter") cpsHUD.style.display=state?"block":"none";
        if(name==="Keystrokes") keystrokesHUD.style.display=state?"grid":"none";
    }

    // === Update injection status indicator ===
    function updateInjectStatus() {
        if (injectionStatus.injected) {
            injectStatus.className = "green";
        } else if (injectionStatus.failed) {
            injectStatus.className = "yellow";
        } else {
            injectStatus.className = "red";
        }
    }

    // === Try to get noa object ===
    function tryGetNoa() {
        try {
            // Always check if noa still exists and is valid
            if (gameState.noa && gameState.noa.entities && typeof gameState.noa.entities.getState === 'function') {
                return true; // Already have valid noa
            }
            
            let e = Object.getOwnPropertyDescriptors(window);
            let i = Object.keys(e).find(key => e[key]?.set?.toString().includes("++"));
            
            if (!i || !window[i]) {
                return false;
            }
            
            (window[i] = window[i]).push([
                [Math.floor(9e4 * Math.random()) + 1e4], {},
                function (t) {
                    gameState.findModule = e => t(Object.keys(t.m)[Object.values(t.m).findIndex((t => t.toString().includes(e)))]);            
                    gameState.Props = Object.values(gameState.findModule("nonBlocksClient:")).find((e => "object" == typeof e));
                    gameState.noa = Object.values(gameState.Props).find((e => e?.entities));
                }
            ]);
            
            return gameState.noa && gameState.noa.entities;
        } catch(err) {
            return false;
        }
    }

    // === Game Injection ===
    function injectGame() {
        try {
            // Try to get noa object
            if (!tryGetNoa()) {
                console.error("[BounceClient] Injection failed: Could not find webpack array or noa object.");
                injectionStatus.injected = false;
                injectionStatus.failed = true;
                updateInjectStatus();
                return false;
            }
            
            // Always reinitialize game state references
            const a = objectUtils.values(gameState.noa.entities)[2];
            const r = Object.entries(gameState.noa.entities);
            gameState.impKey = r.find((([e, t]) => t === a))?.[0];
            gameState.registry = objectUtils.values(gameState.noa)[17];
            gameState.rendering = objectUtils.values(gameState.noa)[12];
            gameState.entities = gameState.noa.entities;
            gameState.world = objectUtils.values(gameState.noa)[11];
            gameState.camera = gameState.noa.camera;
            gameState.bloxd = gameState.noa.bloxd;
            gameState.physics = gameState.noa.physics;
            gameState.entityList = objectUtils.values(gameState.noa)[30];
            
            let f = objectUtils.values(gameState.rendering)[7].meshes[0];
            let m = objectUtils.values(gameState.rendering)[7];
            
            gameState.Lion = {
                scene: m,
                Mesh: f.constructor,
                StandardMaterial: f.material.constructor,
                Color3: f.material.diffuseColor.constructor
            };
            
            injectionStatus.injected = true;
            injectionStatus.failed = false;
            updateInjectStatus();
            console.log("%c[BounceClient] Game injected successfully!","color:var(--primary);font-weight:700;");
            return true;
        } catch(err) {
            console.error("[BounceClient] Injection error:", err);
            injectionStatus.injected = false;
            injectionStatus.failed = true;
            updateInjectStatus();
            return false;
        }
    }

    // === Auto-retry injection ===
    function startInjectionRetry() {
        if (injectionRetryInterval) return;
        
        injectionRetryInterval = setInterval(() => {
            if (!injectionStatus.injected) {
                const gotNoa = tryGetNoa();
                if (gotNoa && !gameState.impKey) {
                    // Have noa but not initialized, try to initialize
                    injectGame();
                } else if (!gotNoa && injectionStatus.injected) {
                    // Lost noa object
                    injectionStatus.injected = false;
                    injectionStatus.failed = true;
                    updateInjectStatus();
                }
            }
        }, 1000);
    }

    // === Inject button handler ===
    injectBtn.onclick = () => {
        injectGame();
    };

    // === Settings panel ===
    function renderSettings() {
        settingsContent.innerHTML = `
            <div class="settings-section">
                <h3>Killaura</h3>
                <div class="setting-item">
                    <span class="setting-label">Delay (ms)</span>
                    <input type="number" class="setting-input" value="${Config.Killaura.delay}" 
                           onchange="window.BounceClient.Config.Killaura.delay = parseInt(this.value); window.BounceClient.saveConfig(); toggleKillaura(window.BounceClient.Modules.Blatant.Killaura);">
                </div>
                <div class="setting-item">
                    <span class="setting-label">Range</span>
                    <input type="number" class="setting-input" step="0.1" value="${Config.Killaura.range}" 
                           onchange="window.BounceClient.Config.Killaura.range = parseFloat(this.value); window.BounceClient.saveConfig();">
                </div>
                <div class="setting-item">
                    <span class="setting-label">Jitter (%)</span>
                    <input type="number" class="setting-input" value="${Config.Killaura.jitter}" 
                           onchange="window.BounceClient.Config.Killaura.jitter = parseInt(this.value); window.BounceClient.saveConfig();">
                </div>
            </div>
            <div class="settings-section">
                <h3>Scaffold</h3>
                <div class="setting-item">
                    <span class="setting-label">Delay (ms)</span>
                    <input type="number" class="setting-input" value="${Config.Scaffold.delay}" 
                           onchange="window.BounceClient.Config.Scaffold.delay = parseInt(this.value); window.BounceClient.saveConfig(); toggleScaffold(window.BounceClient.Modules.Blatant.Scaffold);">
                </div>
            </div>
            <div class="settings-section">
                <h3>ESP</h3>
                <div class="setting-item">
                    <span class="setting-label">Chest Update (ms)</span>
                    <input type="number" class="setting-input" value="${Config.ESP.chestUpdateInterval}" 
                           onchange="window.BounceClient.Config.ESP.chestUpdateInterval = parseInt(this.value); window.BounceClient.saveConfig(); toggleESP(window.BounceClient.Modules.Visual.ESP);">
                </div>
                <div class="setting-item">
                    <span class="setting-label">Player Update (ms)</span>
                    <input type="number" class="setting-input" value="${Config.ESP.playerUpdateInterval}" 
                           onchange="window.BounceClient.Config.ESP.playerUpdateInterval = parseInt(this.value); window.BounceClient.saveConfig(); togglePlayerESP(playerESPEnabled);">
                </div>
            </div>
            <div class="settings-section">
                <h3>Coords</h3>
                <div class="setting-item">
                    <span class="setting-label">Update Interval (ms)</span>
                    <input type="number" class="setting-input" value="${Config.Coords.updateInterval}" 
                           onchange="window.BounceClient.Config.Coords.updateInterval = parseInt(this.value); window.BounceClient.saveConfig(); toggleCoords(window.BounceClient.Modules.Visual.Coords);">
                </div>
            </div>
        `;
    }

    settingsBtn.onclick = () => {
        renderSettings();
        settingsPanel.classList.add("show");
    };

    settingsClose.onclick = () => {
        settingsPanel.classList.remove("show");
    };

    // === ESP ===
    let espInterval = null;
    let espMeshes = {};
    let espChunks = new Set();
    let chunkDataKey = null;

    function getChunkKey(chunk) {
        const [x, y, z] = chunk.pos || [0, 0, 0];
        const chunkX = Math.floor(x / 32);
        const chunkY = Math.floor(y / 32);
        const chunkZ = Math.floor(z / 32);
        return `${chunkX}|${chunkY}|${chunkZ}|overworld`;
    }

    function decodeChunkIndex(index, stride) {
        const x = Math.floor(index / stride[0]);
        const remainder = index % stride[0];
        const y = Math.floor(remainder / stride[1]);
        const z = remainder % stride[1];
        return [x, y, z];
    }

    function findChunkDataKey(chunk) {
        for (const key of Object.keys(chunk)) {
            const value = chunk[key];
            if (value && typeof value === "object" && 
                Array.isArray(value.stride) && value.stride.length === 3 &&
                (Array.isArray(value.data) || ArrayBuffer.isView(value.data))) {
                return key;
            }
        }
        return null;
    }

    function renderChestESP(chunk, blockIds) {
        const chunkData = chunk[chunkDataKey];
        if (!chunkData) return;
        
        const { data, stride } = chunkData;
        const chunkPos = chunk.pos || [0, 0, 0];
        
        if (!data || !stride) return;
        
        const chunkKey = getChunkKey(chunk);
        
        for (let i = 0; i < data.length; i++) {
            const blockId = data[i];
            if (!blockIds.includes(blockId)) continue;
            
            const [localX, localY, localZ] = decodeChunkIndex(i, stride);
            const worldX = chunkPos[0] + localX + 0.5;
            const worldY = chunkPos[1] + localY + 0.5;
            const worldZ = chunkPos[2] + localZ + 0.5;
            
            const mesh = gameState.Lion.Mesh.CreateBox("espbox", 1, gameState.Lion.scene);
            mesh.position.set(worldX, worldY, worldZ);
            mesh.renderingGroupId = 1;
            mesh.material = new gameState.Lion.StandardMaterial("mat", gameState.Lion.scene);
            mesh.material.wireframe = true;
            mesh.material.emissiveColor = new gameState.Lion.Color3(
                Config.ESP.wireframeColor[0], 
                Config.ESP.wireframeColor[1], 
                Config.ESP.wireframeColor[2]
            );
            
            if (!espMeshes[chunkKey]) espMeshes[chunkKey] = [];
            espMeshes[chunkKey].push(mesh);
        }
    }

    function updateESP() {
        if (!gameState?.world || !gameState?.world?.[gameState.impKey]?.hash) return;
        
        const worldChunks = gameState.world[gameState.impKey].hash;
        
        for (const chunkId in worldChunks) {
            const chunk = worldChunks[chunkId];
            
            if (!chunkDataKey) chunkDataKey = findChunkDataKey(chunk);
            
            if (!chunkDataKey || !chunk[chunkDataKey]?.data || !chunk.pos) continue;
            
            const chunkKey = getChunkKey(chunk);
            if (espChunks.has(chunkKey)) continue;
            
            espChunks.add(chunkKey);
            renderChestESP(chunk, [204, 205, 206, 207]);
        }
    }

    function clearESP() {
        for (const chunkKey in espMeshes) {
            for (const mesh of espMeshes[chunkKey]) {
                mesh.dispose();
            }
        }
        espChunks.clear();
        espMeshes = {};
    }

    // === Player ESP ===
    let playerESPInterval = null;
    let playerESPEnabled = false;

    function updatePlayerESP() {
        if (!gameState.rendering || !playerESPEnabled) {
            return;
        }
        
        try {
            const thinMeshes = objectUtils.values(gameState.rendering)[19]?.thinMeshes;
            if (!Array.isArray(thinMeshes)) {
                return;
            }
            
            const renderingGroupId = 2;
            for (const item of thinMeshes) {
                if (item?.meshVariations?.__DEFAULT__?.mesh && typeof item?.meshVariations?.__DEFAULT__?.mesh.renderingGroupId === "number") {
                    item.meshVariations.__DEFAULT__.mesh.renderingGroupId = renderingGroupId;
                }
            }
        } catch(e) {
            console.error("Player ESP error:", e);
        }
    }

    function togglePlayerESP(on) {
        playerESPEnabled = on;
        
        if (on) {
            if (!playerESPInterval) {
                playerESPInterval = setInterval(updatePlayerESP, Config.ESP.playerUpdateInterval);
            }
            updatePlayerESP();
        } else {
            if (playerESPInterval) {
                clearInterval(playerESPInterval);
                playerESPInterval = null;
            }
            
            // Reset to normal rendering
            if (gameState.rendering) {
                try {
                    const thinMeshes = objectUtils.values(gameState.rendering)[19]?.thinMeshes;
                    if (Array.isArray(thinMeshes)) {
                        for (const item of thinMeshes) {
                            if (item.meshVariations?.__DEFAULT__?.mesh && typeof item?.meshVariations?.__DEFAULT__?.mesh.renderingGroupId === "number") {
                                item.meshVariations.__DEFAULT__.mesh.renderingGroupId = 0;
                            }
                        }
                    }
                } catch(e) {
                    console.error("Player ESP reset error:", e);
                }
            }
        }
    }

    function toggleESP(on) {
        if (on) {
            if (!espInterval) {
                espInterval = setInterval(updateESP, Config.ESP.chestUpdateInterval);
            }
            updateESP();
            togglePlayerESP(true);
        } else {
            if (espInterval) {
                clearInterval(espInterval);
                espInterval = null;
            }
            clearESP();
            togglePlayerESP(false);
        }
    }

    // === Coords ===
    let coordsInterval = null;
    function updateCoords() {
        if (!gameState?.bloxd?.entityNames) return;
        
        try {
            for (const entityId in gameState.bloxd.entityNames) {
                if (entityId === "1") continue;
                
                const entityData = gameState.bloxd.entityNames[entityId];
                const positionData = gameState.entities.getState(entityId, "position");
                
                if (!positionData || !positionData.position) continue;
                
                const position = positionData.position;
                const x = Math.round(position[0]);
                const y = Math.round(position[1]);
                const z = Math.round(position[2]);
                
                const baseName = entityData.entityName.replace(/\s*\(\-?\d+,\s*\-?\d+,\s*\-?\d+\)$/, "");
                entityData.entityName = `${baseName} (${x}, ${y}, ${z})`;
            }
        } catch (error) {
            console.error("Error updating coords:", error);
            stopCoords();
        }
    }

    function startCoords() {
        if (!coordsInterval) {
            updateCoords();
            coordsInterval = setInterval(updateCoords, Config.Coords.updateInterval);
        }
    }

    function stopCoords() {
        if (coordsInterval) {
            clearInterval(coordsInterval);
            coordsInterval = null;
        }
    }

    function toggleCoords(on) {
        if (on) startCoords();
        else stopCoords();
    }

    // === Scaffold ===
    let scaffoldInterval = null;

    function placeBlock(position) {
        try {
            const blockItem = objectUtils.values(gameState.noa.entities[gameState.impKey])[22].list[0]._blockItem;
            const firstKey = Object.keys(blockItem)[0];
            const firstValue = Object.values(blockItem)[0];
            const posKey = Object.keys(firstValue)[25];
            const posValue = firstValue[posKey];
            
            blockItem.placeBlock.call(createPlacementProxy(position, blockItem, firstKey, firstValue, posKey, posValue));
        } catch(e) {
            console.error("Scaffold placement error:", e);
        }
    }

    function createPlacementProxy(targetPos, blockItem, firstKey, firstValue, posKey, posValue) {
        return new Proxy({}, {
            get: (target, prop) => {
                if (prop === firstKey) {
                    return new Proxy(firstValue, {
                        get(t, p) {
                            if (p === posKey) {
                                let cloned = structuredClone(posValue) || {};
                                cloned.position = targetPos;
                                return cloned;
                            }
                            return firstValue[p];
                        }
                    });
                } else if (prop === "checkTargetedBlockCanBePlacedOver") {
                    return () => true;
                } else if (typeof blockItem[prop] === "function") {
                    return blockItem[prop].bind(blockItem);
                }
                return blockItem[prop];
            }
        });
    }

    function canPlaceBlock(x, y, z, heldItem) {
        try {
            return heldItem.checkTargetedBlockCanBePlacedOver([x, y, z]) || 
                   0 === objectUtils.values(gameState.world)[47].call(gameState.world, x, y, z);
        } catch(e) {
            return false;
        }
    }

    function scaffoldTick() {
        try {
            const playerPos = gameState.entities.getState(1, "position").position;
            if (!playerPos) return;
            
            const heldItemData = gameState.noa.entities?.[gameState.impKey];
            const currentHeldItem = Object.values(Object.values(heldItemData)?.[22]?.list?.[0])?.[1];
            
            if (!currentHeldItem || !currentHeldItem.heldItemState || currentHeldItem.heldItemState.heldType !== "CubeBlock") return;
            
            const px = playerPos[0];
            const py = Math.floor(playerPos[1]);
            const pz = playerPos[2];
            const fx = Math.floor(px);
            const fz = Math.floor(pz);
            
            if (canPlaceBlock(fx, py - 1, fz, currentHeldItem)) {
                placeBlock([fx, py - 1, fz]);
                return;
            }
            
            const offsetX = px - fx;
            const offsetZ = pz - fz;
            const positions = [];
            
            if (offsetX < 0.3) positions.push([-1, 0]);
            if (offsetX > 0.7) positions.push([1, 0]);
            if (offsetZ < 0.3) positions.push([0, -1]);
            if (offsetZ > 0.7) positions.push([0, 1]);
            
            for (const [dx, dz] of positions) {
                const targetX = fx + dx;
                const targetZ = fz + dz;
                if (canPlaceBlock(targetX, py - 1, targetZ, currentHeldItem)) {
                    placeBlock([targetX, py - 1, targetZ]);
                    return;
                }
            }
        } catch(e) {
            console.error("Scaffold tick error:", e);
        }
    }

    function toggleScaffold(on) {
        if (on) {
            if (!scaffoldInterval) {
                scaffoldInterval = setInterval(scaffoldTick, Config.Scaffold.delay);
            }
        } else {
            if (scaffoldInterval) {
                clearInterval(scaffoldInterval);
                scaffoldInterval = null;
            }
        }
    }

    // === Keybind listeners for module toggles ===
    window.addEventListener("keydown", e => {
        // Don't trigger module keybinds if GUI is open or setting a keybind
        if (visible || listeningKeybind) return;
        
        for (const [module, keybind] of Object.entries(Keybinds)) {
            if (keybind && e.code === keybind) {
                if (module in Modules.Blatant) {
                    Modules.Blatant[module] = !Modules.Blatant[module];
                    toggleModule(module, Modules.Blatant[module]);
                } else if (module in Modules.Visual) {
                    Modules.Visual[module] = !Modules.Visual[module];
                    toggleModule(module, Modules.Visual[module]);
                }
                updateArray();
            }
        }
    });

    // === Legit HUD ---
    const hudStyle = "font-family:'Segoe UI',sans-serif;font-weight:700;color:white;text-align:center;"+
                     "background:rgba(20, 20, 40, 0.8);padding:10px 14px;border-radius:10px;"+
                     "border:1px solid var(--border);box-shadow:0 4px 12px rgba(0, 0, 0, 0.2);backdrop-filter:blur(5px);";

    const fpsHUD=document.createElement("div");
    fpsHUD.style.cssText=`position:fixed;bottom:15px;left:15px;z-index:9999999;${hudStyle}`;
    fpsHUD.textContent="FPS: 0"; fpsHUD.style.display="none"; document.body.appendChild(fpsHUD);

    const cpsHUD=document.createElement("div");
    cpsHUD.style.cssText=`position:fixed;bottom:70px;left:15px;z-index:9999999;${hudStyle}`;
    cpsHUD.textContent="CPS: 0"; cpsHUD.style.display="none"; document.body.appendChild(cpsHUD);

const keystrokesHUD = document.createElement("div");
keystrokesHUD.style.cssText = `
    position: fixed;
    bottom: 20px;
    right: 20px;
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 8px;
    z-index: 9999999;
`;

const keys = {};
function createKey(char, gridArea) {
    const k = document.createElement("div");
    k.textContent = char;
    k.style.cssText = `
        min-width: 40px;
        height: 40px;
        background: rgba(20, 20, 40, 0.8);
        color: white;
        display: flex;
        align-items: center;
        justify-content: center;
        border-radius: 10px;
        font-weight: 700;
        border: 1px solid var(--border);
        box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
        transition: all 0.1s;
        ${gridArea ? `grid-area: ${gridArea};` : ''}
    `;
    keys[char] = k;
    return k;
}

// Add keys in a WASD layout
keystrokesHUD.appendChild(createKey("", "top")); // Empty top-left
keystrokesHUD.appendChild(createKey("W", "top"));
keystrokesHUD.appendChild(createKey("", "top")); // Empty top-right

keystrokesHUD.appendChild(createKey("A", "middle"));
keystrokesHUD.appendChild(createKey("S", "middle"));
keystrokesHUD.appendChild(createKey("D", "middle"));

keystrokesHUD.style.display = "none"; // hide by default
document.body.appendChild(keystrokesHUD);

    // FPS / CPS logic
    let lastFrame=performance.now(),frameCount=0,fps=0;
    function updateFPS(){
        const now=performance.now();
        frameCount++;
        if(now-lastFrame>=1000){ fps=frameCount; frameCount=0; lastFrame=now; fpsHUD.textContent="FPS: "+fps; }
        requestAnimationFrame(updateFPS);
    }
    requestAnimationFrame(updateFPS);

    let clicks=0,cps=0;
    document.addEventListener("mousedown",()=>clicks++);
    setInterval(()=>{
        cps=clicks; clicks=0; cpsHUD.textContent="CPS: "+cps;
    },1000);

    // Key press highlight for keystrokes
    document.addEventListener("keydown",e=>{
        const k=e.key.toUpperCase();
        if(keys[k]) {
            keys[k].style.background="linear-gradient(135deg, var(--primary), var(--secondary))";
            keys[k].style.transform="scale(0.95)";
        }
    });
    document.addEventListener("keyup",e=>{
        const k=e.key.toUpperCase();
        if(keys[k]) {
            keys[k].style.background="rgba(20, 20, 40, 0.8)";
            keys[k].style.transform="scale(1)";
        }
    });

    // --- Initialize ---
    updateCategoryButtons(); refreshModules();
    updateInjectStatus(); // Set initial status
    
    // Start auto-retry injection
    startInjectionRetry();
    
    // Inject game state on load
    setTimeout(() => {
        if (injectGame()) {
            console.log("%c[BounceClient] All systems ready!","color:var(--primary);font-weight:700;");
        }
    }, 2000);
    
    window.BounceClient={Modules, Keybinds, Config, gameState, injectGame, saveConfig};
    console.log("%cBounceClient GUI loaded - Press RightShift","color:var(--primary);font-weight:700;");
})();