Drawaria Drawbot Elemental Animations

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

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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);
    }
})();