Drawaria Drawbot Elemental Animations

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

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 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);
    }
})();