Zlap.io Bot Hack v1.2

Up to 4 bots with WASD + Space mirroring, automated funneling with directions, and custom names. Saves settings. Includes Player Mode and custom delays.

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name Zlap.io Bot Hack v1.2
// @namespace http://tampermonkey.net/
// @version 1.2
// @description Up to 4 bots with WASD + Space mirroring, automated funneling with directions, and custom names. Saves settings. Includes Player Mode and custom delays.
// @author Ech0
// @license MIT
// @match https://www.zlap.io/*
// @grant none
// ==/UserScript==
(function() {
'use strict';

// --- Global State ---
let isBotConnecting = false; // Flag to distinguish bot sockets from game sockets
const MAX_BOTS = 4;
const DEFAULT_SPAWN = 4;
const BOT_ATTACK_INTERVAL_MS = 2500;
const OPCODES = {
    PLAY: 0,
    DIRECTION: 1,
    MOVE_UP: 2,
    MOVE_DOWN: 3,
    MOVE_LEFT: 4,
    MOVE_RIGHT: 5,
    STOP_MOVE_UP: 6,
    STOP_MOVE_DOWN: 7,
    STOP_MOVE_LEFT: 8,
    STOP_MOVE_RIGHT: 9,
};

// Configuration for Funnel Directions
const FUNNEL_CONFIG = {
    "WestBottom": { label: "WestBottom (Hold A, Toggle S)", hold: "moveLeft", toggle: "moveDown" },
    "West":       { label: "West (Hold A Only)", hold: "moveLeft", toggle: null },
    "WestTop":    { label: "WestTop (Hold A, Toggle W)", hold: "moveLeft", toggle: "moveUp" },

    "NorthLeft":  { label: "NorthLeft (Hold W, Toggle A)", hold: "moveUp", toggle: "moveLeft" },
    "North":      { label: "North (Hold W Only)", hold: "moveUp", toggle: null },
    "NorthRight": { label: "NorthRight (Hold W, Toggle D)", hold: "moveUp", toggle: "moveRight" },

    "EastTop":    { label: "EastTop (Hold D, Toggle W)", hold: "moveRight", toggle: "moveUp" },
    "East":       { label: "East (Hold D Only)", hold: "moveRight", toggle: null },
    "EastBottom": { label: "EastBottom (Hold D, Toggle S)", hold: "moveRight", toggle: "moveDown" },

    "SouthRight": { label: "SouthRight (Hold S, Toggle D)", hold: "moveDown", toggle: "moveRight" },
    "South":      { label: "South (Hold S Only)", hold: "moveDown", toggle: null },
    "SouthLeft":  { label: "SouthLeft (Hold S, Toggle A)", hold: "moveDown", toggle: "moveLeft" }
};

const BOT_SKIN = 0;

// --- Settings Management ---
const Settings = {
    botNames: ["Bot 1", "Bot 2", "Bot 3", "Bot 4"],
    mirrorKeys: false,
    funnelActive: false,
    funnelDirection: "WestBottom",
    funnelDelay: 100, // Default 100ms
    playerMode: false, // Player acts as bot

    load() {
        // Load Names
        for(let i=0; i<4; i++) {
            const savedName = localStorage.getItem(`zlap_bot_name_${i}`);
            if (savedName !== null) this.botNames[i] = savedName;
        }
        // Legacy support
        const oldName = localStorage.getItem("zlap_bot_name");
        if (oldName && !localStorage.getItem("zlap_bot_name_0")) {
            this.botNames[0] = oldName;
        }

        const savedMirror = localStorage.getItem("zlap_mirror");
        if (savedMirror !== null) this.mirrorKeys = (savedMirror === "true");

        const savedFunnelActive = localStorage.getItem("zlap_funnel_active");
        if (savedFunnelActive !== null) this.funnelActive = (savedFunnelActive === "true");

        const savedFunnelDir = localStorage.getItem("zlap_funnel_dir");
        if (savedFunnelDir !== null && FUNNEL_CONFIG[savedFunnelDir]) {
            this.funnelDirection = savedFunnelDir;
        }

        const savedDelay = localStorage.getItem("zlap_funnel_delay");
        if (savedDelay !== null) this.funnelDelay = parseInt(savedDelay, 10);

        const savedPlayerMode = localStorage.getItem("zlap_player_mode");
        if (savedPlayerMode !== null) this.playerMode = (savedPlayerMode === "true");
    },

    save() {
        for(let i=0; i<4; i++) {
            localStorage.setItem(`zlap_bot_name_${i}`, this.botNames[i]);
        }
        localStorage.setItem("zlap_mirror", this.mirrorKeys);
        localStorage.setItem("zlap_funnel_active", this.funnelActive);
        localStorage.setItem("zlap_funnel_dir", this.funnelDirection);
        localStorage.setItem("zlap_funnel_delay", this.funnelDelay);
        localStorage.setItem("zlap_player_mode", this.playerMode);
    }
};

Settings.load();

// --- WebSocket Hook ---
let mainPlayerSocket = null;
const NativeWebSocket = window.WebSocket;

// Override the WebSocket constructor to capture the main player's socket
window.WebSocket = function(...args) {
    const ws = new NativeWebSocket(...args);

    // Only capture if this is NOT a bot connection initiated by our script
    if (!isBotConnecting) {
        // Basic check to ensure it's likely a game socket
        if (args[0] && typeof args[0] === 'string' && (args[0].includes("zlap.") || args[0].includes("amazonlightsail"))) {
            mainPlayerSocket = ws;
            BotManager.attachMainPlayer(ws);
        }
    }
    return ws;
};

// CRITICAL: Restore static properties so the game logic (checking WebSocket.OPEN etc.) doesn't break
for (const key in NativeWebSocket) {
    window.WebSocket[key] = NativeWebSocket[key];
}
window.WebSocket.prototype = NativeWebSocket.prototype;

// Ensure standard constants exist (some browsers/environments might miss them in the loop)
window.WebSocket.CONNECTING = 0;
window.WebSocket.OPEN = 1;
window.WebSocket.CLOSING = 2;
window.WebSocket.CLOSED = 3;


class PacketBuilder {
    constructor() {
        this._data = [];
        this._buffer = new ArrayBuffer(8);
        this._u8 = new Uint8Array(this._buffer);
        this._u16 = new Uint16Array(this._buffer);
        this._u32 = new Uint32Array(this._buffer);
        this._i8 = new Int8Array(this._buffer);
        this._i16 = new Int16Array(this._buffer);
        this._i32 = new Int32Array(this._buffer);
        this._f32 = new Float32Array(this._buffer);
    }

    _write(value, view, bytes) {
        view[0] = value;
        for (let i = bytes - 1; i >= 0; i--) {
            this._data.push(this._u8[i]);
        }
    }

    u8(v) { this._data.push(v & 0xFF); }
    string(str) {
        for (let i = 0; i < str.length; i++) {
            this.u8(str.charCodeAt(i) & 0xFF);
        }
        this.u8(0);
    }
    done() {
        const out = new Uint8Array(this._data);
        this._data = [];
        return out;
    }
}

// Wrapper for the main player's socket to allow bot-like control
class PlayerWrapper {
    constructor(ws) {
        this.ws = ws;
        this.isMainPlayer = true;
        this._respawnTimer = null;
        this._setupAutoRespawn();
    }

    _setupAutoRespawn() {
        if (this._respawnTimer) clearInterval(this._respawnTimer);
        this._respawnTimer = setInterval(() => {
            // Auto-swing / Auto-respawn logic if Player Mode is enabled
            if (Settings.playerMode && this.ws.readyState === WebSocket.OPEN) {
                this.sendPlay();
            }
        }, BOT_ATTACK_INTERVAL_MS);
    }

    sendPlay() { this._send(OPCODES.PLAY); }
    moveUp(on) { this._send(on ? OPCODES.MOVE_UP : OPCODES.STOP_MOVE_UP); }
    moveDown(on) { this._send(on ? OPCODES.MOVE_DOWN : OPCODES.STOP_MOVE_DOWN); }
    moveLeft(on) { this._send(on ? OPCODES.MOVE_LEFT : OPCODES.STOP_MOVE_LEFT); }
    moveRight(on) { this._send(on ? OPCODES.MOVE_RIGHT : OPCODES.STOP_MOVE_RIGHT); }

    stopMoving() {
        this.moveUp(false);
        this.moveDown(false);
        this.moveLeft(false);
        this.moveRight(false);
    }

    _send(opcode) {
        if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;
        const pb = new PacketBuilder();
        pb.u8(opcode);
        this.ws.send(pb.done());
    }
}

class ZlapBot {
    constructor(name, skin, cheatString = "") {
        this.name = name;
        this.skin = skin;
        this.cheatString = cheatString;
        this.ws = null;
        this.alive = false;
        this._attackTimer = null;
        this._serverUrl = this._findServer();
        this._connect();
    }

    _findServer() {
        if (mainPlayerSocket) return mainPlayerSocket.url;
        // Fallback default
        return "wss://zlap.6192k7tcej8ks.eu-central-1.cs.amazonlightsail.com";
    }

    _connect() {
        try {
            isBotConnecting = true; // Set flag so WebSocket hook ignores this connection
            const ws = new WebSocket(this._serverUrl);
            isBotConnecting = false; // Reset flag immediately

            ws.binaryType = "arraybuffer";
            this.ws = ws;

            ws.onopen = () => {
                this._sendJoin();
                this._startAttackLoop();
                if (FunnelManager.active) {
                    FunnelManager.applyToBot(this);
                }
            };
            ws.onclose = () => {
                this._stopAttackLoop();
                this.ws = null;
                this.alive = false;
            };
            ws.onerror = () => { this._stopAttackLoop(); };
        } catch (e) {
            isBotConnecting = false;
        }
    }

    _sendRawPacket(buildFn) {
        if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;
        const pb = new PacketBuilder();
        buildFn(pb);
        this.ws.send(pb.done());
    }

    _sendJoin() {
        const pb = new PacketBuilder();
        pb.string(this.name);
        pb.u8(this.skin);
        pb.string(this.cheatString || "");
        this.ws.send(pb.done());
        this.alive = true;
    }

    sendPlay() { this._sendRawPacket(pb => pb.u8(OPCODES.PLAY)); }
    moveUp(on) { this._sendRawPacket(pb => pb.u8(on ? OPCODES.MOVE_UP : OPCODES.STOP_MOVE_UP)); }
    moveDown(on) { this._sendRawPacket(pb => pb.u8(on ? OPCODES.MOVE_DOWN : OPCODES.STOP_MOVE_DOWN)); }
    moveLeft(on) { this._sendRawPacket(pb => pb.u8(on ? OPCODES.MOVE_LEFT : OPCODES.STOP_MOVE_LEFT)); }
    moveRight(on) { this._sendRawPacket(pb => pb.u8(on ? OPCODES.MOVE_RIGHT : OPCODES.STOP_MOVE_RIGHT)); }

    stopMoving() {
        this.moveUp(false);
        this.moveDown(false);
        this.moveLeft(false);
        this.moveRight(false);
    }

    _startAttackLoop() {
        this._attackTimer = setInterval(() => {
            if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;
            this.sendPlay();
        }, BOT_ATTACK_INTERVAL_MS);
    }

    _stopAttackLoop() {
        if (this._attackTimer) {
            clearInterval(this._attackTimer);
            this._attackTimer = null;
        }
    }

    kill() {
        this._stopAttackLoop();
        if (this.ws) {
            try { this.ws.close(); } catch (e) {}
            this.ws = null;
        }
    }
}

const BotManager = {
    bots: [],
    playerWrapper: null,

    attachMainPlayer(ws) {
        this.playerWrapper = new PlayerWrapper(ws);
    },

    spawnBots(count) {
        const available = Math.max(0, MAX_BOTS - this.bots.length);
        const toSpawn = Math.min(count, available);
        if (toSpawn <= 0) return;

        for (let i = 0; i < toSpawn; i++) {
            const nameIndex = this.bots.length % 4;
            const name = Settings.botNames[nameIndex] || Settings.botNames[0];
            const bot = new ZlapBot(name, BOT_SKIN, "");
            this.bots.push(bot);
        }
    },

    killAll() {
        for (const bot of this.bots) bot.kill();
        this.bots = [];
    },

    forEachBot(fn) {
        for (const bot of this.bots) {
            if (bot.ws && bot.ws.readyState === WebSocket.OPEN) fn(bot);
        }
    },

    forEachEntity(fn) {
        this.forEachBot(fn);
        if (Settings.playerMode && this.playerWrapper) {
            fn(this.playerWrapper);
        }
    }
};

window.ZlapBotManager = BotManager;

function setupKeyMirroring() {
    window.addEventListener("keydown", (ev) => {
        if (!Settings.mirrorKeys) return;
        if (ev.repeat) return;
        const code = ev.code || ev.key;
        switch (code) {
            case "KeyW": case "ArrowUp": BotManager.forEachBot(bot => bot.moveUp(true)); break;
            case "KeyS": case "ArrowDown": BotManager.forEachBot(bot => bot.moveDown(true)); break;
            case "KeyA": case "ArrowLeft": BotManager.forEachBot(bot => bot.moveLeft(true)); break;
            case "KeyD": case "ArrowRight": BotManager.forEachBot(bot => bot.moveRight(true)); break;
            case "Space": BotManager.forEachBot(bot => bot.sendPlay()); break;
        }
    });

    window.addEventListener("keyup", (ev) => {
        if (!Settings.mirrorKeys) return;
        if (ev.repeat) return;
        const code = ev.code || ev.key;
        switch (code) {
            case "KeyW": case "ArrowUp": BotManager.forEachBot(bot => bot.moveUp(false)); break;
            case "KeyS": case "ArrowDown": BotManager.forEachBot(bot => bot.moveDown(false)); break;
            case "KeyA": case "ArrowLeft": BotManager.forEachBot(bot => bot.moveLeft(false)); break;
            case "KeyD": case "ArrowRight": BotManager.forEachBot(bot => bot.moveRight(false)); break;
        }
    });
}

const FunnelManager = {
    active: false,
    timeout: null,
    toggleState: false,

    applyToBot(bot) {
        if (!this.active) return;
        const config = FUNNEL_CONFIG[Settings.funnelDirection];
        if (!config) return;
        bot[config.hold](true);
        if (config.toggle) bot[config.toggle](this.toggleState);
    },

    loop() {
        if (!this.active) return;
        const config = FUNNEL_CONFIG[Settings.funnelDirection];
        if (!config) return;

        this.toggleState = !this.toggleState;
        BotManager.forEachEntity(entity => {
            entity[config.hold](true);
            if (config.toggle) entity[config.toggle](this.toggleState);
        });
        this.timeout = setTimeout(() => this.loop(), Settings.funnelDelay);
    },

    start() {
        if (this.active) return;
        this.active = true;
        this.toggleState = true;
        const config = FUNNEL_CONFIG[Settings.funnelDirection];
        if (!config) return;

        BotManager.forEachEntity(entity => {
            entity[config.hold](true);
            if (config.toggle) entity[config.toggle](true);
        });
        this.timeout = setTimeout(() => this.loop(), Settings.funnelDelay);
    },

    stop() {
        if (!this.active) return;
        this.active = false;
        if (this.timeout) {
            clearTimeout(this.timeout);
            this.timeout = null;
        }
        BotManager.forEachEntity(entity => {
            entity.stopMoving();
        });
    },

    restart() {
        if (this.active) {
            this.stop();
            this.start();
        }
    }
};

function createControlPanel() {
    const style = document.createElement('style');
    style.innerHTML = `
        .zlap-panel {
            position: fixed; bottom: 40px; left: 10px; z-index: 999999;
            background-color: rgba(20, 20, 20, 0.95); color: #eee; padding: 15px;
            font-family: 'Segoe UI', Tahoma, sans-serif; font-size: 12px;
            border-radius: 8px; min-width: 250px; border: 1px solid #444;
            box-shadow: 0 4px 15px rgba(0,0,0,0.6); backdrop-filter: blur(4px);
        }
        .zlap-title {
            margin-bottom: 12px; font-weight: 700; text-align: center;
            border-bottom: 1px solid #444; padding-bottom: 8px; font-size: 14px;
        }
        .zlap-input {
            background: #222; border: 1px solid #444; color: #fff; padding: 5px;
            border-radius: 4px; font-size: 11px; width: 100%; box-sizing: border-box; outline: none;
        }
        .zlap-input:focus { border-color: #2ecc71; }
        .zlap-btn {
            flex: 1; padding: 8px; border: none; color: white; cursor: pointer;
            border-radius: 4px; font-weight: 600; transition: opacity 0.2s;
        }
        .zlap-btn:hover { opacity: 0.9; }
        .zlap-btn-green { background: #27ae60; }
        .zlap-btn-red { background: #c0392b; }
        .zlap-toggle-row {
            display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px;
        }
        .zlap-switch {
            position: relative; display: inline-block; width: 36px; height: 20px; flex-shrink: 0;
        }
        .zlap-switch input { opacity: 0; width: 0; height: 0; }
        .zlap-slider {
            position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0;
            background-color: #555; transition: .3s; border-radius: 20px;
        }
        .zlap-slider:before {
            position: absolute; content: ""; height: 14px; width: 14px; left: 3px; bottom: 3px;
            background-color: white; transition: .3s; border-radius: 50%;
        }
        input:checked + .zlap-slider { background-color: #2ecc71; }
        input:checked + .zlap-slider:before { transform: translateX(16px); }
        .zlap-status { margin-top: 12px; font-size: 10px; color: #aaa; text-align: center; }
    `;
    document.head.appendChild(style);

    const panel = document.createElement("div");
    panel.className = "zlap-panel";

    const title = document.createElement("div");
    title.className = "zlap-title";
    title.textContent = "Zlap Bot v1.2";
    panel.appendChild(title);

    const namesGrid = document.createElement("div");
    namesGrid.style.display = "grid";
    namesGrid.style.gridTemplateColumns = "1fr 1fr";
    namesGrid.style.gap = "6px";
    namesGrid.style.marginBottom = "12px";

    for(let i=0; i<4; i++) {
        const inp = document.createElement("input");
        inp.className = "zlap-input";
        inp.placeholder = `Bot ${i+1}`;
        inp.value = Settings.botNames[i];
        inp.oninput = () => { Settings.botNames[i] = inp.value; Settings.save(); };
        namesGrid.appendChild(inp);
    }
    panel.appendChild(namesGrid);

    const btnContainer = document.createElement("div");
    btnContainer.style.display = "flex";
    btnContainer.style.gap = "8px";
    btnContainer.style.marginBottom = "15px";

    const spawnBtn = document.createElement("button");
    spawnBtn.className = "zlap-btn zlap-btn-green";
    spawnBtn.textContent = "Spawn Bots";
    spawnBtn.onclick = () => { BotManager.spawnBots(DEFAULT_SPAWN); updateStatus(); };

    const killBtn = document.createElement("button");
    killBtn.className = "zlap-btn zlap-btn-red";
    killBtn.textContent = "Kill All";
    killBtn.onclick = () => { BotManager.killAll(); updateStatus(); };
    btnContainer.append(spawnBtn, killBtn);
    panel.appendChild(btnContainer);

    const createToggle = (label, settingKey, onChange) => {
        const row = document.createElement("div");
        row.className = "zlap-toggle-row";
        const txt = document.createElement("span");
        txt.textContent = label;
        const switchLabel = document.createElement("label");
        switchLabel.className = "zlap-switch";
        const inp = document.createElement("input");
        inp.type = "checkbox";
        inp.checked = Settings[settingKey];
        inp.onchange = () => {
            Settings[settingKey] = inp.checked;
            Settings.save();
            if(onChange) onChange(inp.checked);
            updateStatus();
        };
        const slider = document.createElement("span");
        slider.className = "zlap-slider";
        switchLabel.append(inp, slider);
        row.append(txt, switchLabel);
        return row;
    };

    panel.appendChild(createToggle("Mirror Keys (WASD)", "mirrorKeys"));
    panel.appendChild(createToggle("Player as Bot", "playerMode", () => {
         if(Settings.funnelActive) FunnelManager.restart();
    }));

    const hr = document.createElement("div");
    hr.style.borderTop = "1px solid #444";
    hr.style.margin = "10px 0";
    panel.appendChild(hr);

    panel.appendChild(createToggle("Enable Funneling", "funnelActive", (checked) => {
        checked ? FunnelManager.start() : FunnelManager.stop();
    }));

    const dirSelect = document.createElement("select");
    dirSelect.className = "zlap-input";
    dirSelect.style.marginBottom = "8px";
    Object.keys(FUNNEL_CONFIG).forEach(key => {
        const opt = document.createElement("option");
        opt.value = key;
        opt.textContent = FUNNEL_CONFIG[key].label;
        if (Settings.funnelDirection === key) opt.selected = true;
        dirSelect.appendChild(opt);
    });
    dirSelect.onchange = () => {
        Settings.funnelDirection = dirSelect.value;
        Settings.save();
        FunnelManager.restart();
    };
    panel.appendChild(dirSelect);

    const delayRow = document.createElement("div");
    delayRow.className = "zlap-toggle-row";
    const delayLbl = document.createElement("span");
    delayLbl.textContent = "Toggle Delay:";
    const delaySelect = document.createElement("select");
    delaySelect.className = "zlap-input";
    delaySelect.style.width = "auto";
    [10, 50, 100, 200, 500, 1000, 2000, 5000].forEach(ms => {
        const opt = document.createElement("option");
        opt.value = ms;
        opt.textContent = ms + " ms";
        if(Settings.funnelDelay === ms) opt.selected = true;
        delaySelect.appendChild(opt);
    });
    delaySelect.onchange = () => {
        Settings.funnelDelay = parseInt(delaySelect.value);
        Settings.save();
        FunnelManager.restart();
    };
    delayRow.append(delayLbl, delaySelect);
    panel.appendChild(delayRow);

    const status = document.createElement("div");
    status.className = "zlap-status";
    panel.appendChild(status);

    const updateStatus = () => {
        status.textContent = `Bots: ${BotManager.bots.length} | Mode: ${Settings.playerMode ? "Player+Bot" : "Bot Only"}`;
    };

    document.body.appendChild(panel);
    setInterval(updateStatus, 1000);
    updateStatus();
}

function onReady(fn) {
    if (document.readyState === "complete" || document.readyState === "interactive") fn();
    else document.addEventListener("DOMContentLoaded", fn);
}

onReady(() => {
    createControlPanel();
    setupKeyMirroring();
    if (Settings.funnelActive) FunnelManager.start();
});
})();