TakeMineClient

A client packed with features! ZoomHack, AimBow, AutoHeal, ESP, Tracers, Scout, WalkUnlock, KillMessage, Gold Bot, HUD

K instalaci tototo skriptu si budete muset nainstalovat rozšíření jako Tampermonkey, Greasemonkey nebo Violentmonkey.

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

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Violentmonkey.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Userscripts.

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

K instalaci tohoto skriptu si budete muset nainstalovat manažer uživatelských skriptů.

(Už mám manažer uživatelských skriptů, nechte mě ho nainstalovat!)

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

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

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

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

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

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

(Už mám manažer uživatelských stylů, nechte mě ho nainstalovat!)

// ==UserScript==
// @name         TakeMineClient
// @namespace    Violentmonkey Scripts
// @icon         https://takemine.io/favicon-32x32.png
// @version      1.0
// @match        *://takemine.io/*
// @grant        unsafeWindow
// @grant        GM_addStyle
// @grant        GM_info
// @author       Drik
// @description  A client packed with features! ZoomHack, AimBow, AutoHeal, ESP, Tracers, Scout, WalkUnlock, KillMessage, Gold Bot, HUD
// @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';

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

    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;
            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;
        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;
    let ahEnabled = state.autoheal.enabled;
    let ahHealing = false;
    let ahProto = false;
    let ahPrevTool = 1;
    let 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 = [];
    const MAP_SIZE = 8160;

    const dgBots = [];
    let dgRunning = false;

    let kmKills = 0;
    const kmQueue = [];

    const BOW_ID = 4;
    const ARROW_SPD = 0.55;

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

    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 (state.killmessage.enabled && typeof ev.data === 'string' && ev.data.startsWith('42')) {
                    try {
                        const p = JSON.parse(ev.data.slice(2));
                        const id = p[0],
                            pl = p[1],
                            c = unsafeWindow.CLIENT;
                        if (id === 11) {
                            const c = unsafeWindow.CLIENT;
                            const name = c?.playerNames?.[pl] || ('Player#' + pl);
                            kmQueue.push(name);
                        }
                        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() || null;
                                    if (name) {
                                        const c = unsafeWindow.CLIENT;
                                        c?.socket?.emit(10, name + ' killed by ' + c?.player?.name);
                                    }
                                }
                            }
                        }
                        if (id === 9) {
                            kmKills = 0;
                            kmQueue.length = 0;
                        }
                    } 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 && 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;
        });
    }

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

    unsafeWindow.addEventListener('keydown', e => {
        let ch = true;
        switch (e.keyCode) {
            case 32:
                wuKeys.attack = true;
                break;
            case 69:
                wuKeys.attack = !wuKeys.attack;
                break;
            case 38:
            case 87:
                wuKeys.up = true;
                break;
            case 39:
            case 68:
                wuKeys.right = true;
                break;
            case 40:
            case 83:
                wuKeys.down = true;
                break;
            case 37:
            case 65:
                wuKeys.left = true;
                break;
            default:
                ch = false;
        }
        if (ch) _wuSend();
    }, true);

    unsafeWindow.addEventListener('keyup', e => {
        let ch = true;
        switch (e.keyCode) {
            case 32:
                wuKeys.attack = false;
                break;
            case 38:
            case 87:
                wuKeys.up = false;
                break;
            case 39:
            case 68:
                wuKeys.right = false;
                break;
            case 40:
            case 83:
                wuKeys.down = false;
                break;
            case 37:
            case 65:
                wuKeys.left = false;
                break;
            default:
                ch = false;
        }
        if (ch) _wuSend();
    }, true);

    unsafeWindow.addEventListener('blur', () => {
        wuKeys.attack = wuKeys.up = wuKeys.right = wuKeys.down = wuKeys.left = false;
        wuLast = null;
    });

    function _applyZoom() {
        const c = unsafeWindow.CLIENT;
        if (!c) return;
        const w = c.screenWidth,
            h = c.screenHeight;
        const base = Math.round(100 * Math.max(w / 1440, h / 900)) / 100;
        const ns = parseFloat((base * zoomFactor).toFixed(4));
        const 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.0, 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 && 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 && c.player && c.socket) {
                if (c.player.tool && c.player.tool.id === BOW_ID) {
                    const t = _findNearest(c);
                    if (t) {
                        const {
                            x,
                            y
                        } = _predictPos(t, 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(t, me) {
        const buf = t.positionBuffer;
        let px = t.x,
            py = t.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;
                const d = Math.sqrt((t.x - me.x) ** 2 + (t.y - me.y) ** 2);
                px = t.x + vx * (d / ARROW_SPD);
                py = t.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 (!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 t = 0; t < d.length; t += 11) {
                const r = d.slice(t, t + 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 t = 0; t < d.length; t += 11) {
                const r = d.slice(t, t + 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;
            const 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>PLAYER: ' + 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.greatest.deepsurf.us/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();
    }


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


    document.addEventListener('DOMContentLoaded', () => {
        const pfhud = document.createElement('div');
        pfhud.id = 'pfhud';
        pfhud.innerHTML = 'FPS: -<br>PING: -<br>PLAYER: -';
        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>
            </div>
            <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('Gold_Bot',    'Gold Bot',    'Bots coming toward you, kill them for gold')}
                    <div class="tm-divider"></div>
                    ${_row('killmessage', 'Kill Message', 'Send a chat message when you kill a player')}
                </div>
                <div class="tm-panel" data-panel="visual">
                    ${_row('zoom',    'Zoom',    'Default zoom hack (CTRL + mouse wheel)')}
                    <div class="tm-divider"></div>
                    ${_row('esp',     'ESP',     'Draws the players precise hitbox')}
                    <div class="tm-divider"></div>
                    ${_row('tracers', 'Tracers', 'Draws a line from you to each enemy')}
                    <div class="tm-divider"></div>
                    ${_row('hud',     'HUD',     'Shows FPS, ping and player counter')}
                </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('scout', 'Scout', 'Second account that marks players on minimap')}
                    <div class="tm-scout-btns">
                        <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 class="tm-panel" data-panel="changelog">
                    <div class="tm-changelog">
                        <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>
        `;
        document.body.appendChild(menu);


        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(t => {
            t.addEventListener('click', () => {
                const k = t.dataset.key;
                state[k].enabled = !state[k].enabled;
                t.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 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();
                menu.style.display = menu.style.display === 'none' ? '' : 'none';
            }
        }, true);

        _makeDraggable(menu, document.getElementById('tm-header'));

        Object.keys(state).forEach(k => {
            const t = menu.querySelector('[data-key="' + k + '"]');
            if (t && state[k]?.enabled) t.classList.add('on');
        });
    });

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

})();