Drawaria.online - Aircraft Script

A highly ambitious, experimental Tampermonkey script to simulate a pilotable aircraft on Drawaria.online.

// ==UserScript==
// @name         Drawaria.online - Aircraft Script
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  A highly ambitious, experimental Tampermonkey script to simulate a pilotable aircraft on Drawaria.online.
// @author       YouTubeDrawaria / Your AI Assistant
// @match        https://drawaria.online/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=drawaria.online
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // --- Configuration ---
    const SCRIPT_ID = 'drawaria-aircraft-script';
    const Z_INDEX_OVERLAY = 9999; // Ensure our canvas is on top

    // --- Game State ---
    const aircraft = {
        x: window.innerWidth / 2,
        y: window.innerHeight / 2,
        speed: 0, // Current forward speed
        maxSpeed: 8,
        acceleration: 0.1,
        deceleration: 0.05,
        turnSpeed: 0.05, // Radians per frame
        pitchAngle: 0,  // Rotation around X-axis (up/down)
        rollAngle: 0,   // Rotation around Z-axis (sideways tilt)
        yawAngle: 0,    // Rotation around Y-axis (heading)
        maxPitchRoll: Math.PI / 4, // Max +/- 45 degrees
        enginePower: 0, // 0-1, affects sound
        fuel: 1000,
        maxFuel: 1000,
        altitude: 0, // Basic altitude simulation
        verticalSpeed: 0 // How fast altitude changes
    };

    const controls = {
        forward: false,
        backward: false,
        turnLeft: false,
        turnRight: false,
        pitchUp: false,
        pitchDown: false,
        rollLeft: false,
        rollRight: false,
        engineUp: false,
        engineDown: false
    };

    const particles = [];
    const MAX_PARTICLES = 100;

    // --- Audio Context & Generators ---
    let audioContext;
    let engineOscillator;
    let engineGain;

    function initAudio() {
        try {
            audioContext = new (window.AudioContext || window.webkitAudioContext)();

            // Engine sound: Basic oscillator with gain controlled by enginePower
            engineOscillator = audioContext.createOscillator();
            engineOscillator.type = 'sawtooth'; // A bit harsher, more like an engine
            engineOscillator.frequency.value = 50; // Base frequency
            engineGain = audioContext.createGain();
            engineGain.gain.value = 0; // Start silent
            engineOscillator.connect(engineGain);
            engineGain.connect(audioContext.destination);
            engineOscillator.start();

            // Background ambient sound (very subtle, procedural "wind")
            const noiseBuffer = audioContext.createBuffer(1, audioContext.sampleRate * 2, audioContext.sampleRate);
            const output = noiseBuffer.getChannelData(0);
            for (let i = 0; i < output.length; i++) {
                output[i] = Math.random() * 2 - 1; // White noise
            }
            const noiseSource = audioContext.createBufferSource();
            noiseSource.buffer = noiseBuffer;
            noiseSource.loop = true;
            const ambientGain = audioContext.createGain();
            ambientGain.gain.value = 0.01; // Very subtle
            noiseSource.connect(ambientGain);
            ambientGain.connect(audioContext.destination);
            noiseSource.start();


        } catch (e) {
            console.warn(`[${SCRIPT_ID}] Web Audio API not supported or failed to initialize:`, e);
        }
    }

    // --- Canvas Setup ---
    let canvas, ctx;

    function setupCanvas() {
        canvas = document.createElement('canvas');
        canvas.id = SCRIPT_ID + '-canvas';
        canvas.style.position = 'fixed';
        canvas.style.top = '0';
        canvas.style.left = '0';
        canvas.style.zIndex = Z_INDEX_OVERLAY;
        canvas.style.pointerEvents = 'none'; // Allow clicking through to Drawaria elements
        document.body.appendChild(canvas);

        ctx = canvas.getContext('2d');
        resizeCanvas();
        window.addEventListener('resize', resizeCanvas);
    }

    function resizeCanvas() {
        canvas.width = window.innerWidth;
        canvas.height = window.innerHeight;
    }

    // --- Drawing Functions ---

    // A utility to draw a glowing effect
    function applyGlow(color, blur) {
        ctx.shadowColor = color;
        ctx.shadowBlur = blur;
    }

    function clearGlow() {
        ctx.shadowColor = 'transparent';
        ctx.shadowBlur = 0;
    }

    // Draws the aircraft model (simplified 2D representation)
    function drawAircraft() {
        ctx.save();
        ctx.translate(aircraft.x, aircraft.y);

        // Apply yaw for overall heading
        ctx.rotate(aircraft.yawAngle);

        // Apply roll and pitch transformations
        // For a 2D projection, pitch affects Y-scale, roll affects X-scale/skew
        // This is a simplified projection for visual effect, not true 3D
        const scaleY = Math.cos(aircraft.pitchAngle); // Squish vertically for pitch
        const skewX = Math.sin(aircraft.rollAngle); // Skew horizontally for roll

        // Base aircraft body
        ctx.fillStyle = 'rgba(100, 100, 120, 0.9)'; // Dark grey
        ctx.strokeStyle = 'rgba(200, 200, 255, 0.8)';
        ctx.lineWidth = 2;

        ctx.beginPath();
        ctx.moveTo(-40 * scaleY + 20 * skewX, 0); // Nose (adjusted for roll/pitch)
        ctx.lineTo(-20 * scaleY + 50 * skewX, -20); // Wing left front
        ctx.lineTo(20 * scaleY + 50 * skewX, -20);  // Wing left back
        ctx.lineTo(40 * scaleY + 20 * skewX, 0);   // Tail (body end)
        ctx.lineTo(20 * scaleY + 50 * skewX, 20);  // Wing right back
        ctx.lineTo(-20 * scaleY + 50 * skewX, 20); // Wing right front
        ctx.closePath();
        ctx.fill();
        ctx.stroke();

        // Cockpit (simulated transparency/reflection)
        ctx.fillStyle = 'rgba(150, 200, 255, 0.4)';
        ctx.beginPath();
        ctx.arc(0, 0, 10 * Math.abs(scaleY), 0, Math.PI * 2);
        ctx.fill();
        applyGlow('rgba(150, 200, 255, 0.6)', 15);
        ctx.arc(0, 0, 10 * Math.abs(scaleY), 0, Math.PI * 2); // Redraw for glow
        ctx.fill();
        clearGlow();


        // Engine exhaust glow/particles
        if (aircraft.enginePower > 0.1) {
            applyGlow(`rgba(255, 150, 0, ${aircraft.enginePower * 0.8})`, aircraft.enginePower * 20 + 5);
            ctx.fillStyle = `rgba(255, 100, 0, ${aircraft.enginePower})`;
            ctx.beginPath();
            ctx.arc(-30 * scaleY, 0, 5 + aircraft.enginePower * 5, 0, Math.PI * 2);
            ctx.fill();
            clearGlow();
        }


        // Propeller/Jet effect (simple spin)
        ctx.strokeStyle = 'white';
        ctx.lineWidth = 1;
        const propLength = 15;
        const spinAngle = Date.now() * 0.05 * aircraft.enginePower * 10;
        for (let i = 0; i < 6; i++) {
            ctx.beginPath();
            ctx.moveTo(-45 * scaleY, 0);
            ctx.lineTo(-45 * scaleY + propLength * Math.cos(spinAngle + i * Math.PI / 3),
                       propLength * Math.sin(spinAngle + i * Math.PI / 3));
            ctx.stroke();
        }

        ctx.restore();
    }

    // Draws the interactive UI elements
    function drawUI() {
        ctx.save();

        // --- Info Panel ---
        ctx.fillStyle = 'rgba(0, 0, 0, 0.7)';
        ctx.fillRect(10, 10, 200, 150);
        ctx.strokeStyle = '#00FF00';
        ctx.lineWidth = 1;
        ctx.strokeRect(10, 10, 200, 150);

        ctx.font = '16px "Press Start 2P", monospace'; // A retro font if available, else monospace
        ctx.fillStyle = '#00FF00';
        let textY = 30;
        const lineHeight = 20;

        ctx.fillText('FLIGHT DATA', 20, textY);
        textY += lineHeight;
        ctx.fillText(`Speed: ${aircraft.speed.toFixed(1)} km/h`, 20, textY);
        textY += lineHeight;
        ctx.fillText(`Altitude: ${aircraft.altitude.toFixed(0)} m`, 20, textY);
        textY += lineHeight;
        ctx.fillText(`Fuel: ${aircraft.fuel.toFixed(0)}/${aircraft.maxFuel}`, 20, textY);
        textY += lineHeight;
        ctx.fillText(`Engine: ${(aircraft.enginePower * 100).toFixed(0)}%`, 20, textY);
        textY += lineHeight;
        ctx.fillText(`Pitch: ${(aircraft.pitchAngle * 180 / Math.PI).toFixed(0)}°`, 20, textY);
        textY += lineHeight;
        ctx.fillText(`Roll: ${(aircraft.rollAngle * 180 / Math.PI).toFixed(0)}°`, 20, textY);

        // --- Controls Overlay (visual representation) ---
        const controlsPanelX = canvas.width - 210;
        const controlsPanelY = 10;
        ctx.fillStyle = 'rgba(0, 0, 0, 0.7)';
        ctx.fillRect(controlsPanelX, controlsPanelY, 200, 250);
        ctx.strokeStyle = '#00FFFF';
        ctx.strokeRect(controlsPanelX, controlsPanelY, 200, 250);

        ctx.fillStyle = '#00FFFF';
        ctx.fillText('CONTROLS', controlsPanelX + 10, controlsPanelY + 20);

        const drawButton = (text, x, y, isPressed) => {
            const width = 80;
            const height = 25;
            ctx.fillStyle = isPressed ? '#00FF00' : 'rgba(50, 50, 50, 0.8)';
            applyGlow(isPressed ? '#00FF00' : 'transparent', isPressed ? 10 : 0);
            ctx.fillRect(x, y, width, height);
            clearGlow();
            ctx.strokeStyle = isPressed ? '#FFFFFF' : '#AAAAAA';
            ctx.strokeRect(x, y, width, height);
            ctx.fillStyle = isPressed ? '#000000' : '#CCCCCC';
            ctx.font = '14px "Press Start 2P", monospace';
            ctx.textAlign = 'center';
            ctx.textBaseline = 'middle';
            ctx.fillText(text, x + width / 2, y + height / 2);
            ctx.textAlign = 'left'; // Reset
        };

        let btnX = controlsPanelX + 10;
        let btnY = controlsPanelY + 40;

        drawButton('W (Fwd)', btnX, btnY, controls.forward);
        drawButton('S (Bwd)', btnX + 90, btnY, controls.backward);
        btnY += 30;
        drawButton('A (Yaw L)', btnX, btnY, controls.turnLeft);
        drawButton('D (Yaw R)', btnX + 90, btnY, controls.turnRight);
        btnY += 30;
        drawButton('Up (Pitch D)', btnX, btnY, controls.pitchDown); // Up arrow means pitch down (nose down)
        drawButton('Down (Pitch U)', btnX + 90, btnY, controls.pitchUp); // Down arrow means pitch up (nose up)
        btnY += 30;
        drawButton('Left (Roll L)', btnX, btnY, controls.rollLeft);
        drawButton('Right (Roll R)', btnX + 90, btnY, controls.rollRight);
        btnY += 30;
        drawButton('+ (Eng Up)', btnX, btnY, controls.engineUp);
        drawButton('- (Eng Down)', btnX + 90, btnY, controls.engineDown);
        btnY += 30;
        ctx.fillStyle = '#FFF';
        ctx.fillText('NUMPAD +/- for Engine', btnX, btnY);


        // --- Animated Compass/Horizon Line ---
        const compassX = canvas.width / 2;
        const compassY = canvas.height - 100;
        const compassRadius = 80;

        ctx.strokeStyle = 'rgba(255, 255, 255, 0.8)';
        ctx.lineWidth = 2;
        ctx.beginPath();
        ctx.arc(compassX, compassY, compassRadius, 0, Math.PI * 2);
        ctx.stroke();

        // Horizon line (relative to pitch and roll)
        ctx.save();
        ctx.translate(compassX, compassY);
        ctx.rotate(-aircraft.rollAngle); // Rotate horizon opposite to roll
        ctx.translate(0, aircraft.pitchAngle * compassRadius * 2); // Shift horizon based on pitch

        ctx.strokeStyle = '#ADD8E6'; // Sky blue
        ctx.fillStyle = '#8B4513'; // Earth brown

        ctx.beginPath();
        ctx.moveTo(-compassRadius * 2, 0); // Extend beyond compass for visual effect
        ctx.lineTo(compassRadius * 2, 0);
        ctx.stroke();

        ctx.fillRect(-compassRadius * 2, 0, compassRadius * 4, compassRadius * 2); // Earth
        ctx.restore();

        // Compass markings
        ctx.fillStyle = 'white';
        ctx.font = '12px Arial';
        const headingDeg = (aircraft.yawAngle * 180 / Math.PI + 360) % 360;
        for (let i = 0; i < 360; i += 45) {
            const angle = (i - headingDeg + 360) % 360; // Relative to current heading
            const rad = (angle - 90) * Math.PI / 180; // Adjust for canvas 0deg right

            const text = (i === 0 ? 'N' : i === 90 ? 'E' : i === 180 ? 'S' : i === 270 ? 'W' : i.toString());
            const textX = compassX + Math.cos(rad) * (compassRadius - 20);
            const textY = compassY + Math.sin(rad) * (compassRadius - 20);
            ctx.textAlign = 'center';
            ctx.textBaseline = 'middle';
            ctx.fillText(text, textX, textY);
        }

        // Aircraft indicator on compass
        ctx.beginPath();
        ctx.moveTo(compassX, compassY - compassRadius + 5);
        ctx.lineTo(compassX - 5, compassY - compassRadius + 15);
        ctx.lineTo(compassX + 5, compassY - compassRadius + 15);
        ctx.closePath();
        ctx.fillStyle = 'red';
        ctx.fill();

        ctx.restore();
    }


    // Draws particles (e.g., exhaust, engine trail)
    function drawParticles() {
        for (let i = particles.length - 1; i >= 0; i--) {
            const p = particles[i];

            p.x += p.vx;
            p.y += p.vy;
            p.alpha -= p.fadeRate;
            p.size += p.growRate;

            if (p.alpha <= 0 || p.size >= 20) {
                particles.splice(i, 1);
                continue;
            }

            ctx.fillStyle = `rgba(${p.color.r}, ${p.color.g}, ${p.color.b}, ${p.alpha})`;
            ctx.beginPath();
            ctx.arc(p.x, p.y, p.size / 2, 0, Math.PI * 2);
            ctx.fill();

            // Simple glow for particles
            applyGlow(`rgba(${p.color.r}, ${p.color.g}, ${p.color.b}, ${p.alpha * 0.5})`, p.size);
            ctx.fill(); // Redraw for glow
            clearGlow();
        }
    }

    function createParticle(x, y) {
        if (particles.length >= MAX_PARTICLES) return;

        const angle = aircraft.yawAngle + Math.PI; // Opposite direction of flight
        const speedFactor = aircraft.speed / aircraft.maxSpeed;
        particles.push({
            x: x + Math.cos(angle) * 10,
            y: y + Math.sin(angle) * 10,
            vx: (Math.random() - 0.5) * 2 - Math.cos(angle) * (speedFactor * 2),
            vy: (Math.random() - 0.5) * 2 - Math.sin(angle) * (speedFactor * 2),
            alpha: 1,
            size: 3 + Math.random() * 3,
            fadeRate: 0.01 + Math.random() * 0.01,
            growRate: 0.1 + Math.random() * 0.1,
            color: { r: 255, g: 150 + Math.random() * 50, b: 0 } // Orange-red for exhaust
        });
    }

    // --- Game Logic ---
    function updateGame() {
        // Engine power control
        if (controls.engineUp) {
            aircraft.enginePower = Math.min(1, aircraft.enginePower + 0.01);
        } else if (controls.engineDown) {
            aircraft.enginePower = Math.max(0, aircraft.enginePower - 0.01);
        }

        // Adjust engine sound
        if (audioContext && engineOscillator && engineGain) {
            engineOscillator.frequency.value = 50 + aircraft.enginePower * 200; // 50Hz to 250Hz
            engineGain.gain.value = aircraft.enginePower * 0.5; // 0 to 0.5 volume
        }

        // Speed calculation based on engine power, drag
        aircraft.speed += aircraft.acceleration * aircraft.enginePower;
        aircraft.speed -= aircraft.deceleration * (1 - aircraft.enginePower); // Natural deceleration
        aircraft.speed = Math.max(0, Math.min(aircraft.maxSpeed, aircraft.speed));


        // Fuel consumption
        if (aircraft.enginePower > 0.05 && aircraft.fuel > 0) {
            aircraft.fuel -= 0.1 * aircraft.enginePower;
            if (aircraft.fuel <= 0) {
                aircraft.fuel = 0;
                aircraft.enginePower = 0; // Engine stalls
                aircraft.speed *= 0.9; // Rapid deceleration
            }
        }

        // Yaw (turn left/right)
        if (controls.turnLeft) {
            aircraft.yawAngle -= aircraft.turnSpeed * (aircraft.speed / aircraft.maxSpeed);
        }
        if (controls.turnRight) {
            aircraft.yawAngle += aircraft.turnSpeed * (aircraft.speed / aircraft.maxSpeed);
        }

        // Pitch (nose up/down)
        if (controls.pitchUp) {
            aircraft.pitchAngle = Math.min(aircraft.maxPitchRoll, aircraft.pitchAngle + aircraft.turnSpeed * 0.5);
        } else if (controls.pitchDown) {
            aircraft.pitchAngle = Math.max(-aircraft.maxPitchRoll, aircraft.pitchAngle - aircraft.turnSpeed * 0.5);
        } else {
            // Self-leveling pitch
            aircraft.pitchAngle *= 0.95; // Gradually return to level
        }

        // Roll (sideways tilt)
        if (controls.rollLeft) {
            aircraft.rollAngle = Math.max(-aircraft.maxPitchRoll, aircraft.rollAngle - aircraft.turnSpeed * 0.7);
        } else if (controls.rollRight) {
            aircraft.rollAngle = Math.min(aircraft.maxPitchRoll, aircraft.rollAngle + aircraft.turnSpeed * 0.7);
        } else {
            // Self-leveling roll
            aircraft.rollAngle *= 0.9; // Gradually return to level
        }

        // Update position based on yaw and speed
        aircraft.x += Math.cos(aircraft.yawAngle) * aircraft.speed;
        aircraft.y += Math.sin(aircraft.yawAngle) * aircraft.speed;

        // Altitude simulation (simple lift/gravity)
        const lift = aircraft.speed * Math.sin(-aircraft.pitchAngle) * 0.5; // Lift based on speed and pitch
        aircraft.verticalSpeed += lift * 0.1 - 0.05; // Gravity effect
        aircraft.altitude += aircraft.verticalSpeed;
        aircraft.altitude = Math.max(0, aircraft.altitude); // Can't go below 0


        // Wrap around screen edges
        if (aircraft.x < 0) aircraft.x = canvas.width;
        if (aircraft.x > canvas.width) aircraft.x = 0;
        if (aircraft.y < 0) aircraft.y = canvas.height;
        if (aircraft.y > canvas.height) aircraft.y = 0;

        // Generate particles if engine is on
        if (aircraft.enginePower > 0.3 && Math.random() < aircraft.enginePower * 0.5) {
            // Particle origin: behind the aircraft, adjusted for yaw
            const particleOriginX = aircraft.x - Math.cos(aircraft.yawAngle) * 40;
            const particleOriginY = aircraft.y - Math.sin(aircraft.yawAngle) * 40;
            createParticle(particleOriginX, particleOriginY);
        }
    }

    // --- Input Handling ---
    function handleKeyDown(event) {
        switch (event.key) {
            case 'w': controls.forward = true; break;
            case 's': controls.backward = true; break;
            case 'a': controls.turnLeft = true; break;
            case 'd': controls.turnRight = true; break;
            case 'ArrowUp': controls.pitchDown = true; break;
            case 'ArrowDown': controls.pitchUp = true; break;
            case 'ArrowLeft': controls.rollLeft = true; break;
            case 'ArrowRight': controls.rollRight = true; break;
            case '+': case 'NumpadAdd': controls.engineUp = true; break;
            case '-': case 'NumpadSubtract': controls.engineDown = true; break;
        }
        // Prevent default browser actions for arrow keys etc.
        if (['w', 's', 'a', 'd', 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', '+', '-', 'NumpadAdd', 'NumpadSubtract'].includes(event.key)) {
            event.preventDefault();
        }
    }

    function handleKeyUp(event) {
        switch (event.key) {
            case 'w': controls.forward = false; break;
            case 's': controls.backward = false; break;
            case 'a': controls.turnLeft = false; break;
            case 'd': controls.turnRight = false; break;
            case 'ArrowUp': controls.pitchDown = false; break;
            case 'ArrowDown': controls.pitchUp = false; break;
            case 'ArrowLeft': controls.rollLeft = false; break;
            case 'ArrowRight': controls.rollRight = false; break;
            case '+': case 'NumpadAdd': controls.engineUp = false; break;
            case '-': case 'NumpadSubtract': controls.engineDown = false; break;
        }
    }

    // --- Main Game Loop ---
    function gameLoop() {
        updateGame();

        ctx.clearRect(0, 0, canvas.width, canvas.height); // Clear entire canvas

        drawAircraft();
        drawParticles();
        drawUI();

        requestAnimationFrame(gameLoop);
    }

    // --- Initialization ---
    function init() {
        console.log(`[${SCRIPT_ID}] Initializing script...`);
        setupCanvas();
        initAudio(); // Initialize audio context
        window.addEventListener('keydown', handleKeyDown);
        window.addEventListener('keyup', handleKeyUp);
        gameLoop(); // Start the game loop
        console.log(`[${SCRIPT_ID}] Script initialized. Use WASD, Arrow Keys, Numpad +/- to control the aircraft.`);
    }

    // Ensure the script runs after the page is loaded
    if (document.readyState === 'complete' || document.readyState === 'interactive') {
        init();
    } else {
        window.addEventListener('DOMContentLoaded', init);
    }
})();