Drawaria Brusher Engine Pro

Implementa un motor de pinceles profesional y una nueva UI, con una arquitectura clara para la integración de red, panel arrastrable y múltiples tipos de pinceles.

// ==UserScript==
// @name         Drawaria Brusher Engine Pro
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Implementa un motor de pinceles profesional y una nueva UI, con una arquitectura clara para la integración de red, panel arrastrable y múltiples tipos de pinceles.
// @author       YouTubeDrawaria
// @match        https://drawaria.online/*
// @grant        none
// @run-at       document-end
// @license      MIT
// @icon         https://www.google.com/s2/favicons?sz=64&domain=drawaria.online
// ==/UserScript==

(function() {
    'use strict';

    // =========================================================================
    // MÓDULO 1: UIManager - GESTOR DE LA INTERFAZ DE USUARIO
    // Responsable de crear y manejar el panel de herramientas.
    // =========================================================================
    const UIManager = {
        panel: null,
        brushState: null,
        isDragging: false,
        dragOffsetX: 0,
        dragOffsetY: 0,

        initialize(initialState) {
            this.brushState = initialState;
            this.createPanel();
            this.attachEventListeners();
        },

        createPanel() {
            this.panel = document.createElement('div');
            this.panel.id = 'pro-brush-panel';
            this.panel.innerHTML = `
                <h4 id="proBrushPanelHeader" title="Arrastra para mover el panel">Motor de Pincel PRO</h4>
                <div class="control-group">
                    <label>Color: <input type="color" id="proBrushColor" value="${this.brushState.color}" title="Selecciona el color del pincel"></label>
                </div>
                <div class="control-group">
                    <label>Tamaño: <span id="proBrushSizeValue">${this.brushState.size}</span></label>
                    <input type="range" id="proBrushSize" min="1" max="150" value="${this.brushState.size}" title="Ajusta el tamaño del pincel">
                </div>
                <div class="control-group">
                    <label>Opacidad: <span id="proBrushOpacityValue">${this.brushState.opacity}</span></label>
                    <input type="range" id="proBrushOpacity" min="0.01" max="1.0" step="0.01" value="${this.brushState.opacity}" title="Ajusta la opacidad del pincel">
                </div>
                <div class="control-group">
                    <label>Tipo de Pincel:</label>
                    <select id="proBrushType" title="Selecciona el tipo de pincel">
                        <option value="pencil">Lápiz (Básico)</option>
                        <option value="dynamic" selected>Dinámico (Velocidad)</option>
                        <option value="calligraphy">Caligrafía</option>
                        <option value="airbrush">Aerógrafo</option>
                        <option value="marker">Rotulador</option>
                        <option value="chalk">Tiza</option>
                        <option value="watercolor">Acuarela (Simple)</option>
                        <option value="pixel">Pixel</option>
                        <option value="dot">Puntos</option>
                        <option value="line">Línea Sólida</option>
                        <option value="dashed">Línea Discontinua</option>
                        <option value="fur">Pelo/Pasto (Simulado)</option>
                        <option value="scatter">Dispersión (Simple)</option>
                        <option value="smoother">Suavizador (Simple)</option>
                        <option value="star">Estrella (Estampado)</option>
                        <option value="heart">Corazón (Estampado)</option>
                        <option value="square">Cuadrado (Estampado)</option>
                        <option value="circle">Círculo (Estampado)</option>
                        <option value="eraser">Borrador</option>
                        <option value="fill">Relleno (Experimental)</option>
                        <option value="blur">Desenfoque (Experimental)</option>
                        <option value="sharpen">Enfocar (Experimental)</option>
                        <option value="lighten">Aclarar (Experimental)</option>
                        <option value="darken">Oscurecer (Experimental)</option>
                        <!-- Total: 24 tipos de pincel (incluyendo los 3 originales) -->
                    </select>
                </div>
                <div class="info-warning">Modo Un Jugador: Solo tú ves estos trazos. Para Multijugador, analiza la red (F12 > Network > WS).</div>
            `;
            document.body.appendChild(this.panel);
            this.injectCSS();
        },

        injectCSS() {
            const style = document.createElement('style');
            style.innerHTML = `
                #pro-brush-panel {
                    position: fixed; top: 15px; right: 160px; background: #fff;
                    border: 1px solid #ccc; padding: 15px; border-radius: 10px;
                    box-shadow: 0 5px 15px rgba(0,0,0,0.25); z-index: 10001;
                    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; min-width: 280px;
                    cursor: default; /* Default cursor for content */
                }
                #proBrushPanelHeader {
                    margin: 0 0 15px 0; padding-bottom: 10px; border-bottom: 1px solid #ddd;
                    cursor: grab; /* Cursor for dragging */
                    user-select: none; /* Prevent text selection during drag */
                    -webkit-user-select: none; /* Safari */
                    -moz-user-select: none; /* Firefox */
                    -ms-user-select: none; /* IE/Edge */
                }
                #proBrushPanelHeader:active {
                    cursor: grabbing;
                }
                .control-group { margin-bottom: 12px; }
                .control-group label { display: flex; justify-content: space-between; align-items: center; }
                .control-group input[type=range] { flex-grow: 1; margin-left: 10px; }
                .info-warning { font-size: 0.8em; color: #888; margin-top: 15px; text-align: center; }
                input[type="color"] { border: none; padding: 0; width: 40px; height: 25px; cursor: pointer; }
                select { width: 100%; padding: 5px; border: 1px solid #ddd; border-radius: 5px; background-color: #f9f9f9; }
            `;
            document.head.appendChild(style);
        },

        attachEventListeners() {
            const header = document.getElementById('proBrushPanelHeader');

            // Draggable functionality
            header.addEventListener('mousedown', (e) => {
                this.isDragging = true;
                this.dragOffsetX = e.clientX - this.panel.getBoundingClientRect().left;
                this.dragOffsetY = e.clientY - this.panel.getBoundingClientRect().top;
                this.panel.style.cursor = 'grabbing';
            });

            document.addEventListener('mousemove', (e) => {
                if (!this.isDragging) return;
                this.panel.style.left = `${e.clientX - this.dragOffsetX}px`;
                this.panel.style.top = `${e.clientY - this.dragOffsetY}px`;
            });

            document.addEventListener('mouseup', () => {
                this.isDragging = false;
                this.panel.style.cursor = 'default';
            });

            // UI Control Event Listeners
            document.getElementById('proBrushColor').addEventListener('input', (e) => this.brushState.color = e.target.value);
            document.getElementById('proBrushSize').addEventListener('input', (e) => {
                this.brushState.size = parseFloat(e.target.value);
                document.getElementById('proBrushSizeValue').textContent = this.brushState.size.toFixed(0);
            });
            document.getElementById('proBrushOpacity').addEventListener('input', (e) => {
                this.brushState.opacity = parseFloat(e.target.value);
                document.getElementById('proBrushOpacityValue').textContent = this.brushState.opacity.toFixed(2);
            });
            document.getElementById('proBrushType').addEventListener('change', (e) => {
                this.brushState.brushType = e.target.value;
                // Reset some properties if changing brush type, e.g., for eraser
                if (e.target.value === 'eraser') {
                    document.getElementById('proBrushColor').value = '#000000'; // Color doesn't matter for eraser visually
                    document.getElementById('proBrushOpacity').value = 1.0;
                    this.brushState.color = '#000000'; // Update internal state too
                    this.brushState.opacity = 1.0;
                    document.getElementById('proBrushOpacityValue').textContent = '1.00';
                }
            });
        }
    };

    // =========================================================================
    // MÓDULO 2: NetworkHandler - GESTOR DE COMUNICACIÓN
    // Responsable de enviar los datos de dibujo al servidor.
    // ESTA ES LA PARTE QUE REQUIERE ANÁLISIS MANUAL.
    // =========================================================================
    const NetworkHandler = {
        // En un escenario real, aquí se usaría el objeto `socket` del juego.
        // Ejemplo: `const socket = window.io;`

        sendDrawCommand(commandData) {
            // TODO: ¡Esta es la parte más crítica y compleja!
            // Para que funcione en multijugador, necesitas descifrar y replicar
            // los mensajes que envía el juego.

            // 1. Usa las "Herramientas de Desarrollador" de tu navegador (F12).
            // 2. Ve a la pestaña "Red" (Network) y filtra por "WS" (WebSockets).
            // 3. Dibuja un trazo en el juego y observa los mensajes enviados.
            // 4. Analiza la estructura de esos datos. Probablemente sea un array o un objeto JSON.
            //    Ejemplo de formato posible: `socket.emit('drawcmd', [0, x1, y1, x2, y2, color, size, opacity]);`

            // Reemplaza el console.log con el `socket.emit` real cuando lo hayas descifrado.
            console.log("Comando de Red (Simulado):", commandData);

            // Si el juego usa un objeto 'socket' global (común con Socket.IO):
            // if (window.socket && typeof window.socket.emit === 'function') {
            //     // Un ejemplo común para dibujar una línea:
            //     // window.socket.emit('draw', {
            //     //     type: 0, // 0 for line, 1 for fill, etc. (depends on game)
            //     //     x1: commandData[1], y1: commandData[2],
            //     //     x2: commandData[3], y2: commandData[4],
            //     //     color: commandData[5],
            //     //     size: commandData[6],
            //     //     opacity: commandData[7]
            //     // });
            //     // O más simple si solo replica la entrada del ratón:
            //     // window.socket.emit('drawPoint', { x: commandData[3], y: commandData[4], color: commandData[5], size: commandData[6], opacity: commandData[7] });
            //     // ¡Es CRÍTICO que el formato coincida con lo que el juego espera!
            //     // Para los pinceles más complejos que dibujan múltiples elementos (ej. aerógrafo, tiza),
            //     // podría ser necesario enviar múltiples comandos de línea/punto simples,
            //     // o si el juego lo soporta, un comando con un 'brushType' personalizado.
            // }
        }
    };

    // =========================================================================
    // MÓDULO 3: BrushEngine - EL MOTOR DE PINCEL
    // Contiene toda la lógica sobre cómo se dibuja en el lienzo.
    // =========================================================================
    const BrushEngine = {
        ctx: null,
        canvas: null,
        brushState: {
            color: '#1a1a1a',
            size: 20,
            opacity: 1.0,
            brushType: 'dynamic', // Default
            isDrawing: false,
            lastX: 0, lastY: 0,
            lastTime: 0, lastWidth: 20
        },

        initialize(canvas) {
            this.canvas = canvas;
            this.ctx = canvas.getContext('2d', { willReadFrequently: true }); // Enable for pixel manipulation
            UIManager.initialize(this.brushState); // Conectar UI al estado del motor
        },

        startDrawing(pos) {
            this.brushState.isDrawing = true;
            [this.brushState.lastX, this.brushState.lastY] = [pos.x, pos.y];
            this.brushState.lastTime = Date.now();
            this.brushState.lastWidth = this.brushState.size;

            // For some brush types, a single 'dot' or 'stamp' might be needed on start
            if (['dot', 'star', 'heart', 'square', 'circle'].includes(this.brushState.brushType)) {
                this.drawSinglePoint(pos);
            }
        },

        stopDrawing() {
            this.brushState.isDrawing = false;
        },

        getDistance(p1, p2) {
            return Math.hypot(p2.x - p1.x, p2.y - p1.y);
        },

        // Function to draw a single point or stamp for certain brush types
        drawSinglePoint(pos) {
            const ctx = this.ctx;
            const state = this.brushState;

            ctx.save(); // Save current state
            ctx.strokeStyle = state.color;
            ctx.fillStyle = state.color;
            ctx.globalAlpha = state.opacity;
            ctx.lineCap = 'round';
            ctx.lineJoin = 'round';
            ctx.lineWidth = state.size;
            ctx.filter = 'none'; // Ensure no lingering filters

            switch(state.brushType) {
                case 'dot':
                    ctx.beginPath();
                    ctx.arc(pos.x, pos.y, state.size / 2, 0, Math.PI * 2);
                    ctx.fill();
                    ctx.closePath();
                    break;
                case 'star':
                    this.drawStar(ctx, pos.x, pos.y, 5, state.size / 2, state.size / 4);
                    ctx.fill();
                    break;
                case 'heart':
                    this.drawHeart(ctx, pos.x, pos.y, state.size / 2);
                    ctx.fill();
                    break;
                case 'square':
                    ctx.fillRect(pos.x - state.size / 2, pos.y - state.size / 2, state.size, state.size);
                    break;
                case 'circle':
                    ctx.beginPath();
                    ctx.arc(pos.x, pos.y, state.size / 2, 0, Math.PI * 2);
                    ctx.fill();
                    break;
                default:
                    // For other brushes, a single point draw isn't typical, but this is a fallback
                    ctx.beginPath();
                    ctx.arc(pos.x, pos.y, state.size / 2, 0, Math.PI * 2);
                    ctx.fill();
                    ctx.closePath();
                    break;
            }
            ctx.restore(); // Restore to previous state
            NetworkHandler.sendDrawCommand([
                0, // Hypothetical command for "point" or "shape"
                pos.x, pos.y, pos.x, pos.y, // start/end same for point
                state.color, state.size, state.opacity, state.brushType // Include brush type for potential network replication
            ]);
        },

        // Helper function for drawing a star
        drawStar(ctx, cx, cy, spikes, outerRadius, innerRadius) {
            let rot = Math.PI / 2 * 3;
            let x = cx;
            let y = cy;
            let step = Math.PI / spikes;

            ctx.beginPath();
            ctx.moveTo(cx, cy - outerRadius);
            for (let i = 0; i < spikes; i++) {
                x = cx + Math.cos(rot) * outerRadius;
                y = cy + Math.sin(rot) * outerRadius;
                ctx.lineTo(x, y);
                rot += step;

                x = cx + Math.cos(rot) * innerRadius;
                y = cy + Math.sin(rot) * innerRadius;
                ctx.lineTo(x, y);
                rot += step;
            }
            ctx.lineTo(cx, cy - outerRadius);
            ctx.closePath();
        },

        // Helper function for drawing a heart
        drawHeart(ctx, x, y, size) {
            ctx.beginPath();
            ctx.moveTo(x, y + size / 4);
            ctx.bezierCurveTo(x + size / 2, y - size / 2, x + size, y, x, y + size);
            ctx.bezierCurveTo(x - size, y, x - size / 2, y - size / 2, x, y + size / 4);
            ctx.closePath();
        },


        draw(pos) {
            if (!this.brushState.isDrawing) return;

            const ctx = this.ctx;
            const state = this.brushState;
            const currentTime = Date.now();
            const timeDiff = currentTime - state.lastTime;
            const distance = this.getDistance({x: state.lastX, y: state.lastY}, pos);
            const speed = distance / (timeDiff || 1); // Avoid division by zero

            let currentWidth = state.size;
            let currentAlpha = state.opacity;
            let currentLineCap = 'round';
            let currentLineJoin = 'round';
            let globalComposite = 'source-over'; // Default blending mode

            ctx.save(); // Save the current canvas state before applying transformations/styles
            ctx.filter = 'none'; // Reset any previous filter

            switch(state.brushType) {
                case 'dynamic':
                    const dynamicWidth = Math.max(state.size / (speed * 0.5 + 1), state.size * 0.1);
                    currentWidth = (state.lastWidth * 0.7) + (dynamicWidth * 0.3); // Smoothed
                    break;
                case 'calligraphy':
                    currentWidth = Math.max(state.size - (distance * 0.5), state.size * 0.2);
                    currentLineCap = 'butt';
                    currentLineJoin = 'miter';
                    break;
                case 'pencil':
                    currentWidth = state.size;
                    currentAlpha = state.opacity * 0.8; // Slightly less opaque
                    break;
                case 'airbrush':
                    // Simulate airbrush by drawing multiple semi-transparent dots or small lines
                    ctx.globalAlpha = state.opacity * 0.1; // Very low opacity for each sub-stroke
                    ctx.fillStyle = state.color;
                    for (let i = 0; i < 5; i++) {
                        const offsetX = (Math.random() - 0.5) * state.size * 2;
                        const offsetY = (Math.random() - 0.5) * state.size * 2;
                        ctx.beginPath();
                        ctx.arc(pos.x + offsetX, pos.y + offsetY, state.size / 4, 0, Math.PI * 2);
                        ctx.fill();
                    }
                    ctx.restore(); // Restore before the main stroke path to not interfere
                    // No main line drawn for airbrush, it's all dots
                    [state.lastX, state.lastY] = [pos.x, pos.y];
                    state.lastTime = currentTime;
                    return; // Skip the main stroke drawing below

                case 'marker':
                    currentWidth = state.size;
                    currentAlpha = state.opacity * 0.9; // Slightly less transparent than full opacity
                    currentLineCap = 'square';
                    currentLineJoin = 'miter';
                    break;
                case 'chalk':
                    // Simulate rough texture by drawing multiple slightly offset lines
                    ctx.globalAlpha = state.opacity * 0.4;
                    ctx.strokeStyle = state.color;
                    for (let i = 0; i < 3; i++) {
                        const offsetX = (Math.random() - 0.5) * state.size * 0.2;
                        const offsetY = (Math.random() - 0.5) * state.size * 0.2;
                        ctx.beginPath();
                        ctx.moveTo(state.lastX + offsetX, state.lastY + offsetY);
                        ctx.lineTo(pos.x + offsetX, pos.y + offsetY);
                        ctx.lineWidth = state.size * (0.8 + Math.random() * 0.4);
                        ctx.stroke();
                        ctx.closePath();
                    }
                    ctx.restore();
                    [state.lastX, state.lastY] = [pos.x, pos.y];
                    state.lastTime = currentTime;
                    return;

                case 'watercolor':
                    // Simulate transparency and blending. Very basic approximation.
                    ctx.globalAlpha = state.opacity * 0.3; // Low opacity
                    ctx.strokeStyle = state.color;
                    ctx.lineWidth = state.size;
                    ctx.lineCap = 'round';
                    ctx.lineJoin = 'round';
                    for (let i = 0; i < 3; i++) {
                        // Draw slightly offset and blurred lines
                        ctx.filter = `blur(${state.size / 10}px)`; // Apply blur
                        ctx.beginPath();
                        ctx.moveTo(state.lastX + (Math.random() - 0.5) * state.size / 5, state.lastY + (Math.random() - 0.5) * state.size / 5);
                        ctx.lineTo(pos.x + (Math.random() - 0.5) * state.size / 5, pos.y + (Math.random() - 0.5) * state.size / 5);
                        ctx.stroke();
                        ctx.closePath();
                    }
                    ctx.filter = 'none'; // Reset filter
                    ctx.restore();
                    [state.lastX, state.lastY] = [pos.x, pos.y];
                    state.lastTime = currentTime;
                    return; // Skip main stroke

                case 'pixel':
                    currentWidth = Math.max(1, Math.round(state.size / 5)) * 5; // Snap to a grid-like size
                    currentAlpha = state.opacity;
                    currentLineCap = 'butt';
                    currentLineJoin = 'miter';
                    ctx.fillStyle = state.color; // Use fill for pixel squares
                    // This creates a continuous pixel line
                    const dx = pos.x - state.lastX;
                    const dy = pos.y - state.lastY;
                    const steps = Math.max(Math.abs(dx), Math.abs(dy)) / currentWidth; // Number of "pixels"
                    for (let i = 0; i <= steps; i++) {
                        const px = state.lastX + (dx * i / steps);
                        const py = state.lastY + (dy * i / steps);
                        ctx.fillRect(Math.round(px / currentWidth) * currentWidth, Math.round(py / currentWidth) * currentWidth, currentWidth, currentWidth);
                    }
                    ctx.restore();
                    [state.lastX, state.lastY] = [pos.x, pos.y];
                    state.lastTime = currentTime;
                    return;

                case 'dot':
                    // Draw dots along the path
                    const stepDistance = state.size * 0.75; // Distance between dots
                    const numDots = Math.floor(distance / stepDistance);
                    for (let i = 0; i <= numDots; i++) {
                        const ratio = i / numDots;
                        const interpolatedX = state.lastX + (pos.x - state.lastX) * ratio;
                        const interpolatedY = state.lastY + (pos.y - state.lastY) * ratio;
                        ctx.beginPath();
                        ctx.arc(interpolatedX, interpolatedY, state.size / 2, 0, Math.PI * 2);
                        ctx.fillStyle = state.color;
                        ctx.globalAlpha = state.opacity;
                        ctx.fill();
                        ctx.closePath();
                    }
                    ctx.restore();
                    [state.lastX, state.lastY] = [pos.x, pos.y];
                    state.lastTime = currentTime;
                    return;

                case 'line': // Simple solid line
                    currentWidth = state.size;
                    break;
                case 'dashed':
                    currentWidth = state.size;
                    ctx.setLineDash([state.size / 2, state.size / 2]); // Dashes and gaps are half size
                    break;
                case 'fur':
                    // Simulate fur by drawing many short, slightly randomized strokes
                    ctx.globalAlpha = state.opacity * 0.5;
                    const angle = Math.atan2(pos.y - state.lastY, pos.x - state.lastX);
                    ctx.strokeStyle = state.color;
                    ctx.lineWidth = Math.max(1, state.size / 5);
                    for (let i = 0; i < 10; i++) {
                        const offsetAngle = angle + (Math.random() - 0.5) * Math.PI / 4; // Vary angle
                        const length = state.size * (0.5 + Math.random() * 0.5); // Vary length
                        const startX = pos.x + Math.cos(offsetAngle + Math.PI / 2) * (Math.random() - 0.5) * state.size * 0.5;
                        const startY = pos.y + Math.sin(offsetAngle + Math.PI / 2) * (Math.random() - 0.5) * state.size * 0.5;
                        ctx.beginPath();
                        ctx.moveTo(startX, startY);
                        ctx.lineTo(startX + Math.cos(offsetAngle) * length, startY + Math.sin(offsetAngle) * length);
                        ctx.stroke();
                        ctx.closePath();
                    }
                    ctx.restore();
                    [state.lastX, state.lastY] = [pos.x, pos.y];
                    state.lastTime = currentTime;
                    return;

                case 'scatter':
                    // Draw random dots around the line path
                    const numScatters = Math.floor(distance / (state.size / 2));
                    ctx.fillStyle = state.color;
                    for (let i = 0; i < numScatters; i++) {
                        const ratio = i / numScatters;
                        const interpolatedX = state.lastX + (pos.x - state.lastX) * ratio;
                        const interpolatedY = state.lastY + (pos.y - state.lastY) * ratio;
                        const scatterX = interpolatedX + (Math.random() - 0.5) * state.size * 2;
                        const scatterY = interpolatedY + (Math.random() - 0.5) * state.size * 2;
                        ctx.beginPath();
                        ctx.arc(scatterX, scatterY, state.size * (0.1 + Math.random() * 0.3), 0, Math.PI * 2);
                        ctx.globalAlpha = state.opacity * (0.2 + Math.random() * 0.8);
                        ctx.fill();
                        ctx.closePath();
                    }
                    ctx.restore();
                    [state.lastX, state.lastY] = [pos.x, pos.y];
                    state.lastTime = currentTime;
                    return;

                case 'smoother':
                    // A simple approximation using blur. Real smoothing is complex.
                    currentWidth = state.size * 1.5;
                    currentAlpha = state.opacity * 0.1; // Very low alpha to subtly blend
                    ctx.globalCompositeOperation = 'source-over'; // Ensures it blends
                    ctx.filter = `blur(${state.size / 5}px)`; // Apply blur
                    ctx.strokeStyle = state.color; // Color doesn't matter much here
                    break;

                case 'star':
                case 'heart':
                case 'square':
                case 'circle':
                    // These are stamping brushes, drawing on mousemove along the path
                    const stampDistance = state.size * 0.5; // Distance between stamps
                    const numStamps = Math.floor(distance / stampDistance);
                    for (let i = 0; i <= numStamps; i++) {
                        const ratio = i / numStamps;
                        const interpolatedX = state.lastX + (pos.x - state.lastX) * ratio;
                        const interpolatedY = state.lastY + (pos.y - state.lastY) * ratio;
                        this.drawSinglePoint({x: interpolatedX, y: interpolatedY}); // Reuse drawSinglePoint
                    }
                    ctx.restore();
                    [state.lastX, state.lastY] = [pos.x, pos.y];
                    state.lastTime = currentTime;
                    return;

                case 'eraser':
                    currentWidth = state.size;
                    currentAlpha = 1.0; // Eraser is typically full opacity
                    globalComposite = 'destination-out'; // Makes pixels transparent
                    break;

                // Experimental Image Manipulation Brushes (VERY CPU Intensive and limited)
                // These will modify existing pixels rather than drawing new ones.
                // Not suitable for networked play as they modify image data directly,
                // not vector commands.
                case 'fill':
                    // This isn't a "brush" in the stroke sense. For a fill tool, you'd usually
                    // click once and fill a contiguous area. As a "brush" it means painting solid color.
                    ctx.beginPath();
                    ctx.arc(pos.x, pos.y, state.size / 2, 0, Math.PI * 2); // Draw a solid circle
                    ctx.fillStyle = state.color;
                    ctx.globalAlpha = state.opacity;
                    ctx.fill();
                    ctx.closePath();
                    ctx.restore();
                    [state.lastX, state.lastY] = [pos.x, pos.y];
                    state.lastTime = currentTime;
                    return;

                case 'blur':
                case 'sharpen':
                case 'lighten':
                case 'darken':
                    // These require direct ImageData manipulation which is extremely slow
                    // for continuous drawing and cannot be sent over network as vector data.
                    // They are generally tools applied on an area, not real-time brushes.
                    // For demonstration, we're applying a filter *globally* to the canvas
                    // but triggering it by drawing, making it seem localized.
                    // This is NOT a true localized pixel manipulation brush.
                    ctx.filter = ''; // Reset filter first

                    if (state.brushType === 'blur') ctx.filter = `blur(${state.size / 10}px)`;
                    else if (state.brushType === 'sharpen') ctx.filter = `contrast(1.1) brightness(1.05)`; // Simple sharpen approx
                    else if (state.brushType === 'lighten') ctx.filter = `brightness(1.1) saturate(1.05)`;
                    else if (state.brushType === 'darken') ctx.filter = `brightness(0.9) saturate(1.05)`;

                    // Draw a transparent line. The purpose is to trigger the filter on the context.
                    // The filter applies to everything drawn *after* it's set until it's reset.
                    // This is a hack, not a true pixel-level brush.
                    ctx.beginPath();
                    ctx.moveTo(state.lastX, state.lastY);
                    ctx.lineTo(pos.x, pos.y);
                    ctx.strokeStyle = state.color; // Color doesn't technically matter for filter effect
                    ctx.lineWidth = state.size;
                    ctx.globalAlpha = 0.01; // Very low alpha so it's practically invisible, but triggers the filter context
                    ctx.stroke();
                    ctx.closePath();

                    ctx.filter = 'none'; // Reset filter immediately
                    ctx.restore();
                    [state.lastX, state.lastY] = [pos.x, pos.y];
                    state.lastTime = currentTime;
                    return;

                default: // Default to pencil if unknown, or basic line for others
                    currentWidth = state.size;
                    break;
            }

            // Apply calculated styles
            ctx.strokeStyle = state.color;
            ctx.lineWidth = currentWidth;
            ctx.globalAlpha = currentAlpha;
            ctx.lineCap = currentLineCap;
            ctx.lineJoin = currentLineJoin;
            ctx.globalCompositeOperation = globalComposite;
            ctx.setLineDash([]); // Ensure no dashes from previous strokes

            ctx.beginPath();
            ctx.moveTo(state.lastX, state.lastY);
            ctx.lineTo(pos.x, pos.y);
            ctx.stroke();
            ctx.closePath();

            ctx.restore(); // Restore the canvas state (important for filters, composite operations etc.)

            // Here, send the command through the network handler.
            // It's crucial to send parameters that the game's server expects.
            // For complex brushes, you might need to send multiple simpler commands
            // or a custom command type if the game supports it.
            NetworkHandler.sendDrawCommand([
                0, // Hypothetical command for "line"
                state.lastX, state.lastY,
                pos.x, pos.y,
                state.color, currentWidth, currentAlpha, state.brushType // Include brush type and final calculated properties
            ]);

            // Update last positions for next segment
            [state.lastX, state.lastY] = [pos.x, pos.y];
            state.lastTime = currentTime;
            state.lastWidth = currentWidth; // Store current width for smoothing
        }
    };

    // =========================================================================
    // MÓDULO 4: CanvasHijacker - INTERCEPTOR DEL LIENZO
    // Toma el control de los eventos del lienzo original del juego.
    // =========================================================================
    const CanvasHijacker = {
        initialize(canvas) {
            // Esta función reemplaza todos los listeners de eventos del ratón
            // clonando el nodo, para desactivar la lógica original del juego.
            // Nota: Clonar el nodo puede resetear algunas propiedades del canvas
            // que el juego pudo haber configurado. Si hay problemas, una alternativa
            // sería usar removeEventListener si se conoce el listener, o añadir
            // nuestros listeners con useCapture = true y stopPropagation().
            const newCanvas = canvas.cloneNode(true);
            canvas.parentNode.replaceChild(newCanvas, canvas);
            console.log("Control del lienzo tomado. La lógica de dibujo original ha sido desactivada.");

            // Asegurar que el nuevo canvas tenga el mismo tamaño
            newCanvas.width = canvas.width;
            newCanvas.height = canvas.height;

            BrushEngine.initialize(newCanvas); // Iniciar nuestro motor en el nuevo lienzo

            function getMousePos(evt) {
                const rect = newCanvas.getBoundingClientRect();
                return { x: evt.clientX - rect.left, y: evt.clientY - rect.top };
            }

            // Mouse Events
            newCanvas.addEventListener('mousedown', (e) => {
                e.stopPropagation(); e.preventDefault(); // Prevent game's default behavior
                BrushEngine.startDrawing(getMousePos(e));
            });
            newCanvas.addEventListener('mousemove', (e) => {
                e.stopPropagation(); e.preventDefault();
                BrushEngine.draw(getMousePos(e));
            });
            newCanvas.addEventListener('mouseup', () => BrushEngine.stopDrawing());
            newCanvas.addEventListener('mouseleave', () => BrushEngine.stopDrawing());

            // Touch Events for basic mobile compatibility
            newCanvas.addEventListener('touchstart', (e) => {
                e.preventDefault(); // Prevent scrolling/zooming on canvas
                const touch = e.touches[0];
                BrushEngine.startDrawing(getMousePos({ clientX: touch.clientX, clientY: touch.clientY }));
            }, { passive: false }); // Use passive: false to allow preventDefault

            newCanvas.addEventListener('touchmove', (e) => {
                e.preventDefault();
                const touch = e.touches[0];
                BrushEngine.draw(getMousePos({ clientX: touch.clientX, clientY: touch.clientY }));
            }, { passive: false });

            newCanvas.addEventListener('touchend', () => BrushEngine.stopDrawing());
            newCanvas.addEventListener('touchcancel', () => BrushEngine.stopDrawing());
        }
    };

    // --- PUNTO DE ENTRADA DEL SCRIPT ---
    const
        // Se ejecuta repetidamente hasta que el lienzo del juego esté listo.
        initInterval = setInterval(() => {
            const gameCanvas = document.getElementById('canvas');
            if (gameCanvas && gameCanvas.getContext) {
                clearInterval(initInterval);
                console.log("Canvas de Drawaria detectado. Iniciando script de Motor de Pincel Profesional...");
                CanvasHijacker.initialize(gameCanvas);
            }
        }, 500);

})();