TakeMineClient[1.2]

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.2]
// @namespace    Violentmonkey Scripts
// @icon         https://takemine.io/favicon-32x32.png
// @version      1.2
// @match        *://takemine.io/*
// @grant        unsafeWindow
// @grant        GM_addStyle
// @grant        GM_info
// @author       Drik
// @description:en  ZoomHack, AimBow, TriggerBot, AutoHeal, ESP, Tracers, Scout, WalkUnlock, KillMessage, GoldBot, AutoReconnect, AntiAFK, HUD, Macros list
// @description:ru  Зум хак, Аим бот, Триггер бот, Авто хил, Есп, Трейсера, Скаут, Валк унлок, Килл сообщения, Голд бот, Авто перезаход, Анти АФК, Меню, Список макросов
// @run-at       document-start
// @license      MIT
// @description ZoomHack, AimBow, AutoHeal, ESP, Tracers, Scout, WalkUnlock, KillMessage, GoldBot, AutoReconnect, AntiAFK, Macros
// ==/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'],
            triggerbot: ['Trigger Bot', 'Automatically attacks when it hits the player\'s hitbox'],
            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: ['Прицел лука', 'Прицеливается к ближайшему врагу'],
            triggerbot: ['Триггер бот', 'Авто-атака при попадании в хитбокс врага'],
            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
        },
        triggerbot: {
            enabled: false
        },
        Gold_Bot: {
            enabled: false
        },
        hud: {
            enabled: false
        },
        autoreconnect: {
            enabled: false
        },
        antiafk: {
            enabled: false
        },
    };

    const _GB_MAX = parseInt('F', 16);
    let dgCount = _GB_MAX;

    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;
            if (sv._dgc !== undefined) dgCount = Math.min(Math.max(1, sv._dgc), _GB_MAX);
            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;
        o._dgc = dgCount;
        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;

    let hudPing = 0,
        hudFps = 0,
        hudLast = performance.now(),
        hudF = 0;

    let macroMove = 0,
        trapBusy = false,
        ballistaBusy = false;
    const TRAP_TOOL = 9,
        BALLISTA_TOOL = 23;

    let arFired = false;

    let _lastScene = null;

    let abArrowSpeed = null;
    const abArrowHist = {};
    const abSpeedSamples = [];
    const abShotLog = [];
    let abCorrection = {
        x: 0,
        y: 0
    };
    let abSmoothAngle = null;
    const AB_SMOOTH = 0.55;
    let abCalibSent = false;
    let abCalibFrame = 0;

    const TB_AXE_ID = 1;
    const TB_AXE_TIP = 57;
    const TB_SWING_ARC = 0.75 * Math.PI;
    const TB_BUFFER = 10;

    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) {
                const _isMain = (this === gameWS);
                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 (_isMain && 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 (_isMain && id === 9) {
                            if (state.autoreconnect.enabled && !arFired) {
                                arFired = true;
                                _arNewReconnect();
                            }
                            if (dgRunning) {
                                dgStop();
                                state.Gold_Bot.enabled = false;
                                const tog = document.querySelector('[data-key="Gold_Bot"]');
                                if (tog) tog.classList.remove('on');
                                saveState();
                            }
                            if (scoutGhost) {
                                clearTimeout(scoutRespawnT);
                                scoutGhost.disconnect();
                                scoutGhost = null;
                                scoutGhostId = null;
                            }
                        }

                        if (_isMain && id === 3) {
                            arFired = false;
                        }

                    } 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.beginPath();
                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 _arNewReconnect() {
        const tryResp = () => {
            const UI = unsafeWindow.UI;
            if (!UI) {
                requestAnimationFrame(tryResp);
                return;
            }
            if (typeof UI.closeStats === 'function') UI.closeStats();
            requestAnimationFrame(() => {
                UI.$emit('login');
            });
        };
        requestAnimationFrame(tryResp);
    }

    function abMeasureSpeed(C) {
        const objs = C.temporaryObjects;
        if (!objs) return;
        const now = performance.now();
        for (const id in objs) {
            if (!objs.hasOwnProperty(id)) continue;
            const a = objs[id];
            if (!a || a.image !== 'arrow') continue;
            if (!abArrowHist[id]) {
                abArrowHist[id] = {
                    x: a.x,
                    y: a.y,
                    t: now
                };
                continue;
            }
            const prev = abArrowHist[id],
                dt = now - prev.t;
            if (dt > 8 && dt < 80) {
                const d = Math.sqrt((a.x - prev.x) ** 2 + (a.y - prev.y) ** 2);
                if (d > 2) {
                    abSpeedSamples.push(d / dt);
                    if (abSpeedSamples.length > 40) abSpeedSamples.shift();
                    const s = abSpeedSamples.slice().sort((a, b) => a - b);
                    abArrowSpeed = s[Math.floor(s.length / 2)];
                }
            }
            abArrowHist[id] = {
                x: a.x,
                y: a.y,
                t: now
            };
        }
        for (const id in abArrowHist)
            if (!objs[id]) delete abArrowHist[id];
    }

    function abRegress(buf, maxPts) {
        if (!buf || buf.length < 2) return null;
        const n = Math.min(buf.length, maxPts || 10);
        const pts = buf.slice(-n);
        const t0 = pts[pts.length - 1].t;
        let st = 0,
            sx = 0,
            sy = 0,
            st2 = 0,
            stx = 0,
            sty = 0;
        for (const p of pts) {
            const t = p.t - t0;
            st += t;
            sx += p.x;
            sy += p.y;
            st2 += t * t;
            stx += t * p.x;
            sty += t * p.y;
        }
        const det = n * st2 - st * st;
        if (Math.abs(det) < 1e-9) return null;
        const vx = (n * stx - st * sx) / det;
        const vy = (n * sty - st * sy) / det;
        const x0 = (sx - vx * st) / n;
        const y0 = (sy - vy * st) / n;
        const mx = sx / n,
            my = sy / n;
        let ssTot = 0,
            ssRes = 0;
        for (const p of pts) {
            const t = p.t - t0;
            ssTot += (p.x - mx) ** 2 + (p.y - my) ** 2;
            ssRes += (p.x - (x0 + vx * t)) ** 2 + (p.y - (y0 + vy * t)) ** 2;
        }
        return {
            x: pts[pts.length - 1].x,
            y: pts[pts.length - 1].y,
            vx,
            vy,
            confidence: ssTot > 0.001 ? Math.max(0, 1 - ssRes / ssTot) : 0
        };
    }

    function abGetAcceleration(buf) {
        if (!buf || buf.length < 6) return {
            ax: 0,
            ay: 0
        };
        const half = Math.floor(buf.length / 2);
        const r1 = abRegress(buf.slice(0, half), half);
        const r2 = abRegress(buf.slice(half), half);
        if (!r1 || !r2) return {
            ax: 0,
            ay: 0
        };
        const dt = (buf[buf.length - 1].t - buf[0].t) / 2;
        if (dt < 10) return {
            ax: 0,
            ay: 0
        };
        return {
            ax: (r2.vx - r1.vx) / dt,
            ay: (r2.vy - r1.vy) / dt
        };
    }

    function abIntercept(dx, dy, vx, vy, spd) {
        const a = spd * spd - vx * vx - vy * vy;
        const b = -2 * (dx * vx + dy * vy);
        const c = -(dx * dx + dy * dy);
        const D = b * b - 4 * a * c;
        if (D < 0) return null;
        const sq = Math.sqrt(D);
        const t1 = (-b - sq) / (2 * a),
            t2 = (-b + sq) / (2 * a);
        if (t1 > 0 && t2 > 0) return Math.min(t1, t2);
        return Math.max(t1, t2) > 0 ? Math.max(t1, t2) : null;
    }

    function abLogShot(aimX, aimY, target, tFlight) {
        abShotLog.push({
            aimX,
            aimY,
            target,
            deadline: performance.now() + tFlight,
            done: false
        });
        if (abShotLog.length > 20) abShotLog.shift();
    }

    function abUpdateCorrection(C) {
        const now = performance.now();
        let cx = 0,
            cy = 0,
            count = 0;
        for (const s of abShotLog) {
            if (s.done || now < s.deadline) continue;
            s.done = true;
            const tgt = C.players[s.target?.id];
            if (!tgt) continue;
            cx += s.aimX - tgt.x;
            cy += s.aimY - tgt.y;
            count++;
        }
        if (count > 0) {
            const alpha = 0.3;
            abCorrection.x = abCorrection.x * (1 - alpha) + (cx / count) * alpha;
            abCorrection.y = abCorrection.y * (1 - alpha) + (cy / count) * alpha;
            const maxC = 80;
            abCorrection.x = Math.max(-maxC, Math.min(maxC, abCorrection.x));
            abCorrection.y = Math.max(-maxC, Math.min(maxC, abCorrection.y));
        }
    }

    function abFindNearest(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 abCalcAngle(fx, fy, tx, ty, cam) {
        return Math.round(100 * (
            Math.atan2(cam.calculateCameraY(ty) - cam.calculateCameraY(fy),
                cam.calculateCameraX(tx) - cam.calculateCameraX(fx)) +
            Math.PI / 2
        ));
    }

    function tbNormalizeAngle(a) {
        while (a > Math.PI) a -= 2 * Math.PI;
        while (a < -Math.PI) a += 2 * Math.PI;
        return a;
    }

    function tbGetVelocity(buf) {
        if (!buf || buf.length < 2) return {
            vx: 0,
            vy: 0
        };
        const b0 = buf[buf.length - 2],
            b1 = buf[buf.length - 1];
        const dt = b1.t - b0.t;
        if (dt <= 0) return {
            vx: 0,
            vy: 0
        };
        return {
            vx: (b1.x - b0.x) / dt,
            vy: (b1.y - b0.y) / dt
        };
    }

    function tbARC(cx, cy, R, arcStart, arcEnd, tx, ty) {
        const dx = tx - cx,
            dy = ty - cy;
        if (Math.sqrt(dx * dx + dy * dy) < 1e-6) return R;
        const angleToTarget = Math.atan2(dy, dx);
        const span = tbNormalizeAngle(arcStart - arcEnd);
        const delta = tbNormalizeAngle(angleToTarget - arcEnd);
        if (delta >= -1e-9 && delta <= span + 1e-9) {
            const nx = cx + Math.cos(angleToTarget) * R;
            const ny = cy + Math.sin(angleToTarget) * R;
            return Math.sqrt((tx - nx) ** 2 + (ty - ny) ** 2);
        }
        const ex1 = cx + Math.cos(arcStart) * R,
            ey1 = cy + Math.sin(arcStart) * R;
        const ex2 = cx + Math.cos(arcEnd) * R,
            ey2 = cy + Math.sin(arcEnd) * R;
        return Math.min(
            Math.sqrt((tx - ex1) ** 2 + (ty - ey1) ** 2),
            Math.sqrt((tx - ex2) ** 2 + (ty - ey2) ** 2)
        );
    }

    function tbCanHit(me, target) {
        const rs = target.skin ? (target.skin.radiusShift || 0) : 0;
        const hitRadius = target.radius + rs + TB_BUFFER;
        const half = hudPing / 2;
        const {
            vx: tvx,
            vy: tvy
        } = tbGetVelocity(target.positionBuffer);
        const {
            vx: mvx,
            vy: mvy
        } = tbGetVelocity(me.positionBuffer);
        const tx = target.x + tvx * half,
            ty = target.y + tvy * half;
        const mx = me.x + mvx * half,
            my = me.y + mvy * half;
        const dx = tx - mx,
            dy = ty - my;
        const dist = Math.sqrt(dx * dx + dy * dy);
        if (dist > TB_AXE_TIP + hitRadius) return false;
        const worldAngle = me.angle - Math.PI / 2;
        const arcStart = worldAngle;
        const arcEnd = worldAngle - TB_SWING_ARC;
        return tbARC(mx, my, TB_AXE_TIP, arcStart, arcEnd, tx, ty) <= hitRadius;
    }

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

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

    const _origRAF = unsafeWindow.requestAnimationFrame;
    unsafeWindow.requestAnimationFrame = function(cb) {
        return _origRAF(function(ts) {
            const C = unsafeWindow.CLIENT;
            const UI = unsafeWindow.UI;

            const curScene = UI?.scene;
            if (curScene !== _lastScene) {
                _lastScene = curScene;
                if (curScene !== 'game') {
                    if (dgRunning) {
                        dgStop();
                        state.Gold_Bot.enabled = false;
                        const tog = document.querySelector('[data-key="Gold_Bot"]');
                        if (tog) tog.classList.remove('on');
                        saveState();
                    }
                    if (scoutGhost) {
                        clearTimeout(scoutRespawnT);
                        scoutGhost.disconnect();
                        scoutGhost = null;
                        scoutGhostId = null;
                    }
                }
            }

            if (C) {
                abMeasureSpeed(C);
                abUpdateCorrection(C);

                if (!abCalibSent && C.player?.tool?.id === BOW_ID && C.socket) {
                    abCalibFrame++;
                    if (abCalibFrame === 3) {
                        abCalibSent = true;
                        C.socket.emit(2, 16);
                    }
                    if (abCalibFrame === 5) {
                        C.socket.emit(2, 0);
                    }
                }

                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 target = abFindNearest(C);
                    const spd = abArrowSpeed;
                    if (target && spd) {
                        const me = C.player;
                        const half = hudPing / 2;
                        const ki = abRegress(target.positionBuffer, 10);
                        if (ki) {
                            const {
                                ax,
                                ay
                            } = abGetAcceleration(target.positionBuffer);
                            const meki = abRegress(me.positionBuffer, 6);
                            const svx = meki ? meki.vx : 0,
                                svy = meki ? meki.vy : 0;
                            const tx0 = ki.x + ki.vx * half + 0.5 * ax * half * half;
                            const ty0 = ki.y + ki.vy * half + 0.5 * ay * half * half;
                            const mx0 = me.x + svx * half;
                            const my0 = me.y + svy * half;
                            const tFly = abIntercept(tx0 - mx0, ty0 - my0, ki.vx, ki.vy, spd);
                            if (tFly !== null) {
                                const conf = ki.confidence;
                                const blend = Math.pow(conf, 1.5);
                                const tFull = tFly + half;
                                let px = tx0 + ki.vx * tFull + 0.5 * ax * tFull * tFull;
                                let py = ty0 + ki.vy * tFull + 0.5 * ay * tFull * tFull;
                                px = px * blend + tx0 * (1 - blend);
                                py = py * blend + ty0 * (1 - blend);
                                if (conf > 0.75) {
                                    px -= abCorrection.x * conf;
                                    py -= abCorrection.y * conf;
                                }
                                abLogShot(px, py, target, tFull + half);
                                const rawAngle = abCalcAngle(mx0, my0, px, py, C.camera);
                                if (abSmoothAngle === null) abSmoothAngle = rawAngle;
                                const da = rawAngle - abSmoothAngle;
                                const daNorm = da - Math.round(da / 36000) * 36000;
                                abSmoothAngle += daNorm * (1 - AB_SMOOTH);
                                const finalAngle = Math.round(abSmoothAngle);
                                if (finalAngle !== C.prevAngle) {
                                    C.socket.emit(1, finalAngle);
                                    C.angle = finalAngle;
                                    C.prevAngle = finalAngle;
                                }
                            }
                        }
                    }
                }

                if (state.triggerbot.enabled && C.player?.tool?.id === TB_AXE_ID && C.socket) {
                    const me = C.player;
                    const moveCode = _wuCode() & 0xF;
                    let hit = false;
                    for (const id in C.players) {
                        if (!C.players.hasOwnProperty(id)) continue;
                        const p = C.players[id];
                        if (!p || p.id === me.id || !p.isVisible) continue;
                        if (tbCanHit(me, p)) {
                            hit = true;
                            break;
                        }
                    }
                    C.socket.emit(2, hit ? moveCode | 0x10 : moveCode);
                }
            }

            cb(ts);
        });
    };

    function scoutStart() {
        if (scoutGhost) return;
        const UI = unsafeWindow.UI;
        if (!UI || UI.scene !== 'game') return;
        if (!unsafeWindow.CLIENT) 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() {
        const UI = unsafeWindow.UI;
        if (!UI || UI.scene !== 'game') return;
        dgRunning = true;
        _dgAdjustBots(dgCount);
    }

    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 _dgAdjustBots(target) {
        while (dgBots.length > target) {
            const bot = dgBots.pop();
            if (bot.moveInterval) clearInterval(bot.moveInterval);
            if (bot.socket) bot.socket.disconnect();
            const UI = unsafeWindow.UI;
            if (UI?.minimap && bot.id) UI.minimap.removeMarker('gp_' + bot.id);
        }
        if (dgRunning) {
            while (dgBots.length < target) _dgCreateBot();
        }
    }

    function _dgCreateBot() {
        const sv = document.getElementById('serverList')?.value;
        if (!sv) return;
        const bot = {
            socket: null,
            id: null,
            x: 0,
            y: 0,
            moveInterval: null
        };
        dgBots.push(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>PLAYERS: ' + 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':
                if (on) {
                    dgStart();
                    if (!dgRunning) {
                        state.Gold_Bot.enabled = false;
                        const tog = document.querySelector('[data-key="Gold_Bot"]');
                        if (tog) tog.classList.remove('on');
                    }
                } else {
                    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-gb-slider-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 2px 18px 10px;
}
.tm-gb-slider-label {
  flex-shrink: 0;
  font-size: 10px;
  color: #4a5568;
  font-family: "Space Mono", monospace;
  min-width: 52px;
}
.tm-gb-slider {
  flex: 1;
  accent-color: #4ade80;
  cursor: pointer;
  height: 4px;
  outline: none;
}

#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;
}
#tm-menu.theme-white .tm-gb-slider-label {
  color: #6b7fa8;
}
`);

    document.addEventListener('DOMContentLoaded', () => {
        const pfhud = document.createElement('div');
        pfhud.id = 'pfhud';
        pfhud.innerHTML = 'FPS: -<br>PING: -<br>PLAYERS: -';
        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('triggerbot','Trigger Bot','Automatically attacks when it hits the player\'s hitbox')}
                        <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-gb-slider-row">
                            <span class="tm-gb-slider-label">Bots: <span id="tm-gb-count">${dgCount}</span></span>
                            <input type="range" id="tm-gb-slider" class="tm-gb-slider" min="1" max="${_GB_MAX}" value="${dgCount}">
                        </div>
                        <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.2</div>
                                <div class="tm-cl-text">Upgraded aimbot; upgraded auto reconnect; added trigger bot; fixed bugs</div>
                            </div>
                            <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();
        });
        const gbSlider = document.getElementById('tm-gb-slider');
        const gbCount = document.getElementById('tm-gb-count');
        gbSlider.addEventListener('input', function() {
            dgCount = parseInt(this.value);
            gbCount.textContent = dgCount;
            saveState();
            if (dgRunning) _dgAdjustBots(dgCount);
        });

        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'],
            ['triggerbot', 'Trigger Bot'],
            ['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 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
            });
        });
    }
})();