☰

Drawaria Canvas Live Kart 🏎️

Simulador de Karts con seguimiento de avatar y modo de conducciΓ³n automΓ‘tica (Auto Drive).

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

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

(I already have a user script manager, let me install it!)

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.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         Drawaria Canvas Live Kart 🏎️
// @namespace    http://tampermonkey.net/
// @version      1.3
// @description  Simulador de Karts con seguimiento de avatar y modo de conducciΓ³n automΓ‘tica (Auto Drive).
// @author       YouTubeDrawaria
// @match        https://drawaria.online/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=drawaria.online
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    let drawariaSocket = null;
    let drawariaCanvas = null;
    let drawariaCtx = null;

    const commandQueue = [];
    let batchProcessor = null;
    const BATCH_SIZE = 55;
    const BATCH_INTERVAL = 20;

    const originalWebSocketSend = WebSocket.prototype.send;
    WebSocket.prototype.send = function (...args) {
        if (!drawariaSocket && this.url && this.url.includes('drawaria')) {
            drawariaSocket = this;
            startBatchProcessor();
        }
        return originalWebSocketSend.apply(this, args);
    };

    function startBatchProcessor() {
        if (batchProcessor) return;
        batchProcessor = setInterval(() => {
            if (!drawariaSocket || drawariaSocket.readyState !== WebSocket.OPEN || commandQueue.length === 0) return;
            const batch = commandQueue.splice(0, BATCH_SIZE);
            batch.forEach(cmd => { try { drawariaSocket.send(cmd); } catch (e) {} });
        }, BATCH_INTERVAL);
    }

    function enqueueDrawCommand(x1, y1, x2, y2, color, thickness) {
        if (!drawariaCanvas) return;
        if (drawariaCtx) {
            drawariaCtx.strokeStyle = color;
            drawariaCtx.lineWidth = thickness;
            drawariaCtx.lineCap = 'round';
            drawariaCtx.beginPath();
            drawariaCtx.moveTo(x1, y1);
            drawariaCtx.lineTo(x2, y2);
            drawariaCtx.stroke();
        }
        const normX1 = (x1 / drawariaCanvas.width).toFixed(4);
        const normY1 = (y1 / drawariaCanvas.height).toFixed(4);
        const normX2 = (x2 / drawariaCanvas.width).toFixed(4);
        const normY2 = (y2 / drawariaCanvas.height).toFixed(4);
        const cmd = `42["drawcmd",0,[${normX1},${normY1},${normX2},${normY2},false,${-Math.abs(thickness)},"${color}",0,0,{}]]`;
        commandQueue.push(cmd);
    }

    function drawPlatform(x, y, w, h, color) {
        const thickness = h;
        const startX = x + h/2;
        const endX = Math.max(startX + 0.1, x + w - h/2);
        const cy = y + h/2;
        enqueueDrawCommand(startX, cy, endX, cy, color, thickness);
    }

    /* ---------- KART GAME ENGINE ---------- */
    const Game = {
        active: false,
        followMode: false,
        autoDrive: false, // Nueva propiedad
        x: 200,
        y: 400,
        speed: 20,
        wheelState: 0,
        roadOffset: 0,
        loopTimer: null,
        syncTimer: null,
        isMoving: false,
        keys: { w: false, a: false, s: false, d: false },

        start() {
            if (!drawariaCanvas) return;
            this.active = true;
            this.x = drawariaCanvas.width * 0.2;
            this.y = drawariaCanvas.height * 0.7;
            this.runLoop();
            this.startSyncLoop();
        },

        stop() {
            this.active = false;
            this.followMode = false;
            this.autoDrive = false;
            clearTimeout(this.loopTimer);
            clearInterval(this.syncTimer);
            this.updateMenuButtons();
        },

        startSyncLoop() {
            clearInterval(this.syncTimer);
            this.syncTimer = setInterval(() => {
                if (this.active && this.followMode) this.syncAvatar();
            }, 50);
        },

        syncAvatar() {
            if (!drawariaCanvas) return;
            const avatarX = ((this.x + 55) / drawariaCanvas.width) * 100;
            const avatarY = ((this.y - 48) / drawariaCanvas.height) * 100;
            if (window.game && typeof window.game.sendMove === "function") {
                window.game.sendMove(avatarX, avatarY);
            } else if (drawariaSocket) {
                const encodedPos = 1e4 * Math.floor((avatarX / 100) * 1e4) + Math.floor((avatarY / 100) * 1e4);
                drawariaSocket.send(`42["clientcmd",103,[${encodedPos},false]]`);
            }
        },

        runLoop() {
            if (!this.active) return;
            this.handleMovement();

            // LΓ³gica de animaciΓ³n
            if (this.isMoving || this.autoDrive) {
                this.wheelState = (this.wheelState === 1) ? 2 : 1;
                this.roadOffset -= 65;
                if (this.roadOffset <= -120) this.roadOffset = 0;
            } else {
                this.wheelState = 0;
            }

            this.renderFrame();
            const nextTick = (this.isMoving || this.autoDrive) ? 280 : 1500;
            this.loopTimer = setTimeout(() => this.runLoop(), nextTick);
        },

        handleMovement() {
            // Se considera movimiento si hay teclas pulsadas O si el auto-drive estΓ‘ activo
            this.isMoving = this.keys.w || this.keys.s || this.keys.a || this.keys.d;

            let moveX = 0;
            let moveY = 0;

            if (this.keys.w) moveY -= this.speed;
            if (this.keys.s) moveY += this.speed;
            if (this.keys.a) moveX -= this.speed;
            if (this.keys.d) moveX += this.speed;

            // Si el auto-drive estΓ‘ activo y no estamos frenando manualmente, el kart avanza a velocidad crucero
            if (this.autoDrive && !this.keys.a) {
                this.x += (this.speed * 0.3); // Avance automΓ‘tico suave
            }

            this.x += moveX;
            this.y += moveY;

            const w = drawariaCanvas.width, h = drawariaCanvas.height;
            if (this.y < h * 0.55) this.y = h * 0.55;
            if (this.y > h - 60) this.y = h - 60;
            if (this.x < -30) this.x = -30;
            if (this.x > w - 120) this.x = w - 120;
        },

        renderFrame() {
            const w = drawariaCanvas.width, h = drawariaCanvas.height;
            const skyH = h * 0.6;
            enqueueDrawCommand(-100, skyH/2, w+100, skyH/2, '#87CEEB', skyH + 10);
            const roadH = h * 0.4;
            enqueueDrawCommand(-100, h*0.6 + roadH/2, w+100, h*0.6 + roadH/2, '#444444', roadH + 10);

            for (let dx = this.roadOffset - 120; dx < w + 120; dx += 120) {
                drawPlatform(dx, h * 0.8 - 5, 60, 10, '#FFD700');
            }

            const kx = this.x; const ky = this.y - 20;
            drawPlatform(kx, ky, 140, 30, '#e30000');
            drawPlatform(kx + 110, ky + 10, 40, 20, '#cc0000');
            drawPlatform(kx - 10, ky - 15, 30, 10, '#cc0000');
            enqueueDrawCommand(kx + 10, ky - 15, kx, ky + 10, '#e30000', 12);
            drawPlatform(kx + 30, ky - 20, 45, 30, '#222222');
            enqueueDrawCommand(kx + 35, ky - 25, kx + 40, ky, '#111111', 12);
            enqueueDrawCommand(kx + 80, ky + 5, kx + 65, ky - 15, '#555555', 6);
            enqueueDrawCommand(kx + 60, ky - 20, kx + 70, ky - 10, '#111111', 8);
            this.drawWheel(kx + 20, ky + 20);
            this.drawWheel(kx + 110, ky + 20);
        },

        drawWheel(wx, wy) {
            enqueueDrawCommand(wx, wy, wx+0.1, wy+0.1, '#111111', 40);
            if (this.wheelState === 1) {
                enqueueDrawCommand(wx, wy-14, wx, wy-4, '#ffffff', 10);
                enqueueDrawCommand(wx, wy+4, wx, wy+14, '#ffffff', 10);
            } else if (this.wheelState === 2) {
                enqueueDrawCommand(wx-14, wy, wx-4, wy, '#ffffff', 10);
                enqueueDrawCommand(wx+4, wy, wx+14, wy, '#ffffff', 10);
            }
            enqueueDrawCommand(wx, wy, wx+0.1, wy+0.1, '#555555', 12);
        },

        updateMenuButtons() {
            const fbtn = document.getElementById('km-follow-btn');
            const abtn = document.getElementById('km-auto-btn');
            if (fbtn) {
                fbtn.style.background = this.followMode ? '#9333ea' : '#6b7280';
                fbtn.textContent = this.followMode ? 'πŸ›°οΈ Following: ON' : 'πŸ›°οΈ Follow Car: OFF';
            }
            if (abtn) {
                abtn.style.background = this.autoDrive ? '#f59e0b' : '#6b7280';
                abtn.textContent = this.autoDrive ? 'πŸ€– Auto Drive: ON' : 'πŸ€– Toggle Auto Drive';
            }
        }
    };

    /* ---------- INPUTS ---------- */
    window.addEventListener('keydown', (e) => {
        const key = e.key.toLowerCase();
        if (['w', 'a', 's', 'd'].includes(key)) Game.keys[key] = true;
    });
    window.addEventListener('keyup', (e) => {
        const key = e.key.toLowerCase();
        if (['w', 'a', 's', 'd'].includes(key)) Game.keys[key] = false;
    });

    /* ---------- UI ---------- */
    function initUI() {
        if (document.getElementById('kart-menu')) return;
        const menu = document.createElement('div');
        menu.id = 'kart-menu';
        menu.style.cssText = `position: fixed; top: 120px; right: 20px; width: 280px; background: linear-gradient(135deg, #111827, #1f2937); border: 2px solid #facc15; border-radius: 12px; box-shadow: 0 10px 30px rgba(0,0,0,0.8); color: white; font-family: sans-serif; z-index: 999999;`;
        menu.innerHTML = `
            <div id="km-header" style="background: #facc15; padding: 12px; cursor: move; border-top-left-radius: 10px; border-top-right-radius: 10px; font-weight: bold; color: black; display: flex; justify-content: space-between;">
                <span>🏎️ Kart Simulator v1.3</span>
                <span id="km-close" style="cursor: pointer;">Γ—</span>
            </div>
            <div style="padding: 15px;">
                <button id="km-start-btn" style="width: 100%; background: #10b981; color: white; border: none; padding: 10px; border-radius: 6px; cursor: pointer; font-weight: bold; margin-bottom: 8px;">▢️ Start Engine</button>
                <button id="km-stop-btn" style="width: 100%; background: #ef4444; color: white; border: none; padding: 10px; border-radius: 6px; cursor: pointer; font-weight: bold; margin-bottom: 12px;">Stop Engine</button>

                <h4 style="margin: 10px 0; font-size: 11px; color: #facc15; border-top: 1px solid #334155; padding-top: 10px; text-align: center;">MODO PILOTO AUTOMÁTICO</h4>
                <button id="km-auto-btn" style="width: 100%; background: #6b7280; color: white; border: none; padding: 10px; border-radius: 6px; cursor: pointer; font-weight: bold; margin-bottom: 8px;">πŸ€– Toggle Auto Drive</button>
                <button id="km-follow-btn" style="width: 100%; background: #6b7280; color: white; border: none; padding: 10px; border-radius: 6px; cursor: pointer; font-weight: bold; margin-bottom: 8px;">πŸ›°οΈ Follow Car: OFF</button>
                <button id="km-sync-btn" style="width: 100%; background: #3b82f6; color: white; border: none; padding: 10px; border-radius: 6px; cursor: pointer; font-weight: bold;">πŸ”„ Force Seat Sync</button>
            </div>
        `;
        document.body.appendChild(menu);

        document.getElementById('km-start-btn').onclick = () => Game.start();
        document.getElementById('km-stop-btn').onclick = () => Game.stop();
        document.getElementById('km-close').onclick = () => menu.style.display = 'none';

        document.getElementById('km-auto-btn').onclick = () => {
            if (!Game.active) return alert("Β‘Enciende el motor primero!");
            Game.autoDrive = !Game.autoDrive;
            Game.updateMenuButtons();
        };

        document.getElementById('km-follow-btn').onclick = () => {
            if (!Game.active) return alert("Β‘Enciende el motor primero!");
            Game.followMode = !Game.followMode;
            Game.updateMenuButtons();
        };

        document.getElementById('km-sync-btn').onclick = () => {
            if (!Game.active) return alert("Β‘Enciende el motor primero!");
            Game.syncAvatar();
        };

        let isDragging = false, offsetX, offsetY;
        const header = document.getElementById('km-header');
        header.onmousedown = (e) => { isDragging = true; offsetX = e.clientX - menu.offsetLeft; offsetY = e.clientY - menu.offsetTop; };
        document.onmousemove = (e) => { if (isDragging) { menu.style.left = (e.clientX - offsetX) + 'px'; menu.style.top = (e.clientY - offsetY) + 'px'; } };
        document.onmouseup = () => isDragging = false;
    }

    function init() {
        const canvas = document.getElementById('canvas');
        if (canvas) { drawariaCanvas = canvas; drawariaCtx = canvas.getContext('2d'); initUI(); }
        else { setTimeout(init, 1000); }
    }
    init();
})();