⚔️EraBOT🕺

SCRIPT EVOWARS.IO ERABOTVN VIP - ULTIMATE AI EDITION

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

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

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

Advertisement:

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

Advertisement:

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name         ⚔️EraBOT🕺
// @namespace    http://tampermonkey.net/
// @version      16.0.0
// @description  SCRIPT EVOWARS.IO ERABOTVN VIP - ULTIMATE AI EDITION
// @author       #NLHH
// @match        https://evowars.io/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=evowars.io
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // =============================================================
    // 1. CẤU HÌNH
    // =============================================================
    const C = {
        eraBot: false,
        autoAttack: true,
        filterLow: true,
        hitbox: true,
        zoom: 0.9,
        autoRejoin: true,
        autoDuel: true,
        collectFood: true,
        fleeDanger: true,
        sprintMode: 1,          // 0: Always, 1: Towards Enemy, 2: Never
        lvlMin: 0,
        lvlMax: 100,
        maxHigher: 5,
        minLower: 0,
        safeDistance: 1.5,
        showEnemyRange: true,
        showCooldown: true,
        showTracers: true,
        showTargetGlow: true,
        aggression: 0.7,        // Mức độ tấn công (0-1)
        dodgeReaction: 0.6,     // Tốc độ phản ứng né (0-1)
        baitChance: 0.3,        // Khả năng lừa đối thủ (0-1)
    };

    // =============================================================
    // 2. HÀM CƠ BẢN
    // =============================================================
    function toast(msg, type = 'success', dur = 2500) {
        const old = document.querySelector('.erabot-toast');
        if (old) old.remove();
        if (window._toastTimer) clearTimeout(window._toastTimer);
        const el = document.createElement('div');
        el.className = `erabot-toast ${type}`;
        el.textContent = msg;
        document.body.appendChild(el);
        setTimeout(() => el.classList.add('show'), 50);
        window._toastTimer = setTimeout(() => {
            el.classList.remove('show');
            setTimeout(() => el.remove(), 400);
        }, dur);
    }

    function isOrb(obj) {
        try { return obj && obj.width < 30 && obj.height < 30 && obj.collision_poly; } catch(e) { return false; }
    }
    function isLow(lv) { return lv >= 0 && lv <= 4; }
    function getPts(o) {
        try {
            if (!o) return [];
            if (o.bbox_changed && o.update_bbox) o.update_bbox();
            if (o.collision_poly && typeof o.collision_poly.is_empty === 'function' && !o.collision_poly.is_empty()) {
                o.collision_poly.cache_poly(o.width, o.height, o.angle);
                const pts = [], c = o.collision_poly.pts_cache;
                for (let i = 0; i < o.collision_poly.pts_count; i++) {
                    pts.push({ x: c[i*2] + o.x, y: c[i*2+1] + o.y });
                }
                return pts;
            }
            if (o.bquad) {
                const b = o.bquad;
                return [
                    { x: b.tlx, y: b.tly },
                    { x: b.trx, y: b.try_ },
                    { x: b.brx, y: b.bry },
                    { x: b.blx, y: b.bly }
                ];
            }
        } catch(e) {}
        return [];
    }

    // =============================================================
    // 3. UTILITY
    // =============================================================
    const gl = o => o?.instance_vars?.[10] || 0;
    const gt = o => o?.instance_vars?.[11] || -1;
    const gh = o => o?.instance_vars?.[0] || 1000;
    const dist = (a,b) => Math.hypot(a.x-b.x, a.y-b.y);
    const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
    const angleDiff = (a,b) => { let d = b - a; while(d > Math.PI) d -= 2*Math.PI; while(d < -Math.PI) d += 2*Math.PI; return d; };
    const rand = (min, max) => Math.random() * (max - min) + min;
    const hash = (s) => { let h = 0; for (let i = 0; i < s.length; i++) h = (h * 31 + s.charCodeAt(i)) & 0xFFFFFFFF; return h; };

    function getReach(lv) {
        const STATIC = {
            0:{d:200,deg:125},2:{d:235,deg:90},3:{d:245,deg:125},4:{d:260,deg:125},
            5:{d:300,deg:133},6:{d:340,deg:125},7:{d:380,deg:131},8:{d:343,deg:130},
            9:{d:350,deg:125},10:{d:470,deg:133},11:{d:510,deg:129},12:{d:520,deg:133},
            13:{d:555,deg:134},14:{d:595,deg:125},15:{d:650,deg:129},16:{d:655,deg:131},
            17:{d:660,deg:125},18:{d:695,deg:125},19:{d:690,deg:125},20:{d:710,deg:130},
            21:{d:775,deg:130},22:{d:805,deg:136},23:{d:680,deg:122},24:{d:870,deg:125},
            25:{d:940,deg:137},26:{d:975,deg:130},27:{d:1050,deg:125},28:{d:1095,deg:125},
            29:{d:1000,deg:135},30:{d:995,deg:125},31:{d:1050,deg:130},32:{d:1145,deg:134},
            33:{d:1120,deg:139},34:{d:1125,deg:124},35:{d:1145,deg:135},36:{d:1250,deg:122},
            37:{d:1300,deg:125},38:{d:1300,deg:125},39:{d:1300,deg:125}
        };
        return STATIC[lv] || { d: Math.max(200, lv*30), deg: 125 };
    }
    function getMul(lv) {
        const m = {1:.6,2:.75,3:.7,4:.75,5:.8,6:.75,7:.76,8:.75,9:.9,10:.95,11:.8,12:.75,
            13:.75,14:.8,15:.8,16:.7,17:.75,18:.8,19:.8,20:.85,21:.85,22:.81,23:.82,24:1.05,
            25:.85,26:.8,27:.85,28:.78,29:.7,30:.8,31:.85,32:.85,33:.8,34:.8,35:.83,36:.9,
            37:.85,38:.9,39:.95,40:.91,41:1.06};
        return m[Math.floor(lv)+1] || .8;
    }
    function getWeaponTimings(lv) {
        const cooldown = Math.max(110, 110 + lv * 8);
        const windup = Math.max(120, 150 + lv * 2);
        return { windup, cooldown };
    }

    // =============================================================
    // 4. AI HỌC HÀNH VI (PATTERN LEARNING NÂNG CAO)
    // =============================================================
    class EnemyAI {
        constructor(uid) {
            this.uid = uid;
            this.h = [];
            this.maxH = 100;
            this.confidence = 0.3;
            this.lastAttack = 0;
            this.attackIntervals = [];
            this.swingAngles = [];
            this.baitFreq = 0;
            this.dodgePref = 0;
            this.lastUpdate = 0;
            this.lastSeen = 0;
            this.accX = 0;
            this.accY = 0;
            this.predCache = null;
            this.predTime = 0;
            // Học thói quen
            this.prefDistance = 0;        // Khoảng cách ưa thích khi tấn công
            this.prefAngle = 0;           // Góc tấn công ưa thích
            this.strafing = false;        // Có thói quen strafe không
            this.aggressiveness = 0.5;    // Mức độ hung hăng
            this.patternDetected = false;
            this.patternTime = 0;
        }

        update(pos, vx, vy, now) {
            const angle = Math.atan2(vy, vx);
            const speed = Math.hypot(vx, vy);
            this.h.push({ x: pos.x, y: pos.y, t: now, vx, vy, angle, speed });
            if (this.h.length > this.maxH) this.h.shift();
            this.lastSeen = now;
            if (now - this.lastUpdate > 50) {
                this._analyze(now);
                this.lastUpdate = now;
            }
            // Tính gia tốc
            if (this.h.length >= 3) {
                const l1 = this.h[this.h.length-1];
                const l2 = this.h[this.h.length-2];
                const l3 = this.h[this.h.length-3];
                const dt1 = (l1.t - l2.t) || 16;
                const dt2 = (l2.t - l3.t) || 16;
                const ax = ((l1.vx - l2.vx)/dt1 - (l2.vx - l3.vx)/dt2) / ((dt1+dt2)/2) * 16;
                const ay = ((l1.vy - l2.vy)/dt1 - (l2.vy - l3.vy)/dt2) / ((dt1+dt2)/2) * 16;
                this.accX = this.accX * 0.7 + ax * 0.3;
                this.accY = this.accY * 0.7 + ay * 0.3;
            }
            this.predCache = null;
        }

        _analyze(now) {
            if (this.h.length < 8) return;
            const recent = this.h.slice(-12);
            let angleChanges = 0, totalAngle = 0;
            let totalDist = 0, distCount = 0;
            let strafeCount = 0;

            for (let i = 1; i < recent.length; i++) {
                const da = angleDiff(recent[i-1].angle, recent[i].angle);
                if (Math.abs(da) > 0.3) angleChanges++;
                totalAngle += da;
                if (Math.abs(da) > 1) this.swingAngles.push(da);

                // Khoảng cách trung bình
                const d = Math.hypot(recent[i].x - recent[i-1].x, recent[i].y - recent[i-1].y);
                totalDist += d;
                distCount++;

                // Phát hiện strafe (thay đổi hướng liên tục mà không tiến lên)
                if (Math.abs(da) > 0.2 && Math.abs(da) < 0.8 && recent[i].speed < 3) {
                    strafeCount++;
                }
            }
            if (this.swingAngles.length > 15) this.swingAngles.shift();

            this.baitFreq = Math.min(1, angleChanges / (recent.length - 1) * 1.5);
            this.dodgePref = clamp(totalAngle / (recent.length - 1) * 1.5, -1, 1);
            this.prefDistance = totalDist / distCount;
            this.strafing = strafeCount > 3;

            // Độ hung hăng: dựa trên tốc độ tấn công
            const avgSpeed = recent.reduce((s, p) => s + p.speed, 0) / recent.length;
            this.aggressiveness = clamp(avgSpeed / 10, 0, 1);

            // Tăng confidence khi có đủ dữ liệu
            if (this.h.length > 20) this.confidence = Math.min(1, this.confidence + 0.03);
            if (this.attackIntervals.length > 3) {
                const avg = this.attackIntervals.reduce((a,b) => a+b, 0) / this.attackIntervals.length;
                const std = Math.sqrt(this.attackIntervals.reduce((a,b) => a + (b-avg)*(b-avg), 0) / this.attackIntervals.length);
                if (std / avg < 0.25) {
                    this.confidence = Math.min(1, this.confidence + 0.05);
                    this.patternDetected = true;
                    this.patternTime = now;
                }
            }
        }

        recordAttack(now) {
            if (this.lastAttack > 0) {
                const interval = now - this.lastAttack;
                if (interval > 100 && interval < 5000) {
                    this.attackIntervals.push(interval);
                    if (this.attackIntervals.length > 10) this.attackIntervals.shift();
                }
            }
            this.lastAttack = now;
            this.predCache = null;
        }

        predictNextAttack(now) {
            if (this.attackIntervals.length < 2 || this.confidence < 0.45) return null;
            const avg = this.attackIntervals.reduce((a,b) => a+b, 0) / this.attackIntervals.length;
            if (this.lastAttack > 0 && now - this.lastAttack > avg * 0.5) {
                return this.lastAttack + avg;
            }
            return null;
        }

        predictPos(dt) {
            if (this.predCache && (performance.now() - this.predTime) < 40) return this.predCache;
            if (this.h.length < 4) return null;
            const last = this.h[this.h.length-1];
            const prev = this.h[this.h.length-2];
            const dtSec = dt / 1000;
            let vx = last.vx || (last.x - prev.x) / ((last.t - prev.t) || 16) * 16;
            let vy = last.vy || (last.y - prev.y) / ((last.t - prev.t) || 16) * 16;
            const ax = this.accX || 0;
            const ay = this.accY || 0;
            // Nếu có pattern, điều chỉnh dự đoán
            if (this.patternDetected && this.confidence > 0.6) {
                // Điều chỉnh theo thói quen
                vx += ax * dtSec * 0.3;
                vy += ay * dtSec * 0.3;
            }
            const result = {
                x: last.x + vx * dtSec + 0.5 * ax * dtSec * dtSec,
                y: last.y + vy * dtSec + 0.5 * ay * dtSec * dtSec,
            };
            this.predCache = result;
            this.predTime = performance.now();
            return result;
        }

        getSwingAngle() {
            if (this.swingAngles.length < 2) return 0;
            return this.swingAngles.reduce((a,b) => a+b, 0) / this.swingAngles.length;
        }

        // Dự đoán hướng né của enemy
        predictDodgeDirection() {
            if (this.h.length < 5) return 0;
            const recent = this.h.slice(-5);
            let totalDodge = 0;
            for (let i = 1; i < recent.length; i++) {
                const dx = recent[i].x - recent[i-1].x;
                const dy = recent[i].y - recent[i-1].y;
                // Nếu di chuyển vuông góc với hướng nhìn
                const angle = recent[i].angle;
                const moveAngle = Math.atan2(dy, dx);
                const diff = angleDiff(angle, moveAngle);
                if (Math.abs(diff) > 0.5) {
                    totalDodge += Math.sign(diff);
                }
            }
            return clamp(totalDodge / recent.length * 2, -1, 1);
        }
    }

    const enemyAIs = new Map();

    // =============================================================
    // 5. WEBSOCKET HOOK
    // =============================================================
    let wsHooked = false;
    let pingHistory = [];
    let pingEstimate = 50;
    let wsSendTimes = new Map();

    function hookWebSocket() {
        if (wsHooked) return;
        try {
            const OriginalWebSocket = window.WebSocket;
            window.WebSocket = new Proxy(OriginalWebSocket, {
                construct(target, args) {
                    const socket = new target(...args);
                    const origSend = socket.send;
                    socket.send = function(data) {
                        try {
                            if (typeof data === 'string') {
                                const p = JSON.parse(data);
                                let uid = null;
                                if (p?.action === 'send' && p?.data?.a === 'ps') {
                                    uid = p.data.d?.id || p.data.d?.uid;
                                } else if (p?.a === 'ps' && p?.d) {
                                    uid = p.d.id || p.d.uid;
                                } else if (p?.type === 'attack' && p?.id) {
                                    uid = p.id;
                                } else if (p?.ps && p.ps.id) {
                                    uid = p.ps.id;
                                }
                                if (uid) {
                                    const now = performance.now();
                                    if (enemyMap.has(uid)) {
                                        const e = enemyMap.get(uid);
                                        e.attackStart = now;
                                        let ai = enemyAIs.get(uid);
                                        if (!ai) { ai = new EnemyAI(uid); enemyAIs.set(uid, ai); }
                                        ai.recordAttack(now);
                                    }
                                    wsSendTimes.set(uid, now);
                                }
                            }
                        } catch(e) {}
                        return origSend.call(this, data);
                    };
                    socket.addEventListener('message', function(ev) {
                        try {
                            if (typeof ev.data === 'string') {
                                const p = JSON.parse(ev.data);
                                if (p?.action === 'ps' || p?.a === 'ps') {
                                    const now = performance.now();
                                    const uid = p.d?.id || p.d?.uid;
                                    if (uid && wsSendTimes.has(uid)) {
                                        const sendTime = wsSendTimes.get(uid);
                                        const ping = now - sendTime;
                                        if (ping > 0 && ping < 500) {
                                            pingHistory.push(ping);
                                            if (pingHistory.length > 10) pingHistory.shift();
                                            pingEstimate = pingHistory.reduce((a,b) => a+b, 0) / pingHistory.length;
                                            wsSendTimes.delete(uid);
                                        }
                                    }
                                }
                            }
                        } catch(e) {}
                    });
                    return socket;
                }
            });
            wsHooked = true;
        } catch(e) { console.warn('[EraBOT] WS hook failed:', e); }
    }

    // =============================================================
    // 6. FALLBACK ATTACK DETECTION
    // =============================================================
    function detectAttackFallback(uid, data, now) {
        if (!data) return false;
        const speed = Math.hypot(data.vx || 0, data.vy || 0);
        const last = data.lastSpeed || 0;
        data.lastSpeed = speed;
        if (speed > 4 && speed > last * 1.6 && (data.lastDist || 0) < 380) {
            const angle = Math.atan2(data.vy || 0, data.vx || 0);
            const lastAngle = data.lastAngle || angle;
            const dAngle = Math.abs(angleDiff(lastAngle, angle));
            data.lastAngle = angle;
            if (dAngle > 0.25 || speed > last * 2.2) {
                const elapsed = now - (data._fallbackTime || 0);
                if (elapsed > 180) {
                    data._fallbackTime = now;
                    data.attackStart = now;
                    let ai = enemyAIs.get(uid);
                    if (!ai) { ai = new EnemyAI(uid); enemyAIs.set(uid, ai); }
                    ai.recordAttack(now);
                    return true;
                }
            }
        }
        data.lastDist = Math.hypot(data.vx || 0, data.vy || 0);
        return false;
    }

    // =============================================================
    // 7. SPRINT SIMULATION
    // =============================================================
    let shiftSimulated = false;

    function setSprint(active) {
        if (active && !shiftSimulated) {
            shiftSimulated = true;
            try {
                const ev = new KeyboardEvent('keydown', { key: 'Shift', code: 'ShiftLeft', keyCode: 16, which: 16, bubbles: true });
                Object.defineProperty(ev, 'isTrusted', { value: true });
                document.dispatchEvent(ev);
                setTimeout(() => {
                    const ev2 = new KeyboardEvent('keydown', { key: 'Shift', code: 'ShiftLeft', keyCode: 16, which: 16, bubbles: true });
                    Object.defineProperty(ev2, 'isTrusted', { value: true });
                    document.dispatchEvent(ev2);
                }, 40);
                // Fallback: mouse event
                const ev3 = new MouseEvent('mousedown', { button: 2, buttons: 2, bubbles: true });
                Object.defineProperty(ev3, 'isTrusted', { value: true });
                document.dispatchEvent(ev3);
                if (cv) cv.dispatchEvent(ev3);
            } catch(e) {}
        } else if (!active && shiftSimulated) {
            shiftSimulated = false;
            try {
                const ev = new KeyboardEvent('keyup', { key: 'Shift', code: 'ShiftLeft', keyCode: 16, which: 16, bubbles: true });
                Object.defineProperty(ev, 'isTrusted', { value: true });
                document.dispatchEvent(ev);
                setTimeout(() => {
                    const ev2 = new KeyboardEvent('keyup', { key: 'Shift', code: 'ShiftLeft', keyCode: 16, which: 16, bubbles: true });
                    Object.defineProperty(ev2, 'isTrusted', { value: true });
                    document.dispatchEvent(ev2);
                }, 40);
                const ev3 = new MouseEvent('mouseup', { button: 2, buttons: 0, bubbles: true });
                Object.defineProperty(ev3, 'isTrusted', { value: true });
                document.dispatchEvent(ev3);
                if (cv) cv.dispatchEvent(ev3);
            } catch(e) {}
        }
    }

    // =============================================================
    // 8. STATE
    // =============================================================
    let rt = null, pt = null, cv = null, mi = null;
    let mx = window.innerWidth/2, my = window.innerHeight/2;
    let lastAttack = 0, isRightDown = false;
    let ghostAngle = null, angleLock = 0;
    let enemyMap = new Map();
    let menuVisible = false;
    let rejoinTimer = null, duelTimer = null;
    let rejoinObserver = null, duelObserver = null;
    let toggleBtn = null, menu = null, overlay = null, ctx = null;
    let currentTarget = null;
    let move = { angle: 0, dist: 0, sprint: false, phase: 0 };
    let mode = 'combat';
    let modeTimer = 0;
    let lastReset = 0;
    let frame = 0;
    let isRunning = false;
    let targetLost = 0;
    let isGameActive = false;
    let targetHistory = [];
    let stuckCount = 0;
    let lastPos = { x: 0, y: 0 };
    let lastPosTime = 0;

    // =============================================================
    // 9. UI – CSS
    // =============================================================
    function injectCSS() {
        const style = document.createElement('style');
        style.textContent = `
            @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap');
            #erabot-toggle{position:fixed;bottom:20px;left:20px;width:48px;height:48px;display:flex;align-items:center;justify-content:center;background:linear-gradient(135deg,#1a0a2e,#2d1b4e);color:#fff;border-radius:50%;cursor:pointer;z-index:999999;font-size:22px;border:2px solid #fbbf24;box-shadow:0 0 25px rgba(251,191,36,.3);transition:all .3s;user-select:none;}
            #erabot-toggle:hover{transform:scale(1.1);box-shadow:0 0 40px rgba(251,191,36,.6);}
            #erabot-menu{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);width:340px;background:rgba(10,6,20,.96);backdrop-filter:blur(16px);border-radius:14px;font-family:'Inter',sans-serif;color:#e2e8f0;z-index:999998;border:1px solid rgba(251,191,36,.25);display:none;flex-direction:column;overflow:hidden;box-shadow:0 20px 60px rgba(0,0,0,.9);}
            #erabot-menu.visible{display:flex;}
            #erabot-header{padding:14px 18px;font-size:17px;font-weight:700;color:#fbbf24;border-bottom:1px solid rgba(251,191,36,.15);display:flex;align-items:center;justify-content:space-between;background:rgba(0,0,0,.3);cursor:grab;user-select:none;}
            #erabot-header:active{cursor:grabbing;}
            #erabot-close{cursor:pointer;font-size:22px;color:#666;transition:.2s;line-height:1;padding:0 4px;}
            #erabot-close:hover{color:#ef4444;}
            .erabot-body{padding:14px 18px;display:flex;flex-direction:column;gap:10px;max-height:70vh;overflow-y:auto;}
            .erabot-row{display:flex;justify-content:space-between;align-items:center;font-size:13px;color:#c8c8d0;padding:3px 0;}
            .erabot-row .label{display:flex;align-items:center;gap:6px;}
            .erabot-row .key{font-size:9px;color:#666;background:rgba(255,255,255,.05);padding:1px 7px;border-radius:3px;}
            .erabot-switch{position:relative;width:36px;height:18px;flex-shrink:0;}
            .erabot-switch input{opacity:0;width:0;height:0;}
            .erabot-slider{position:absolute;cursor:pointer;top:0;left:0;right:0;bottom:0;background:#333;transition:.3s;border-radius:18px;}
            .erabot-slider:before{position:absolute;content:"";height:12px;width:12px;left:3px;bottom:3px;background:#fff;transition:.3s;border-radius:50%;}
            input:checked+.erabot-slider{background:#fbbf24;}
            input:checked+.erabot-slider:before{transform:translateX(18px);}
            .erabot-range{display:flex;align-items:center;gap:8px;flex:1;max-width:140px;}
            .erabot-range input[type=range]{-webkit-appearance:none;width:100%;height:3px;background:#333;border-radius:2px;outline:none;}
            .erabot-range input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;width:12px;height:12px;border-radius:50%;background:#fbbf24;cursor:pointer;}
            .erabot-val{color:#fbbf24;font-size:12px;font-weight:600;min-width:30px;text-align:right;}
            .erabot-toast{position:fixed;top:16px;right:16px;background:rgba(10,6,20,.95);backdrop-filter:blur(12px);border:1px solid #fbbf24;border-radius:8px;padding:8px 16px;color:#fff;font-family:'Inter',sans-serif;font-size:13px;font-weight:600;z-index:9999999;box-shadow:0 8px 30px rgba(0,0,0,.8);opacity:0;transform:translateX(60px);transition:all .4s;max-width:300px;}
            .erabot-toast.show{opacity:1;transform:translateX(0);}
            .erabot-toast.success{border-color:#34d399;}
            .erabot-toast.warning{border-color:#fbbf24;}
            .erabot-toast.error{border-color:#ef4444;}
            #erabot-watermark{position:fixed;bottom:6px;left:50%;transform:translateX(-50%);color:rgba(251,191,36,.15);font-size:9px;font-family:'Inter',sans-serif;font-weight:600;z-index:999995;pointer-events:none;letter-spacing:.5px;}
            .erabot-shortcuts{display:flex;flex-wrap:wrap;gap:4px;margin-top:4px;}
            .erabot-shortcut{font-size:9px;color:#666;background:rgba(255,255,255,.04);padding:2px 8px;border-radius:3px;border:1px solid rgba(255,255,255,.04);}
            .erabot-shortcut .k{color:#fbbf24;font-weight:700;}
            .erabot-version{font-size:8px;color:#444;text-align:center;padding-top:6px;border-top:1px solid rgba(255,255,255,.04);margin-top:4px;}
            .erabot-select{background:#2a1840;color:#c8b8e0;border:1px solid #4a2080;border-radius:4px;padding:3px 6px;font-size:11px;outline:none;font-family:inherit;}
            .erabot-select option{background:#1a0a2e;}
        `;
        document.head.appendChild(style);
    }

    // =============================================================
    // 10. UI – MENU
    // =============================================================
    function buildMenu() {
        if (document.getElementById('erabot-menu')) return;
        const html = `
            <div id="erabot-menu">
                <div id="erabot-header">
                    <span>🕺 EraBOT AI</span>
                    <span id="erabot-close">×</span>
                </div>
                <div class="erabot-body">
                    <div class="erabot-row"><span class="label">🕺 EraBOT <span class="key">(K)</span></span><label class="erabot-switch"><input type="checkbox" id="c-eraBot"><span class="erabot-slider"></span></label></div>
                    <div class="erabot-row"><span class="label">⚔️ Auto Attack <span class="key">(C)</span></span><label class="erabot-switch"><input type="checkbox" id="c-autoAttack" checked><span class="erabot-slider"></span></label></div>
                    <div class="erabot-row"><span class="label">🔽 Filter Low Lv <span class="key">(E)</span></span><label class="erabot-switch"><input type="checkbox" id="c-filterLow" checked><span class="erabot-slider"></span></label></div>
                    <div class="erabot-row"><span class="label">👁️ Hitbox <span class="key">(H)</span></span><label class="erabot-switch"><input type="checkbox" id="c-hitbox" checked><span class="erabot-slider"></span></label></div>
                    <div class="erabot-row"><span class="label">🔄 Auto Rejoin</span><label class="erabot-switch"><input type="checkbox" id="c-autoRejoin" checked><span class="erabot-slider"></span></label></div>
                    <div class="erabot-row"><span class="label">⚔️ Auto Duel</span><label class="erabot-switch"><input type="checkbox" id="c-autoDuel" checked><span class="erabot-slider"></span></label></div>
                    <div class="erabot-row"><span class="label">🍖 Collect Food</span><label class="erabot-switch"><input type="checkbox" id="c-collectFood" checked><span class="erabot-slider"></span></label></div>
                    <div class="erabot-row"><span class="label">🏃 Flee Danger</span><label class="erabot-switch"><input type="checkbox" id="c-fleeDanger" checked><span class="erabot-slider"></span></label></div>
                    <div class="erabot-row"><span class="label">⚡ Sprint Mode</span><select class="erabot-select" id="c-sprintMode"><option value="0">Always</option><option value="1" selected>Towards Enemy</option><option value="2">Never</option></select></div>
                    <div class="erabot-row"><span class="label">🎯 Target Lv Range</span><span class="erabot-val" id="v-lvlRange">0-100</span></div>
                    <div class="erabot-row" style="gap:8px;"><input type="range" id="c-lvlMin" min="0" max="100" value="0" style="flex:1;"><input type="range" id="c-lvlMax" min="0" max="100" value="100" style="flex:1;"></div>
                    <div class="erabot-row"><span class="label">⬆ Max Higher</span><div class="erabot-range"><input type="range" id="c-maxHigher" min="0" max="20" value="5"><span class="erabot-val" id="v-maxHigher">5</span></div></div>
                    <div class="erabot-row"><span class="label">⬇ Min Lower</span><div class="erabot-range"><input type="range" id="c-minLower" min="0" max="20" value="0"><span class="erabot-val" id="v-minLower">0</span></div></div>
                    <div class="erabot-row"><span class="label">🛡️ Safe Distance</span><div class="erabot-range"><input type="range" id="c-safeDistance" min="1" max="3" step="0.1" value="1.5"><span class="erabot-val" id="v-safeDistance">1.5x</span></div></div>
                    <div class="erabot-row"><span class="label">🎯 Enemy Range</span><label class="erabot-switch"><input type="checkbox" id="c-enemyRange" checked><span class="erabot-slider"></span></label></div>
                    <div class="erabot-row"><span class="label">⏳ Cooldown Timer</span><label class="erabot-switch"><input type="checkbox" id="c-cooldownTimer" checked><span class="erabot-slider"></span></label></div>
                    <div class="erabot-row"><span class="label">📏 Tracers</span><label class="erabot-switch"><input type="checkbox" id="c-tracers" checked><span class="erabot-slider"></span></label></div>
                    <div class="erabot-row"><span class="label">✨ Target Glow</span><label class="erabot-switch"><input type="checkbox" id="c-targetGlow" checked><span class="erabot-slider"></span></label></div>
                    <div class="erabot-row"><span class="label">🔍 Zoom</span><div class="erabot-range"><input type="range" id="c-zoom" min="0.3" max="2" step="0.05" value="0.9"><span class="erabot-val" id="v-zoom">0.90</span></div></div>
                    <div class="erabot-shortcuts">
                        <span class="erabot-shortcut"><span class="k">K</span> EraBOT</span>
                        <span class="erabot-shortcut"><span class="k">C</span> Attack</span>
                        <span class="erabot-shortcut"><span class="k">E</span> Filter</span>
                        <span class="erabot-shortcut"><span class="k">H</span> Hitbox</span>
                    </div>
                    <div class="erabot-version">v16.0.0</div>
                </div>
            </div>
        `;
        const div = document.createElement('div');
        div.innerHTML = html;
        document.body.appendChild(div);
        menu = document.getElementById('erabot-menu');
        bindMenu();
    }

    function bindMenu() {
        if (!menu) return;
        toggleBtn = document.getElementById('erabot-toggle');
        if (toggleBtn) toggleBtn.addEventListener('click', toggleMenu);
        document.getElementById('erabot-close').addEventListener('click', toggleMenu);
        document.addEventListener('click', (e) => {
            if (menuVisible && menu && !menu.contains(e.target) && e.target !== toggleBtn) toggleMenu();
        });

        const header = document.getElementById('erabot-header');
        let drag = false, ox, oy;
        header.addEventListener('mousedown', (e) => {
            if (e.target.id === 'erabot-close') return;
            drag = true;
            const rect = menu.getBoundingClientRect();
            ox = e.clientX - rect.left;
            oy = e.clientY - rect.top;
            menu.style.top = rect.top + 'px';
            menu.style.left = rect.left + 'px';
            menu.style.transform = 'none';
            e.preventDefault();
        });
        document.addEventListener('mousemove', (e) => {
            if (!drag) return;
            let x = e.clientX - ox, y = e.clientY - oy;
            x = Math.max(0, Math.min(x, window.innerWidth - 200));
            y = Math.max(0, Math.min(y, window.innerHeight - 100));
            menu.style.left = x + 'px';
            menu.style.top = y + 'px';
        });
        document.addEventListener('mouseup', () => { drag = false; });

        const toggleMap = {
            'c-eraBot': 'eraBot',
            'c-autoAttack': 'autoAttack',
            'c-filterLow': 'filterLow',
            'c-hitbox': 'hitbox',
            'c-autoRejoin': 'autoRejoin',
            'c-autoDuel': 'autoDuel',
            'c-collectFood': 'collectFood',
            'c-fleeDanger': 'fleeDanger',
            'c-enemyRange': 'showEnemyRange',
            'c-cooldownTimer': 'showCooldown',
            'c-tracers': 'showTracers',
            'c-targetGlow': 'showTargetGlow',
        };
        Object.keys(toggleMap).forEach(id => {
            const el = document.getElementById(id);
            if (!el) return;
            const key = toggleMap[id];
            if (!(key in C)) C[key] = true;
            el.checked = C[key];
            el.addEventListener('change', () => {
                C[key] = el.checked;
                saveConfig();
                if (key === 'autoRejoin') { if (C.autoRejoin) startRejoin(); else stopRejoin(); }
                if (key === 'autoDuel') { if (C.autoDuel) startDuel(); else stopDuel(); }
            });
        });

        const smEl = document.getElementById('c-sprintMode');
        if (smEl) {
            smEl.value = C.sprintMode;
            smEl.addEventListener('change', () => {
                C.sprintMode = parseInt(smEl.value);
                saveConfig();
            });
        }

        const lvlMinEl = document.getElementById('c-lvlMin');
        const lvlMaxEl = document.getElementById('c-lvlMax');
        const lvlRangeDisp = document.getElementById('v-lvlRange');
        if (lvlMinEl && lvlMaxEl && lvlRangeDisp) {
            lvlMinEl.value = C.lvlMin;
            lvlMaxEl.value = C.lvlMax;
            lvlRangeDisp.textContent = `${C.lvlMin}-${C.lvlMax}`;
            const updateLvlRange = () => {
                let mn = parseInt(lvlMinEl.value);
                let mx = parseInt(lvlMaxEl.value);
                if (mn > mx) { [mn, mx] = [mx, mn]; lvlMinEl.value = mn; lvlMaxEl.value = mx; }
                C.lvlMin = mn; C.lvlMax = mx;
                lvlRangeDisp.textContent = `${mn}-${mx}`;
                saveConfig();
            };
            lvlMinEl.addEventListener('input', updateLvlRange);
            lvlMaxEl.addEventListener('input', updateLvlRange);
        }

        const maxH = document.getElementById('c-maxHigher');
        const vMaxH = document.getElementById('v-maxHigher');
        if (maxH && vMaxH) {
            maxH.value = C.maxHigher;
            vMaxH.textContent = C.maxHigher;
            maxH.addEventListener('input', () => {
                C.maxHigher = parseInt(maxH.value);
                vMaxH.textContent = C.maxHigher;
                saveConfig();
            });
        }

        const minL = document.getElementById('c-minLower');
        const vMinL = document.getElementById('v-minLower');
        if (minL && vMinL) {
            minL.value = C.minLower;
            vMinL.textContent = C.minLower;
            minL.addEventListener('input', () => {
                C.minLower = parseInt(minL.value);
                vMinL.textContent = C.minLower;
                saveConfig();
            });
        }

        const sd = document.getElementById('c-safeDistance');
        const vSd = document.getElementById('v-safeDistance');
        if (sd && vSd) {
            sd.value = C.safeDistance;
            vSd.textContent = C.safeDistance.toFixed(1) + 'x';
            sd.addEventListener('input', () => {
                C.safeDistance = parseFloat(sd.value);
                vSd.textContent = C.safeDistance.toFixed(1) + 'x';
                saveConfig();
            });
        }

        const zoom = document.getElementById('c-zoom');
        const vZoom = document.getElementById('v-zoom');
        if (zoom && vZoom) {
            zoom.value = C.zoom;
            vZoom.textContent = C.zoom.toFixed(2);
            zoom.addEventListener('input', () => {
                C.zoom = parseFloat(zoom.value);
                vZoom.textContent = C.zoom.toFixed(2);
                saveConfig();
                if (rt && rt.running_layout) {
                    try {
                        for (const layer of rt.running_layout.layers) {
                            if (layer) layer.scale = C.zoom;
                        }
                    } catch(e) {}
                }
            });
        }
    }

    function toggleMenu() {
        if (!menu) return;
        menuVisible = !menuVisible;
        menu.classList.toggle('visible', menuVisible);
    }

    function saveConfig() {
        try { localStorage.setItem('erabot_cfg', JSON.stringify(C)); } catch(e) {}
    }
    function loadConfig() {
        try {
            const data = localStorage.getItem('erabot_cfg');
            if (data) {
                const parsed = JSON.parse(data);
                Object.keys(parsed).forEach(k => { if (k in C) C[k] = parsed[k]; });
            }
        } catch(e) {}
        // defaults
        if (!('collectFood' in C)) C.collectFood = true;
        if (!('fleeDanger' in C)) C.fleeDanger = true;
        if (!('sprintMode' in C)) C.sprintMode = 1;
        if (!('lvlMin' in C)) C.lvlMin = 0;
        if (!('lvlMax' in C)) C.lvlMax = 100;
        if (!('maxHigher' in C)) C.maxHigher = 5;
        if (!('minLower' in C)) C.minLower = 0;
        if (!('safeDistance' in C)) C.safeDistance = 1.5;
        if (!('showEnemyRange' in C)) C.showEnemyRange = true;
        if (!('showCooldown' in C)) C.showCooldown = true;
        if (!('showTracers' in C)) C.showTracers = true;
        if (!('showTargetGlow' in C)) C.showTargetGlow = true;
        if (!('autoRejoin' in C)) C.autoRejoin = true;
        if (!('autoDuel' in C)) C.autoDuel = true;
        if (!('aggression' in C)) C.aggression = 0.7;
        if (!('dodgeReaction' in C)) C.dodgeReaction = 0.6;
        if (!('baitChance' in C)) C.baitChance = 0.3;
    }

    // =============================================================
    // 11. KEYBOARD
    // =============================================================
    document.addEventListener('keydown', (e) => {
        if (document.activeElement?.tagName === 'INPUT') return;
        if (e.key === 'k' || e.key === 'K') {
            e.preventDefault();
            C.eraBot = !C.eraBot;
            const cb = document.getElementById('c-eraBot');
            if (cb) { cb.checked = C.eraBot; cb.dispatchEvent(new Event('change')); }
            saveConfig();
            if (C.eraBot) {
                toast('🕺 EraBOT AI đã bật', 'success');
                if (!C.autoAttack) { C.autoAttack = true; const at = document.getElementById('c-autoAttack'); if (at) at.checked = true; }
                if (C.autoRejoin) startRejoin();
                if (C.autoDuel) startDuel();
            } else {
                toast('🕺 EraBOT AI đã tắt', 'warning');
                stopRejoin(); stopDuel();
            }
            return;
        }
        const map = { 'c':'autoAttack', 'e':'filterLow', 'h':'hitbox' };
        const key = e.key.toLowerCase();
        if (map[key]) {
            const cfg = map[key];
            C[cfg] = !C[cfg];
            const cb = document.getElementById('c-'+cfg);
            if (cb) cb.checked = C[cfg];
            saveConfig();
            e.preventDefault();
        }
        if (e.key === 'Insert') { toggleMenu(); e.preventDefault(); }
    });

    // =============================================================
    // 12. AUTO REJOIN / DUEL
    // =============================================================
    function startRejoin() {
        if (rejoinTimer) clearInterval(rejoinTimer);
        if (rejoinObserver) rejoinObserver.disconnect();
        if (!C.autoRejoin) return;

        const clickPlay = () => {
            try {
                const dead = document.querySelector('.dead-screen, .respawn-timer, .you-died, .game-over, .death-screen, .game-overlay');
                if (dead) {
                    const btns = document.querySelectorAll('button, .btn, .button, [role="button"], .play-btn, .rejoin-btn, .respawn-btn, .continue-btn, .action-btn');
                    for (const btn of btns) {
                        const t = (btn.textContent || '').toLowerCase();
                        if (t.includes('play') || t.includes('chơi') || t.includes('rejoin') || t.includes('vào lại') || t.includes('respawn') || t.includes('continue') || t.includes('tiếp tục')) {
                            btn.click();
                            break;
                        }
                    }
                }
            } catch(e) {}
        };

        rejoinTimer = setInterval(clickPlay, 1500);
        rejoinObserver = new MutationObserver(clickPlay);
        rejoinObserver.observe(document.body, { childList: true, subtree: true, attributes: true });
    }
    function stopRejoin() {
        if (rejoinTimer) { clearInterval(rejoinTimer); rejoinTimer = null; }
        if (rejoinObserver) { rejoinObserver.disconnect(); rejoinObserver = null; }
    }

    function startDuel() {
        if (duelTimer) clearInterval(duelTimer);
        if (duelObserver) duelObserver.disconnect();
        if (!C.autoDuel) return;

        const clickDuel = () => {
            try {
                const btns = document.querySelectorAll('button, .btn, .button, [role="button"]');
                for (const btn of btns) {
                    const t = (btn.textContent || '').toLowerCase();
                    if (t.includes('duel') || t.includes('đấu')) {
                        btn.click();
                        setTimeout(() => {
                            try {
                                const btns2 = document.querySelectorAll('button, .btn, .button, [role="button"]');
                                for (const b of btns2) {
                                    const t2 = (b.textContent || '').toLowerCase();
                                    if ((t2.includes('fight') || t2.includes('chiến') || t2.includes('đánh')) && !b.disabled) {
                                        b.click();
                                        break;
                                    }
                                }
                            } catch(e) {}
                        }, 800);
                        return;
                    }
                }
            } catch(e) {}
        };

        duelTimer = setInterval(clickDuel, 2500);
        duelObserver = new MutationObserver(clickDuel);
        duelObserver.observe(document.body, { childList: true, subtree: true, attributes: true });
    }
    function stopDuel() {
        if (duelTimer) { clearInterval(duelTimer); duelTimer = null; }
        if (duelObserver) { duelObserver.disconnect(); duelObserver = null; }
    }

    // =============================================================
    // 13. OVERLAY & MOUSE
    // =============================================================
    function createOverlay() {
        if (overlay) overlay.remove();
        overlay = document.createElement('canvas');
        overlay.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:9997;';
        document.body.appendChild(overlay);
        ctx = overlay.getContext('2d');
        resizeOverlay();
        window.addEventListener('resize', resizeOverlay);
    }
    function resizeOverlay() { if (overlay) { overlay.width = window.innerWidth; overlay.height = window.innerHeight; } }

    function clickAt(x,y) {
        if (!cv) return;
        try {
            cv.dispatchEvent(new MouseEvent('mousedown', {clientX:x, clientY:y, button:0, bubbles:true}));
            setTimeout(() => {
                cv.dispatchEvent(new MouseEvent('mouseup', {clientX:x, clientY:y, button:0, bubbles:true}));
            }, 6);
        } catch(e) {}
    }
    function rightClick(down,x,y) {
        if (!cv) return;
        try {
            const ev = down ? 'mousedown' : 'mouseup';
            cv.dispatchEvent(new MouseEvent(ev, {clientX:x, clientY:y, button:2, buttons: down ? 2 : 0, bubbles:true, cancelable:true}));
        } catch(e) {}
    }

    // =============================================================
    // 14. TARGET SELECTION (nâng cao)
    // =============================================================
    function scoreTarget(me, enemy, myLevel, myReach, now) {
        const eLevel = gl(enemy);
        const d = dist(me, enemy);
        const hp = gh(enemy);
        const data = enemyMap.get(enemy.uid);
        const ai = enemyAIs.get(enemy.uid);

        // Lọc level
        if (eLevel < C.lvlMin || eLevel > C.lvlMax) return -Infinity;
        if (eLevel > myLevel + C.maxHigher) return -Infinity;
        if (eLevel < myLevel - C.minLower) return -Infinity;

        let score = 0;

        // Khoảng cách lý tưởng
        if (d < myReach + 30 && d > myReach * 0.3) score += 40;
        else if (d < myReach + 70) score += 25;
        else if (d < myReach * 1.5) score += 10;
        else if (d > myReach * 2.5) score -= 40;
        else if (d < myReach * 0.2) score -= 30;

        // Máu
        if (hp < 300) score += 60;
        else if (hp < 600) score += 35;
        else if (hp < 1000) score += 10;

        // Level
        if (eLevel < myLevel - 2) score += 30;
        else if (eLevel < myLevel) score += 15;
        else if (eLevel > myLevel + 4) score -= 50;
        else if (eLevel > myLevel + 2) score -= 25;

        // AI: nếu enemy đang trong pattern, dễ đoán
        if (ai && ai.confidence > 0.6) {
            const next = ai.predictNextAttack(now);
            if (next && now > next - 150 && now < next) score -= 40;
            if (ai.patternDetected) score += 20;
            if (ai.strafing) score += 10;
        }

        // Đang tấn công?
        if (data && data.attackStart) {
            const elapsed = now - data.attackStart;
            const timings = getWeaponTimings(eLevel);
            if (elapsed > timings.windup && elapsed < timings.windup + timings.cooldown) {
                score += 30;
            }
        }

        // Đang di chuyển nhanh?
        if (data && data.vx && data.vy && Math.hypot(data.vx, data.vy) > 5) score += 20;

        return score;
    }

    function bestTarget(me, enemies, myLevel, myReach, now) {
        let best = null, bestScore = -Infinity;
        for (const enemy of enemies) {
            const s = scoreTarget(me, enemy, myLevel, myReach, now);
            if (s > bestScore) { bestScore = s; best = enemy; }
        }
        return { target: best, score: bestScore };
    }

    // =============================================================
    // 15. DANGER MAP (nâng cao)
    // =============================================================
    function dangerMap(me, enemies, reach, now) {
        let dx=0, dy=0, total=0, count=0;
        for (const enemy of enemies) {
            const d = dist(me, enemy);
            if (d > reach * 2.5) continue;
            count++;
            const lv = gl(enemy);
            const r = getReach(lv).d * getMul(lv);
            const data = enemyMap.get(enemy.uid);
            const ai = enemyAIs.get(enemy.uid);
            let w = 1 / (d + 10);
            w *= 1 + lv / 50;
            w *= 1 + r / 300;
            if (data && data.attackStart) {
                const elapsed = now - data.attackStart;
                const timings = getWeaponTimings(lv);
                if (elapsed < timings.windup) w *= 2.5;
                else if (elapsed < timings.windup + timings.cooldown) w *= 1.3;
            }
            if (ai && ai.confidence > 0.5) {
                const next = ai.predictNextAttack(now);
                if (next && now > next - 200 && now < next) w *= 2;
                // Enemy có pattern dễ đoán -> ít nguy hiểm hơn
                if (ai.patternDetected) w *= 0.8;
            }
            const angle = Math.atan2(enemy.y - me.y, enemy.x - me.x);
            dx += Math.cos(angle) * w;
            dy += Math.sin(angle) * w;
            total += w;
        }
        if (total > 0) { dx /= total; dy /= total; }
        return { x: dx, y: dy, count };
    }

    function safeDir(me, enemies, reach, now) {
        const d = dangerMap(me, enemies, reach, now);
        const angle = Math.atan2(d.y, d.x) + Math.PI + rand(-0.15, 0.15);
        const dist2 = 200 + Math.sin(now * 0.002) * 30;
        return {
            x: me.x + Math.cos(angle) * dist2,
            y: me.y + Math.sin(angle) * dist2,
            threat: d.count / (enemies.length + 1)
        };
    }

    // =============================================================
    // 16. DODGE SWING (nâng cao)
    // =============================================================
    function dodgeSwing(me, enemy, enemyReach, enemySize, now) {
        const ai = enemyAIs.get(enemy.uid);
        if (!ai || ai.confidence < 0.4) return null;
        const swing = ai.getSwingAngle();
        if (Math.abs(swing) < 0.25) return null;
        const dodgeAngle = swing > 0 ? -Math.PI/2 : Math.PI/2;
        const dist2 = (enemyReach + enemySize) * 0.65 * C.dodgeReaction;
        return {
            x: me.x + Math.cos(dodgeAngle) * dist2,
            y: me.y + Math.sin(dodgeAngle) * dist2,
            angle: dodgeAngle
        };
    }

    // =============================================================
    // 17. SMART MOVEMENT + BAIT + OBSTACLE AVOIDANCE
    // =============================================================
    const MAP_MIN = 120, MAP_MAX = 19880;

    function clampPos(x, y) {
        return { x: clamp(x, MAP_MIN, MAP_MAX), y: clamp(y, MAP_MIN, MAP_MAX) };
    }

    function avoidWalls(me, targetX, targetY, step = 60) {
        let bestX = targetX, bestY = targetY;
        let bestScore = -Infinity;
        const candidates = [
            { x: targetX, y: targetY },
            { x: targetX + step, y: targetY },
            { x: targetX - step, y: targetY },
            { x: targetX, y: targetY + step },
            { x: targetX, y: targetY - step },
            { x: targetX + step * 0.7, y: targetY + step * 0.7 },
            { x: targetX - step * 0.7, y: targetY + step * 0.7 },
            { x: targetX + step * 0.7, y: targetY - step * 0.7 },
            { x: targetX - step * 0.7, y: targetY - step * 0.7 },
        ];
        for (const c of candidates) {
            const cx = clamp(c.x, MAP_MIN, MAP_MAX);
            const cy = clamp(c.y, MAP_MIN, MAP_MAX);
            const edgeDist = Math.min(cx - MAP_MIN, MAP_MAX - cx, cy - MAP_MIN, MAP_MAX - cy);
            const score = edgeDist * 2 - Math.hypot(cx - targetX, cy - targetY) * 0.3;
            if (score > bestScore) {
                bestScore = score;
                bestX = cx;
                bestY = cy;
            }
        }
        return { x: bestX, y: bestY };
    }

    function findNearestOrb(me, now, maxRadius) {
        if (!rt || !rt.running_layout) return null;
        maxRadius = maxRadius || 700;
        let best = null, bestD = Infinity;
        try {
            for (const layer of rt.running_layout.layers) {
                if (layer && layer.objects) {
                    for (const obj of layer.objects) {
                        if (isOrb(obj)) {
                            const d = Math.hypot(me.x - obj.x, me.y - obj.y);
                            if (d < maxRadius && d < bestD) {
                                bestD = d;
                                best = obj;
                            }
                        }
                    }
                }
            }
        } catch(e) {}
        return best;
    }

    function findWanderTarget(me, enemies) {
        let nearest = null, nd = Infinity;
        if (enemies && enemies.length > 0) {
            for (const e of enemies) {
                const d = dist(me, e);
                if (d < nd) { nd = d; nearest = e; }
            }
        }
        if (nearest && nd < 1200) {
            return { x: nearest.x, y: nearest.y };
        }
        const cx = 10000, cy = 10000;
        const d = Math.hypot(me.x - cx, me.y - cy);
        if (d > 200) {
            return { x: cx, y: cy };
        }
        move.phase += 0.02;
        const angle = move.phase * 0.7 + Math.sin(move.phase * 1.3) * 0.5;
        const dist2 = 200 + Math.sin(move.phase * 0.5) * 100;
        return {
            x: me.x + Math.cos(angle) * dist2,
            y: me.y + Math.sin(angle) * dist2
        };
    }

    function calcMove(me, target, myReach, enemyReach, enemySize, now, allEnemies) {
        const myLevel = gl(me);

        // === ƯU TIÊN ORB KHI LEVEL <= 5 ===
        if (C.collectFood && myLevel <= 5) {
            const orb = findNearestOrb(me, now, 700);
            if (orb) {
                const d = Math.hypot(orb.x - me.x, orb.y - me.y);
                if (d > 20) {
                    const angle = Math.atan2(orb.y - me.y, orb.x - me.x);
                    let avoid = { x: 0, y: 0 };
                    if (allEnemies) {
                        for (const e of allEnemies) {
                            const ed = dist(me, e);
                            if (ed < 300) {
                                const w = (300 - ed) / 300;
                                avoid.x += (me.x - e.x) / Math.max(1, ed) * w * 0.5;
                                avoid.y += (me.y - e.y) / Math.max(1, ed) * w * 0.5;
                            }
                        }
                    }
                    const finalAngle = angle + Math.atan2(avoid.y, avoid.x) * 0.3;
                    let tx = me.x + Math.cos(finalAngle) * Math.min(300, d);
                    let ty = me.y + Math.sin(finalAngle) * Math.min(300, d);
                    const pos = avoidWalls(me, tx, ty);
                    return { x: pos.x, y: pos.y, sprint: true };
                }
            }
        }

        // === KHÔNG CÓ TARGET ===
        if (!target) {
            targetLost++;
            if (targetLost < 5 && currentTarget) {
                target = currentTarget;
            } else {
                targetLost = 0;
                const wanderTarget = findWanderTarget(me, allEnemies);
                const d = Math.hypot(wanderTarget.x - me.x, wanderTarget.y - me.y);
                if (d > 20) {
                    const angle = Math.atan2(wanderTarget.y - me.y, wanderTarget.x - me.x);
                    let tx = me.x + Math.cos(angle) * Math.min(250, d);
                    let ty = me.y + Math.sin(angle) * Math.min(250, d);
                    const pos = avoidWalls(me, tx, ty);
                    return { x: pos.x, y: pos.y, sprint: false };
                }
                move.phase += 0.015;
                const wa = move.phase * 0.6 + Math.sin(move.phase * 1.2) * 0.4;
                const wd = 100 + Math.sin(move.phase * 0.6) * 40;
                let tx = me.x + Math.cos(wa) * wd;
                let ty = me.y + Math.sin(wa) * wd;
                const pos = avoidWalls(me, tx, ty);
                return { x: pos.x, y: pos.y, sprint: false };
            }
        } else {
            targetLost = 0;
        }

        // === CÓ TARGET ===
        const dx = target.x - me.x;
        const dy = target.y - me.y;
        const d = Math.hypot(dx, dy);
        if (d < 0.5) { const pos = avoidWalls(me, me.x + 50, me.y + 50); return { x: pos.x, y: pos.y, sprint: false }; }

        const angle = Math.atan2(dy, dx);
        const data = enemyMap.get(target.uid);
        const ai = enemyAIs.get(target.uid);

        const dangerZone = enemyReach + enemySize + 10;
        const attackZone = myReach + 10;

        let isAttacking = false, isCooldown = false;
        const lv = gl(target);
        const timings = getWeaponTimings(lv);
        if (data && data.attackStart) {
            const elapsed = now - data.attackStart;
            if (elapsed < timings.windup) isAttacking = true;
            else if (elapsed < timings.windup + timings.cooldown) isCooldown = true;
        }

        const pingOffset = pingEstimate / 1000;
        let predX = target.x, predY = target.y;
        if (ai && ai.confidence > 0.5) {
            const pred = ai.predictPos(200 + pingEstimate);
            if (pred) { predX = pred.x; predY = pred.y; }
        } else if (data && data.vx && data.vy) {
            predX += data.vx * (0.2 + pingOffset * 0.5);
            predY += data.vy * (0.2 + pingOffset * 0.5);
        }
        const predAngle = Math.atan2(predY - me.y, predX - me.x);

        // Dự đoán hướng né của enemy để tấn công ngược
        let antiDodge = 0;
        if (ai && ai.confidence > 0.5) {
            antiDodge = ai.predictDodgeDirection() * 0.3;
        }

        // Danger map
        const danger = dangerMap(me, allEnemies || [], myReach, now);
        let newMode = mode;
        const modeCooldown = 300;
        if (now >= modeTimer) {
            if (danger.count >= 3 && d < attackZone + 100) {
                newMode = 'retreat';
                modeTimer = now + modeCooldown;
            } else if (ai && ai.baitFreq > 0.4 && d > attackZone + 30 && d < attackZone + 100) {
                newMode = 'bait';
                modeTimer = now + modeCooldown;
            } else if (danger.count > 0 && allEnemies && allEnemies.length > 1) {
                let hasAlly = false;
                for (const e of allEnemies) {
                    if (dist(target, e) < 300 && e.uid !== target.uid) { hasAlly = true; break; }
                }
                if (hasAlly) { newMode = 'flank'; modeTimer = now + modeCooldown; }
                else newMode = 'combat';
            } else {
                newMode = 'combat';
            }
            if (newMode !== mode) { mode = newMode; modeTimer = now + modeCooldown; }
        }

        // === FLEE DANGER ===
        if (C.fleeDanger && target) {
            const eLv = gl(target);
            if (eLv > myLevel + C.maxHigher + 3) {
                const d2 = dist(me, target);
                const eReach = getReach(eLv).d * getMul(eLv);
                if (d2 < eReach * C.safeDistance) {
                    const fleeAngle = Math.atan2(me.y - target.y, me.x - target.x);
                    let tx = me.x + Math.cos(fleeAngle) * 250;
                    let ty = me.y + Math.sin(fleeAngle) * 250;
                    const pos = avoidWalls(me, tx, ty);
                    return { x: pos.x, y: pos.y, sprint: true };
                }
            }
        }

        // === BAIT / FAKE MOVEMENT ===
        let baitOffset = 0;
        let fakeDirection = 0;
        if (ai && ai.confidence > 0.5 && d < attackZone + 40 && d > attackZone - 30 && !isAttacking && Math.random() < C.baitChance) {
            const phase = (now * 0.001 + hash(target.uid.toString()) * 0.3) % 1;
            if (phase > 0.6 && phase < 0.75) {
                baitOffset = -40;
                fakeDirection = 0.3;
            } else if (phase > 0.75 && phase < 0.9) {
                baitOffset = 70;
                fakeDirection = -0.4;
            }
            if (phase > 0.5 && phase < 0.6) {
                fakeDirection = 0.6;
            }
        }

        // Swing dodge
        const swing = dodgeSwing(me, target, enemyReach, enemySize, now);
        if (swing && isAttacking && d < dangerZone + 20) {
            const dist2 = 180 + Math.sin(now * 0.002) * 20;
            const angle2 = swing.angle + rand(-0.1, 0.1);
            let tx = me.x + Math.cos(angle2) * dist2;
            let ty = me.y + Math.sin(angle2) * dist2;
            const pos = avoidWalls(me, tx, ty);
            return { x: pos.x, y: pos.y, sprint: true };
        }

        let targetDist, desiredAngle;
        switch(mode) {
            case 'retreat': {
                const safe = safeDir(me, allEnemies || [], myReach, now);
                const ra = Math.atan2(safe.y - me.y, safe.x - me.x);
                targetDist = 250 + Math.sin(now * 0.002) * 30;
                desiredAngle = ra + rand(-0.1, 0.1);
                move.sprint = true;
                if (Math.hypot(safe.x - me.x, safe.y - me.y) < 100) { mode = 'combat'; modeTimer = now + modeCooldown; }
                break;
            }
            case 'bait': {
                const phase = (now * 0.001 + (target.uid % 100) * 0.01) % 1;
                if (phase < 0.3) {
                    targetDist = Math.min(200, d * 0.6 + 20 + baitOffset * 0.5);
                    desiredAngle = predAngle + fakeDirection + antiDodge + Math.sin(now * 0.003) * 0.1;
                } else if (phase < 0.6) {
                    targetDist = Math.max(150, d * 1.2 + 30 + baitOffset * 0.3);
                    desiredAngle = angle + Math.PI * (0.8 + fakeDirection) + antiDodge * 0.5 + Math.sin(now * 0.002) * 0.2;
                } else {
                    targetDist = attackZone * 0.9;
                    const tangent = angle + (Math.PI/2) * (Math.sin(now * 0.0015) > 0 ? 1 : -1);
                    desiredAngle = tangent + antiDodge + Math.sin(now * 0.004) * 0.3;
                }
                move.sprint = (phase < 0.3);
                break;
            }
            case 'flank': {
                const fa = angle + Math.PI * 0.7 + Math.sin(now * 0.001 + target.uid * 0.01) * 0.3 + antiDodge * 0.3;
                targetDist = Math.min(300, Math.max(150, d * 0.9));
                desiredAngle = fa;
                move.sprint = true;
                const behind = Math.abs(angleDiff(angle, Math.atan2(me.y - target.y, me.x - target.x))) < 0.5;
                if (behind && d < attackZone + 30) { mode = 'combat'; modeTimer = now + modeCooldown; }
                break;
            }
            default: {
                const aggression = C.aggression || 0.7;
                if (d > attackZone + 60) {
                    targetDist = Math.min(300, d * 0.7 + 30 + baitOffset * 0.2);
                    desiredAngle = predAngle + fakeDirection * 0.3 + antiDodge * 0.2 + Math.sin(move.phase * 1.3) * 0.2;
                    move.phase += 0.03;
                } else if ((isCooldown || (data && Math.hypot(data.vx||0, data.vy||0) > 5)) &&
                           d < attackZone + 30 && d > attackZone * 0.4) {
                    targetDist = Math.min(200, Math.max(50, d * 0.3 + 20 + baitOffset * 0.1));
                    desiredAngle = predAngle + fakeDirection * 0.2 + antiDodge * 0.3;
                    move.phase += 0.05;
                } else if (d < attackZone * 0.4) {
                    targetDist = Math.min(250, Math.max(150, attackZone * 0.6));
                    desiredAngle = angle + Math.PI * (0.7 + fakeDirection * 0.3 + antiDodge * 0.2 + Math.sin(move.phase * 0.5) * 0.2);
                    move.phase += 0.02;
                } else {
                    targetDist = attackZone * 0.85 + Math.sin(move.phase * 0.7) * 10;
                    const tangent = angle + (Math.PI/2) * (Math.sin(move.phase * 1.1) > 0 ? 1 : -1);
                    desiredAngle = tangent + fakeDirection * 0.2 + antiDodge * 0.2 + Math.sin(move.phase * 2.3) * 0.3;
                    move.phase += 0.04;
                }
                switch(C.sprintMode) {
                    case 0: move.sprint = true; break;
                    case 1: move.sprint = (d > attackZone + 80 || isAttacking); break;
                    case 2: move.sprint = false; break;
                    default: move.sprint = false;
                }
            }
        }

        if (ai && ai.confidence > 0.6 && Math.abs(ai.dodgePref) > 0.3) {
            desiredAngle += ai.dodgePref * 0.15;
        }

        const ad = angleDiff(move.angle, desiredAngle);
        move.angle += ad * 0.2;
        move.dist += (targetDist - move.dist) * 0.15;
        move.dist = clamp(move.dist, 40, 400);

        const ma = move.angle;
        const md = move.dist;
        let tx = me.x + Math.cos(ma) * md;
        let ty = me.y + Math.sin(ma) * md;

        const maxMove = 380;
        const actual = Math.hypot(tx-me.x, ty-me.y);
        if (actual > maxMove) {
            tx = me.x + (tx-me.x)/actual * maxMove;
            ty = me.y + (ty-me.y)/actual * maxMove;
        }

        const nd = Math.hypot(tx - target.x, ty - target.y);
        const minSafe = dangerZone * 0.5;
        if (nd < minSafe) {
            const pa = Math.atan2(ty - target.y, tx - target.x);
            const pd = dangerZone * 0.8;
            tx = target.x + Math.cos(pa) * pd;
            ty = target.y + Math.sin(pa) * pd;
        }

        const pos = avoidWalls(me, tx, ty);
        return { x: pos.x, y: pos.y, sprint: move.sprint };
    }

    // =============================================================
    // 18. MAIN LOOP
    // =============================================================
    let loopCount = 0;

    function mainLoop() {
        if (!isRunning) {
            if (!isGameActive) {
                setTimeout(() => {
                    if (rt && rt.running_layout) {
                        isGameActive = true;
                        isRunning = true;
                        mainLoop();
                    } else {
                        findRuntime();
                    }
                }, 2000);
            }
            return;
        }

        try {
            if (!rt || !rt.running_layout || !pt || !cv) {
                isGameActive = false;
                if (ctx) ctx.clearRect(0,0,overlay.width,overlay.height);
                if (isRightDown) { rightClick(false, mx, my); isRightDown = false; }
                if (isRunning) { isRunning = false; }
                requestAnimationFrame(mainLoop);
                return;
            }

            isGameActive = true;
            const now = performance.now();
            loopCount++;
            frame++;
            if (now > angleLock) ghostAngle = null;

            const sx = rt.running_layout.scrollX;
            const sy = rt.running_layout.scrollY;

            let me = null, minD = Infinity;
            for (const inst of pt.instances) {
                const d = Math.hypot(inst.x - sx, inst.y - sy);
                if (d < minD) { minD = d; me = inst; }
            }
            if (!me) {
                if (ctx) ctx.clearRect(0,0,overlay.width,overlay.height);
                if (isRightDown) { rightClick(false, mx, my); isRightDown = false; }
                requestAnimationFrame(mainLoop);
                return;
            }

            // Check stuck
            const now2 = performance.now();
            if (now2 - lastPosTime > 500) {
                const moved = Math.hypot(me.x - lastPos.x, me.y - lastPos.y);
                if (moved < 5) {
                    stuckCount++;
                    if (stuckCount > 5) {
                        // Force move
                        const forceAngle = Math.random() * Math.PI * 2;
                        me.x += Math.cos(forceAngle) * 30;
                        me.y += Math.sin(forceAngle) * 30;
                        stuckCount = 0;
                    }
                } else {
                    stuckCount = 0;
                }
                lastPos = { x: me.x, y: me.y };
                lastPosTime = now2;
            }

            const myLevel = gl(me);
            const myTeam = gt(me);
            const mySize = me.width * 0.35;
            const reachData = getReach(myLevel);
            const reachMul = getMul(myLevel);
            const myReach = reachData.d * reachMul;
            const degrees = reachData.deg;
            const myTimings = getWeaponTimings(myLevel);
            const attackCD = myTimings.cooldown;

            const rect = cv.getBoundingClientRect();
            const sx2 = rect.width / cv.width;
            const sy2 = rect.height / cv.height;
            const ls = me.layer.getScale();

            const cx = rect.left + rect.width/2;
            const cy = rect.top + rect.height/2;
            const px = cx + (me.x - sx) * ls * sx2;
            const py = cy + (me.y - sy) * ls * sy2;

            if (ctx) ctx.clearRect(0,0,overlay.width,overlay.height);

            // ---- Collect enemies ----
            let candidateEnemies = [];
            const seen = new Set();
            const allInstances = pt.instances;

            for (const inst of allInstances) {
                if (inst.uid === me.uid || inst.width <= 0) continue;
                const enemyTeam = gt(inst);
                if (myTeam !== -1 && enemyTeam === myTeam) continue;
                const d = Math.hypot(me.x - inst.x, me.y - inst.y);
                if (d > 1200) continue;
                candidateEnemies.push({ inst, d });
            }
            candidateEnemies.sort((a,b) => a.d - b.d);
            if (candidateEnemies.length > 20) candidateEnemies.length = 20;

            const enemies = [];
            for (const item of candidateEnemies) {
                const inst = item.inst;
                seen.add(inst.uid);
                const enemyLevel = gl(inst);
                const enemySize = inst.width * 0.35;
                const enemyReach = getReach(enemyLevel).d * getMul(enemyLevel);
                const d = item.d;

                let data = enemyMap.get(inst.uid);
                if (!data) {
                    data = { attackStart: 0, lastX: inst.x, lastY: inst.y, vx: 0, vy: 0, lastSpeed: 0, lastAngle: 0, lastDist: 0 };
                    enemyMap.set(inst.uid, data);
                }
                data.vx = inst.x - data.lastX;
                data.vy = inst.y - data.lastY;
                data.lastX = inst.x;
                data.lastY = inst.y;

                detectAttackFallback(inst.uid, data, now);

                if (frame % 5 === 0) {
                    let ai = enemyAIs.get(inst.uid);
                    if (!ai) { ai = new EnemyAI(inst.uid); enemyAIs.set(inst.uid, ai); }
                    ai.update(inst, data.vx, data.vy, now);
                }

                let ai = enemyAIs.get(inst.uid);
                let isSwinging = false;
                const eTimings = getWeaponTimings(enemyLevel);
                if (data.attackStart) {
                    const elapsed = now - data.attackStart;
                    if (elapsed < eTimings.windup) isSwinging = true;
                }
                if (ai && ai.confidence > 0.5) {
                    const next = ai.predictNextAttack(now);
                    if (next && now > next - 100 && now < next) isSwinging = true;
                }

                let valid = true;
                if (C.filterLow && C.eraBot) {
                    if (isLow(enemyLevel) && d > myReach + enemySize + 20) valid = false;
                }

                if (valid) {
                    enemies.push(inst);
                }

                // ---- ESP (vẽ mỗi 5 frame) ----
                if (C.hitbox && frame % 5 === 0 && enemies.length < 15) {
                    const pts = getPts(inst);
                    if (pts.length > 1) {
                        ctx.beginPath();
                        ctx.moveTo(cx + (pts[0].x - sx)*ls*sx2, cy + (pts[0].y - sy)*ls*sy2);
                        for (let i=1; i<pts.length; i++) {
                            ctx.lineTo(cx + (pts[i].x - sx)*ls*sx2, cy + (pts[i].y - sy)*ls*sy2);
                        }
                        ctx.closePath();
                        ctx.fillStyle = 'rgba(251,191,36,0.10)';
                        ctx.fill();
                        ctx.strokeStyle = 'rgba(251,191,36,0.35)';
                        ctx.lineWidth = 1.2;
                        ctx.stroke();
                    }
                    const lvY = cy + (inst.y - sy)*ls*sy2 - enemySize*ls*sy2 - 6;
                    ctx.font = '500 10px Inter, sans-serif';
                    ctx.textAlign = 'center';
                    ctx.fillStyle = 'rgba(255,255,255,0.5)';
                    ctx.fillText('Lv.' + (Math.floor(enemyLevel)+1), cx + (inst.x - sx)*ls*sx2, lvY);
                }

                if (C.showTracers && valid && enemies.length < 15) {
                    ctx.beginPath();
                    ctx.moveTo(px, py);
                    ctx.lineTo(cx + (inst.x - sx)*ls*sx2, cy + (inst.y - sy)*ls*sy2);
                    ctx.strokeStyle = isSwinging ? 'rgba(239,68,68,0.2)' : 'rgba(251,191,36,0.12)';
                    ctx.lineWidth = 0.5;
                    ctx.stroke();
                }

                if (C.showEnemyRange && valid && enemies.length < 15) {
                    ctx.beginPath();
                    ctx.arc(cx + (inst.x - sx)*ls*sx2, cy + (inst.y - sy)*ls*sy2, enemyReach * ls * sx2, 0, Math.PI * 2);
                    ctx.strokeStyle = 'rgba(255,60,60,0.2)';
                    ctx.lineWidth = 1;
                    ctx.setLineDash([3, 3]);
                    ctx.stroke();
                    ctx.setLineDash([]);
                }

                if (C.showCooldown && valid && data.attackStart && enemies.length < 15) {
                    const elapsed = now - data.attackStart;
                    const total = eTimings.windup + eTimings.cooldown;
                    const pct = Math.min(1, elapsed / total);
                    const eX = cx + (inst.x - sx)*ls*sx2;
                    const eY = cy + (inst.y - sy)*ls*sy2;
                    ctx.beginPath();
                    ctx.arc(eX, eY, 22, -Math.PI/2, -Math.PI/2 + Math.PI * 2 * pct);
                    ctx.strokeStyle = pct < 0.15 ? '#22c55e' : '#f59e0b';
                    ctx.lineWidth = 2;
                    ctx.stroke();
                    ctx.fillStyle = '#fff';
                    ctx.font = 'bold 8px Inter';
                    ctx.textAlign = 'center';
                    ctx.fillText(((total - elapsed)/1000).toFixed(1) + 's', eX, eY - 28);
                }

                if (C.showTargetGlow && currentTarget && currentTarget.uid === inst.uid && enemies.length < 15) {
                    const eX = cx + (inst.x - sx)*ls*sx2;
                    const eY = cy + (inst.y - sy)*ls*sy2;
                    ctx.shadowColor = '#a855f7';
                    ctx.shadowBlur = 25;
                    ctx.beginPath();
                    ctx.arc(eX, eY, 26, 0, Math.PI * 2);
                    ctx.strokeStyle = '#a855f7';
                    ctx.lineWidth = 2.5;
                    ctx.setLineDash([3, 3]);
                    ctx.stroke();
                    ctx.setLineDash([]);
                    ctx.shadowBlur = 0;
                }
            }

            if (frame % 60 === 0) {
                const now3 = performance.now();
                for (const [uid, ai] of enemyAIs) {
                    if (now3 - ai.lastSeen > 5000) { enemyAIs.delete(uid); }
                }
                for (const key of enemyMap.keys()) {
                    if (!seen.has(key)) { enemyMap.delete(key); }
                }
            }

            // ---- Auto Attack target ----
            let attackTarget = null, attackD = Infinity;
            for (const inst of enemies) {
                const d = dist(me, inst);
                const eSize = inst.width * 0.35;
                if (d < myReach + eSize && d < attackD) {
                    attackD = d;
                    attackTarget = inst;
                }
            }

            // ---- Decision ----
            let targetX = mx, targetY = my;
            let shouldRight = false, shouldAttack = false;

            if (C.eraBot) {
                let huntTarget = null;
                if (enemies.length > 0) {
                    const result = bestTarget(me, enemies, myLevel, myReach, now);
                    huntTarget = result.target;
                }

                if (currentTarget && enemyMap.has(currentTarget.uid)) {
                    const currentScore = scoreTarget(me, currentTarget, myLevel, myReach, now);
                    const newScore = huntTarget ? scoreTarget(me, huntTarget, myLevel, myReach, now) : -Infinity;
                    if (huntTarget && newScore > currentScore * 1.08) {
                        currentTarget = huntTarget;
                    }
                } else {
                    currentTarget = huntTarget;
                }

                const moveTarget = currentTarget;

                if (!moveTarget) {
                    if (now - lastReset > 3000) lastReset = now;
                    const moveResult = calcMove(me, null, myReach, 0, 0, now, enemies);
                    const delta = Math.hypot(moveResult.x - me.x, moveResult.y - me.y);
                    if (delta > 0) {
                        targetX = cx + ((moveResult.x - me.x)/delta) * 70;
                        targetY = cy + ((moveResult.y - me.y)/delta) * 70;
                    }
                    shouldRight = true;
                    move.sprint = moveResult.sprint;
                } else {
                    const enemyLevel = gl(moveTarget);
                    const enemyReach = getReach(enemyLevel).d * getMul(enemyLevel);
                    const enemySize = moveTarget.width * 0.35;
                    const moveResult = calcMove(me, moveTarget, myReach, enemyReach, enemySize, now, enemies);
                    const delta = Math.hypot(moveResult.x - me.x, moveResult.y - me.y);
                    if (delta > 0) {
                        targetX = cx + ((moveResult.x - me.x)/delta) * 70;
                        targetY = cy + ((moveResult.y - me.y)/delta) * 70;
                    }
                    shouldRight = true;
                    move.sprint = moveResult.sprint;

                    if (C.autoAttack && moveTarget === attackTarget && now - lastAttack > attackCD) {
                        const d = Math.hypot(moveTarget.x - me.x, moveTarget.y - me.y);
                        const data = enemyMap.get(moveTarget.uid);
                        const ai = enemyAIs.get(moveTarget.uid);
                        const eTimings2 = getWeaponTimings(gl(moveTarget));
                        let vulnerable = false;
                        if (data && data.attackStart) {
                            const elapsed = now - data.attackStart;
                            if (elapsed > eTimings2.windup && elapsed < eTimings2.windup + eTimings2.cooldown) {
                                vulnerable = true;
                            }
                        }
                        if (data && Math.hypot(data.vx||0, data.vy||0) > 5) vulnerable = true;
                        if (ai && ai.confidence > 0.5) {
                            const next = ai.predictNextAttack(now);
                            if (next && now > next + 100) vulnerable = true;
                        }
                        if (d < myReach + 20 && vulnerable) {
                            shouldAttack = true;
                            let predX = moveTarget.x + (data?.vx||0) * 12;
                            let predY = moveTarget.y + (data?.vy||0) * 12;
                            let angleOff = degrees;
                            if (moveTarget.width < 150 || gl(moveTarget) < 10) angleOff *= 0.3;
                            const angle = Math.atan2(predY - me.y, predX - me.x) + (angleOff * Math.PI/180);
                            let final = (angle * 180/Math.PI) % 360;
                            if (final < 0) final += 360;
                            ghostAngle = final;
                            angleLock = now + Math.min(150, attackCD * 0.3 + pingEstimate * 0.2);
                            if (d > 0) {
                                targetX = cx + ((moveTarget.x - me.x)/d) * 35;
                                targetY = cy + ((moveTarget.y - me.y)/d) * 35;
                            }
                        }
                    }
                }
            }

            if (C.autoAttack && attackTarget && !C.eraBot && now - lastAttack > attackCD) {
                const d = Math.hypot(attackTarget.x - me.x, attackTarget.y - me.y);
                const data = enemyMap.get(attackTarget.uid);
                const ai = enemyAIs.get(attackTarget.uid);
                const eTimings2 = getWeaponTimings(gl(attackTarget));
                let vulnerable = false;
                if (data && data.attackStart) {
                    const elapsed = now - data.attackStart;
                    if (elapsed > eTimings2.windup && elapsed < eTimings2.windup + eTimings2.cooldown) {
                        vulnerable = true;
                    }
                }
                if (data && Math.hypot(data.vx||0, data.vy||0) > 5) vulnerable = true;
                if (ai && ai.confidence > 0.5) {
                    const next = ai.predictNextAttack(now);
                    if (next && now > next + 100) vulnerable = true;
                }
                if (d < myReach + 20 && (vulnerable || d < myReach * 0.5)) {
                    shouldAttack = true;
                    let predX = attackTarget.x + (data?.vx||0) * 12;
                    let predY = attackTarget.y + (data?.vy||0) * 12;
                    let angleOff = degrees;
                    if (attackTarget.width < 150 || gl(attackTarget) < 10) angleOff *= 0.3;
                    const angle = Math.atan2(predY - me.y, predX - me.x) + (angleOff * Math.PI/180);
                    let final = (angle * 180/Math.PI) % 360;
                    if (final < 0) final += 360;
                    ghostAngle = final;
                    angleLock = now + Math.min(150, attackCD * 0.3 + pingEstimate * 0.2);
                    if (d > 0) {
                        targetX = cx + ((attackTarget.x - me.x)/d) * 35;
                        targetY = cy + ((attackTarget.y - me.y)/d) * 35;
                    }
                }
            }

            if (mi) {
                try { mi.mouseXcanvas = targetX; mi.mouseYcanvas = targetY; } catch(e) {}
            }

            if (C.eraBot) {
                const sprintNeeded = move.sprint || (attackTarget && dist(me, attackTarget) > myReach + 100);
                setSprint(sprintNeeded);
            } else {
                setSprint(false);
            }

            if (shouldRight) {
                if (!isRightDown) { rightClick(true, targetX, targetY); isRightDown = true; }
            } else if (isRightDown) {
                rightClick(false, targetX, targetY); isRightDown = false;
            }

            if (shouldAttack && now - lastAttack > 50) {
                clickAt(targetX, targetY);
                lastAttack = now;
            }

        } catch(e) {
            console.warn('[EraBOT] Loop error:', e);
        }
        requestAnimationFrame(mainLoop);
    }

    // =============================================================
    // 19. HOOK GAME
    // =============================================================
    let hookAttempts = 0;

    function hookGame() {
        try {
            cv = rt.canvas;
            if (!cv) { hookAttempts++; if (hookAttempts < 5) setTimeout(hookGame, 1000); return; }

            hookWebSocket();

            try {
                if (window.cr?.plugins_?.Mouse) {
                    const proto = window.cr.plugins_.Mouse.prototype.Instance.prototype;
                    const orig = proto.onMouseMove;
                    proto.onMouseMove = function() {
                        mi = this;
                        orig.apply(this, arguments);
                    };
                }
            } catch(e) {}

            try {
                if (window.cr?.plugins_?.NSG_PowerWS) {
                    const proto = window.cr.plugins_.NSG_PowerWS.prototype.Instance.prototype;
                    const orig = proto.tick;
                    proto.tick = function() {
                        if (this.wsWorker && !this.wsWorker._hooked) {
                            this.wsWorker._hooked = true;
                            const origPost = this.wsWorker.postMessage;
                            this.wsWorker.postMessage = function(msg) {
                                try {
                                    if (msg?.action === 'send' && msg?.data?.a === 'ps' && ghostAngle !== null) {
                                        msg.data.d.a = Math.round(ghostAngle);
                                    }
                                } catch(e) {}
                                return origPost.apply(this, arguments);
                            };
                        }
                        orig.apply(this, arguments);
                    };
                }
            } catch(e) {}

            let found = false;
            try {
                for (const type of rt.types_by_index) {
                    if (!type || !type.instances || type.instances.length === 0) continue;
                    const inst = type.instances[0];
                    if (inst && typeof inst.width === 'number' && typeof inst.height === 'number' && typeof inst.angle === 'number') {
                        if (type.instvar_sids && type.instvar_sids.length >= 12) {
                            if (inst.width > 20 && inst.width < 100 && inst.collision_poly) {
                                pt = type;
                                found = true;
                                break;
                            }
                        }
                    }
                }
                if (!found) {
                    let maxCount = 0;
                    for (const type of rt.types_by_index) {
                        if (!type || !type.instances) continue;
                        const count = type.instances.length;
                        if (count > maxCount && type.instances.length > 0) {
                            const inst = type.instances[0];
                            if (inst && inst.collision_poly && inst.width > 20 && inst.width < 100) {
                                maxCount = count;
                                pt = type;
                                found = true;
                            }
                        }
                    }
                }
            } catch(e) {}

            if (!pt) {
                hookAttempts++;
                if (hookAttempts < 5) { setTimeout(hookGame, 1000); return; }
                else { toast('⚠️ Không tìm thấy player type', 'warning'); }
            }

            try {
                for (const layer of rt.running_layout.layers) {
                    if (layer) layer.scale = C.zoom;
                }
            } catch(e) {}

            toast('✅ Đã kết nối game!', 'success', 2000);
            isRunning = true;
            isGameActive = true;
            mainLoop();

        } catch(e) {
            console.warn('[EraBOT] Hook error:', e);
            hookAttempts++;
            if (hookAttempts < 5) setTimeout(hookGame, 1000);
        }
    }

    // =============================================================
    // 20. FIND RUNTIME
    // =============================================================
    function findRuntime() {
        let attempts = 0;
        const iv = setInterval(() => {
            attempts++;
            try {
                if (window.cr_getC2Runtime) {
                    const r = window.cr_getC2Runtime();
                    if (r) {
                        rt = r;
                        clearInterval(iv);
                        hookGame();
                        return;
                    }
                }
            } catch(e) {}
            if (attempts >= 30) {
                clearInterval(iv);
                toast('⚠️ Không tìm thấy game, refresh F5', 'warning', 3000);
            }
        }, 500);
    }

    // =============================================================
    // 21. ACTIVATE
    // =============================================================
    function activate() {
        loadConfig();
        injectCSS();

        toggleBtn = document.createElement('div');
        toggleBtn.id = 'erabot-toggle';
        toggleBtn.textContent = '🕺';
        toggleBtn.style.display = 'flex';
        document.body.appendChild(toggleBtn);

        // MutationObserver để giữ toggle button
        const observer = new MutationObserver(() => {
            if (!document.getElementById('erabot-toggle')) {
                const btn = document.createElement('div');
                btn.id = 'erabot-toggle';
                btn.textContent = '🕺';
                btn.style.display = 'flex';
                btn.style.cssText = toggleBtn.style.cssText;
                document.body.appendChild(btn);
                btn.addEventListener('click', toggleMenu);
                toggleBtn = btn;
            }
        });
        observer.observe(document.body, { childList: true, subtree: true });

        const wm = document.createElement('div');
        wm.id = 'erabot-watermark';
        wm.textContent = 'EraBOT v16.0.0';
        document.body.appendChild(wm);

        buildMenu();
        createOverlay();

        if (C.autoRejoin) startRejoin();
        if (C.autoDuel) startDuel();

        findRuntime();

        document.addEventListener('mousemove', (e) => {
            if (e.isTrusted) { mx = e.clientX; my = e.clientY; }
        });
        document.addEventListener('contextmenu', (e) => {
            if (e.target === cv) e.preventDefault();
        });

        // Auto-fix UI
        setInterval(() => {
            try {
                if (!toggleBtn || !document.body.contains(toggleBtn)) {
                    const btn = document.createElement('div');
                    btn.id = 'erabot-toggle';
                    btn.textContent = '🕺';
                    btn.style.display = 'flex';
                    document.body.appendChild(btn);
                    toggleBtn = document.getElementById('erabot-toggle');
                    if (toggleBtn) toggleBtn.addEventListener('click', toggleMenu);
                }
                if (!menu || !document.body.contains(menu)) {
                    buildMenu();
                    menu = document.getElementById('erabot-menu');
                }
                if (!overlay || !document.body.contains(overlay)) {
                    createOverlay();
                }
            } catch(e) {}
        }, 5000);

        toast('🕺 EraBOT v16.0.0 ULTIMATE AI đã sẵn sàng!', 'success', 3000);
    }

    // =============================================================
    // 22. START
    // =============================================================
    activate();
    console.log('⚔️EraBOT🕺 v16.0.0 - ULTIMATE AI EDITION (ALL FIXED)');
})();