Drawaria Animation Library

Advanced drawing animation library for multiplayer drawing games

Tento skript by neměl být instalován přímo. Jedná se o knihovnu, kterou by měly jiné skripty využívat pomocí meta příkazu // @require https://update.greatest.deepsurf.us/scripts/546216/1643828/Drawaria%20Animation%20Library.js

// ==UserScript==
// @name         Drawaria Animation Library
// @namespace    drawaria-animations
// @version      3.0
// @description  Complete animation library for Drawaria with all effects
// @author       DrawArtist
// @license      MIT
// @match        https://drawaria.online/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';
    
    // Verificar que getGameSocket esté disponible
    function getGameSocket() {
        return window.getGameSocket ? window.getGameSocket() : null;
    }
    
    // Core drawing animation functions
    const DRAWING_FUNCTIONS = {
        // Utility functions
        _delay: function(ms) {
            return new Promise(resolve => setTimeout(resolve, ms));
        },
        
        _getRandomColor: function(saturation = 70, lightness = 50) {
            const hue = Math.floor(Math.random() * 360);
            return `hsl(${hue}, ${saturation}%, ${lightness}%)`;
        },
        
        _sendDrawCmd: function(startPoint, endPoint, color, thickness) {
            try {
                const socket = getGameSocket();
                if (!socket || socket.readyState !== WebSocket.OPEN) {
                    console.warn('WebSocket not available or not open');
                    return false;
                }
                
                const cmd = `42["drawcmd",0,[${startPoint.toFixed(4)},${startPoint[1].toFixed(4)},${endPoint.toFixed(4)},${endPoint[1].toFixed(4)},false,${0-thickness},"${color}",0,0,{"2":0,"3":0.5,"4":0.5}]]`;
                socket.send(cmd);
                return true;
            } catch (error) {
                console.error('Error sending draw command:', error);
                return false;
            }
        },
        
        _drawPixel: async function(x, y, size, color, delay = 0) {
            const endX = x + size * 0.0001;
            const endY = y + size * 0.0001;
            const thickness = size * 1000;
            const result = this._sendDrawCmd([x, y], [endX, endY], color, thickness);
            if (delay > 0) await this._delay(delay);
            return result;
        },
        
        notify: function(type, message) {
            console.log(`[DRAWARIA-ANIMATIONS][${type.toUpperCase()}] ${message}`);
        },
        
        // State management
        _drawingActive: false,
        _globalFrameCount: 0,

        // Pixel font for text rendering
        _pixelFont: {
            'A': ["0110", "1001", "1111", "1001", "1001"],
            'B': ["1110", "1001", "1110", "1001", "1110"],
            'C': ["0110", "1000", "1000", "1000", "0110"],
            'D': ["1110", "1001", "1001", "1001", "1110"],
            'E': ["1111", "1000", "1110", "1000", "1111"],
            'G': ["0110", "1000", "1011", "1001", "0111"],
            'H': ["1001", "1001", "1111", "1001", "1001"],
            'I': ["111", "010", "010", "010", "111"],
            'K': ["1001", "1010", "1100", "1010", "1001"],
            'L': ["1000", "1000", "1000", "1000", "1111"],
            'M': ["10001", "11011", "10101", "10001", "10001"],
            'N': ["1001", "1101", "1011", "1001", "1001"],
            'O': ["0110", "1001", "1001", "1001", "0110"],
            'P': ["1110", "1001", "1110", "1000", "1000"],
            'R': ["1110", "1001", "1110", "1010", "1001"],
            'S': ["0111", "1000", "0110", "0001", "1110"],
            'U': ["1001", "1001", "1001", "1001", "0110"],
            'V': ["10001", "10001", "01010", "01010", "00100"],
            ' ': ["000", "000", "000", "000", "000"]
        },
        _charHeight: 5,

        async _drawPixelText(text, startX, startY, charPixelSize, color, textPixelDelay, letterSpacingFactor = 0.8) {
            let currentX = startX;
            text = text.toUpperCase();

            for (const char of text) {
                if (!this._drawingActive) return;
                const charData = this._pixelFont[char];
                if (charData) {
                    let charWidth = 0;
                    for (let y = 0; y < this._charHeight; y++) {
                        if (!this._drawingActive) return;
                        const row = charData[y];
                        charWidth = Math.max(charWidth, row.length);
                        for (let x = 0; x < row.length; x++) {
                            if (!this._drawingActive) return;
                            if (row[x] === '1') {
                                const dX = currentX + x * charPixelSize;
                                const dY = startY + y * charPixelSize;
                                if (!await this._drawPixel(dX, dY, charPixelSize, color, textPixelDelay)) return;
                            }
                        }
                    }
                    currentX += (charWidth + letterSpacingFactor) * charPixelSize;
                } else {
                    currentX += (3 + letterSpacingFactor) * charPixelSize;
                }
            }
        },

        // COMPLETE ANIMATION IMPLEMENTATIONS
        async pixelArtCharacters() {
            const socket = getGameSocket();
            if (!socket || socket.readyState !== WebSocket.OPEN) {
                this.notify("error", "Not connected to game. Please be in a room.");
                return;
            }
            
            this._drawingActive = true;
            this.notify("info", "Starting Pixel Art Characters...");

            try {
                const Q_TOP_LEFT = { xMin: 0.0, yMin: 0.0, xMax: 0.5, yMax: 0.5 };
                const Q_TOP_RIGHT = { xMin: 0.5, yMin: 0.0, xMax: 1.0, yMax: 0.5 };
                const Q_BOTTOM_LEFT = { xMin: 0.0, yMin: 0.5, xMax: 0.5, yMax: 1.0 };
                const Q_BOTTOM_RIGHT = { xMin: 0.5, yMin: 0.5, xMax: 1.0, yMax: 1.0 };

                const marioSprite = {
                    name: "MARIO", nameColor: "#FF0000", width: 12,
                    data: ["____RRRRR___", "___RRRRRRR__", "___NNNYNY___", "__NSSYSYYN__", "__NSSYSYYYNN", "__NYYYYYYYYN", "____BBBB____", "__RBBBRBBR__", "_RBBRRRBBRR_", "RBBBBBRBBBB_", "BBBBBBRBBBBB", "BBBB__BBBB__", "NNN____NNN__", "_NN____NN___"],
                    colors: { R: "#E60000", N: "#7A3D03", Y: "#FBD000", S: "#FFCC99", B: "#0040FF" },
                    quadrant: Q_TOP_LEFT, textOffsetY: -0.08
                };

                const pikachuSprite = {
                    name: "PIKACHU", nameColor: "#FFA500", width: 13,
                    data: ["____PPPPP____", "___PKKKPKK___", "__PKKPKPKKK__", "_PKKPKKPKPKK_", "_PKKPOKPKPOKK", "PPKPKKKPKPKPP", "PPKPK_KPKPKPP", "_PKPKKKPKPKP_", "__PKKKKKPKP__", "___PPPPPPP___", "____PP_PP____"],
                    colors: { P: "#FFDE38", K: "#000000", O: "#FF4444", W: "#FFFFFF" },
                    quadrant: Q_TOP_RIGHT, textOffsetY: -0.08
                };

                const characters = [marioSprite, pikachuSprite];
                const pixelDrawDelay = 3;
                const textPixelDelay = 2;
                const textCharPixelSize = 0.008;

                // Draw dividing lines
                const lineThickness = 8;
                if (!this._sendDrawCmd([0, 0.5], [1, 0.5], marioSprite.colors.R, lineThickness)) { 
                    this._drawingActive = false; 
                    return; 
                }
                if (!this._sendDrawCmd([0.5, 0], [0.5, 1], pikachuSprite.colors.O, lineThickness)) { 
                    this._drawingActive = false; 
                    return; 
                }

                await this._delay(100);

                // Draw "VS" text
                if (this._drawingActive) {
                    const vsTextSize = 0.02;
                    const vsColor = "#000000";
                    await this._drawPixelText("VS", 0.5 - (vsTextSize * 5) / 2, 0.5 - (vsTextSize * this._charHeight) / 2, vsTextSize, vsColor, textPixelDelay);
                }

                await this._delay(100);

                // Draw characters
                for (const char of characters) {
                    if (!this._drawingActive) break;

                    const charHeightPx = char.data.length;
                    const charWidthPx = char.width;
                    const quadW = char.quadrant.xMax - char.quadrant.xMin;
                    const quadH = char.quadrant.yMax - char.quadrant.yMin;

                    const scaleFactor = 0.65;
                    const pixelSizeX = (quadW * scaleFactor) / charWidthPx;
                    const pixelSizeY = (quadH * scaleFactor) / charHeightPx;
                    const finalPixelSize = Math.min(pixelSizeX, pixelSizeY);

                    const totalSpriteW = charWidthPx * finalPixelSize;
                    const totalSpriteH = charHeightPx * finalPixelSize;
                    const startX = char.quadrant.xMin + (quadW - totalSpriteW) / 2;
                    const startY = char.quadrant.yMin + (quadH - totalSpriteH) / 2;

                    // Draw character name
                    const nameLenEst = char.name.length * 3 * textCharPixelSize;
                    const textStartX = char.quadrant.xMin + (quadW - nameLenEst) / 2;
                    let textStartY = startY + char.textOffsetY - (this._charHeight * textCharPixelSize);
                    textStartY = Math.max(char.quadrant.yMin + 0.01, Math.min(char.quadrant.yMax - 0.01 - (this._charHeight * textCharPixelSize), textStartY));

                    if (this._drawingActive) {
                        await this._drawPixelText(char.name, textStartX, textStartY, textCharPixelSize, char.nameColor, textPixelDelay);
                    }
                    
                    await this._delay(50);

                    // Draw sprite pixels
                    for (let y = 0; y < charHeightPx; y++) {
                        if (!this._drawingActive) break;
                        for (let x = 0; x < charWidthPx; x++) {
                            if (!this._drawingActive) break;
                            const colorChar = char.data[y][x];
                            if (colorChar !== "_" && char.colors[colorChar]) {
                                const dX = startX + x * finalPixelSize;
                                const dY = startY + y * finalPixelSize;
                                if (!await this._drawPixel(dX, dY, finalPixelSize, char.colors[colorChar], pixelDrawDelay)) {
                                    this._drawingActive = false;
                                    break;
                                }
                            }
                        }
                    }
                    if (!this._drawingActive) break;
                    await this._delay(200);
                }

                this._drawingActive = false;
                this.notify("success", "Pixel Art Characters finished.");
            } catch (error) {
                this._drawingActive = false;
                this.notify("error", "Animation failed: " + error.message);
            }
        },

        async pulsatingStainedGlass() {
            const socket = getGameSocket();
            if (!socket || socket.readyState !== WebSocket.OPEN) {
                this.notify("error", "Not connected to game. Please be in a room.");
                return;
            }
            
            this._drawingActive = true;
            this.notify("info", "Starting Pulsating Stained Glass...");
            
            try {
                const gridX = 5 + Math.floor(Math.random() * 4);
                const gridY = 4 + Math.floor(Math.random() * 3);
                const cellW = 1 / gridX;
                const cellH = 1 / gridY;
                const animSteps = 150;
                const globalDelay = 50;
                const lineThickness = 3;
                const lineColor = "rgb(40,40,40)";
                let cells = [];

                // Create grid cells
                for (let r = 0; r < gridY; r++) {
                    for (let c = 0; c < gridX; c++) {
                        const cellType = Math.random();
                        let points = [];
                        const x = c * cellW, y = r * cellH, w = cellW, h = cellH;
                        
                        if (cellType < 0.33) {
                            points = [[x, y], [x + w, y], [x + w, y + h], [x, y + h]];
                        } else if (cellType < 0.66) {
                            if (Math.random() < 0.5) {
                                points = [[x, y], [x + w, y], [x + w, y + h], [x, y], [x, y + h], [x + w, y + h]];
                            } else {
                                points = [[x, y], [x + w, y], [x, y + h], [x + w, y], [x + w, y + h], [x, y + h]];
                            }
                        } else {
                            const cx = x + w / 2, cy = y + h / 2;
                            points = [[x, y], [x + w, y], [cx, cy], [x + w, y], [x + w, y + h], [cx, cy], [x + w, y + h], [x, y + h], [cx, cy], [x, y + h], [x, y], [cx, cy]];
                        }
                        
                        cells.push({
                            basePoints: points,
                            hue: Math.random() * 360,
                            lightPhase: Math.random() * Math.PI * 2,
                            lightSpeed: 0.05 + Math.random() * 0.1
                        });
                    }
                }

                // Draw cell outlines
                for (const cell of cells) {
                    if (!this._drawingActive) break;
                    for (let i = 0; i < cell.basePoints.length; i += 3) {
                        if (!this._drawingActive || i + 2 >= cell.basePoints.length) break;
                        const p1 = cell.basePoints[i], p2 = cell.basePoints[i + 1], p3 = cell.basePoints[i + 2];
                        if (!this._sendDrawCmd(p1, p2, lineColor, lineThickness)) { this._drawingActive = false; break; }
                        if (!this._sendDrawCmd(p2, p3, lineColor, lineThickness)) { this._drawingActive = false; break; }
                        if (!this._sendDrawCmd(p3, p1, lineColor, lineThickness)) { this._drawingActive = false; break; }
                        await this._delay(5);
                    }
                }

                // Animate pulsating colors
                for (let frame = 0; frame < animSteps && this._drawingActive; frame++) {
                    for (const cell of cells) {
                        if (!this._drawingActive) break;
                        const currentLightness = 40 + 20 * Math.sin(cell.lightPhase + frame * cell.lightSpeed);
                        const color = `hsl(${cell.hue},80%,${currentLightness}%)`;
                        
                        for (let i = 0; i < cell.basePoints.length; i += 3) {
                            if (!this._drawingActive || i + 2 >= cell.basePoints.length) break;
                            const p1 = cell.basePoints[i], p2 = cell.basePoints[i + 1], p3 = cell.basePoints[i + 2];
                            const m12 = [(p1 + p2) / 2, (p1[1] + p2[1]) / 2];
                            const m23 = [(p2 + p3) / 2, (p2[1] + p3[1]) / 2];
                            
                            if (!this._sendDrawCmd(m12, p3, color, lineThickness * 3 + 2)) { this._drawingActive = false; break; }
                            if (!this._sendDrawCmd(m23, p1, color, lineThickness * 3 + 2)) { this._drawingActive = false; break; }
                        }
                    }
                    if (!this._drawingActive) break;
                    await this._delay(globalDelay);
                }
                
                this._drawingActive = false;
                this.notify("success", "Pulsating Stained Glass finished.");
            } catch (error) {
                this._drawingActive = false;
                this.notify("error", "Animation failed: " + error.message);
            }
        },

        async celestialBallet() {
            const socket = getGameSocket();
            if (!socket || socket.readyState !== WebSocket.OPEN) {
                this.notify("error", "Not connected to game. Please be in a room.");
                return;
            }
            
            this._drawingActive = true;
            this.notify("info", "Starting Celestial Ballet...");
            
            try {
                const numDots = 8 + Math.floor(Math.random() * 5);
                const steps = 150;
                const thickness = 3;
                const baseDelay = 25;
                let dots = [];

                // Initialize dancing dots
                for (let i = 0; i < numDots; i++) {
                    dots.push({
                        x: 0.5, y: 0.5,
                        vx: (Math.random() - 0.5) * 0.02,
                        vy: (Math.random() - 0.5) * 0.02,
                        orbitCenterX: 0.5 + (Math.random() - 0.5) * 0.4,
                        orbitCenterY: 0.5 + (Math.random() - 0.5) * 0.4,
                        orbitSpeed: (Math.random() * 0.05 + 0.02) * (Math.random() < 0.5 ? 1 : -1),
                        hue: Math.random() * 360,
                        lastX: 0.5, lastY: 0.5
                    });
                }

                // Animate the celestial ballet
                for (let step = 0; step < steps && this._drawingActive; step++) {
                    for (const dot of dots) {
                        if (!this._drawingActive) break;
                        
                        // Store last position
                        dot.lastX = dot.x;
                        dot.lastY = dot.y;
                        
                        // Calculate orbital motion
                        const angleToOrbit = Math.atan2(dot.y - dot.orbitCenterY, dot.x - dot.orbitCenterX);
                        dot.vx += Math.cos(angleToOrbit + Math.PI / 2) * dot.orbitSpeed * 0.1;
                        dot.vy += Math.sin(angleToOrbit + Math.PI / 2) * dot.orbitSpeed * 0.1;
                        
                        // Add center attraction
                        dot.vx += (0.5 - dot.x) * 0.0005;
                        dot.vy += (0.5 - dot.y) * 0.0005;
                        
                        // Apply damping
                        dot.vx *= 0.97;
                        dot.vy *= 0.97;
                        
                        // Update position
                        dot.x += dot.vx;
                        dot.y += dot.vy;
                        
                        // Boundary collision
                        if (dot.x < 0.01 || dot.x > 0.99) dot.vx *= -0.8;
                        if (dot.y < 0.01 || dot.y > 0.99) dot.vy *= -0.8;
                        
                        // Keep in bounds
                        dot.x = Math.max(0.01, Math.min(0.99, dot.x));
                        dot.y = Math.max(0.01, Math.min(0.99, dot.y));
                        
                        // Update color
                        dot.hue = (dot.hue + 0.5) % 360;
                        const color = `hsl(${dot.hue},100%,70%)`;
                        
                        // Draw trail
                        if (!this._sendDrawCmd([dot.lastX, dot.lastY], [dot.x, dot.y], color, thickness)) {
                            this._drawingActive = false;
                            break;
                        }
                    }
                    if (baseDelay > 0 && this._drawingActive) await this._delay(baseDelay);
                }
                
                this._drawingActive = false;
                this.notify("success", "Celestial Ballet finished.");
            } catch (error) {
                this._drawingActive = false;
                this.notify("error", "Animation failed: " + error.message);
            }
        },

        async recursiveStarPolygonNova() {
            const socket = getGameSocket();
            if (!socket || socket.readyState !== WebSocket.OPEN) {
                this.notify("error", "Not connected to game. Please be in a room.");
                return;
            }
            
            this._drawingActive = true;
            this.notify("info", "Starting Recursive Star Polygon Nova...");
            
            try {
                const centerX = 0.5, centerY = 0.5;
                const initialRadius = 0.25;
                const maxDepth = 3 + Math.floor(Math.random() * 1);
                const numPoints = 5 + Math.floor(Math.random() * 2) * 2;
                const skipFactor = 2 + Math.floor(Math.random() * 1);
                const recursiveScaleFactor = 0.4;
                const stepDelay = 15;
                const baseHue = Math.random() * 360;
                let globalRotation = this._globalFrameCount * 0.01;

                const drawStar = async (cx, cy, radius, points, skip, depth, currentHue, currentThickness, parentAngle) => {
                    if (!this._drawingActive || depth > maxDepth || radius < 0.005) return;
                    
                    const starCorners = [];
                    for (let i = 0; i < points; i++) {
                        const angle = (i / points) * 2 * Math.PI + parentAngle + globalRotation;
                        starCorners.push({
                            x: cx + radius * Math.cos(angle),
                            y: cy + radius * Math.sin(angle)
                        });
                    }
                    
                    const color = `hsl(${(currentHue + depth * 30) % 360},95%,${65 - depth * 10}%)`;
                    const thickness = Math.max(1, currentThickness);
                    
                    // Draw star lines
                    for (let i = 0; i < points && this._drawingActive; i++) {
                        const p1 = starCorners[i];
                        const p2 = starCorners[(i + skip) % points];
                        if (!this._sendDrawCmd([p1.x, p1.y], [p2.x, p2.y], color, thickness)) {
                            this._drawingActive = false;
                            return;
                        }
                        if (stepDelay > 0 && this._drawingActive) await this._delay(stepDelay);
                    }
                    
                    // Recursive calls
                    for (let i = 0; i < points && this._drawingActive; i++) {
                        const newAngle = (i / points) * 2 * Math.PI + parentAngle + Math.PI / points;
                        await drawStar(starCorners[i].x, starCorners[i].y, radius * recursiveScaleFactor, points, skip, depth + 1, currentHue, thickness * 0.7, newAngle);
                    }
                };

                await drawStar(centerX, centerY, initialRadius, numPoints, skipFactor, 1, baseHue, 6, 0);
                
                this._globalFrameCount++;
                this._drawingActive = false;
                this.notify("success", "Recursive Star Polygon Nova finished.");
            } catch (error) {
                this._drawingActive = false;
                this.notify("error", "Animation failed: " + error.message);
            }
        },

        async fractalBloomMandala() {
            const socket = getGameSocket();
            if (!socket || socket.readyState !== WebSocket.OPEN) {
                this.notify("error", "Not connected to game. Please be in a room.");
                return;
            }
            
            this._drawingActive = true;
            this.notify("info", "Starting Fractal Bloom Mandala...");
            
            try {
                const centerX = 0.5, centerY = 0.5;
                const maxDepth = 4;
                const initialBranches = 6 + Math.floor(Math.random() * 3);
                const initialLength = 0.15;
                const lengthRatio = 0.65;
                const angleStep = Math.PI / (3 + Math.random() * 2);
                const delay = 20;
                const baseHue = Math.random() * 360;
                let globalRotation = this._globalFrameCount * 0.01;

                const drawBranch = async (cx, cy, angle, length, depth, currentHue, branchThickness) => {
                    if (!this._drawingActive || depth > maxDepth || length < 0.005) return;
                    
                    const x2 = cx + length * Math.cos(angle);
                    const y2 = cy + length * Math.sin(angle);
                    const thickness = Math.max(1, branchThickness * Math.pow(lengthRatio, depth - 1) * 2);
                    const color = `hsl(${(currentHue + depth * 20) % 360},${80 - depth * 10}%,${60 - depth * 8}%)`;
                    
                    if (!this._sendDrawCmd([cx, cy], [x2, y2], color, thickness)) {
                        this._drawingActive = false;
                        return;
                    }
                    if (delay > 0) await this._delay(delay);
                    if (!this._drawingActive) return;
                    
                    // Branch left and right
                    await drawBranch(x2, y2, angle - angleStep, length * lengthRatio, depth + 1, currentHue, branchThickness);
                    if (!this._drawingActive) return;
                    await drawBranch(x2, y2, angle + angleStep, length * lengthRatio, depth + 1, currentHue, branchThickness);
                    
                    // Sometimes add a middle branch
                    if (depth < maxDepth - 1 && Math.random() < 0.4) {
                        if (!this._drawingActive) return;
                        await drawBranch(x2, y2, angle, length * lengthRatio * 0.8, depth + 1, currentHue, branchThickness);
                    }
                };

                // Draw mandala branches
                for (let i = 0; i < initialBranches && this._drawingActive; i++) {
                    const angle = (i / initialBranches) * 2 * Math.PI + globalRotation;
                    await drawBranch(centerX, centerY, angle, initialLength, 1, (baseHue + i * (360 / initialBranches)) % 360, 10);
                    if (delay > 0 && this._drawingActive) await this._delay(delay * 3);
                }
                
                this._globalFrameCount++;
                this._drawingActive = false;
                this.notify("success", "Fractal Bloom Mandala finished.");
            } catch (error) {
                this._drawingActive = false;
                this.notify("error", "Animation failed: " + error.message);
            }
        },

        async directionalHueBlast() {
            const socket = getGameSocket();
            if (!socket || socket.readyState !== WebSocket.OPEN) {
                this.notify("error", "Not connected to game. Please be in a room.");
                return;
            }
            
            this._drawingActive = true;
            this.notify("info", "Starting Directional Hue Blast...");
            
            try {
                const directions = [
                    { s: [0.5, 0], e: [0.5, 1] },   // Top to bottom
                    { s: [0.5, 1], e: [0.5, 0] },   // Bottom to top
                    { s: [0, 0.5], e: [1, 0.5] },   // Left to right
                    { s: [1, 0.5], e: [0, 0.5] }    // Right to left
                ];

                for (let i = 0; i < 100 && this._drawingActive; i++) {
                    for (let dir of directions) {
                        if (!this._drawingActive) break;
                        
                        let t = i / 100;
                        let x = dir.s + (dir.e - dir.s) * t;
                        let y = dir.s[1] + (dir.e[1] - dir.s[1]) * t;
                        
                        if (!this._sendDrawCmd([x, y], [x + 0.001, y + 0.001], `hsl(${i * 3.6},100%,50%)`, 10 + i * 0.5)) {
                            this._drawingActive = false;
                            break;
                        }
                    }
                    if (!this._drawingActive) break;
                    await this._delay(40);
                }
                
                this._drawingActive = false;
                this.notify("success", "Directional Hue Blast finished.");
            } catch (error) {
                this._drawingActive = false;
                this.notify("error", "Animation failed: " + error.message);
            }
        },

        async colorFestival() {
            const socket = getGameSocket();
            if (!socket || socket.readyState !== WebSocket.OPEN) {
                this.notify("error", "Not connected to game. Please be in a room.");
                return;
            }
            
            this._drawingActive = true;
            this.notify("info", "Starting Color Festival...");
            
            try {
                const numShapes = 120;
                const frameDelay = 60;
                const shapeDelay = 10;
                
                for (let i = 0; i < numShapes && this._drawingActive; i++) {
                    let x = Math.random() * 0.8 + 0.1;
                    let y = Math.random() * 0.8 + 0.1;
                    let size = Math.random() * 0.08 + 0.03;
                    let color = this._getRandomColor(90, 55);
                    let thickness = Math.floor(Math.random() * 10) + 4;
                    let shapeType = Math.floor(Math.random() * 4);
                    
                    let success = true;
                    
                    if (shapeType === 0) {
                        // Rectangle lines
                        for (let j = 0; j < size * 100 && success && this._drawingActive; j += thickness / 2) {
                            let lineY = y - size / 2 + (j / 100);
                            if (lineY > y + size / 2) break;
                            success = this._sendDrawCmd([x - size / 2, lineY], [x + size / 2, lineY], color, thickness);
                            if (shapeDelay > 0 && success) await this._delay(shapeDelay);
                        }
                    } else if (shapeType === 1) {
                        // Triangle
                        const s = size;
                        success = this._sendDrawCmd([x, y - s / 2], [x + s / 2, y + s / 2], color, thickness);
                        if (success && this._drawingActive && shapeDelay > 0) await this._delay(shapeDelay);
                        if (!success || !this._drawingActive) break;
                        success = this._sendDrawCmd([x + s / 2, y + s / 2], [x - s / 2, y + s / 2], color, thickness);
                        if (success && this._drawingActive && shapeDelay > 0) await this._delay(shapeDelay);
                        if (!success || !this._drawingActive) break;
                        success = this._sendDrawCmd([x - s / 2, y + s / 2], [x, y - s / 2], color, thickness);
                    } else if (shapeType === 2) {
                        // Star pattern
                        const s = size * 0.7;
                        for (let k = 0; k < 8 && success && this._drawingActive; k++) {
                            const angle = (k / 8) * 2 * Math.PI;
                            success = this._sendDrawCmd([x, y], [x + s * Math.cos(angle), y + s * Math.sin(angle)], color, thickness);
                            if (shapeDelay > 0 && success) await this._delay(shapeDelay);
                        }
                    } else {
                        // Spiral
                        let lastX = x, lastY = y;
                        for (let k = 0; k <= 20 && success && this._drawingActive; k++) {
                            const angle = (k / 20) * 4 * Math.PI;
                            const radius = (k / 20) * size;
                            const currentX = x + radius * Math.cos(angle);
                            const currentY = y + radius * Math.sin(angle);
                            if (k > 0) success = this._sendDrawCmd([lastX, lastY], [currentX, currentY], color, thickness);
                            lastX = currentX;
                            lastY = currentY;
                            if (shapeDelay > 0 && success) await this._delay(shapeDelay);
                        }
                    }
                    
                    if (!success || !this._drawingActive) break;
                    if (frameDelay > 0) await this._delay(frameDelay);
                }
                
                this._drawingActive = false;
                this.notify("success", "Color Festival finished.");
            } catch (error) {
                this._drawingActive = false;
                this.notify("error", "Animation failed: " + error.message);
            }
        },

        async fireworks() {
            const socket = getGameSocket();
            if (!socket || socket.readyState !== WebSocket.OPEN) {
                this.notify("error", "Not connected to game. Please be in a room.");
                return;
            }
            
            this._drawingActive = true;
            this.notify("info", "Starting Fireworks...");
            
            try {
                const numFireworks = 8;
                const fireworkDelay = 600;
                
                for (let i = 0; i < numFireworks && this._drawingActive; i++) {
                    // Launch trail
                    let startX = Math.random() * 0.6 + 0.2;
                    let startY = 0.95;
                    let peakX = startX + (Math.random() - 0.5) * 0.3;
                    let peakY = Math.random() * 0.4 + 0.05;
                    let launchColor = this._getRandomColor(100, 70);
                    let launchThickness = 6;
                    let particleCount = 40 + Math.floor(Math.random() * 40);
                    let particleThickness = 4 + Math.floor(Math.random() * 4);
                    let launchSteps = 25;
                    let launchStepDelay = 4;
                    let explosionParticleDelay = 8;
                    let success = true;

                    // Draw launch trail
                    for (let step = 0; step < launchSteps && success && this._drawingActive; step++) {
                        let progress = step / launchSteps;
                        let nextProgress = (step + 1) / launchSteps;
                        let currentX = startX + (peakX - startX) * progress;
                        let currentY = startY + (peakY - startY) * progress;
                        let nextX = startX + (peakX - startX) * nextProgress;
                        let nextY = startY + (peakY - startY) * nextProgress;
                        
                        success = this._sendDrawCmd([currentX, currentY], [nextX, nextY], launchColor, launchThickness);
                        if (launchStepDelay > 0 && success) await this._delay(launchStepDelay);
                    }
                    
                    if (!success || !this._drawingActive) break;
                    
                    // Explosion
                    const explosionHue = Math.random() * 360;
                    for (let j = 0; j < particleCount && success && this._drawingActive; j++) {
                        const angle = Math.random() * 2 * Math.PI;
                        const distance = Math.random() * 0.20 + 0.05;
                        const endX = peakX + distance * Math.cos(angle);
                        const endY = peakY + distance * Math.sin(angle);
                        
                        const particleHue = (explosionHue + (Math.random() - 0.5) * 60 + 360) % 360;
                        success = this._sendDrawCmd([peakX, peakY], [endX, endY], `hsl(${particleHue},100%,60%)`, particleThickness);
                        if (explosionParticleDelay > 0 && success) await this._delay(explosionParticleDelay);
                    }
                    
                    if (!success || !this._drawingActive) break;
                    if (fireworkDelay > 0) await this._delay(fireworkDelay);
                }
                
                this._drawingActive = false;
                this.notify("success", "Fireworks finished.");
            } catch (error) {
                this._drawingActive = false;
                this.notify("error", "Animation failed: " + error.message);
            }
        },

        stopDrawing() {
            this._drawingActive = false;
            this.notify("info", "Drawing stopped by user.");
        }
    };

    // Library interface
    window.DRAWARIA_ANIMATIONS = {
        getAnimations: function() {
            try {
                return Object.keys(DRAWING_FUNCTIONS).filter(key => 
                    typeof DRAWING_FUNCTIONS[key] === 'function' && 
                    !key.startsWith('_') && 
                    key !== 'notify' && 
                    key !== 'stopDrawing'
                );
            } catch (error) {
                console.error('Error getting animations:', error);
                return [];
            }
        },

        runAnimation: function(animationName) {
            try {
                if (typeof DRAWING_FUNCTIONS[animationName] === 'function') {
                    return DRAWING_FUNCTIONS[animationName].call(DRAWING_FUNCTIONS);
                } else {
                    console.error('Animation not found:', animationName);
                    return Promise.reject('Animation not found');
                }
            } catch (error) {
                console.error('Error running animation:', error);
                return Promise.reject(error);
            }
        },

        getRandomAnimation: function() {
            try {
                const animations = this.getAnimations();
                if (animations.length === 0) return null;
                return animations[Math.floor(Math.random() * animations.length)];
            } catch (error) {
                console.error('Error getting random animation:', error);
                return null;
            }
        },

        stop: function() {
            try {
                DRAWING_FUNCTIONS.stopDrawing();
            } catch (error) {
                console.error('Error stopping animation:', error);
            }
        },

        isDrawing: function() {
            return DRAWING_FUNCTIONS._drawingActive;
        },

        animations: DRAWING_FUNCTIONS
    };

    // Backward compatibility
    window.DRAWING_FUNCTIONS = DRAWING_FUNCTIONS;
    
    console.log('🎨 Drawaria Animation Library loaded successfully');
    console.log('Available animations:', window.DRAWARIA_ANIMATIONS.getAnimations());

})();