Drawaria Drawbot Elemental Animations

High-quality elemental animations (Ocean, Magma, Poison, PurpleGoo) with solid bases, outlines, and thematic particles for Drawaria.online

// ==UserScript==
// @name         Drawaria Drawbot Elemental Animations
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  High-quality elemental animations (Ocean, Magma, Poison, PurpleGoo) with solid bases, outlines, and thematic particles for Drawaria.online
// @author       YouTubeDrawaria
// @match        https://drawaria.online/*
// @license      MIT
// @icon         https://www.google.com/s2/favicons?sz=64&domain=drawaria.online
// @grant        none
// ==/UserScript==

(() => {
    'use_strict';

    const EL = (sel) => document.querySelector(sel);
    const ELL = (sel) => document.querySelectorAll(sel);

    let drawing_active = false;
    let originalCanvas = null;
    let cw = 0;
    let ch = 0;

    function delay(ms) {
        return new Promise((resolve) => setTimeout(resolve, ms));
    }

    function getRandomInt(min, max) {
        return Math.floor(Math.random() * (max - min + 1)) + min;
    }

    function getRandomFloat(min, max) {
        return Math.random() * (max - min) + min;
    }

    function sendDrawCmd(socket, start, end, color, thickness, isEraser = false, algo = 0) {
        if (!socket || socket.readyState !== WebSocket.OPEN) {
            console.warn("Bot socket not open.");
            return false;
        }
        const p1x = Math.max(0, Math.min(1, start[0])), p1y = Math.max(0, Math.min(1, start[1]));
        const p2x = Math.max(0, Math.min(1, end[0])), p2y = Math.max(0, Math.min(1, end[1]));
        let numThickness = parseFloat(thickness);
        if (isNaN(numThickness)) {
            console.warn("Invalid thickness provided, defaulting to 10:", thickness);
            numThickness = 10;
        }
        const gT = isEraser ? numThickness : 0 - numThickness;
        socket.send(`42["drawcmd",0,[${p1x},${p1y},${p2x},${p2y},${isEraser},${gT},"${color}",0,0,{"2":${algo},"3":0.5,"4":0.5}]]`);
        return true;
    }

    async function animateElementalWave(type) {
        if (!window.___BOT || !window.___BOT.conn || !window.___BOT.conn.socket || window.___BOT.conn.socket.readyState !== WebSocket.OPEN) {
            alert("Bot not connected.");
            return;
        }
        if (drawing_active) {
            console.log("Animation already in progress.");
            alert("An animation is already running. Please wait for it to finish or clear the canvas.");
            return;
        }
        drawing_active = true;
        const socket = window.___BOT.conn.socket;
        console.log(`Starting ${type} Animation...`);

        let config;
        switch (type) {
            case 'Ocean':
                config = {
                    baseFillColor: '#00008B',
                    waveColors: ['#1E90FF', '#4682B4'],
                    outlineColor: '#AFEEEE',
                    particleColor: '#00FFFF',
                    particleType: 'blob',
                    particleChance: 0.15,
                    particleSizeMin: 0.01,
                    particleSizeMax: 0.03,
                    particleThickness: 8,
                    startHeight: 0.88,
                    waveAmplitude: 0.06,
                    waveFrequency: 2.5,
                    waveSpeed: 0.02,
                    outlineOffset: 0.005,
                    outlineThicknessReduction: 3,
                    mainThickness: 30,
                    waveStyle: 'smooth',
                    frames: 100,
                    frameDelay: 50
                };
                break;
            case 'Magma':
                config = {
                    baseFillColor: '#660000',
                    waveColors: ['#FF2400', '#CC5500'],
                    outlineColor: '#282828',
                    particleColor: '#FFA500',
                    particleType: 'ember',
                    particleChance: 0.2,
                    particleSizeMin: 0.008,
                    particleSizeMax: 0.02,
                    particleThickness: 4,
                    startHeight: 0.86,
                    waveAmplitude: 0.04,
                    waveFrequency: 2,
                    waveSpeed: 0.01,
                    outlineOffset: 0.01,
                    outlineThicknessReduction: 1,
                    mainThickness: 28,
                    waveStyle: 'jagged',
                    frames: 100,
                    frameDelay: 70
                };
                break;
            case 'Poison':
                config = {
                    baseFillColor: '#556B2F',
                    waveColors: ['#6B8E23', '#808000'],
                    outlineColor: '#ADFF2F',
                    particleColor: '#7FFF00',
                    particleType: 'blob',
                    particleChance: 0.25,
                    particleSizeMin: 0.015,
                    particleSizeMax: 0.035,
                    particleThickness: 10,
                    startHeight: 0.87,
                    waveAmplitude: 0.055,
                    waveFrequency: 1.8,
                    waveSpeed: 0.018,
                    outlineOffset: 0.006,
                    outlineThicknessReduction: 2,
                    mainThickness: 26,
                    waveStyle: 'gloopy',
                    frames: 100,
                    frameDelay: 65
                };
                break;
             case 'PurpleGoo':
                config = {
                    baseFillColor: '#4B0082',
                    waveColors: ['#800080', '#9932CC'],
                    outlineColor: '#FF00FF',
                    particleColor: '#DA70D6',
                    particleType: 'blob',
                    particleChance: 0.2,
                    particleSizeMin: 0.01,
                    particleSizeMax: 0.03,
                    particleThickness: 9,
                    startHeight: 0.88,
                    waveAmplitude: 0.05,
                    waveFrequency: 2.2,
                    waveSpeed: 0.022,
                    outlineOffset: 0.007,
                    outlineThicknessReduction: 2,
                    mainThickness: 27,
                    waveStyle: 'jagged',
                    frames: 100,
                    frameDelay: 55
                };
                break;
            default:
                drawing_active = false;
                console.warn(`Unknown animation type: ${type}`);
                return;
        }

        await botClearCanvas();

        const segments = 30;
        const segmentWidth = 1 / segments;

        // --- Draw Solid Base Fill (Alternative approach) ---
        const fillToY = config.startHeight + config.waveAmplitude; // Lowest point of waves
        let currentYFill = 0.99; // Start near the bottom
        const fillStep = 0.05; // How much to move up for each fill line
        const fillThickness = 150; // Drawaria thickness for fill lines

        while (currentYFill > fillToY && drawing_active) {
            if (!sendDrawCmd(socket, [0, currentYFill], [1, currentYFill], config.baseFillColor, fillThickness)) {
                 drawing_active = false; console.error("Failed to draw base segment."); break;
            }
            currentYFill -= fillStep;
            if (config.frameDelay > 0 && fillStep < 0.1) await delay(5); // Small delay only if steps are small
        }
        if (!drawing_active) { console.log("Animation stopped during base fill."); return; }
        await delay(50);


        for (let frame = 0; frame < config.frames && drawing_active; frame++) {
            let wavePoints = [];
            for (let i = 0; i <= segments; i++) {
                const x = i * segmentWidth;
                let yOffset = Math.sin((x * config.waveFrequency * Math.PI) + (frame * config.waveSpeed)) * config.waveAmplitude;

                if (config.waveStyle === 'gloopy') {
                    yOffset += (Math.sin((x * config.waveFrequency * 1.7 * Math.PI) + (frame * config.waveSpeed * 1.3) + 0.5) * config.waveAmplitude * 0.4) * Math.sin(x * Math.PI);
                } else if (config.waveStyle === 'jagged') {
                    yOffset += (Math.random() - 0.5) * config.waveAmplitude * 0.3;
                }
                const y = config.startHeight - yOffset;
                wavePoints.push([x, y]);
            }

            for (let i = 0; i < wavePoints.length - 1 && drawing_active; i++) {
                const p1 = wavePoints[i];
                const p2 = wavePoints[i + 1];
                const waveColor = config.waveColors[i % config.waveColors.length];

                if (!sendDrawCmd(socket, p1, p2, waveColor, config.mainThickness)) {
                    drawing_active = false; break;
                }

                const p1_outline = [p1[0], p1[1] - config.outlineOffset];
                const p2_outline = [p2[0], p2[1] - config.outlineOffset];
                const outlineThickness = Math.max(1, config.mainThickness - config.outlineThicknessReduction);
                if (!sendDrawCmd(socket, p1_outline, p2_outline, config.outlineColor, outlineThickness)) {
                    drawing_active = false; break;
                }

                if (Math.random() < config.particleChance) {
                    const particleX = (p1[0] + p2[0]) / 2 + (Math.random() - 0.5) * 0.05;
                    const particleY = Math.min(p1[1], p2[1]) - 0.02 - Math.random() * 0.08;
                    const particleSize = getRandomFloat(config.particleSizeMin, config.particleSizeMax);

                    if (config.particleType === 'dot') {
                        if (!sendDrawCmd(socket, [particleX, particleY], [particleX + 0.001, particleY + 0.001], config.particleColor, config.particleThickness * (particleSize / config.particleSizeMin))) {
                            drawing_active = false; break;
                        }
                    } else if (config.particleType === 'blob') {
                        let blobPx = particleX;
                        let blobPy = particleY;
                        const blobSegments = getRandomInt(3, 5);
                        for (let k = 0; k < blobSegments && drawing_active; k++) {
                            const nextBlobPx = blobPx + (Math.random() - 0.5) * particleSize;
                            const nextBlobPy = blobPy + (Math.random() - 0.5) * particleSize;
                            if (!sendDrawCmd(socket, [blobPx, blobPy], [nextBlobPx, nextBlobPy], config.particleColor, config.particleThickness)) {
                                drawing_active = false; break;
                            }
                            blobPx = nextBlobPx;
                            blobPy = nextBlobPy;
                        }
                         if (!drawing_active) break;
                        if (blobSegments > 0 && Math.random() < 0.5 && drawing_active) {
                             if (!sendDrawCmd(socket, [blobPx, blobPy], [particleX, particleY], config.particleColor, config.particleThickness)) {
                                drawing_active = false; break;
                            }
                        }
                    } else if (config.particleType === 'ember') {
                        const emberEndX = particleX + (Math.random() - 0.5) * particleSize * 0.5;
                        const emberEndY = particleY - particleSize;
                         if (!sendDrawCmd(socket, [particleX, particleY], [emberEndX, emberEndY], config.particleColor, config.particleThickness)) {
                            drawing_active = false; break;
                        }
                    }
                }
                 if (!drawing_active) break;
            }
             if (!drawing_active) break;

            if (config.frameDelay > 0) await delay(config.frameDelay);
        }

        drawing_active = false;
        console.log(`${type} Animation finished.`);
    }

    async function botClearCanvas() {
        if (!window.___BOT || !window.___BOT.conn || !window.___BOT.conn.socket || window.___BOT.conn.socket.readyState !== WebSocket.OPEN) {
            alert("Bot not connected.");
            return;
        }
        sendDrawCmd(window.___BOT.conn.socket, [0.001, 0.5], [0.999, 0.5], "#FFFFFF", 2000, false);
        console.log("Canvas clear (whiteout) attempted.");
        await delay(200);
    }

    function addBoxIcons() {
        if (document.querySelector('link[href*="boxicons"]')) return;
        let b = document.createElement('link');
        b.href = 'https://unpkg.com/[email protected]/css/boxicons.min.css';
        b.rel = 'stylesheet';
        document.head.appendChild(b);
    }

    function createStylesheet() {
        if (document.getElementById('drawaria-elemental-style')) return;
        let s = document.createElement('style');
        s.id = 'drawaria-elemental-style';
        s.innerHTML = `
            #elemental-menu{position:fixed;top:70px;left:10px;width:360px;background-color:#333A44;color:#E0E0E0;border:1px solid #555E69;border-radius:8px;box-shadow:0 5px 15px rgba(0,0,0,0.3);z-index:10001;font-family:'Arial',sans-serif;font-size:14px;display:none}
            #elemental-menu-header{padding:10px 15px;background-color:#4A525E;color:#FFF;cursor:move;border-top-left-radius:7px;border-top-right-radius:7px;display:flex;justify-content:space-between;align-items:center}
            #elemental-menu-header h3{margin:0;font-size:16px;font-weight:bold}
            #elemental-menu-close{background:none;border:none;color:#FFF;font-size:24px;cursor:pointer;padding:0 5px;}
            #elemental-menu-body{padding:15px;max-height:calc(90vh - 50px);overflow-y:auto;background-color:#39414C}
            .elemental-section{margin-bottom:18px;padding-bottom:12px;border-bottom:1px solid #4A525E}
            .elemental-section:last-child{border-bottom:none;margin-bottom:0;}
            .elemental-section h4{margin-top:0;margin-bottom:10px;color:#A0D2EB;font-size:15px;border-bottom:1px solid #4A525E;padding-bottom:5px;}
            .elemental-button-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:10px}
            .elemental-button{padding:10px 12px;background-color:#4CAF50;color:white;border:none;border-radius:5px;cursor:pointer;text-align:center;font-size:14px;transition:background-color .2s ease, transform .1s ease; display:flex; align-items:center; justify-content:center;}
            .elemental-button i{margin-right:8px; font-size: 1.2em;}
            .elemental-button:hover{background-color:#45a049; transform: translateY(-1px);}
            .elemental-button:active{transform: translateY(0px);}
            .elemental-button.ocean{background-color:#1E90FF;} .elemental-button.ocean:hover{background-color:#187CDA;}
            .elemental-button.magma{background-color:#8B0000;} .elemental-button.magma:hover{background-color:#7A0000;} /* Magma icon is bxs-volcano */
            .elemental-button.poison{background-color:#6B8E23;} .elemental-button.poison:hover{background-color:#5A7D13;}
            .elemental-button.purplegoo{background-color:#800080;} .elemental-button.purplegoo:hover{background-color:#700070;}
            #elemental-toggle-button{margin-left:5px; background-color: #6c757d; color:white;}
            #elemental-toggle-button.active{background-color: #5a6268; box-shadow: inset 0 1px 3px rgba(0,0,0,.2);}
            .elemental-clear-button{padding:10px 12px;background-color:#ff9800;color:white;border:none;border-radius:5px;cursor:pointer;text-align:center;font-size:14px;transition:background-color .2s ease, transform .1s ease;width:100%;margin-top:12px;display:flex; align-items:center; justify-content:center;}
            .elemental-clear-button i{margin-right:8px; font-size: 1.2em;}
            .elemental-clear-button:hover{background-color:#f57c00;transform: translateY(-1px);}
            .elemental-clear-button:active{transform: translateY(0px);}`;
        document.head.appendChild(s);
    }

    function makeDraggable(m, h) {
        let oX, oY, d = false;
        h.onmousedown = e => {
            if (e.target.closest('button,input,select,a')) return;
            d = true;
            oX = e.clientX - m.getBoundingClientRect().left;
            oY = e.clientY - m.getBoundingClientRect().top;
            m.style.userSelect = 'none';
            document.body.style.cursor = 'grabbing';
        };
        document.onmousemove = e => {
            if (!d) return;
            m.style.left = `${e.clientX - oX}px`;
            m.style.top = `${e.clientY - oY}px`;
        };
        document.onmouseup = () => {
            if (d) {
                d = false;
                m.style.userSelect = '';
                document.body.style.cursor = '';
            }
        };
        h.style.cursor = 'move';
    }

    function buildMenu() {
        addBoxIcons();
        createStylesheet();
        const M = document.createElement('div');
        M.id = 'elemental-menu';
        const H = document.createElement('div');
        H.id = 'elemental-menu-header';
        H.innerHTML = `<h3><i class='bx bxs-color-fill' style="margin-right:8px;"></i>Elemental Animations</h3><button id="elemental-menu-close" title="Close Menu"><i class='bx bx-x'></i></button>`;
        M.appendChild(H);
        const B = document.createElement('div');
        B.id = 'elemental-menu-body';
        B.innerHTML = `
            <div class="elemental-section">
                <h4><i class='bx bxs-magic-wand'></i> Animations</h4>
                <div class="elemental-button-grid">
                    <button id="effect-ocean" class="elemental-button ocean" title="Dynamic ocean waves with foamy crests."><i class='bx bxs-droplet'></i> Ocean</button>
                    <button id="effect-magma" class="elemental-button magma" title="Thick, crusty magma flow with embers."><i class='bx bxs-volcano'></i> Magma</button>
                    <button id="effect-poison" class="elemental-button poison" title="Gloopy toxic poison with bubbling blobs."><i class='bx bxs-skull'></i> Poison</button>
                    <button id="effect-purplegoo" class="elemental-button purplegoo" title="Viscous purple goo with magenta highlights."><i class='bx bxs-vial'></i> Purple Goo</button>
                </div>
            </div>
             <div class="elemental-section">
                <h4><i class='bx bx-eraser'></i> Utilities</h4>
                <button id="bot-clear-canvas" class="elemental-clear-button"><i class='bx bxs-eraser'></i> Bot Clear (Whiteout)</button>
            </div>`;
        M.appendChild(B);
        document.body.appendChild(M);
        makeDraggable(M, H);

        EL('#elemental-menu-close').onclick = () => M.style.display = 'none';
        EL('#effect-ocean').onclick = () => animateElementalWave('Ocean');
        EL('#effect-magma').onclick = () => animateElementalWave('Magma');
        EL('#effect-poison').onclick = () => animateElementalWave('Poison');
        EL('#effect-purplegoo').onclick = () => animateElementalWave('PurpleGoo');
        EL('#bot-clear-canvas').onclick = botClearCanvas;

        const chatInputContainer = EL('#chatbox_textinput')?.parentElement;
        if (chatInputContainer && !EL('#elemental-toggle-button')) {
            let tB = document.createElement('button');
            tB.id = 'elemental-toggle-button';
            tB.className = 'btn btn-sm';
            tB.innerHTML = `<i class='bx bx-palette bx-sm'></i>`;
            tB.title = "Toggle Elemental Animations Menu";
            tB.style.marginLeft = "5px";
            tB.onclick = e => {
                e.preventDefault();
                const menu = EL('#elemental-menu');
                menu.style.display = (menu.style.display === 'none' || menu.style.display === '') ? 'block' : 'none';
                tB.classList.toggle('active', menu.style.display === 'block');
            };

            const sendButton = EL('#chatbox_form button[type="submit"], #chatbox_form button:not([id])'); // Try to find the send button
            if (sendButton && sendButton.parentElement) {
                 sendButton.parentElement.insertBefore(tB, sendButton.nextSibling); // Insert after send button
            } else if (chatInputContainer.querySelector('.input-group-append')) {
                 chatInputContainer.querySelector('.input-group-append').appendChild(tB);
            }
            else {
                chatInputContainer.appendChild(tB);
            }
        }
    }

    function initializeWhenReady() {
        originalCanvas = document.getElementById('canvas');
        const cI = document.getElementById('chatbox_textinput');
        if (!originalCanvas || !cI) {
            setTimeout(initializeWhenReady, 500);
            return;
        }
        if (originalCanvas) {
            cw = originalCanvas.width;
            ch = originalCanvas.height;
            console.log(`Canvas dimensions captured: ${cw}x${ch}`);
        }
        console.log("Drawaria Elemental Animations Enhanced V1.6 init...");
        window['___ENGINE'] = { animateElementalWave, botClearCanvas };
        buildMenu();
        console.log("Drawaria Elemental Animations Enhanced V1.6 Loaded!");
    }

    if (document.readyState === "complete" || document.readyState === "interactive") {
        initializeWhenReady();
    } else {
        window.addEventListener('load', initializeWhenReady);
    }
})();