Super Mario Bros. Level Editor for Drawaria.online

Transform Drawaria.online into an immersive Super Mario Bros. level editor with dynamic elements and enhanced realism.

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Super Mario Bros. Level Editor for Drawaria.online
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Transform Drawaria.online into an immersive Super Mario Bros. level editor with dynamic elements and enhanced realism.
// @author       YouTubeDrawaria
// @match        https://drawaria.online/*
// @grant        none
// @license      MIT
// @icon         https://www.google.com/s2/favicons?sz=64&domain=drawaria.online
// ==/UserScript==

(function() {
    'use strict';

    // --- Configuration ---
    const EDITOR_WIDTH = 800; // Optimal width for the editor canvas
    const EDITOR_HEIGHT = 600; // Optimal height for the editor canvas
    const TILE_SIZE = 32; // Standard tile size for Mario elements
    const GRID_COLOR = 'rgba(0, 0, 0, 0.2)';

    // --- Global State ---
    let editorActive = false;
    let currentTool = 'block'; // 'block', 'enemy', 'powerup', 'mario_start', 'clear'
    let currentElement = null; // Specific element type (e.g., 'brick', 'goomba', 'mushroom')
    let levelData = []; // Stores the level layout: [{x, y, type}, ...]
    let gameCanvas = null;
    let gameCtx = null;
    let editorUI = null;
    let audioContext = null;
    let backgroundMusic = null;
    let gameInterval = null; // For game simulation

    // --- Asset Generation (Conceptual - Highly Complex in Reality) ---
    // In a real scenario, this would be done using pixel-by-pixel drawing on OffscreenCanvas
    // or through complex WebGL/SVG generation for high-fidelity vector graphics.
    // For sounds, Web Audio API's OscillatorNode and gain manipulation for synthesis.

    const MarioAssets = {
        // Example: Programmatic generation of a simple Mario sprite (very basic representation)
        generateMarioSprite: () => {
            const canvas = document.createElement('canvas');
            canvas.width = TILE_SIZE;
            canvas.height = TILE_SIZE;
            const ctx = canvas.getContext('2d');
            ctx.fillStyle = 'red'; // Mario's hat/shirt
            ctx.fillRect(TILE_SIZE * 0.1, TILE_SIZE * 0.1, TILE_SIZE * 0.8, TILE_SIZE * 0.8);
            ctx.fillStyle = 'brown'; // Mario's overalls
            ctx.fillRect(TILE_SIZE * 0.2, TILE_SIZE * 0.5, TILE_SIZE * 0.6, TILE_SIZE * 0.4);
            ctx.fillStyle = 'peachpuff'; // Mario's skin
            ctx.beginPath();
            ctx.arc(TILE_SIZE / 2, TILE_SIZE * 0.3, TILE_SIZE * 0.2, 0, Math.PI * 2);
            ctx.fill();
            return canvas; // Returns a canvas element as a sprite
        },
        // More sprites for blocks, enemies, power-ups would follow similar complex patterns.
        // Each would require meticulous drawing commands for every pixel or vector path.
        generateBlockSprite: (type) => {
            const canvas = document.createElement('canvas');
            canvas.width = TILE_SIZE;
            canvas.height = TILE_SIZE;
            const ctx = canvas.getContext('2d');
            ctx.strokeStyle = '#444';
            ctx.lineWidth = 2;
            ctx.strokeRect(0, 0, TILE_SIZE, TILE_SIZE);
            if (type === 'brick') {
                ctx.fillStyle = '#A0522D'; // Sienna
                ctx.fillRect(2, 2, TILE_SIZE - 4, TILE_SIZE - 4);
                ctx.fillStyle = '#8B4513'; // SaddleBrown
                ctx.fillRect(TILE_SIZE * 0.2, TILE_SIZE * 0.2, TILE_SIZE * 0.6, TILE_SIZE * 0.6);
            } else if (type === 'question_block') {
                ctx.fillStyle = '#FFD700'; // Gold
                ctx.fillRect(2, 2, TILE_SIZE - 4, TILE_SIZE - 4);
                ctx.fillStyle = '#DAA520'; // Goldenrod
                ctx.fillRect(TILE_SIZE * 0.2, TILE_SIZE * 0.2, TILE_SIZE * 0.6, TILE_SIZE * 0.6);
                ctx.font = `${TILE_SIZE * 0.7}px Arial`;
                ctx.fillStyle = 'black';
                ctx.textAlign = 'center';
                ctx.textBaseline = 'middle';
                ctx.fillText('?', TILE_SIZE / 2, TILE_SIZE / 2);
            }
            return canvas;
        },
        generateGoombaSprite: () => {
            const canvas = document.createElement('canvas');
            canvas.width = TILE_SIZE;
            canvas.height = TILE_SIZE;
            const ctx = canvas.getContext('2d');
            ctx.fillStyle = '#8B4513'; // Goomba body color
            ctx.beginPath();
            ctx.ellipse(TILE_SIZE / 2, TILE_SIZE * 0.6, TILE_SIZE * 0.4, TILE_SIZE * 0.3, 0, 0, Math.PI * 2);
            ctx.fill();
            ctx.fillStyle = 'white'; // Eyes
            ctx.beginPath();
            ctx.arc(TILE_SIZE * 0.35, TILE_SIZE * 0.45, TILE_SIZE * 0.1, 0, Math.PI * 2);
            ctx.arc(TILE_SIZE * 0.65, TILE_SIZE * 0.45, TILE_SIZE * 0.1, 0, Math.PI * 2);
            ctx.fill();
            ctx.fillStyle = 'black'; // Pupils
            ctx.beginPath();
            ctx.arc(TILE_SIZE * 0.35, TILE_SIZE * 0.45, TILE_SIZE * 0.05, 0, Math.PI * 2);
            ctx.arc(TILE_SIZE * 0.65, TILE_SIZE * 0.45, TILE_SIZE * 0.05, 0, Math.PI * 2);
            ctx.fill();
            return canvas;
        },
        generateMushroomSprite: () => {
            const canvas = document.createElement('canvas');
            canvas.width = TILE_SIZE;
            canvas.height = TILE_SIZE;
            const ctx = canvas.getContext('2d');
            // Mushroom top
            ctx.fillStyle = 'red';
            ctx.beginPath();
            ctx.arc(TILE_SIZE / 2, TILE_SIZE * 0.4, TILE_SIZE * 0.4, Math.PI, Math.PI * 2);
            ctx.closePath();
            ctx.fill();
            // Mushroom stem
            ctx.fillStyle = 'white';
            ctx.fillRect(TILE_SIZE * 0.4, TILE_SIZE * 0.5, TILE_SIZE * 0.2, TILE_SIZE * 0.4);
            return canvas;
        },

        // Sound Synthesis: Very basic example, actual music requires complex sequencing
        // and instrument synthesis.
        generateCoinSound: () => {
            if (!audioContext) audioContext = new (window.AudioContext || window.webkitAudioContext)();
            const oscillator = audioContext.createOscillator();
            const gainNode = audioContext.createGain();

            oscillator.connect(gainNode);
            gainNode.connect(audioContext.destination);

            oscillator.type = 'triangle';
            oscillator.frequency.setValueAtTime(1000, audioContext.currentTime); // High frequency
            gainNode.gain.setValueAtTime(1, audioContext.currentTime);
            gainNode.gain.exponentialRampToValueAtTime(0.001, audioContext.currentTime + 0.2); // Quick decay

            oscillator.start();
            oscillator.stop(audioContext.currentTime + 0.2);
        },
        generateJumpSound: () => {
            if (!audioContext) audioContext = new (window.AudioContext || window.webkitAudioContext)();
            const oscillator = audioContext.createOscillator();
            const gainNode = audioContext.createGain();

            oscillator.connect(gainNode);
            gainNode.connect(audioContext.destination);

            oscillator.type = 'sine';
            oscillator.frequency.setValueAtTime(440, audioContext.currentTime); // A4
            oscillator.frequency.linearRampToValueAtTime(660, audioContext.currentTime + 0.1); // Rise
            gainNode.gain.setValueAtTime(0.5, audioContext.currentTime);
            gainNode.gain.exponentialRampToValueAtTime(0.001, audioContext.currentTime + 0.1);

            oscillator.start();
            oscillator.stop(audioContext.currentTime + 0.1);
        },
        generateBackgroundMusic: () => {
            if (!audioContext) audioContext = new (window.AudioContext || window.webkitAudioContext)();
            // This is a highly simplified placeholder.
            // Actual music would involve multiple oscillators, complex envelopes,
            // rhythm sequencing, and potentially custom waveforms.
            // For example, generating the Super Mario Bros. theme from scratch is a massive undertaking.
            // This function would return a Web Audio API graph or a playback function.
            console.warn("Generating complex background music from scratch is an advanced audio synthesis task.");
            // Example: A very simple, sustained low tone for 'background'
            const oscillator = audioContext.createOscillator();
            oscillator.type = 'square';
            oscillator.frequency.setValueAtTime(100, audioContext.currentTime);
            const gainNode = audioContext.createGain();
            gainNode.gain.setValueAtTime(0.1, audioContext.currentTime);
            oscillator.connect(gainNode);
            gainNode.connect(audioContext.destination);
            oscillator.start();
            backgroundMusic = {
                play: () => { if (audioContext.state === 'suspended') audioContext.resume(); },
                stop: () => { if (audioContext.state === 'running') audioContext.suspend(); }
            };
        }
    };

    // Store generated sprites
    const sprites = {
        mario: MarioAssets.generateMarioSprite(),
        brick_block: MarioAssets.generateBlockSprite('brick'),
        question_block: MarioAssets.generateBlockSprite('question_block'),
        goomba: MarioAssets.generateGoombaSprite(),
        mushroom: MarioAssets.generateMushroomSprite(),
        // Add more dynamically generated sprites here
    };

    // --- UI Creation ---
    function createEditorUI() {
        // Create main editor container
        editorUI = document.createElement('div');
        editorUI.id = 'mario-editor-container';
        Object.assign(editorUI.style, {
            position: 'fixed',
            top: '0',
            left: '0',
            width: '100vw',
            height: '100vh',
            backgroundColor: 'rgba(0, 0, 0, 0.8)',
            zIndex: '9999',
            display: 'flex',
            flexDirection: 'column',
            justifyContent: 'center',
            alignItems: 'center',
            fontFamily: 'Pixelify Sans, sans-serif', // Use a pixel font for authenticity
            color: 'white',
            overflow: 'hidden'
        });

        // Add Google Fonts Pixelify Sans
        const fontLink = document.createElement('link');
        fontLink.href = 'https://fonts.googleapis.com/css2?family=Pixelify+Sans&display=swap';
        fontLink.rel = 'stylesheet';
        document.head.appendChild(fontLink);

        // Header
        const header = document.createElement('h1');
        header.textContent = 'Super Mario Level Editor';
        Object.assign(header.style, {
            fontSize: '3em',
            marginBottom: '20px',
            textShadow: '4px 4px 0px blue', // Add some depth
        });
        editorUI.appendChild(header);

        // Editor Canvas
        gameCanvas = document.createElement('canvas');
        gameCanvas.id = 'mario-editor-canvas';
        gameCanvas.width = EDITOR_WIDTH;
        gameCanvas.height = EDITOR_HEIGHT;
        Object.assign(gameCanvas.style, {
            border: '5px solid #8B4513', // Brown border
            background: 'linear-gradient(to bottom, skyblue, lightblue 70%, green 70%)', // Sky and ground
            boxShadow: '0 0 20px rgba(255, 255, 0, 0.7)', // Glow
            cursor: 'crosshair'
        });
        editorUI.appendChild(gameCanvas);
        gameCtx = gameCanvas.getContext('2d');

        // Tool Panel
        const toolPanel = document.createElement('div');
        Object.assign(toolPanel.style, {
            display: 'flex',
            gap: '10px',
            marginTop: '20px',
            padding: '10px',
            background: 'rgba(0, 0, 0, 0.6)',
            borderRadius: '8px',
            boxShadow: '0 0 10px rgba(0, 0, 0, 0.5)'
        });
        editorUI.appendChild(toolPanel);

        // Tools
        const tools = [
            { id: 'tool-block', text: 'Block', type: 'block', elements: ['brick_block', 'question_block'] },
            { id: 'tool-enemy', text: 'Enemy', type: 'enemy', elements: ['goomba'] },
            { id: 'tool-powerup', text: 'Power-up', type: 'powerup', elements: ['mushroom'] },
            { id: 'tool-mario', text: 'Mario Start', type: 'mario_start', elements: ['mario'] },
            { id: 'tool-clear', text: 'Clear', type: 'clear' },
        ];

        tools.forEach(tool => {
            const button = document.createElement('button');
            button.id = tool.id;
            button.textContent = tool.text;
            Object.assign(button.style, {
                padding: '8px 15px',
                fontSize: '1em',
                backgroundColor: '#007bff', // Blue button
                color: 'white',
                border: 'none',
                borderRadius: '5px',
                cursor: 'pointer',
                transition: 'background-color 0.2s',
                fontFamily: 'inherit'
            });
            button.onmouseover = () => button.style.backgroundColor = '#0056b3';
            button.onmouseout = () => button.style.backgroundColor = '#007bff';
            button.onclick = () => {
                currentTool = tool.type;
                if (tool.elements) {
                    currentElement = tool.elements[0]; // Select first element by default
                } else {
                    currentElement = null;
                }
                updateToolPanelSelection();
            };
            toolPanel.appendChild(button);
        });

        // Element Selector (for block, enemy, powerup types)
        const elementSelector = document.createElement('select');
        elementSelector.id = 'element-selector';
        Object.assign(elementSelector.style, {
            padding: '8px',
            fontSize: '1em',
            borderRadius: '5px',
            border: '1px solid #ccc',
            backgroundColor: 'white',
            fontFamily: 'inherit',
            marginLeft: '10px'
        });
        elementSelector.onchange = (e) => {
            currentElement = e.target.value;
        };
        toolPanel.appendChild(elementSelector);

        // Action Buttons
        const actionPanel = document.createElement('div');
        Object.assign(actionPanel.style, {
            display: 'flex',
            gap: '10px',
            marginTop: '10px'
        });
        editorUI.appendChild(actionPanel);

        const playButton = document.createElement('button');
        playButton.textContent = 'Test Level (Conceptual)';
        Object.assign(playButton.style, {
            padding: '10px 20px',
            fontSize: '1.2em',
            backgroundColor: '#28a745', // Green button
            color: 'white',
            border: 'none',
            borderRadius: '5px',
            cursor: 'pointer',
            transition: 'background-color 0.2s',
            fontFamily: 'inherit'
        });
        playButton.onmouseover = () => playButton.style.backgroundColor = '#218838';
        playButton.onmouseout = () => playButton.style.backgroundColor = '#28a745';
        playButton.onclick = () => {
            alert('Level testing is a complex game simulation not fully implemented. Imagine Mario running here!');
            MarioAssets.generateJumpSound(); // Simulate a jump sound
        };
        actionPanel.appendChild(playButton);

        const exitButton = document.createElement('button');
        exitButton.textContent = 'Exit Editor';
        Object.assign(exitButton.style, {
            padding: '10px 20px',
            fontSize: '1.2em',
            backgroundColor: '#dc3545', // Red button
            color: 'white',
            border: 'none',
            borderRadius: '5px',
            cursor: 'pointer',
            transition: 'background-color 0.2s',
            fontFamily: 'inherit'
        });
        exitButton.onmouseover = () => exitButton.style.backgroundColor = '#c82333';
        exitButton.onmouseout = () => exitButton.style.backgroundColor = '#dc3545';
        exitButton.onclick = disableEditor;
        actionPanel.appendChild(exitButton);

        document.body.appendChild(editorUI);
        updateToolPanelSelection(); // Initial update
    }

    // --- Editor Logic ---
    function drawGrid() {
        gameCtx.strokeStyle = GRID_COLOR;
        for (let i = 0; i < EDITOR_WIDTH / TILE_SIZE; i++) {
            gameCtx.beginPath();
            gameCtx.moveTo(i * TILE_SIZE, 0);
            gameCtx.lineTo(i * TILE_SIZE, EDITOR_HEIGHT);
            gameCtx.stroke();
        }
        for (let i = 0; i < EDITOR_HEIGHT / TILE_SIZE; i++) {
            gameCtx.beginPath();
            gameCtx.moveTo(0, i * TILE_SIZE);
            gameCtx.lineTo(EDITOR_WIDTH, i * TILE_SIZE);
            gameCtx.stroke();
        }
    }

    function drawLevel() {
        gameCtx.clearRect(0, 0, EDITOR_WIDTH, EDITOR_HEIGHT);
        // The linear gradient is drawn as background of the canvas element itself
        // so it persists on clearRect.
        // gameCtx.fillStyle = 'linear-gradient(to bottom, skyblue, lightblue 70%, green 70%)'; // Sky and ground
        // gameCtx.fillRect(0, 0, EDITOR_WIDTH, EDITOR_HEIGHT);

        drawGrid();

        levelData.forEach(item => {
            const sprite = sprites[item.type];
            if (sprite) {
                gameCtx.drawImage(sprite, item.x, item.y, TILE_SIZE, TILE_SIZE);

                // Add glowing particle effects for power-ups (conceptual)
                if (item.type === 'mushroom') {
                    // This would involve rendering many small, semi-transparent circles
                    // or squares around the power-up, fading in and out.
                    // Example: simple static glow effect for demonstration
                    gameCtx.fillStyle = 'rgba(255, 255, 0, 0.3)'; // Yellow glow
                    gameCtx.beginPath();
                    gameCtx.arc(item.x + TILE_SIZE / 2, item.y + TILE_SIZE / 2, TILE_SIZE * 0.7, 0, Math.PI * 2);
                    gameCtx.fill();
                }
            }
        });
    }

    function handleCanvasClick(event) {
        if (!editorActive) return;

        const rect = gameCanvas.getBoundingClientRect();
        const mouseX = event.clientX - rect.left;
        const mouseY = event.clientY - rect.top;

        const gridX = Math.floor(mouseX / TILE_SIZE) * TILE_SIZE;
        const gridY = Math.floor(mouseY / TILE_SIZE) * TILE_SIZE;

        const existingElementIndex = levelData.findIndex(item => item.x === gridX && item.y === gridY);

        if (currentTool === 'clear') {
            if (existingElementIndex !== -1) {
                levelData.splice(existingElementIndex, 1);
            }
        } else if (currentElement) {
            if (existingElementIndex !== -1) {
                levelData[existingElementIndex] = { x: gridX, y: gridY, type: currentElement };
            } else {
                levelData.push({ x: gridX, y: gridY, type: currentElement });
            }
        }

        drawLevel();
        if (currentElement === 'brick_block' || currentElement === 'question_block') {
             MarioAssets.generateCoinSound(); // Play a sound when placing blocks
        }
    }

    function updateToolPanelSelection() {
        // Reset all button styles
        document.querySelectorAll('#mario-editor-container button').forEach(btn => {
            btn.style.backgroundColor = '#007bff';
        });

        // Highlight selected tool button
        let selectedButton = null;
        if (currentTool === 'block') selectedButton = document.getElementById('tool-block');
        else if (currentTool === 'enemy') selectedButton = document.getElementById('tool-enemy');
        else if (currentTool === 'powerup') selectedButton = document.getElementById('tool-powerup');
        else if (currentTool === 'mario_start') selectedButton = document.getElementById('tool-mario');
        else if (currentTool === 'clear') selectedButton = document.getElementById('tool-clear');

        if (selectedButton) {
            selectedButton.style.backgroundColor = '#0056b3'; // Darker blue for selected
        }

        // Update element selector options
        const elementSelector = document.getElementById('element-selector');
        elementSelector.innerHTML = ''; // Clear previous options

        let elementsForTool = [];
        if (currentTool === 'block') elementsForTool = ['brick_block', 'question_block'];
        else if (currentTool === 'enemy') elementsForTool = ['goomba'];
        else if (currentTool === 'powerup') elementsForTool = ['mushroom'];
        else if (currentTool === 'mario_start') elementsForTool = ['mario'];

        elementsForTool.forEach(el => {
            const option = document.createElement('option');
            option.value = el;
            option.textContent = el.replace(/_/g, ' ').toUpperCase();
            elementSelector.appendChild(option);
        });

        elementSelector.style.display = elementsForTool.length > 0 ? 'inline-block' : 'none';

        // Set currentElement to the first option if available, otherwise null
        currentElement = elementsForTool.length > 0 ? elementsForTool[0] : null;
        if (elementSelector.options.length > 0) {
            elementSelector.value = currentElement;
        }
    }

    // --- Editor Control ---
    function enableEditor() {
        if (editorActive) return;

        // Hide Drawaria.online's main content
        const mainContent = document.getElementById('main');
        if (mainContent) mainContent.style.display = 'none';
        const loginSection = document.getElementById('login');
        if (loginSection) loginSection.style.display = 'none';

        createEditorUI();
        drawLevel();
        editorActive = true;
        gameCanvas.addEventListener('click', handleCanvasClick);

        // Start conceptual background music
        MarioAssets.generateBackgroundMusic();
        if (backgroundMusic) backgroundMusic.play();
    }

    function disableEditor() {
        if (!editorActive) return;

        if (editorUI && editorUI.parentNode) {
            editorUI.parentNode.removeChild(editorUI);
        }

        // Show Drawaria.online's main content
        const mainContent = document.getElementById('main');
        if (mainContent) mainContent.style.display = '';
        const loginSection = document.getElementById('login');
        if (loginSection) loginSection.style.display = '';

        gameCanvas.removeEventListener('click', handleCanvasClick);
        editorActive = false;

        // Stop conceptual background music
        if (backgroundMusic) backgroundMusic.stop();
        backgroundMusic = null;
    }

    // --- Main Entry Point / Button to toggle editor ---
    function addToggleButton() {
        const toggleButton = document.createElement('button');
        toggleButton.textContent = 'Toggle Mario Editor';
        Object.assign(toggleButton.style, {
            position: 'fixed',
            bottom: '20px',
            right: '20px',
            padding: '15px 30px',
            fontSize: '1.5em',
            backgroundColor: '#FF4500', // Orange-red
            color: 'white',
            border: 'none',
            borderRadius: '10px',
            cursor: 'pointer',
            zIndex: '10000',
            boxShadow: '0 5px 15px rgba(0,0,0,0.3)',
            transition: 'background-color 0.3s, transform 0.2s',
            fontFamily: 'Pixelify Sans, sans-serif'
        });
        toggleButton.onmouseover = () => {
            toggleButton.style.backgroundColor = '#E03C00';
            toggleButton.style.transform = 'scale(1.05)';
        };
        toggleButton.onmouseout = () => {
            toggleButton.style.backgroundColor = '#FF4500';
            toggleButton.style.transform = 'scale(1)';
        };
        toggleButton.onclick = () => {
            if (editorActive) {
                disableEditor();
            } else {
                enableEditor();
            }
        };
        document.body.appendChild(toggleButton);
    }

    // Initial setup when the script runs
    window.addEventListener('load', () => {
        addToggleButton();
    });

})();