Drawaria Canvas Live Kart 🏎️

Simulador de Karts con seguimiento de avatar y modo de conducción automática (Auto Drive).

Dovrai installare un'estensione come Tampermonkey, Greasemonkey o Violentmonkey per installare questo script.

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

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Userscripts per installare questo script.

Dovrai installare un'estensione come ad esempio Tampermonkey per installare questo script.

Dovrai installare un gestore di script utente per installare questo script.

(Ho già un gestore di script utente, lasciamelo installare!)

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

(Ho già un gestore di stile utente, lasciamelo installare!)

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