TakeMineClient[1.1]

ZoomHack, AimBow, AutoHeal, ESP, Tracers, Scout, WalkUnlock, KillMessage, GoldBot, AutoReconnect, AntiAFK, Macros

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

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

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

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

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

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

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

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

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

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

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

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

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

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

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name         TakeMineClient[1.1]
// @namespace    Violentmonkey Scripts
// @icon         https://takemine.io/favicon-32x32.png
// @version      1.1
// @match        *://takemine.io/*
// @grant        unsafeWindow
// @grant        GM_addStyle
// @grant        GM_info
// @author       Drik
// @description  ZoomHack, AimBow, AutoHeal, ESP, Tracers, Scout, WalkUnlock, KillMessage, GoldBot, AutoReconnect, AntiAFK, Macros
// @run-at       document-start
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    const _$gi = (typeof GM_info !== 'undefined') ? GM_info : null;
    const _$raw = _$gi && _$gi.script ? (_$gi.script.updateURL || '') : null;
    if (_$raw === null) {
        alert('Install the original TakeMineClient from GreasyFork');
        throw new Error('TMC');
    }

    const CFG_KEY = 'TakeMineClient';
    let menuKey = 'F4';
    let currentTheme = 'dark';
    let currentLang = 'en';
    let trapBind = '';
    let ballistaBind = '';

    const I18N = {
        en: {
            autoheal: ['Auto Heal', 'Auto heal when HP < 60% and food available'],
            aimbow: ['Aim Bow', 'Aim with a bow toward the nearest enemy'],
            autoreconnect: ['Auto Reconnect', 'Instantly respawn on death'],
            killmessage: ['Kill Message', 'Send a chat message when you kill a player'],
            zoom: ['Zoom', 'Zoom hack (CTRL + mouse wheel)'],
            esp: ['ESP', 'Draws precise hitboxes on players'],
            tracers: ['Tracers', 'Draws lines from you to each enemy'],
            hud: ['HUD', 'Shows FPS, ping and player count'],
            skinunlock: ['Skin Unlock', 'Unlocks Orc, Elf, Undead without sharing'],
            walkunlock: ['Walk Unlock', 'Move even with chat or menus open'],
            scout: ['Scout', 'Second account marks players on minimap'],
            Gold_Bot: ['Gold Bot', 'Bots come toward you, kill them for gold'],
            antiafk: ['Anti-AFK', 'Prevents server kick for inactivity'],
            macros: ['Macros', 'PvP macros list'],
            trapMacro: ['Place Trap', 'Place trap on bind key'],
            ballistaMacro: ['Place Ballista', 'Place ballista on bind key'],
            settings: ['Settings', ''],
            theme: ['Theme', ''],
            language: ['Language', ''],
            resetCfg: ['Reset Config', ''],
            escClear: ['Esc - clear bind', ''],
        },
        ru: {
            autoheal: ['Авто-хил', 'Хил при HP < 60% если есть еда'],
            aimbow: ['Прицел лука', 'Прицеливается к ближайшему врагу'],
            autoreconnect: ['Авто-реконнект', 'Мгновенный респавн при смерти'],
            killmessage: ['Сообщение о киле', 'Сообщение в чат при убийстве игрока'],
            zoom: ['Зум', 'Зум хак (CTRL + колесо мыши)'],
            esp: ['ESP', 'Рисует хитбоксы игроков'],
            tracers: ['Трейсеры', 'Линии от тебя до врагов'],
            hud: ['HUD', 'Показывает FPS, пинг, кол-во игроков'],
            skinunlock: ['Скины', 'Открывает орка, эльфа, нежить без шера'],
            walkunlock: ['Движение', 'Бег при открытом чате или меню'],
            scout: ['Скаут', 'Второй акк помечает игроков на минимапе'],
            Gold_Bot: ['Голд бот', 'Боты идут к тебе, убивай за золото'],
            antiafk: ['Анти-АФК', 'Защита от кика за неактивность'],
            macros: ['Макросы', 'Список макросов для пвп'],
            trapMacro: ['Капкан', 'Ставит капкан по кнопке'],
            ballistaMacro: ['Баллиста', 'Ставит баллисту по кнопке'],
            settings: ['Настройки', ''],
            theme: ['Тема', ''],
            language: ['Язык', ''],
            resetCfg: ['Сбросить конфиг', ''],
            escClear: ['Esc - очистить бинд', ''],
        }
    };

    function t(key, idx) {
        return (I18N[currentLang] || I18N.en)[key]?.[idx || 0] || key;
    }

    const _defaults = {
        zoom: {
            enabled: false
        },
        autoheal: {
            enabled: false
        },
        skinunlock: {
            enabled: false
        },
        esp: {
            enabled: false
        },
        tracers: {
            enabled: false
        },
        scout: {
            enabled: false
        },
        walkunlock: {
            enabled: false
        },
        killmessage: {
            enabled: false
        },
        aimbow: {
            enabled: false
        },
        Gold_Bot: {
            enabled: false
        },
        hud: {
            enabled: false
        },
        autoreconnect: {
            enabled: false
        },
        antiafk: {
            enabled: false
        },
    };

    function loadState() {
        try {
            const sv = JSON.parse(localStorage.getItem(CFG_KEY) || '{}');
            const st = {};
            for (const k in _defaults) st[k] = {
                enabled: sv[k] !== undefined ? !!sv[k] : _defaults[k].enabled
            };
            if (sv._mk) menuKey = sv._mk;
            if (sv._th) currentTheme = sv._th;
            if (sv._ln) currentLang = sv._ln;
            if (sv._tb) trapBind = sv._tb;
            if (sv._bb) ballistaBind = sv._bb;
            return st;
        } catch {
            return JSON.parse(JSON.stringify(_defaults));
        }
    }

    function saveState() {
        const o = {};
        for (const k in state) o[k] = state[k].enabled;
        o._mk = menuKey;
        o._th = currentTheme;
        o._ln = currentLang;
        o._tb = trapBind;
        o._bb = ballistaBind;
        localStorage.setItem(CFG_KEY, JSON.stringify(o));
    }

    const state = loadState();

    if (state.skinunlock.enabled)['elf', 'orc', 'undead'].forEach(h => {
        unsafeWindow.localStorage[h] = true;
    });

    let gameWS = null,
        ahEnabled = state.autoheal.enabled,
        ahHealing = false,
        ahProto = false,
        ahPrevTool = 1,
        ahFrames = 0;
    const ZOOM_BASE = 1.06;
    let zoomFactor = ZOOM_BASE;
    const wuKeys = {
        attack: false,
        up: false,
        right: false,
        down: false,
        left: false
    };
    let wuLast = null;
    let scoutGhost = null,
        scoutGhostId = null,
        scoutRespawnT = null;
    const scoutMarks = [],
        MAP_SIZE = 8160;
    const dgBots = [];
    let dgRunning = false;
    let kmKills = 0;
    const kmQueue = [];
    const BOW_ID = 4,
        ARROW_SPD = 0.55;
    let hudPing = 0,
        hudFps = 0,
        hudLast = performance.now(),
        hudF = 0;
    let macroMove = 0,
        trapBusy = false,
        ballistaBusy = false;
    const TRAP_TOOL = 9,
        BALLISTA_TOOL = 23;
    const AR_BTN = '#game_scene > div > div > div > div.modal-body > div > div:nth-child(6) > button';

    if (typeof _$raw === 'string' && _$raw.length === 0) {
        alert('Install the original TakeMineClient from GreasyFork');
        throw new Error('TMC');
    }

    const _nativeSend = unsafeWindow.WebSocket.prototype.send;
    unsafeWindow.WebSocket.prototype.send = function(data) {
        if (typeof data === 'string' && data.startsWith('42[1,') && !gameWS) gameWS = this;
        return _nativeSend.call(this, data);
    };

    function rawSend(s) {
        if (gameWS && gameWS.readyState === 1) _nativeSend.call(gameWS, s);
    }

    const _omDesc = Object.getOwnPropertyDescriptor(unsafeWindow.WebSocket.prototype, 'onmessage');
    Object.defineProperty(unsafeWindow.WebSocket.prototype, 'onmessage', {
        get() {
            return this._tmc_om || null;
        },
        set(fn) {
            this._tmc_om = fn;
            _omDesc.set.call(this, function(ev) {
                if (typeof ev.data === 'string' && ev.data.startsWith('42')) {
                    try {
                        const p = JSON.parse(ev.data.slice(2));
                        const id = p[0],
                            pl = p[1];
                        if (state.killmessage.enabled) {
                            const c = unsafeWindow.CLIENT;
                            if (id === 11) {
                                kmQueue.push(c?.playerNames?.[pl] || ('Player#' + pl));
                            }
                            if (id === 6 && Array.isArray(pl)) {
                                for (let i = 0; i < pl.length; i += 2) {
                                    if (pl[i] === 0 && pl[i + 1] > kmKills) {
                                        kmKills = pl[i + 1];
                                        const name = kmQueue.shift();
                                        if (name) c?.socket?.emit(10, name + ' killed by ' + c?.player?.name);
                                    }
                                }
                            }
                            if (id === 9) {
                                kmKills = 0;
                                kmQueue.length = 0;
                            }
                        }
                        if (id === 9 && state.autoreconnect.enabled) _arWaitForBtn();
                    } catch {}
                }
                if (fn) fn.call(this, ev);
            });
        },
        configurable: true
    });

    Object.defineProperty(unsafeWindow, 'Vue', {
        configurable: true,
        set(V) {
            Object.defineProperty(unsafeWindow, 'Vue', {
                value: V,
                writable: true,
                configurable: true
            });
            const _O = V;

            function PV(opts) {
                const i = new _O(opts);
                if (opts?.el === '#ui') unsafeWindow.UI = i;
                return i;
            }
            PV.prototype = _O.prototype;
            ['config', 'use', 'component', 'set', 'delete', 'nextTick', 'extend', 'mixin', 'compile', 'observable', 'version'].forEach(k => {
                if (_O[k] !== undefined) PV[k] = typeof _O[k] === 'function' ? (...a) => _O[k].apply(_O, a) : _O[k];
            });
            Object.defineProperty(unsafeWindow, 'Vue', {
                value: PV,
                writable: true,
                configurable: true
            });
        }
    });

    const _origBind = unsafeWindow.Function.prototype.bind;
    unsafeWindow.Function.prototype.bind = function(thisArg, ...args) {
        if (!unsafeWindow.CLIENT && thisArg !== null && thisArg !== undefined && typeof thisArg === 'object' &&
            thisArg.camera !== undefined && thisArg.mainCanvas !== undefined && thisArg.mainCtx !== undefined && thisArg.screenWidth !== undefined) {
            unsafeWindow.CLIENT = thisArg;
            unsafeWindow.Function.prototype.bind = _origBind;
            _initFeatures(thisArg);
        }
        return _origBind.apply(this, [thisArg, ...args]);
    };

    function _initFeatures(c) {
        _initAutoHeal(c);
        _initRender(c);
        _initWalkUnlock(c);
        if (c.socket?.io) c.socket.io.on('pong', ms => {
            hudPing = ms;
        });
        _startAntiAFK();
    }

    function _initAutoHeal(c) {
        let _p = null;
        Object.defineProperty(c, 'player', {
            get() {
                return _p;
            },
            set(val) {
                _p = val;
                if (val && !ahProto) {
                    ahProto = true;
                    _hookSetHP(Object.getPrototypeOf(val));
                }
            },
            configurable: true
        });
    }

    function _hookSetHP(proto) {
        const _orig = proto.setHP;
        proto.setHP = function(hp) {
            const res = _orig.call(this, hp);
            const c = unsafeWindow.CLIENT;
            if (!c || !c.player || this !== c.player) return res;
            if (ahEnabled && !ahHealing && hp > 0 && hp < this.maxHP * 0.6 && c.player.food >= 10) {
                ahPrevTool = c.player.tool ? c.player.tool.id : 1;
                ahHealing = true;
                ahFrames = 0;
                _doHeal();
            }
            return res;
        };
    }

    function _doHeal() {
        rawSend('42[3,2]');
        requestAnimationFrame(() => {
            rawSend('42[2,16]');
            requestAnimationFrame(() => {
                rawSend('42[2,0]');
                ahFrames = 0;
            });
        });
    }

    function _initRender(c) {
        const _orig = c.render.bind(c);
        c.render = function() {
            _orig();
            const ctx = c.mainCtx,
                cam = c.camera,
                me = c.player,
                pl = c.players;
            if (!me || !pl) return;
            if (state.esp.enabled) {
                ctx.save();
                ctx.globalAlpha = 1;
                ctx.shadowColor = 'transparent';
                ctx.shadowBlur = ctx.shadowOffsetX = ctx.shadowOffsetY = 0;
                for (const id in pl) {
                    if (!pl.hasOwnProperty(id)) continue;
                    const p = pl[id];
                    if (!p || !p.isVisible || p.id === me.id) continue;
                    const cx = cam.calculateCameraX(p.x),
                        cy = cam.calculateCameraY(p.y);
                    const r = p.radius,
                        rs = p.skin ? p.skin.radiusShift : 0;
                    ctx.beginPath();
                    ctx.arc(cx, cy, r + rs, 0, 6.2832);
                    ctx.strokeStyle = 'rgba(255,200,0,1)';
                    ctx.lineWidth = 1;
                    ctx.stroke();
                    ctx.beginPath();
                    ctx.arc(cx, cy, r, 0, 6.2832);
                    ctx.strokeStyle = 'rgba(255,50,50,0.9)';
                    ctx.lineWidth = 1.5;
                    ctx.stroke();
                }
                ctx.beginPath();
                ctx.restore();
            }
            if (state.tracers.enabled) {
                const mx = cam.calculateCameraX(me.x),
                    my = cam.calculateCameraY(me.y);
                const sorted = [];
                for (const id in pl) {
                    if (!pl.hasOwnProperty(id)) continue;
                    const p = pl[id];
                    if (!p || p.id === me.id) continue;
                    const dx = p.x - me.x,
                        dy = p.y - me.y;
                    sorted.push({
                        p,
                        d: dx * dx + dy * dy
                    });
                }
                sorted.sort((a, b) => a.d - b.d);
                ctx.save();
                sorted.forEach(({
                    p,
                    d
                }, i) => {
                    const px = cam.calculateCameraX(p.x),
                        py = cam.calculateCameraY(p.y);
                    const ratio = Math.min(d / (3000 * 3000), 1),
                        rv = Math.round(255 * ratio),
                        gv = Math.round(255 * (1 - ratio));
                    ctx.beginPath();
                    ctx.moveTo(mx, my);
                    ctx.lineTo(px, py);
                    ctx.strokeStyle = `rgba(${rv},${gv},0,0.75)`;
                    ctx.lineWidth = i === 0 ? 2.5 / cam.scale : 1.5 / cam.scale;
                    ctx.stroke();
                    ctx.fillStyle = `rgba(${rv},${gv},0,0.9)`;
                    ctx.beginPath();
                    ctx.arc(px, py, 4 / cam.scale, 0, Math.PI * 2);
                    ctx.fill();
                });
                ctx.restore();
            }
        };
    }

    function _initWalkUnlock(c) {
        const s = c.socket,
            _oe = s.emit.bind(s);
        s.emit = function(op, val) {
            if (op === 2 && state.walkunlock.enabled) {
                const oc = _wuCode();
                if (val === 0 && oc !== 0) {
                    wuLast = oc;
                    return _oe(2, oc);
                }
                if (val === oc) {
                    wuLast = val;
                    return _oe.apply(this, arguments);
                }
            }
            return _oe.apply(this, arguments);
        };
    }

    function _wuCode() {
        return parseInt('' + +wuKeys.attack + +wuKeys.up + +wuKeys.right + +wuKeys.down + +wuKeys.left, 2);
    }

    function _wuSend() {
        const c = unsafeWindow.CLIENT;
        if (!c || !c.socket || !state.walkunlock.enabled) return;
        const code = _wuCode();
        if (code === wuLast) return;
        wuLast = code;
        c.socket.emit(2, code);
    }

    function _startAntiAFK() {
        setInterval(() => {
            if (!state.antiafk.enabled) return;
            const C = unsafeWindow.CLIENT;
            if (!C?.socket || !C.player) return;
            C.socket.emit(1, C.angle || 0);
            C.socket.emit(2, 0);
        }, 15000);
    }

    unsafeWindow.addEventListener('keydown', e => {
        switch (e.keyCode) {
            case 38:
            case 87:
                wuKeys.up = true;
                macroMove |= 8;
                break;
            case 39:
            case 68:
                wuKeys.right = true;
                macroMove |= 4;
                break;
            case 40:
            case 83:
                wuKeys.down = true;
                macroMove |= 2;
                break;
            case 37:
            case 65:
                wuKeys.left = true;
                macroMove |= 1;
                break;
            case 32:
                wuKeys.attack = true;
                break;
            case 69:
                wuKeys.attack = !wuKeys.attack;
                break;
        }
        _wuSend();
        if (trapBind && e.key === trapBind && !trapBusy) _doTrap();
        if (ballistaBind && e.key === ballistaBind && !ballistaBusy) _doBallista();
    }, true);

    unsafeWindow.addEventListener('keyup', e => {
        switch (e.keyCode) {
            case 38:
            case 87:
                wuKeys.up = false;
                macroMove &= ~8;
                break;
            case 39:
            case 68:
                wuKeys.right = false;
                macroMove &= ~4;
                break;
            case 40:
            case 83:
                wuKeys.down = false;
                macroMove &= ~2;
                break;
            case 37:
            case 65:
                wuKeys.left = false;
                macroMove &= ~1;
                break;
            case 32:
                wuKeys.attack = false;
                break;
        }
        _wuSend();
    }, true);
    unsafeWindow.addEventListener('blur', () => {
        wuKeys.attack = wuKeys.up = wuKeys.right = wuKeys.down = wuKeys.left = false;
        wuLast = null;
    });

    function _doTrap() {
        const C = unsafeWindow.CLIENT;
        if (!C?.socket?.emit || !C.player) return;
        const prev = C.player.tool.id;
        if (prev === TRAP_TOOL) return;
        trapBusy = true;
        const s = C.socket;
        s.emit(3, TRAP_TOOL);
        requestAnimationFrame(() => {
            s.emit(2, macroMove | 0x10);
            requestAnimationFrame(() => {
                s.emit(2, macroMove);
                s.emit(3, prev);
                trapBusy = false;
            });
        });
    }

    function _doBallista() {
        const C = unsafeWindow.CLIENT;
        if (!C?.socket?.emit || !C.player) return;
        const prev = C.player.tool.id;
        if (prev === BALLISTA_TOOL) return;
        ballistaBusy = true;
        const s = C.socket;
        s.emit(3, BALLISTA_TOOL);
        requestAnimationFrame(() => {
            s.emit(2, macroMove | 0x10);
            requestAnimationFrame(() => {
                s.emit(2, macroMove);
                s.emit(3, prev);
                ballistaBusy = false;
            });
        });
    }

    function _arIsVisible(el) {
        if (!el) return false;
        const r = el.getBoundingClientRect();
        if (!r.width || !r.height || r.top < 0 || r.left < 0) return false;
        const st = unsafeWindow.getComputedStyle(el);
        return st.display !== 'none' && st.visibility !== 'hidden' && st.opacity !== '0';
    }

    function _arWaitForBtn() {
        if (!state.autoreconnect.enabled) return;
        const btn = document.querySelector(AR_BTN);
        if (!_arIsVisible(btn)) {
            requestAnimationFrame(_arWaitForBtn);
            return;
        }
        btn.dispatchEvent(new MouseEvent('click', {
            bubbles: true,
            cancelable: true
        }));
        const UI = unsafeWindow.UI;
        if (UI?.$emit) UI.$emit('login');
        requestAnimationFrame(() => {
            if (_arIsVisible(document.querySelector(AR_BTN))) _arWaitForBtn();
        });
    }

    function _applyZoom() {
        const c = unsafeWindow.CLIENT;
        if (!c) return;
        const w = c.screenWidth,
            h = c.screenHeight,
            base = Math.round(100 * Math.max(w / 1440, h / 900)) / 100;
        const ns = parseFloat((base * zoomFactor).toFixed(4)),
            pr = unsafeWindow.PIXEL_RATIO;
        c.camera.scale = ns;
        c.camera.width = parseInt(w / ns);
        c.camera.height = parseInt(h / ns);
        c.camera.widthHalf = c.camera.width / 2;
        c.camera.heightHalf = c.camera.height / 2;
        c.camera.isChanged = true;
        c.mainCtx.setTransform(pr, 0, 0, pr, 0, 0);
        c.mainCtx.scale(ns, ns);
        c.backgroundCtx.setTransform(pr, 0, 0, pr, 0, 0);
        c.backgroundCtx.scale(ns, ns);
    }
    unsafeWindow.addEventListener('wheel', e => {
        if (!state.zoom.enabled || !e.ctrlKey) return;
        e.preventDefault();
        e.stopPropagation();
        e.stopImmediatePropagation();
        zoomFactor = parseFloat((e.deltaY < 0 ? Math.min(3, zoomFactor + 0.1) : Math.max(0.1, zoomFactor - 0.1)).toFixed(2));
        _applyZoom();
    }, {
        passive: false,
        capture: true
    });

    const _origRAF = unsafeWindow.requestAnimationFrame;
    unsafeWindow.requestAnimationFrame = function(cb) {
        return _origRAF(function(ts) {
            const c = unsafeWindow.CLIENT;
            if (ahHealing && c?.player) {
                if (c.player.hp >= c.player.maxHP || c.player.food < 10) {
                    ahHealing = false;
                    ahFrames = 0;
                    rawSend('42[3,' + ahPrevTool + ']');
                } else {
                    ahFrames++;
                    if (ahFrames >= 3) _doHeal();
                }
            }
            if (state.aimbow.enabled && c?.player?.tool?.id === BOW_ID && c.socket) {
                const tg = _findNearest(c);
                if (tg) {
                    const {
                        x,
                        y
                    } = _predictPos(tg, c.player);
                    const ang = _bowAngle(c.player, x, y);
                    if (ang !== c.prevAngle) {
                        c.socket.emit(1, ang);
                        c.angle = ang;
                        c.prevAngle = ang;
                    }
                }
            }
            cb(ts);
        });
    };

    function _findNearest(c) {
        const me = c.player;
        let cl = null,
            md = Infinity;
        for (const id in c.players) {
            if (!c.players.hasOwnProperty(id)) continue;
            const p = c.players[id];
            if (!p || p.id === me.id) continue;
            const d = (p.x - me.x) ** 2 + (p.y - me.y) ** 2;
            if (d < md) {
                md = d;
                cl = p;
            }
        }
        return cl;
    }

    function _predictPos(tg, me) {
        const buf = tg.positionBuffer;
        let px = tg.x,
            py = tg.y;
        if (buf && buf.length >= 2) {
            const b0 = buf[buf.length - 2],
                b1 = buf[buf.length - 1],
                dt = b1.t - b0.t;
            if (dt > 0) {
                const vx = (b1.x - b0.x) / dt,
                    vy = (b1.y - b0.y) / dt,
                    d = Math.sqrt((tg.x - me.x) ** 2 + (tg.y - me.y) ** 2);
                px = tg.x + vx * (d / ARROW_SPD);
                py = tg.y + vy * (d / ARROW_SPD);
            }
        }
        return {
            x: px,
            y: py
        };
    }

    function _bowAngle(me, tx, ty) {
        const cam = unsafeWindow.CLIENT.camera;
        return Math.round(100 * (Math.atan2(cam.calculateCameraY(ty) - cam.calculateCameraY(me.y), cam.calculateCameraX(tx) - cam.calculateCameraX(me.x)) + Math.PI / 2));
    }

    function scoutStart() {
        if (scoutGhost) return;
        if (!unsafeWindow.CLIENT || !unsafeWindow.UI) return;
        const sv = document.getElementById('serverList')?.value;
        if (!sv) return;
        scoutGhost = unsafeWindow.io.connect(sv, {
            forceNew: true
        });
        scoutGhost.on('connect', () => _scoutSpawn());
        scoutGhost.on(3, d => {
            scoutGhostId = d[0];
            clearTimeout(scoutRespawnT);
            scoutRespawnT = setTimeout(_scoutSpawn, 700);
        });
        scoutGhost.on(4, d => {
            const UI = unsafeWindow.UI,
                C = unsafeWindow.CLIENT;
            if (!UI?.minimap) return;
            const mn = C?.player?.name;
            for (let i = 0; i < d.length; i += 11) {
                const r = d.slice(i, i + 11);
                if (r[0] === scoutGhostId) continue;
                if (mn && C?.playerNames?.[r[0]] === mn) continue;
                const key = 'scout_' + r[0];
                UI.minimap.setMarker(key, MAP_SIZE, MAP_SIZE, r[2], r[3], '#ffff00');
                if (!scoutMarks.includes(key)) scoutMarks.push(key);
            }
        });
        scoutGhost.on(9, () => {
            clearTimeout(scoutRespawnT);
            _scoutSpawn();
        });
    }

    function scoutStop() {
        clearTimeout(scoutRespawnT);
        if (scoutGhost) {
            scoutGhost.disconnect();
            scoutGhost = null;
            scoutGhostId = null;
        }
    }

    function _scoutSpawn() {
        if (scoutGhost) scoutGhost.emit(0, 'Drik_Scout', 1);
    }

    function scoutClear() {
        const UI = unsafeWindow.UI;
        if (!UI) return;
        scoutMarks.forEach(k => UI.minimap.removeMarker(k));
        scoutMarks.length = 0;
    }

    function dgStart() {
        dgRunning = true;
        for (let i = 0; i < 15; i++) _dgCreateBot(i);
    }

    function dgStop() {
        dgRunning = false;
        const UI = unsafeWindow.UI;
        dgBots.forEach(b => {
            if (b.moveInterval) clearInterval(b.moveInterval);
            if (b.socket) b.socket.disconnect();
            if (UI?.minimap && b.id) UI.minimap.removeMarker('gp_' + b.id);
        });
        dgBots.length = 0;
    }

    function _dgCreateBot(idx) {
        const sv = document.getElementById('serverList')?.value;
        if (!sv) return;
        const bot = {
            socket: null,
            id: null,
            x: 0,
            y: 0,
            moveInterval: null
        };
        dgBots[idx] = bot;
        bot.socket = unsafeWindow.io.connect(sv, {
            forceNew: true
        });
        bot.socket.on('connect', () => bot.socket.emit(0, 'Drik_Gold', 1));
        bot.socket.on(3, d => {
            bot.id = d[0];
            _dgMove(bot);
        });
        bot.socket.on(4, d => {
            for (let i = 0; i < d.length; i += 11) {
                const r = d.slice(i, i + 11);
                if (r[0] === bot.id) {
                    bot.x = r[2];
                    bot.y = r[3];
                    _dgMark(bot);
                }
            }
        });
        bot.socket.on(9, () => {
            clearInterval(bot.moveInterval);
            if (dgRunning) setTimeout(() => bot.socket.emit(0, 'Drik_Gold', 1), 500);
        });
    }

    function _dgMove(bot) {
        clearInterval(bot.moveInterval);
        bot.moveInterval = setInterval(() => {
            const c = unsafeWindow.CLIENT;
            if (!c?.player) return;
            const dx = c.player.x - bot.x,
                dy = c.player.y - bot.y,
                d = Math.sqrt(dx * dx + dy * dy);
            if (d < 80) {
                bot.socket.emit(2, 0);
                return;
            }
            const kc = parseInt('0' + (dy < -40 ? '1' : '0') + (dx > 40 ? '1' : '0') + (dy > 40 ? '1' : '0') + (dx < -40 ? '1' : '0'), 2);
            bot.socket.emit(1, Math.round(100 * (Math.atan2(dy, dx) + Math.PI / 2)));
            bot.socket.emit(2, kc);
        }, 50);
    }

    function _dgMark(bot) {
        const UI = unsafeWindow.UI;
        if (UI?.minimap) UI.minimap.setMarker('gp_' + bot.id, MAP_SIZE, MAP_SIZE, bot.x, bot.y, '#ffd700');
    }

    (function _hudLoop(now) {
        hudF++;
        const dt = now - hudLast;
        if (dt >= 1000) {
            hudFps = Math.round(hudF * 1000 / dt);
            hudF = 0;
            hudLast = now;
        }
        if (state.hud.enabled) {
            const el = document.getElementById('pfhud');
            if (el) {
                const c = unsafeWindow.CLIENT;
                const pl = c?.playerNames ? Object.keys(c.playerNames).length : '-';
                el.innerHTML = 'FPS: ' + (hudFps || '-') + '<br>PING: ' + (hudPing ? hudPing + 'ms' : '-') + '<br>PLR: ' + pl;
            }
        }
        _origRAF(_hudLoop);
    })(performance.now());

    (function() {
        let _u;
        try {
            _u = decodeURIComponent(_$raw).toLowerCase();
        } catch {
            _u = (_$raw || '').toLowerCase();
        }
        const _valid = _u.length > 0 && _u.startsWith('https://update.greasyfork.org/scripts/') && _u.endsWith('.meta.js') && /takemineclient(\[\d+(\.\d+)?\])?/.test(_u);
        if (!_valid) {
            alert('Invalid source. Download TakeMineClient from official page');
            throw new Error('TMC');
        }
    })();

    function onToggle(key, on) {
        switch (key) {
            case 'skinunlock':
                if (on)['elf', 'orc', 'undead'].forEach(h => {
                    unsafeWindow.localStorage[h] = true;
                });
                break;
            case 'autoheal':
                ahEnabled = on;
                if (!on && ahHealing) {
                    ahHealing = false;
                    ahFrames = 0;
                    rawSend('42[3,' + ahPrevTool + ']');
                }
                break;
            case 'Gold_Bot':
                on ? dgStart() : dgStop();
                break;
            case 'hud': {
                const el = document.getElementById('pfhud');
                if (el) el.style.display = on ? '' : 'none';
                break;
            }
        }
        saveState();
    }

    function applyTheme(theme) {
        currentTheme = theme;
        const menu = document.getElementById('tm-menu');
        if (menu) {
            menu.classList.remove('theme-dark', 'theme-white');
            menu.classList.add('theme-' + theme);
        }
    }

    GM_addStyle(`@import url("https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&family=Rajdhani:wght@400;500;600;700&display=swap");
#tm-menu* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}
#tm-menu {
  position: fixed;
  top: 14px;
  right: 14px;
  z-index: 999999;
  width: 320px;
  background: #161b27;
  border: 1px solid #252d3d;
  border-radius: 12px;
  box-shadow: 0 8px 40px rgba(0, 0, 0, 0.6), 0 0 0 1px rgba(255, 255, 255, 0.04);
  font-family: "Rajdhani", sans-serif;
  overflow: hidden;
  user-select: none;
}
#tm-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 18px;
  background: #1a2030;
  border-bottom: 1px solid #252d3d;
  cursor: move;
}
#tm-title {
  font-size: 16px;
  font-weight: 700;
  letter-spacing: 3px;
  text-transform: uppercase;
  color: #e2e8f0;
  font-family: "Space Mono", monospace;
}
#tm-title span {
  color: #4ade80;
}
#tm-gear {
  background: none;
  border: none;
  cursor: pointer;
  color: #4a5568;
  padding: 0;
  line-height: 1;
  transition: color 0.2s;
  font-size: 16px;
}
#tm-gear:hover {
  color: #4ade80;
}
#tm-tabs {
  display: flex;
  border-bottom: 1px solid #252d3d;
  background: #161b27;
}
.tm-tab {
  flex: 1;
  padding: 11px 0;
  border: none;
  background: transparent;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  font-family: "Rajdhani", sans-serif;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: #4a5568;
  transition: color 0.2s;
  border-bottom: 2px solid transparent;
  position: relative;
  top: 1px;
}
.tm-tab svg {
  width: 13px;
  height: 13px;
  flex-shrink: 0;
}
.tm-tab.active {
  color: #4ade80;
  border-bottom: 2px solid #4ade80;
}
.tm-tab:hover:not(.active) {
  color: #94a3b8;
}
#tm-body {
  padding: 6px 0;
  max-height: 400px;
  overflow-y: auto;
}
#tm-body::-webkit-scrollbar {
  width: 3px;
}
#tm-body::-webkit-scrollbar-track {
  background: transparent;
}
#tm-body::-webkit-scrollbar-thumb {
  background: #252d3d;
  border-radius: 3px;
}
.tm-panel {
  display: none;
}
.tm-panel.active {
  display: block;
}
.tm-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  padding: 11px 18px;
  gap: 14px;
  transition: background 0.15s;
}
.tm-row:hover {
  background: rgba(255, 255, 255, 0.025);
}
.tm-row-info {
  flex: 1;
  min-width: 0;
}
.tm-row-name {
  font-size: 14px;
  font-weight: 700;
  color: #e2e8f0;
  letter-spacing: 0.4px;
  line-height: 1.2;
}
.tm-row-desc {
  font-size: 11px;
  color: #4a5568;
  margin-top: 3px;
  line-height: 1.45;
  font-weight: 400;
  font-family: "Space Mono", monospace;
}
.tm-toggle {
  flex-shrink: 0;
  width: 40px;
  height: 22px;
  background: #252d3d;
  border-radius: 11px;
  position: relative;
  cursor: pointer;
  transition: background 0.25s;
  margin-top: 2px;
}
.tm-toggle.on {
  background: #16a34a;
}
.tm-toggle::after {
  content: "";
  position: absolute;
  top: 3px;
  left: 3px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: #fff;
  transition: transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1);
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
}
.tm-toggle.on::after {
  transform: translateX(18px);
}
.tm-divider {
  height: 1px;
  background: #1e2636;
  margin: 0 18px;
}
#tm-footer {
  padding: 9px 18px;
  border-top: 1px solid #252d3d;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
}
#tm-footer-label {
  font-size: 9px;
  letter-spacing: 1.5px;
  color: #2d3748;
  text-transform: uppercase;
  font-family: "Space Mono", monospace;
}
#tm-menu-key-btn {
  background: #1a2030;
  border: 1px solid #2d3748;
  border-radius: 4px;
  padding: 2px 8px;
  font-size: 10px;
  color: #94a3b8;
  cursor: pointer;
  font-family: "Space Mono", monospace;
  transition: border-color 0.2s, color 0.2s;
}
#tm-menu-key-btn:hover {
  border-color: #4ade80;
  color: #4ade80;
}
#tm-menu-key-btn.listening {
  border-color: #f59e0b;
  color: #f59e0b;
  animation: tm-pulse 0.8s ease infinite;
}
@keyframes tm-pulse {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.3;
  }
}
.tm-scout-btns {
  display: flex;
  gap: 8px;
  padding: 6px 18px 14px;
}
.tm-scout-btn {
  flex: 1;
  padding: 5px 0;
  border-radius: 5px;
  border: 1px solid #252d3d;
  background: #1a2030;
  color: #94a3b8;
  font-family: "Space Mono", monospace;
  font-size: 9px;
  letter-spacing: 1px;
  text-transform: uppercase;
  cursor: pointer;
  transition: border-color 0.2s, color 0.2s;
}
.tm-scout-btn:hover {
  border-color: #4ade80;
  color: #4ade80;
}
#tm-scout-stop:hover {
  border-color: #ef4444;
  color: #ef4444;
}
#tm-scout-clear:hover {
  border-color: #f59e0b;
  color: #f59e0b;
}
.tm-changelog {
  padding: 10px 18px 14px;
}
.tm-cl-entry {
  display: flex;
  gap: 12px;
  align-items: flex-start;
  padding: 8px 0;
  border-bottom: 1px solid #1e2636;
}
.tm-cl-entry:last-child {
  border-bottom: none;
}
.tm-cl-version {
  flex-shrink: 0;
  font-family: "Space Mono", monospace;
  font-size: 10px;
  color: #4ade80;
  background: rgba(74, 222, 128, 0.08);
  border: 1px solid rgba(74, 222, 128, 0.2);
  border-radius: 4px;
  padding: 2px 7px;
  margin-top: 1px;
}
.tm-cl-text {
  font-size: 12px;
  color: #64748b;
  font-family: "Space Mono", monospace;
  line-height: 1.5;
}
#pfhud {
  position: fixed;
  top: 12px;
  left: 12px;
  z-index: 99998;
  background: rgba(0, 0, 0, 0.5);
  color: #fff;
  font-family: monospace;
  font-size: 12px;
  padding: 4px 9px;
  border-radius: 5px;
  pointer-events: none;
  line-height: 1.6;
  border-left: 2px solid #4af;
}

.tm-macros-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  padding: 11px 18px;
  gap: 14px;
  cursor: pointer;
  transition: background 0.15s;
}
.tm-macros-header:hover {
  background: rgba(255, 255, 255, 0.025);
}
.tm-macros-arrow {
  color: #4a5568;
  font-size: 12px;
  margin-top: 3px;
  transition: transform 0.2s;
  flex-shrink: 0;
}
.tm-macros-arrow.open {
  transform: rotate(180deg);
}
.tm-macros-body {
  padding: 0 18px 10px;
}
.tm-macro-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 7px 0;
  gap: 10px;
}
.tm-macro-name {
  font-size: 13px;
  font-weight: 600;
  color: #e2e8f0;
}
.tm-macro-desc {
  font-size: 10px;
  color: #4a5568;
  margin-top: 2px;
  font-family: "Space Mono", monospace;
}
.tm-bind-btn {
  flex-shrink: 0;
  background: #1a2030;
  border: 1px solid #2d3748;
  border-radius: 4px;
  padding: 2px 10px;
  font-size: 10px;
  color: #94a3b8;
  cursor: pointer;
  font-family: "Space Mono", monospace;
  min-width: 36px;
  text-align: center;
  transition: border-color 0.2s, color 0.2s;
  user-select: none;
}
.tm-bind-btn:hover {
  border-color: #4ade80;
  color: #4ade80;
}
.tm-bind-btn.listening {
  border-color: #f59e0b;
  color: #f59e0b;
  animation: tm-pulse 0.8s ease infinite;
}
.tm-macro-hint {
  font-size: 9px;
  color: #2d3748;
  font-family: "Space Mono", monospace;
  text-align: center;
  padding: 4px 0 2px;
}

#tm-settings {
  display: none;
}
#tm-settings-header {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 14px 18px;
  background: #1a2030;
  border-bottom: 1px solid #252d3d;
}
#tm-settings-back {
  background: none;
  border: none;
  cursor: pointer;
  color: #4a5568;
  font-size: 16px;
  padding: 0;
  line-height: 1;
  transition: color 0.2s;
}
#tm-settings-back:hover {
  color: #4ade80;
}
#tm-settings-title {
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 2px;
  text-transform: uppercase;
  color: #94a3b8;
  font-family: "Space Mono", monospace;
}
.tm-setting-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 18px;
  border-bottom: 1px solid #1e2636;
}
.tm-setting-label {
  font-size: 13px;
  font-weight: 600;
  color: #e2e8f0;
}
.tm-setting-select {
  background: #1a2030;
  border: 1px solid #2d3748;
  border-radius: 4px;
  padding: 3px 8px;
  font-size: 11px;
  color: #94a3b8;
  cursor: pointer;
  font-family: "Space Mono", monospace;
  outline: none;
  transition: border-color 0.2s, color 0.2s;
}
.tm-setting-select:hover,
.tm-setting-select:focus {
  border-color: #4ade80;
  color: #4ade80;
}
.tm-setting-reset {
  background: #1a2030;
  border: 1px solid #2d3748;
  border-radius: 4px;
  padding: 4px 14px;
  font-size: 10px;
  color: #94a3b8;
  cursor: pointer;
  font-family: "Space Mono", monospace;
  transition: border-color 0.2s, color 0.2s;
  letter-spacing: 1px;
  text-transform: uppercase;
}
.tm-setting-reset:hover {
  border-color: #ef4444;
  color: #ef4444;
}

#tm-menu.theme-white {
  background: #f4f7ff;
  border-color: #d4dcf0;
  box-shadow: 0 8px 40px rgba(80, 100, 180, 0.12),
    0 0 0 1px rgba(80, 100, 180, 0.08);
}
#tm-menu.theme-white #tm-header {
  background: linear-gradient(135deg, #eef2ff, #e6ebff);
  border-bottom-color: #d4dcf0;
}
#tm-menu.theme-white #tm-title {
  color: #1e2a4a;
}
#tm-menu.theme-white #tm-gear {
  color: #94a3b8;
}
#tm-menu.theme-white #tm-gear:hover {
  color: #4ade80;
}
#tm-menu.theme-white #tm-tabs {
  background: #f4f7ff;
  border-bottom-color: #d4dcf0;
}
#tm-menu.theme-white .tm-tab {
  color: #94a3b8;
  background: #f4f7ff;
}
#tm-menu.theme-white .tm-tab.active {
  color: #16a34a;
  border-bottom-color: #16a34a;
}
#tm-menu.theme-white .tm-tab:hover:not(.active) {
  color: #4a5568;
}
#tm-menu.theme-white #tm-body {
  background: #f4f7ff;
}
#tm-menu.theme-white #tm-body::-webkit-scrollbar-thumb {
  background: #d4dcf0;
}
#tm-menu.theme-white .tm-row:hover {
  background: rgba(80, 100, 180, 0.05);
}
#tm-menu.theme-white .tm-row-name {
  color: #1e2a4a;
}
#tm-menu.theme-white .tm-row-desc {
  color: #6b7fa8;
}
#tm-menu.theme-white .tm-toggle {
  background: #c8d4ec;
}
#tm-menu.theme-white .tm-toggle.on {
  background: #16a34a;
}
#tm-menu.theme-white .tm-toggle::after {
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15);
}
#tm-menu.theme-white .tm-divider {
  background: #d4dcf0;
}
#tm-menu.theme-white #tm-footer {
  background: #eef2ff;
  border-top-color: #d4dcf0;
}
#tm-menu.theme-white #tm-footer-label {
  color: #94a3b8;
}
#tm-menu.theme-white #tm-menu-key-btn {
  background: #e6ebff;
  border-color: #b8c4e0;
  color: #4a5878;
}
#tm-menu.theme-white .tm-scout-btns {
  background: #f4f7ff;
}
#tm-menu.theme-white .tm-scout-btn {
  background: #e6ebff;
  border-color: #b8c4e0;
  color: #4a5878;
}
#tm-menu.theme-white .tm-macros-header:hover {
  background: rgba(80, 100, 180, 0.05);
}
#tm-menu.theme-white .tm-macros-arrow {
  color: #94a3b8;
}
#tm-menu.theme-white .tm-macro-name {
  color: #1e2a4a;
}
#tm-menu.theme-white .tm-macro-desc {
  color: #6b7fa8;
}
#tm-menu.theme-white .tm-bind-btn {
  background: #e6ebff;
  border-color: #b8c4e0;
  color: #4a5878;
}
#tm-menu.theme-white .tm-macro-hint {
  color: #94a3b8;
}
#tm-menu.theme-white #tm-settings-header {
  background: linear-gradient(135deg, #eef2ff, #e6ebff);
  border-bottom-color: #d4dcf0;
}
#tm-menu.theme-white #tm-settings-title {
  color: #6b7fa8;
}
#tm-menu.theme-white .tm-setting-row {
  border-bottom-color: #d4dcf0;
}
#tm-menu.theme-white .tm-setting-label {
  color: #1e2a4a;
}
#tm-menu.theme-white .tm-setting-select {
  background: #e6ebff;
  border-color: #b8c4e0;
  color: #4a5878;
}
#tm-menu.theme-white .tm-setting-reset {
  background: #e6ebff;
  border-color: #b8c4e0;
  color: #4a5878;
}
#tm-menu.theme-white .tm-cl-entry {
  border-bottom-color: #d4dcf0;
}
#tm-menu.theme-white .tm-cl-text {
  color: #6b7fa8;
}
#tm-menu.theme-white .tm-changelog {
  background: #f4f7ff;
}

`);

    document.addEventListener('DOMContentLoaded', () => {
        const pfhud = document.createElement('div');
        pfhud.id = 'pfhud';
        pfhud.innerHTML = 'FPS: -<br>PING: -<br>PLR: -';
        pfhud.style.display = state.hud.enabled ? '' : 'none';
        document.body.appendChild(pfhud);

        const menu = document.createElement('div');
        menu.id = 'tm-menu';
        menu.innerHTML = `
            <div id="tm-header">
                <div id="tm-title">TakeMine<span>Client</span></div>
                <button id="tm-gear" title="Settings">&#9881;</button>
            </div>
            <div id="tm-settings">
                <div id="tm-settings-header">
                    <button id="tm-settings-back">&#8592;</button>
                    <div id="tm-settings-title">SETTINGS</div>
                </div>
                <div class="tm-setting-row">
                    <span class="tm-setting-label">Theme</span>
                    <select id="tm-theme-select" class="tm-setting-select">
                        <option value="dark">Dark</option>
                        <option value="white">White</option>
                    </select>
                </div>
                <div class="tm-setting-row">
                    <span class="tm-setting-label">Language</span>
                    <select id="tm-lang-select" class="tm-setting-select">
                        <option value="en">EN</option>
                        <option value="ru">RU</option>
                    </select>
                </div>
                <div class="tm-setting-row" style="border-bottom:none;">
                    <span class="tm-setting-label"></span>
                    <button id="tm-cfg-reset" class="tm-setting-reset">Reset Config</button>
                </div>
            </div>
            <div id="tm-main">
                <div id="tm-tabs">
                    <button class="tm-tab active" data-tab="combat">
                        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><line x1="19" y1="5" x2="5" y2="19"/><polyline points="16 5 19 5 19 8"/><polyline points="5 8 5 5 8 5"/></svg>Combat
                    </button>
                    <button class="tm-tab" data-tab="visual">
                        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M2 12s4-7 10-7 10 7 10 7-4 7-10 7-10-7-10-7z"/></svg>Visual
                    </button>
                    <button class="tm-tab" data-tab="misc">
                        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><circle cx="5" cy="12" r="1.2"/><circle cx="12" cy="12" r="1.2"/><circle cx="19" cy="12" r="1.2"/></svg>Misc
                    </button>
                    <button class="tm-tab" data-tab="changelog">
                        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><line x1="10" y1="9" x2="8" y2="9"/></svg>Log
                    </button>
                </div>
                <div id="tm-body">
                    <div class="tm-panel active" data-panel="combat">
                        ${_row('autoheal','Auto Heal','Auto heal when HP < 60% and food available')}
                        <div class="tm-divider"></div>
                        ${_row('aimbow','Aim Bow','Aim with a bow toward the nearest enemy')}
                        <div class="tm-divider"></div>
                        ${_row('autoreconnect','Auto Reconnect','Instantly respawn on death')}
                        <div class="tm-divider"></div>
                        ${_row('killmessage','Kill Message','Send a chat message when you kill a player')}
                        <div class="tm-divider"></div>
                        <div class="tm-macros-header" id="tm-macros-toggle">
                            <div class="tm-row-info">
                                <div class="tm-row-name" id="tm-macros-name">Macros</div>
                                <div class="tm-row-desc" id="tm-macros-desc">PvP macros list</div>
                            </div>
                            <div class="tm-macros-arrow" id="tm-macros-arrow">&#9660;</div>
                        </div>
                        <div class="tm-macros-body" id="tm-macros-body" style="display:none;">
                            <div class="tm-macro-row">
                                <div><div class="tm-macro-name" id="tm-macro-trap-name">Place Trap</div><div class="tm-macro-desc" id="tm-macro-trap-desc">Place trap on bind key</div></div>
                                <div class="tm-bind-btn" id="tm-bind-trap">${trapBind || '-'}</div>
                            </div>
                            <div class="tm-divider" style="margin:4px 0;"></div>
                            <div class="tm-macro-row">
                                <div><div class="tm-macro-name" id="tm-macro-ball-name">Place Ballista</div><div class="tm-macro-desc" id="tm-macro-ball-desc">Place ballista on bind key</div></div>
                                <div class="tm-bind-btn" id="tm-bind-ballista">${ballistaBind || '-'}</div>
                            </div>
                            <div class="tm-macro-hint" id="tm-macro-hint">Esc - clear bind</div>
                        </div>
                    </div>
                    <div class="tm-panel" data-panel="visual">
                        ${_row('zoom','Zoom','Zoom hack (CTRL + mouse wheel)')}
                        <div class="tm-divider"></div>
                        ${_row('esp','ESP','Draws precise hitboxes on players')}
                        <div class="tm-divider"></div>
                        ${_row('tracers','Tracers','Draws lines from you to each enemy')}
                        <div class="tm-divider"></div>
                        ${_row('hud','HUD','Shows FPS, ping and player count')}
                    </div>
                    <div class="tm-panel" data-panel="misc">
                        ${_row('skinunlock','Skin Unlock','Unlocks Orc, Elf, Undead without sharing')}
                        <div class="tm-divider"></div>
                        ${_row('walkunlock','Walk Unlock','Move even with chat or menus open')}
                        <div class="tm-divider"></div>
                        ${_row('antiafk','Anti-AFK','Prevents server kick for inactivity')}
                        <div class="tm-divider"></div>
                        ${_row('Gold_Bot','Gold Bot','Bots come toward you, kill them for gold')}
                        <div class="tm-divider"></div>
                        <div class="tm-row" style="flex-direction:column;gap:6px;">
                            <div class="tm-row-info"><div class="tm-row-name">Scout</div><div class="tm-row-desc" id="tm-scout-desc">Second account marks players on minimap</div></div>
                            <div class="tm-scout-btns" style="padding:0;">
                                <button class="tm-scout-btn" id="tm-scout-start">Start</button>
                                <button class="tm-scout-btn" id="tm-scout-stop">Stop</button>
                                <button class="tm-scout-btn" id="tm-scout-clear">Clear</button>
                            </div>
                        </div>
                    </div>
                    <div class="tm-panel" data-panel="changelog">
                        <div class="tm-changelog">
                            <div class="tm-cl-entry">
                                <div class="tm-cl-version">v1.1</div>
                                <div class="tm-cl-text">Fixed bug with scout. UI upgraded: settings panel, themes, language. New: Auto Reconnect, Anti-AFK, Macros list (PlaceTrap, PlaceBallista)</div>
                            </div>
                            <div class="tm-cl-entry">
                                <div class="tm-cl-version">v1.0</div>
                                <div class="tm-cl-text">Client released. First public build. Zoom, ESP, Tracers, AutoHeal, AimBow, Scout, WalkUnlock, KillMessage, Gold Bot, HUD</div>
                            </div>
                        </div>
                    </div>
                </div>
                <div id="tm-footer">
                    <div id="tm-footer-label">Menu key:</div>
                    <button id="tm-menu-key-btn">${menuKey}</button>
                </div>
            </div>
        `;
        document.body.appendChild(menu);

        applyTheme(currentTheme);
        document.getElementById('tm-theme-select').value = currentTheme;
        document.getElementById('tm-lang-select').value = currentLang;

        function showSettings() {
            document.getElementById('tm-settings').style.display = 'block';
            document.getElementById('tm-main').style.display = 'none';
        }

        function showMain() {
            document.getElementById('tm-settings').style.display = 'none';
            document.getElementById('tm-main').style.display = 'block';
        }

        document.getElementById('tm-gear').addEventListener('click', e => {
            e.stopPropagation();
            showSettings();
        });
        document.getElementById('tm-settings-back').addEventListener('click', () => showMain());

        document.getElementById('tm-theme-select').addEventListener('change', function() {
            currentTheme = this.value;
            applyTheme(currentTheme);
            saveState();
        });
        document.getElementById('tm-lang-select').addEventListener('change', function() {
            currentLang = this.value;
            updateLang();
            saveState();
        });
        document.getElementById('tm-cfg-reset').addEventListener('click', () => {
            localStorage.removeItem(CFG_KEY);
            location.reload();
        });

        menu.querySelectorAll('.tm-tab').forEach(tab => {
            tab.addEventListener('click', () => {
                menu.querySelectorAll('.tm-tab').forEach(t => t.classList.remove('active'));
                menu.querySelectorAll('.tm-panel').forEach(p => p.classList.remove('active'));
                tab.classList.add('active');
                menu.querySelector('[data-panel="' + tab.dataset.tab + '"]')?.classList.add('active');
                document.getElementById('tm-footer').style.display = tab.dataset.tab === 'changelog' ? 'none' : '';
            });
        });

        menu.querySelectorAll('.tm-toggle').forEach(tog => {
            tog.addEventListener('click', () => {
                const k = tog.dataset.key;
                state[k].enabled = !state[k].enabled;
                tog.classList.toggle('on', state[k].enabled);
                onToggle(k, state[k].enabled);
            });
        });

        document.getElementById('tm-scout-start').addEventListener('click', scoutStart);
        document.getElementById('tm-scout-stop').addEventListener('click', scoutStop);
        document.getElementById('tm-scout-clear').addEventListener('click', scoutClear);

        const macrosToggle = document.getElementById('tm-macros-toggle');
        const macrosBody = document.getElementById('tm-macros-body');
        const macrosArrow = document.getElementById('tm-macros-arrow');
        macrosToggle.addEventListener('click', () => {
            const open = macrosBody.style.display === 'none';
            macrosBody.style.display = open ? '' : 'none';
            macrosArrow.classList.toggle('open', open);
        });

        function setupBind(btnId, getVal, setVal) {
            const btn = document.getElementById(btnId);
            btn.addEventListener('click', () => {
                btn.classList.add('listening');
                btn.textContent = '...';
                const h = e => {
                    e.preventDefault();
                    e.stopPropagation();
                    if (e.key === 'Escape') {
                        setVal('');
                        btn.textContent = '-';
                    } else {
                        setVal(e.key);
                        btn.textContent = e.key;
                    }
                    btn.classList.remove('listening');
                    window.removeEventListener('keydown', h, true);
                    saveState();
                };
                window.addEventListener('keydown', h, {
                    capture: true,
                    once: true
                });
            });
        }
        setupBind('tm-bind-trap', () => trapBind, v => {
            trapBind = v;
        });
        setupBind('tm-bind-ballista', () => ballistaBind, v => {
            ballistaBind = v;
        });

        const mkBtn = document.getElementById('tm-menu-key-btn');
        const BLOCKED = new Set(['KeyW', 'KeyA', 'KeyS', 'KeyD', 'Escape', 'Space', 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight']);
        mkBtn.addEventListener('click', () => {
            mkBtn.classList.add('listening');
            mkBtn.textContent = '...';
            const h = e => {
                e.preventDefault();
                if (BLOCKED.has(e.code)) {
                    mkBtn.textContent = 'blocked!';
                    setTimeout(() => {
                        mkBtn.textContent = menuKey;
                        mkBtn.classList.remove('listening');
                        window.removeEventListener('keydown', h, true);
                    }, 800);
                    return;
                }
                menuKey = e.key;
                mkBtn.textContent = menuKey;
                mkBtn.classList.remove('listening');
                window.removeEventListener('keydown', h, true);
                saveState();
            };
            window.addEventListener('keydown', h, {
                capture: true
            });
        });

        window.addEventListener('keydown', e => {
            if (e.key === menuKey && !_isInput()) {
                e.preventDefault();
                const visible = menu.style.display !== 'none';
                menu.style.display = visible ? 'none' : '';
                if (!visible) showMain();
            }
        }, true);

        _makeDraggable(menu, document.getElementById('tm-header'));
        Object.keys(state).forEach(k => {
            const tog = menu.querySelector('[data-key="' + k + '"]');
            if (tog && state[k]?.enabled) tog.classList.add('on');
        });
        updateLang();
    });

    function updateLang() {
        const L = I18N[currentLang] || I18N.en;
        const pairs = [
            ['autoheal', 'Auto Heal'],
            ['aimbow', 'Aim Bow'],
            ['autoreconnect', 'Auto Reconnect'],
            ['killmessage', 'Kill Message'],
            ['zoom', 'Zoom'],
            ['esp', 'ESP'],
            ['tracers', 'Tracers'],
            ['hud', 'HUD'],
            ['skinunlock', 'Skin Unlock'],
            ['walkunlock', 'Walk Unlock'],
            ['Gold_Bot', 'Gold Bot'],
            ['antiafk', 'Anti-AFK'],
        ];
        pairs.forEach(([key]) => {
            const row = document.querySelector('[data-key="' + key + '"]');
            if (!row) return;
            const p = row.parentElement;
            const ds = p.querySelector('.tm-row-desc');
            if (ds && L[key]) ds.textContent = L[key][1];
        });
        const set = (id, v) => {
            const el = document.getElementById(id);
            if (el) el.textContent = v;
        };
        set('tm-macros-desc', (L.macros || ['', 'PvP macros list'])[1]);
        set('tm-macro-trap-desc', (L.trapMacro || ['', 'Place trap on bind key'])[1]);
        set('tm-macro-ball-desc', (L.ballistaMacro || ['', 'Place ballista on bind key'])[1]);
        set('tm-scout-desc', (L.scout || ['Scout', 'Second account marks players on minimap'])[1]);
        set('tm-macro-hint', (L.escClear || ['Esc - clear bind'])[0]);
        set('tm-settings-title', (L.settings || ['Settings'])[0].toUpperCase());
        const thLbl = document.querySelector('.tm-setting-row .tm-setting-label');
        const thLabels = document.querySelectorAll('.tm-setting-label');
        if (thLabels[0]) thLabels[0].textContent = (L.theme || ['Theme'])[0];
        if (thLabels[1]) thLabels[1].textContent = (L.language || ['Language'])[0];
        const resetBtn = document.getElementById('tm-cfg-reset');
        if (resetBtn) resetBtn.textContent = (L.resetCfg || ['Reset Config'])[0];
    }

    function _row(key, name, desc) {
        return `<div class="tm-row"><div class="tm-row-info"><div class="tm-row-name">${name}</div><div class="tm-row-desc">${desc}</div></div><div class="tm-toggle${state[key]?.enabled?' on':''}" data-key="${key}"></div></div>`;
    }

    function _isInput() {
        const el = document.activeElement;
        if (!el) return false;
        const tag = el.tagName.toLowerCase();
        if (tag === 'input' || tag === 'textarea' || el.isContentEditable) return true;
        try {
            const ui = unsafeWindow.UI;
            if (ui && (ui.chatVisible || ui.teamVisible)) return true;
        } catch {}
        return false;
    }

    function _makeDraggable(el, handle) {
        let ox = 0,
            oy = 0;
        handle.addEventListener('mousedown', e => {
            if (e.target.id === 'tm-gear') return;
            e.preventDefault();
            ox = e.clientX - el.offsetLeft;
            oy = e.clientY - el.offsetTop;
            const mv = e => {
                el.style.left = (e.clientX - ox) + 'px';
                el.style.top = (e.clientY - oy) + 'px';
                el.style.right = 'auto';
            };
            document.addEventListener('mousemove', mv);
            document.addEventListener('mouseup', () => document.removeEventListener('mousemove', mv), {
                once: true
            });
        });
    }
})();