Simulador de Karts con seguimiento de avatar y modo de conducción automática (Auto Drive).
// ==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();
})();