您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Recreate the visual effect of an expanding tunnel of squares, using ultra-optimization of sockets.
// ==UserScript== // @name Drawaria Tunnel Visualizer // @namespace http://tampermonkey.net/ // @version 2.1 // @description Recreate the visual effect of an expanding tunnel of squares, using ultra-optimization of sockets. // @author YouTubeDrawaria // @match https://drawaria.online/* // @grant none // @license MIT // @icon https://www.google.com/s2/favicons?sz=64&domain=drawaria.online // ==/UserScript== (function() { 'use strict'; /* ---------- COMPONENTES COMPARTIDOS DEL SISTEMA (ULTRA OPTIMIZADOS) ---------- */ let drawariaSocket = null; let drawariaCanvas = null; let drawariaCtx = null; // OPTIMIZACIÓN ULTRA: Reducido a 15 FPS para máxima fluidez y menor carga. const FRAME_RATE = 10; // Cola de comandos optimizada con agrupamiento inteligente (ULTRA BATCHING) const commandQueue = []; let batchProcessor = null; // OPTIMIZACIÓN ULTRA: Tamaño del lote aumentado a 100 para procesar más comandos de una vez. const BATCH_SIZE = 100; // OPTIMIZACIÓN ULTRA: Intervalo reducido a 5ms para envío más rápido. const BATCH_INTERVAL = 5; // Intercepta WebSocket para capturar el socket del juego (ORIGINAL) const originalWebSocketSend = WebSocket.prototype.send; WebSocket.prototype.send = function(...args) { if (!drawariaSocket && this.url && this.url.includes('drawaria')) { drawariaSocket = this; console.log('🔗 Drawaria WebSocket capturado para el Túnel Visualizer.'); 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) { console.warn('⚠️ Fallo al enviar el comando:', e); } }); }, BATCH_INTERVAL); } /** * Función unificada para encolar comandos de dibujo. * CRÍTICO: Usa grosor positivo para línea, grosor negativo para relleno. * Si thickness es negativo (relleno), usa la implementación de Drawaria: * - Envía el valor absoluto del grosor como *negativo* en el comando. * OPTIMIZACIÓN ULTRA: Reducida precisión a toFixed(3) para comandos más ligeros. * @param {number} x1 - Coordenada X inicial * @param {number} y1 - Coordenada Y inicial * @param {number} x2 - Coordenada X final * @param {number} y2 - Coordenada Y final * @param {string} color - Color del objeto (ej. '#FFFFFF') * @param {number} thickness - Grosor (Positivo para línea, Negativo para relleno) */ function enqueueDrawCommand(x1, y1, x2, y2, color, thickness) { if (!drawariaCanvas || !drawariaSocket) return; const normX1 = (x1 / drawariaCanvas.width).toFixed(3); const normY1 = (y1 / drawariaCanvas.height).toFixed(3); const normX2 = (x2 / drawariaCanvas.width).toFixed(3); const normY2 = (y2 / drawariaCanvas.height).toFixed(3); // CORRECCIÓN CRÍTICA: Aplica el truco de grosor negativo solo si se pide relleno (thickness < 0) const cmdThickness = thickness < 0 ? -Math.abs(thickness) : thickness; const cmd = `42["drawcmd",0,[${normX1},${normY1},${normX2},${normY2},false,${cmdThickness},"${color}",0,0,{}]]`; commandQueue.push(cmd); // Renderizado local para retroalimentación visual inmediata if (drawariaCtx) { if (thickness > 0) { // Para líneas (positivo) drawariaCtx.strokeStyle = color; drawariaCtx.lineWidth = thickness; drawariaCtx.lineCap = 'butt'; drawariaCtx.lineJoin = 'miter'; drawariaCtx.beginPath(); drawariaCtx.moveTo(x1, y1); drawariaCtx.lineTo(x2, y2); drawariaCtx.stroke(); } else { // Para rellenos (negativo), simula un relleno local si es todo el canvas if (x1 === 0 && y1 === 0 && x2 === drawariaCanvas.width && y2 === drawariaCanvas.height) { drawariaCtx.fillStyle = color; drawariaCtx.fillRect(0, 0, drawariaCanvas.width, drawariaCanvas.height); } } } } // Función auxiliar para forzar un relleno (usando el método de línea gruesa negativa) // CORRECCIÓN: Usa diagonal completa (0,0 a width,height) como en el script de Snake para asegurar que funcione. function enqueueDrawFillCommand(width, height, color) { if (!drawariaCanvas || !drawariaSocket) return; // CORRECCIÓN CRÍTICA: Llama a enqueueDrawCommand con grosor negativo y diagonal completa. // Se usa un grosor negativo muy grande para asegurar el relleno completo. const fillThickness = -(Math.max(width, height) * 2); enqueueDrawCommand(0, 0, width, height, color, fillThickness); } /* ---------- LÓGICA DEL TUNNEL VISUALIZER (USANDO LA ESTRUCTURA) ---------- */ class TunnelVisualizer { constructor() { this.isActive = false; this.animationInterval = null; this.frame = 0; // OPTIMIZACIÓN ULTRA: Reducido número de cuadrados a 10 para menor carga por frame. this.squareCount = 1; this.maxSquareSize = 0; this.minSquareSize = 1; this.center = { x: 0, y: 0 }; this.init(); } init() { const checkGameReady = () => { const gameCanvas = document.getElementById('canvas'); if (gameCanvas) { drawariaCanvas = gameCanvas; drawariaCtx = gameCanvas.getContext('2d'); this.center = { x: drawariaCanvas.width / 2, y: drawariaCanvas.height / 2 }; // Multiplicador ligeramente mayor para que el cuadrado más grande esté fuera de vista this.maxSquareSize = Math.max(drawariaCanvas.width, drawariaCanvas.height) * 1.8; this.createGamePanel(); console.log('✅ Tunnel Visualizer inicializado.'); } else { setTimeout(checkGameReady, 100); } }; checkGameReady(); } createGamePanel() { const existingPanel = document.getElementById('tunnel-panel'); if (existingPanel) existingPanel.remove(); const panel = document.createElement('div'); panel.id = 'tunnel-panel'; panel.style.cssText = ` position: fixed !important; top: 250px !important; right: 20px !important; width: 250px !important; z-index: 2147483647 !important; background: linear-gradient(135deg, #2a2a3a, #1a1a2e) !important; border: 2px solid #5d5dff !important; border-radius: 12px !important; color: white !important; font-family: 'Segoe UI', Arial, sans-serif !important; box-shadow: 0 0 20px rgba(93, 93, 255, 0.3) !important; padding: 15px !important; text-align: center !important; `; panel.innerHTML = ` <h3 style="margin-top: 0; color: #5d5dff; cursor: grab;">🌌 Drawaria Tunnel Effect</h3> <div id="status-display" style="margin-bottom: 10px; color: #ffc107;"> Status: Paused </div> <button id="toggle-animation" style=" width: 100%; padding: 10px; background: #5d5dff; color: white; border: none; border-radius: 8px; cursor: pointer; font-size: 14px; font-weight: bold; ">▶️ Start Animation</button> <button id="draw-black-bg" style=" width: 100%; padding: 10px; background: #333; color: white; border: none; border-radius: 8px; cursor: pointer; font-size: 14px; font-weight: bold; margin-top: 10px; ">⚫ Draw Black Background (Click First!)</button> `; document.body.appendChild(panel); this.setupEventListeners(); this.makePanelDraggable(panel); } setupEventListeners() { document.getElementById('toggle-animation').addEventListener('click', () => this.toggleAnimation()); document.getElementById('draw-black-bg').addEventListener('click', () => this.drawBlackBackground()); } drawBlackBackground() { if (!drawariaCanvas || !drawariaSocket) return; // Llama a la función de relleno corregida enqueueDrawFillCommand(drawariaCanvas.width, drawariaCanvas.height, '#000000'); document.getElementById('status-display').textContent = 'Status: Background Black'; } toggleAnimation() { const btn = document.getElementById('toggle-animation'); const statusEl = document.getElementById('status-display'); if (!this.isActive) { this.startAnimation(); btn.textContent = '⏸️ Pause Animation'; btn.style.background = '#ffc107'; statusEl.textContent = 'Status: Running'; } else { this.pauseAnimation(); btn.textContent = '▶️ Resume Animation'; btn.style.background = '#5d5dff'; statusEl.textContent = 'Status: Paused'; } } startAnimation() { if (this.isActive) return; this.isActive = true; this.animationLoop(); } pauseAnimation() { this.isActive = false; if (this.animationInterval) { clearTimeout(this.animationInterval); this.animationInterval = null; } } animationLoop() { if (!this.isActive) return; this.drawTunnelFrame(); this.frame++; this.animationInterval = setTimeout(() => this.animationLoop(), Math.floor(1000 / FRAME_RATE)); } drawTunnelFrame() { // CORRECCIÓN CRÍTICA: Limpia el fondo cada 10 fotogramas para ultra-reducir la carga del servidor (optimización). // Esto es necesario para que no se vean los rastros de los cuadrados anteriores. if (this.frame % 10 === 0) { enqueueDrawFillCommand(drawariaCanvas.width, drawariaCanvas.height, '#000000'); } const baseThickness = 2; const sizeIncrement = this.maxSquareSize / this.squareCount; // OPTIMIZACIÓN ULTRA: Velocidad de animación ajustada para mayor suavidad y menor cómputo. const speed = 1.5; for (let i = 0; i < this.squareCount; i++) { const sizeOffset = (this.frame * speed) % (sizeIncrement * this.squareCount); let size = (i * sizeIncrement) + sizeOffset; // Cálculo modular para un bucle continuo de tamaño. const normalizedSize = size % this.maxSquareSize; const color = '#FFFFFF'; // Dibuja el contorno del cuadrado usando el método original con grosor POSITIVO this.drawSquareOutline(normalizedSize, color, baseThickness); } // Cuadrado central azul this.drawSquareOutline(this.minSquareSize + 5, '#87CEEB', baseThickness * 1.5); } drawSquareOutline(size, color, thickness) { const cX = this.center.x; const cY = this.center.y; const halfSize = size / 2; const x1 = cX - halfSize; const y1 = cY - halfSize; const x2 = cX + halfSize; const y2 = cY + halfSize; // Usando la función original, ahora corregida para líneas enqueueDrawCommand(x1, y1, x2, y1, color, thickness); // Superior enqueueDrawCommand(x2, y1, x2, y2, color, thickness); // Derecha enqueueDrawCommand(x2, y2, x1, y2, color, thickness); // Inferior enqueueDrawCommand(x1, y2, x1, y1, color, thickness); // Izquierda } makePanelDraggable(panel) { let isDragging = false; let currentX, currentY, initialX, initialY, xOffset = 0, yOffset = 0; const header = panel.querySelector('h3'); if (!header) return; const dragStart = (e) => { initialX = e.clientX - xOffset; initialY = e.clientY - yOffset; isDragging = true; header.style.cursor = 'grabbing'; }; const dragEnd = () => { initialX = currentX; initialY = currentY; isDragging = false; header.style.cursor = 'grab'; }; const drag = (e) => { if (isDragging) { e.preventDefault(); currentX = e.clientX - initialX; currentY = e.clientY - initialY; xOffset = currentX; yOffset = currentY; panel.style.transform = `translate3d(${currentX}px, ${currentY}px, 0)`; } }; header.addEventListener("mousedown", dragStart); document.addEventListener("mouseup", dragEnd); document.addEventListener("mousemove", drag); } } // Inicialización del visualizador (MISMO MÉTODO QUE SNAKE) const initVisualizer = () => { const visualizer = new TunnelVisualizer(); }; if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initVisualizer); } else { setTimeout(initVisualizer, 500); } })();