Drawaria VS Character Drawer Menu

Pastes various 'VS' themed characters/actions directly onto your Drawaria.online canvas from APIs. (Client-side only)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Drawaria VS Character Drawer Menu
// @namespace    http://tampermonkey.net/
// @version      1.2
// @description  Pastes various 'VS' themed characters/actions directly onto your Drawaria.online canvas from APIs. (Client-side only)
// @author       YouTubeDrawaria
// @include      https://drawaria.online*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=drawaria.online/room/
// @grant        GM_xmlhttpRequest
// @license MIT
// ==/UserScript==

(() => {
    'use strict';

    console.log("VS Character Drawer (Direct Canvas Paste): Script starting...");

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

    let previewCanvas = document.createElement('canvas'); // Used to prepare the image
    let originalCanvas = null; // The game's main canvas

    function addBoxIconsAndFonts() {
        console.log("VS Character Drawer: addBoxIconsAndFonts called");
        // Boxicons
        if (!document.querySelector('link[href="https://unpkg.com/[email protected]/css/boxicons.min.css"]')) {
            let boxicons_link = document.createElement('link');
            boxicons_link.href = 'https://unpkg.com/[email protected]/css/boxicons.min.css';
            boxicons_link.rel = 'stylesheet';
            document.head.appendChild(boxicons_link);
        }
        // Google Font: Press Start 2P
        if (!document.querySelector('link[href="https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap"]')) {
            let font_link = document.createElement('link');
            font_link.href = 'https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap';
            font_link.rel = 'stylesheet';
            document.head.appendChild(font_link);
        }
    }

    function CreateStylesheet() {
        console.log("VS Character Drawer: CreateStylesheet called");
        if (document.getElementById('VsCharacterDrawerStyles')) {
            return;
        }
        let styleElement = document.createElement('style');
        styleElement.id = 'VsCharacterDrawerStyles';
        styleElement.innerHTML = `
            /* VS Character Drawer Styles */
            input[type="number"] { text-align: center; -webkit-appearance: none; -moz-appearance: textfield; }
            .hidden { display: none !important; }

            .vs-drawer-container {
                position: relative; width: 100%; background-color: #333; /* Dark Grey */
                border: 2px solid #F00; /* Red */
                border-radius: 15px; padding: 10px; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.4);
                margin-top: 10px; font-family: 'Press Start 2P', cursive; /* Retro Gaming Font */
                color: #FFD700; /* Gold */
                text-shadow: 1px 1px 2px rgba(0,0,0,0.5);
            }
            .vs-drawer-container h3 {
                text-align: center; color: #DC143C; /* Crimson */
                margin-bottom: 10px; display: flex;
                align-items: center; justify-content: center; gap: 8px;
                font-size: 1.2em; /* Slightly larger title */
            }
            .vs-drawer-row { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 8px; justify-content: center; align-items: center;}
            .vs-drawer-row > * { flex: 1 1 auto; min-width: 80px; text-align: center; }
            .vs-border-input {
                border: 1px solid #DC143C; /* Crimson */
                border-radius: 8px; padding: 5px;
                background-color: #555; /* Darker Grey */
                color: #FFD700; /* Gold */
                font-size: 13px;
                font-family: 'Press Start 2P', cursive;
            }
            .vs-drawer-button {
                background-color: #DC143C; /* Crimson */
                color: white; border: none; border-radius: 10px;
                padding: 10px 15px; font-size: 15px; cursor: pointer; transition: all 0.3s ease;
                box-shadow: 0 3px 6px rgba(0, 0, 0, 0.2); display: flex; align-items: center;
                justify-content: center; gap: 8px;
                font-family: 'Press Start 2P', cursive;
                text-transform: uppercase;
            }
            .vs-drawer-button:hover { background-color: #FFD700; color: #DC143C; transform: translateY(-3px); box-shadow: 0 5px 10px rgba(0, 0, 0, 0.3); }
            .vs-drawer-button:active { transform: translateY(0); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); }
            .vs-drawer-button:disabled { background-color: #777; color: #BBB; cursor: not-allowed; }

            #vsImageDisplay {
                width: 100%; max-height: 200px; object-fit: contain; border: 1px dashed #DC143C; /* Crimson Dashed */
                border-radius: 10px; margin-bottom: 10px; background-color: #222; /* Even darker for image placeholder */
            }
            #toggleVsDrawer {
                background-color: #DC143C; /* Crimson */
                color: white; border: none; border-radius: 50%;
                width: 45px; height: 45px; font-size: 26px; cursor: pointer; transition: all 0.3s ease;
                box-shadow: 0 3px 6px rgba(0,0,0,0.3); display: flex; align-items: center;
                justify-content: center; margin-left: 8px; z-index: 10001;
            }
            #toggleVsDrawer:hover { background-color: #FFD700; color: #DC143C; transform: rotate(15deg) scale(1.1); }
            #toggleVsDrawer.active { background-color: #FFD700; color: #DC143C; } /* Active color for contrast */
            .vs-category-select {
                border: 1px solid #DC143C; /* Crimson */
                border-radius: 8px; padding: 5px;
                background-color: #555; /* Darker Grey */
                color: #FFD700; /* Gold */
                font-size: 13px; cursor: pointer;
                font-family: 'Press Start 2P', cursive;
            }
            #status_message { font-size: 12px; padding: 3px; margin-top: 5px; flex-basis: 100% !important; text-align: center; color: #FFD700; /* Gold */ }
        `;
        document.head.appendChild(styleElement);
    }

    async function fetchVsImage(category) {
        console.log("VS Character Drawer: fetchVsImage called for", category);
        return new Promise((resolve, reject) => {
            const targetUrl = `https://api.waifu.pics/sfw/${category}`;
            GM_xmlhttpRequest({
                method: "GET", url: targetUrl,
                onload: function(response) {
                    if (response.status >= 200 && response.status < 300) {
                        try {
                            const result = JSON.parse(response.responseText);
                            if (result && result.url) { resolve(result.url); }
                            else { reject(new Error('API response did not contain a URL.')); }
                        } catch (e) { reject(e); }
                    } else { reject(new Error(`HTTP error! status: ${response.statusText}`)); }
                },
                onerror: function(response) { reject(new Error(`Network error: ${response.statusText || response.error}`)); }
            });
        });
    }

    async function loadImage(url, vsImageDisplayElement) {
        console.log("VS Character Drawer: loadImage called for URL:", url);
        return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: 'GET', url: url, responseType: 'blob',
                onload: function(response) {
                    if (response.status === 200) {
                        var img = new Image();
                        var objectURL = URL.createObjectURL(response.response);
                        img.onload = () => {
                            if (!originalCanvas) {
                                reject(new Error("Main game canvas not found during loadImage.onload.")); return;
                            }
                            // Set previewCanvas dimensions to match the game's canvas
                            previewCanvas.width = originalCanvas.width;
                            previewCanvas.height = originalCanvas.height;

                            var ctx = previewCanvas.getContext('2d');
                            ctx.clearRect(0, 0, previewCanvas.width, previewCanvas.height); // Clear before drawing

                            // Calculate dimensions to draw the image centered and scaled to fit onto previewCanvas
                            let imgAspectRatio = img.width / img.height;
                            let canvasAspectRatio = previewCanvas.width / previewCanvas.height;
                            let drawWidth, drawHeight, offsetX_preview, offsetY_preview;

                            if (imgAspectRatio > canvasAspectRatio) {
                                drawWidth = previewCanvas.width;
                                drawHeight = previewCanvas.width / imgAspectRatio;
                                offsetX_preview = 0;
                                offsetY_preview = (previewCanvas.height - drawHeight) / 2;
                            } else {
                                drawHeight = previewCanvas.height;
                                drawWidth = previewCanvas.height * imgAspectRatio;
                                offsetY_preview = 0;
                                offsetX_preview = (previewCanvas.width - drawWidth) / 2;
                            }
                            ctx.drawImage(img, offsetX_preview, offsetY_preview, drawWidth, drawHeight);
                            console.log(`VS Character Drawer: Image drawn to previewCanvas (size ${previewCanvas.width}x${previewCanvas.height})`);
                            if (vsImageDisplayElement) {
                                vsImageDisplayElement.src = objectURL;
                                vsImageDisplayElement.classList.remove('hidden');
                            }
                            resolve(); // Resolve once previewCanvas is ready
                        };
                        img.onerror = (e) => { URL.revokeObjectURL(objectURL); reject(e); };
                        img.src = objectURL;
                    } else { reject(new Error(`Failed to download image: ${response.statusText}`)); }
                },
                onerror: function(response) { reject(new Error(`Network error downloading image: ${response.statusText || response.error}`));}
            });
        });
    }

    function VsCharacterDrawerEngine() {
        console.log("VS Character Drawer: VsCharacterDrawerEngine called.");
        let vsDrawerContainer = document.getElementById('VsCharacterDrawerContainer');
        if (vsDrawerContainer) { return; } // Already initialized
        vsDrawerContainer = document.createElement('div');
        vsDrawerContainer.id = 'VsCharacterDrawerContainer';
        vsDrawerContainer.className = 'vs-drawer-container hidden';
        const chatMessagesElement = document.getElementById('chatbox_messages');
        if (chatMessagesElement) { chatMessagesElement.after(vsDrawerContainer); }
        else { document.body.appendChild(vsDrawerContainer); }

        CreateToggleButton(vsDrawerContainer);
        VsImageControls(vsDrawerContainer);
    }

    function CreateToggleButton(containerToToggle) {
        console.log("VS Character Drawer: CreateToggleButton called.");
        let target = document.getElementById('chatbox_textinput');
        if (!target) { console.error("VS Character Drawer: Chat input for toggle button not found."); return; }
        if (document.getElementById('toggleVsDrawer')) { return; }

        let btncontainer = document.createElement('div');
        btncontainer.className = 'input-group-append';
        let togglebtn = document.createElement('button');
        togglebtn.id = 'toggleVsDrawer';
        togglebtn.innerHTML = '<i class="bx bxs-bolt"></i>'; // Lightning bolt icon
        togglebtn.title = "Toggle VS Character Drawer";
        togglebtn.type = "button";
        togglebtn.addEventListener('click', (e) => {
            e.preventDefault();
            togglebtn.classList.toggle('active');
            if (containerToToggle) { containerToToggle.classList.toggle('hidden'); }
        });
        btncontainer.appendChild(togglebtn);
        target.after(btncontainer);
        console.log("VS Character Drawer: Toggle button added.");
    }

    function VsImageControls(container) {
        console.log("VS Character Drawer: VsImageControls called.");
        let controlsDiv = document.createElement('div');
        controlsDiv.innerHTML = `
            <h3>VS Character Menu <i class='bx bxs-swords'></i></h3> <!-- Crossed swords icon -->
            <img id="vsImageDisplay" src="" alt="VS Image Preview" class="hidden">
            <div class="vs-drawer-row">
                <select id="vsCategorySelect" class="vs-category-select"></select>
                <button id="loadVsBtn" class="vs-drawer-button"><i class='bx bxs-rocket'></i> Load VS Image</button>
            </div>
            <div class="vs-drawer-row">
                 <label for="engine_offset_x" class="vs-border-input" title="Offset X (% of canvas width)">Offset X (%):</label>
                <input type="number" id="engine_offset_x" min="-100" max="100" value="0" class="vs-border-input">
                <label for="engine_offset_y" class="vs-border-input" title="Offset Y (% of canvas height)">Offset Y (%):</label>
                <input type="number" id="engine_offset_y" min="-100" max="100" value="0" class="vs-border-input">
            </div>
            <div class="vs-drawer-row">
                <button id="pasteToCanvasBtn" class="vs-drawer-button"><i class='bx bxs-brush'></i> Paste to Canvas</button>
                <button id="clearPreviewBtn" class="vs-drawer-button" title="Clear the preview image and any pasted image (by drawing white over area)"><i class='bx bx-trash'></i> Clear Preview</button>
            </div>
            <div class="vs-drawer-row">
                 <span id="status_message"></span>
            </div>
        `;
        container.appendChild(controlsDiv);

        const vsImageDisplay = EL('#vsImageDisplay');
        const vsCategorySelect = EL('#vsCategorySelect');
        const loadVsBtn = EL('#loadVsBtn');
        const pasteToCanvasBtn = EL('#pasteToCanvasBtn');
        const clearPreviewBtn = EL('#clearPreviewBtn');
        const statusMessage = EL('#status_message');

        // Categorías de acción/VS de waifu.pics, filtradas
        const categories = [
            'kill', 'kick', 'slap', 'bonk', 'yeet', 'bite', 'bully'
        ];
        categories.forEach(cat => {
            const option = document.createElement('option');
            option.value = cat;
            option.textContent = cat.charAt(0).toUpperCase() + cat.slice(1).replace(/_/g, ' '); // Formatear para mejor lectura
            vsCategorySelect.appendChild(option);
        });

        loadVsBtn.addEventListener('click', async () => {
            const category = vsCategorySelect.value;
            loadVsBtn.disabled = true; loadVsBtn.innerHTML = '<i class="bx bx-loader-alt bx-spin"></i> Loading...';
            statusMessage.textContent = 'Fetching image URL...';
            try {
                const imageUrl = await fetchVsImage(category);
                if (imageUrl) {
                    statusMessage.textContent = 'Image URL fetched. Loading image data...';
                    await loadImage(imageUrl, vsImageDisplay); // loadImage now prepares previewCanvas
                    statusMessage.textContent = 'Image loaded. Ready to paste.';
                } else {
                    statusMessage.textContent = 'Failed to get image URL.';
                }
            } catch (error) {
                console.error('VS Character Drawer: Error in loadVsBtn click:', error);
                statusMessage.textContent = 'Error loading image. See console.';
            } finally {
                loadVsBtn.disabled = false; loadVsBtn.innerHTML = '<i class="bx bxs-rocket"></i> Load VS Image';
            }
        });

        pasteToCanvasBtn.addEventListener('click', () => {
            if (!previewCanvas || previewCanvas.width === 0 || previewCanvas.height === 0) {
                statusMessage.textContent = 'Load an image first to prepare it on the preview canvas.';
                alert('Please load a VS image first!');
                return;
            }
            if (!originalCanvas) {
                 statusMessage.textContent = 'Game canvas not found.';
                 alert('Error: Game canvas not found!');
                 return;
            }

            const gameCtx = originalCanvas.getContext('2d');
            if (!gameCtx) {
                statusMessage.textContent = 'Could not get game canvas context.';
                alert('Error: Could not get game canvas context!');
                return;
            }

            const offsetXPercent = parseFloat(EL('#engine_offset_x').value) || 0;
            const offsetYPercent = parseFloat(EL('#engine_offset_y').value) || 0;

            const dx = (offsetXPercent / 100) * originalCanvas.width;
            const dy = (offsetYPercent / 100) * originalCanvas.height;

            try {
                gameCtx.drawImage(previewCanvas, dx, dy);
                statusMessage.textContent = 'Image pasted to your canvas!';
                console.log(`VS Character Drawer: Pasted previewCanvas to game canvas at ${dx.toFixed(0)}, ${dy.toFixed(0)}`);
            } catch (e) {
                statusMessage.textContent = 'Error pasting image. See console.';
                console.error("VS Character Drawer: Error during gameCtx.drawImage():", e);
                alert("An error occurred while pasting the image. Check console.");
            }
        });

        clearPreviewBtn.addEventListener('click', () => {
            vsImageDisplay.src = '';
            vsImageDisplay.classList.add('hidden');
            if (previewCanvas) {
                const prevCtx = previewCanvas.getContext('2d');
                prevCtx.clearRect(0, 0, previewCanvas.width, previewCanvas.height);
            }
            if(originalCanvas && previewCanvas && previewCanvas.width > 0 && previewCanvas.height > 0){
                const gameCtx = originalCanvas.getContext('2d');
                 const offsetXPercent = parseFloat(EL('#engine_offset_x').value) || 0;
                 const offsetYPercent = parseFloat(EL('#engine_offset_y').value) || 0;
                 const dx = (offsetXPercent / 100) * originalCanvas.width;
                 const dy = (offsetYPercent / 100) * originalCanvas.height;
                gameCtx.fillStyle = 'white'; // Assuming default bg is white
                gameCtx.fillRect(dx, dy, previewCanvas.width, previewCanvas.height); // Draw white over the area
                 statusMessage.textContent = 'Preview cleared and area on canvas whited out.';
            } else {
                 statusMessage.textContent = 'Preview cleared.';
            }
            console.log("VS Character Drawer: Preview cleared.");
        });
    }

    function initializeScript() {
        console.log("VS Character Drawer: initializeScript called.");
        originalCanvas = document.getElementById('canvas'); // Get the game's canvas

        if (!originalCanvas) {
            console.log("VS Character Drawer: Game canvas ('canvas') not found yet. Retrying...");
            setTimeout(initializeScript, 1000); return;
        }
        console.log(`VS Character Drawer: Game canvas found. Dimensions: ${originalCanvas.width}x${originalCanvas.height}`);

        previewCanvas.width = originalCanvas.width; // Initialize with game canvas size
        previewCanvas.height = originalCanvas.height;

        const chatInput = document.getElementById('chatbox_textinput');
        const chatMessages = document.getElementById('chatbox_messages');
        if (!chatInput || !chatMessages) {
            console.log("VS Character Drawer: Chat elements not found yet. Retrying...");
            setTimeout(initializeScript, 1000); return;
        }

        if (!document.getElementById('VsCharacterDrawerContainer')) {
            addBoxIconsAndFonts();
            CreateStylesheet();
            VsCharacterDrawerEngine();
            console.log('VS Character Drawer (Direct Canvas Paste) UI Initialized!');
        }
    }

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

})();