您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Advanced baseball physics with professional MLB diamond and batting system!
// ==UserScript== // @name Drawaria Physics Engine Baseball⚾ // @namespace http://tampermonkey.net/ // @version 1.0.0 // @description Advanced baseball physics with professional MLB diamond and batting system! // @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'; /* ---------- SHARED SYSTEM COMPONENTS ---------- */ let drawariaSocket = null; let drawariaCanvas = null; let drawariaCtx = null; // Optimized command queue const commandQueue = []; let batchProcessor = null; const BATCH_SIZE = 18; const BATCH_INTERVAL = 35; const positionCache = new Map(); const MOVEMENT_THRESHOLD = 2; // ✅ COLORES OFICIALES DE BASEBALL MLB const BASEBALL_COLORS = { fieldColor: '#228B22', // Verde césped del campo dirtColor: '#DEB887', // Tierra del diamante lineColor: '#FFFFFF', // Líneas blancas oficiales baseColor: '#FFFAF0', // Bases blancas moundColor: '#8B7355', // Montículo marrón fenceColor: '#8B4513', // Cerca marrón textColor: '#FFFFFF', // Texto blanco dugoutColor: '#654321' // Dugouts marrones }; // Baseball physics constants[1][2] const BASEBALL_PHYSICS = { GRAVITY: 450, // Gravedad más fuerte para trayectorias parabólicas BALL_MASS: 0.15, // Masa de pelota de baseball BALL_RADIUS: 12, // Pelota más pequeña que baloncesto TIMESTEP: 1/60, MAX_VELOCITY: 1000, // Velocidad muy alta para home runs AIR_RESISTANCE: 0.002, // Poca resistencia para vuelos largos RESTITUTION_BALL: 0.4, // Rebote bajo en tierra RESTITUTION_WALL: 0.3, // Rebote muy bajo en cercas FRICTION_DIRT: 0.9, // Alta fricción en tierra FRICTION_GRASS: 0.8, // Fricción media en césped PLAYER_INTERACTION_FORCE: 500, PLAYER_PUSH_MULTIPLIER: 3.0, PLAYER_RESTITUTION: 0.95, // Baseball specific[3] BAT_FORCE: 600, PITCH_FORCE: 300, BALL_COLOR: '#87CEEB', // Azul claro como pidió WIND_EFFECT: 0.1, HOME_RUN_DISTANCE: 400 }; const BASEBALL_GAME = { INNINGS: 9, OUTS_PER_INNING: 3, STRIKES_FOR_OUT: 3, BALLS_FOR_WALK: 4 }; let isDrawing = false; let isStopped = false; // WebSocket interception const originalWebSocketSend = WebSocket.prototype.send; WebSocket.prototype.send = function (...args) { if (!drawariaSocket && this.url && this.url.includes('drawaria')) { drawariaSocket = this; console.log('🔗 Drawaria WebSocket captured for baseball engine.'); startBatchProcessor(); } return originalWebSocketSend.apply(this, args); }; function startBatchProcessor() { if (batchProcessor) return; batchProcessor = setInterval(() => { if (!drawariaSocket || drawariaSocket.readyState !== WebSocket.OPEN || commandQueue.length === 0) { return; } const batch = commandQueue.splice(0, BATCH_SIZE); batch.forEach(cmd => { try { drawariaSocket.send(cmd); } catch (e) { console.warn('Failed to send command:', e); } }); }, BATCH_INTERVAL); } function enqueueDrawCommand(x1, y1, x2, y2, color, thickness) { if (!drawariaCanvas || !drawariaSocket) return; const normX1 = (x1 / drawariaCanvas.width).toFixed(4); const normY1 = (y1 / drawariaCanvas.height).toFixed(4); const normX2 = (x2 / drawariaCanvas.width).toFixed(4); const normY2 = (y2 / drawariaCanvas.height).toFixed(4); const cmd = `42["drawcmd",0,[${normX1},${normY1},${normX2},${normY2},false,${-Math.abs(thickness)},"${color}",0,0,{}]]`; commandQueue.push(cmd); if (drawariaCtx) { drawariaCtx.strokeStyle = color; drawariaCtx.lineWidth = thickness; drawariaCtx.lineCap = 'round'; drawariaCtx.lineJoin = 'round'; drawariaCtx.beginPath(); drawariaCtx.moveTo(x1, y1); drawariaCtx.lineTo(x2, y2); drawariaCtx.stroke(); } } // Helper functions function clamp(value, min, max) { return Math.min(Math.max(value, min), max); } function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } // ✅ SISTEMA DE COORDENADAS DE BASEBALL PROFESIONAL function getCanvasSize() { return { width: drawariaCanvas.width, height: drawariaCanvas.height }; } function calculateBaseballCoordinates() { const size = getCanvasSize(); // Home plate en la parte inferior const homeX = size.width * 0.5; const homeY = size.height * 0.85; // Distancia entre bases (proporción MLB) const baseDistance = Math.min(size.width, size.height) * 0.25; const coords = { // Home plate y bases del diamante home: { x: homeX, y: homeY }, firstBase: { x: homeX + baseDistance * Math.cos(-Math.PI/4), y: homeY + baseDistance * Math.sin(-Math.PI/4) }, secondBase: { x: homeX, y: homeY - baseDistance * Math.sqrt(2) }, thirdBase: { x: homeX - baseDistance * Math.cos(-Math.PI/4), y: homeY + baseDistance * Math.sin(-Math.PI/4) }, // Pitcher's mound pitcherMound: { x: homeX, y: homeY - baseDistance * 0.6, radius: baseDistance * 0.08 }, // Líneas de foul foulLines: { leftStart: { x: homeX, y: homeY }, leftEnd: { x: homeX - size.width * 0.4, y: size.height * 0.1 }, rightStart: { x: homeX, y: homeY }, rightEnd: { x: homeX + size.width * 0.4, y: size.height * 0.1 } }, // Outfield fence outfield: { centerX: homeX, centerY: homeY - baseDistance * 1.8, radius: baseDistance * 1.5, startAngle: Math.PI * 0.75, endAngle: Math.PI * 0.25 }, // Dugouts dugouts: { left: { x: homeX - baseDistance * 1.2, y: homeY + baseDistance * 0.3, width: baseDistance * 0.6, height: baseDistance * 0.2 }, right: { x: homeX + baseDistance * 0.6, y: homeY + baseDistance * 0.3, width: baseDistance * 0.6, height: baseDistance * 0.2 } }, // Texto text: { x: Math.floor(size.width * 0.5), y: Math.floor(size.height * 0.05), pixelSize: Math.max(2, Math.floor(size.width * 0.004)) } }; return coords; } function sendDrawCommand(x, y, x2, y2, color, thickness) { if (!drawariaSocket || !drawariaCanvas) return; const normX = (x / drawariaCanvas.width).toFixed(4); const normY = (y / drawariaCanvas.height).toFixed(4); const normX2 = (x2 / drawariaCanvas.width).toFixed(4); const normY2 = (y2 / drawariaCanvas.height).toFixed(4); const command = `42["drawcmd",0,[${normX},${normY},${normX2},${normY2},false,${0 - thickness},"${color}",0,0,{}]]`; drawariaSocket.send(command); } async function drawLineLocalAndServer(startX, startY, endX, endY, color, thickness, delay = 45) { if (isStopped) { isDrawing = false; return; } const canvasSize = getCanvasSize(); startX = clamp(startX, -50, canvasSize.width + 50); startY = clamp(startY, 0, canvasSize.height); endX = clamp(endX, -50, canvasSize.width + 50); endY = clamp(endY, 0, canvasSize.height); if (drawariaCtx && startX >= 0 && startX <= canvasSize.width && startY >= 0 && startY <= canvasSize.height) { drawariaCtx.strokeStyle = color; drawariaCtx.lineWidth = thickness; drawariaCtx.lineCap = 'round'; drawariaCtx.lineJoin = 'round'; drawariaCtx.beginPath(); drawariaCtx.moveTo(startX, startY); drawariaCtx.lineTo(endX, endY); drawariaCtx.stroke(); } sendDrawCommand(startX, startY, endX, endY, color, thickness); await sleep(delay); } async function drawPixel(x, y, color, size = 2) { if (isStopped) return; const canvasSize = getCanvasSize(); x = clamp(x, 0, canvasSize.width - size); y = clamp(y, 0, canvasSize.height - size); if (drawariaCtx) { drawariaCtx.fillStyle = color; drawariaCtx.fillRect(x, y, size, size); } sendDrawCommand(x, y, x + 1, y + 1, color, size); await sleep(12); } // ✅ FUNCIONES DE DIBUJO DE CANCHA DE BASEBALL async function drawBaseballField() { if (isStopped) return; updateStatus(document.getElementById('baseball-status'), "⚾ Dibujando campo de césped MLB...", BASEBALL_COLORS.fieldColor); const canvasSize = getCanvasSize(); const coords = calculateBaseballCoordinates(); // Césped del outfield (fondo verde) for (let y = 20; y < canvasSize.height - 20; y += 6) { await drawLineLocalAndServer(20, y, canvasSize.width - 20, y, BASEBALL_COLORS.fieldColor, 2, 20); if (isStopped) break; } // Diamante de tierra (infield) await drawBaseballDiamond(coords); } async function drawBaseballDiamond(coords) { if (isStopped) return; // Diamante de tierra conectando todas las bases[4] await drawLineLocalAndServer(coords.home.x, coords.home.y, coords.firstBase.x, coords.firstBase.y, BASEBALL_COLORS.dirtColor, 8, 50); await drawLineLocalAndServer(coords.firstBase.x, coords.firstBase.y, coords.secondBase.x, coords.secondBase.y, BASEBALL_COLORS.dirtColor, 8, 50); await drawLineLocalAndServer(coords.secondBase.x, coords.secondBase.y, coords.thirdBase.x, coords.thirdBase.y, BASEBALL_COLORS.dirtColor, 8, 50); await drawLineLocalAndServer(coords.thirdBase.x, coords.thirdBase.y, coords.home.x, coords.home.y, BASEBALL_COLORS.dirtColor, 8, 50); // Rellenar el diamante con tierra const steps = 15; for (let i = 1; i < steps; i++) { const factor = i / steps; // Líneas horizontales del diamante const leftX = coords.thirdBase.x + (coords.home.x - coords.thirdBase.x) * factor; const rightX = coords.firstBase.x + (coords.home.x - coords.firstBase.x) * factor; const y = coords.home.y + (coords.secondBase.y - coords.home.y) * factor; await drawLineLocalAndServer(leftX, y, rightX, y, BASEBALL_COLORS.dirtColor, 3, 25); if (isStopped) break; } } async function drawBaseballBases(coords) { if (isStopped) return; updateStatus(document.getElementById('baseball-status'), "⚪ Dibujando bases oficiales...", BASEBALL_COLORS.baseColor); const baseSize = 12; // Home plate (forma de pentágono) await drawHomeplate(coords.home, baseSize); // Primera base await drawSquareBase(coords.firstBase, baseSize, '1st'); // Segunda base await drawSquareBase(coords.secondBase, baseSize, '2nd'); // Tercera base await drawSquareBase(coords.thirdBase, baseSize, '3rd'); // Pitcher's mound await drawPitcherMound(coords.pitcherMound); } async function drawHomeplate(homeCoords, size) { // Home plate tiene forma pentagonal característica const points = [ { x: homeCoords.x, y: homeCoords.y - size }, // Top { x: homeCoords.x + size, y: homeCoords.y - size/2 }, // Top right { x: homeCoords.x + size, y: homeCoords.y + size/2 }, // Bottom right { x: homeCoords.x, y: homeCoords.y + size }, // Bottom point { x: homeCoords.x - size, y: homeCoords.y + size/2 }, // Bottom left { x: homeCoords.x - size, y: homeCoords.y - size/2 } // Top left ]; for (let i = 0; i < points.length; i++) { const current = points[i]; const next = points[(i + 1) % points.length]; await drawLineLocalAndServer(current.x, current.y, next.x, next.y, BASEBALL_COLORS.baseColor, 4, 40); if (isStopped) break; } } async function drawSquareBase(baseCoords, size, label) { // Base cuadrada await drawRectangleOutline({ x: baseCoords.x - size/2, y: baseCoords.y - size/2, width: size, height: size }, BASEBALL_COLORS.baseColor, 4); // Rellenar la base for (let i = 0; i < size - 2; i += 2) { await drawLineLocalAndServer( baseCoords.x - size/2 + 1, baseCoords.y - size/2 + 1 + i, baseCoords.x + size/2 - 1, baseCoords.y - size/2 + 1 + i, BASEBALL_COLORS.baseColor, 1, 15 ); if (isStopped) break; } } async function drawPitcherMound(moundCoords) { // Círculo del montículo del pitcher await drawCircle(moundCoords.x, moundCoords.y, moundCoords.radius, BASEBALL_COLORS.moundColor, 5); // Textura del montículo const steps = 8; for (let i = 0; i < steps; i++) { const angle = (Math.PI * 2 * i) / steps; const innerRadius = moundCoords.radius * 0.3; const outerRadius = moundCoords.radius * 0.8; const x1 = moundCoords.x + Math.cos(angle) * innerRadius; const y1 = moundCoords.y + Math.sin(angle) * innerRadius; const x2 = moundCoords.x + Math.cos(angle) * outerRadius; const y2 = moundCoords.y + Math.sin(angle) * outerRadius; await drawLineLocalAndServer(x1, y1, x2, y2, '#A0522D', 2, 20); if (isStopped) break; } } async function drawBaseballOutfield(coords) { if (isStopped) return; updateStatus(document.getElementById('baseball-status'), "🏟️ Dibujando outfield y cerca...", BASEBALL_COLORS.fenceColor); // Líneas de foul await drawLineLocalAndServer( coords.foulLines.leftStart.x, coords.foulLines.leftStart.y, coords.foulLines.leftEnd.x, coords.foulLines.leftEnd.y, BASEBALL_COLORS.lineColor, 4, 60 ); await drawLineLocalAndServer( coords.foulLines.rightStart.x, coords.foulLines.rightStart.y, coords.foulLines.rightEnd.x, coords.foulLines.rightEnd.y, BASEBALL_COLORS.lineColor, 4, 60 ); // Cerca del outfield (arco) await drawArc(coords.outfield, BASEBALL_COLORS.fenceColor, 6, coords.outfield.startAngle, coords.outfield.endAngle); // Dugouts await drawRectangleOutline(coords.dugouts.left, BASEBALL_COLORS.dugoutColor, 5); await drawRectangleOutline(coords.dugouts.right, BASEBALL_COLORS.dugoutColor, 5); // Rellenar dugouts await fillRectangle(coords.dugouts.left, BASEBALL_COLORS.dugoutColor); await fillRectangle(coords.dugouts.right, BASEBALL_COLORS.dugoutColor); } // ✅ FUNCIONES GEOMÉTRICAS async function drawCircle(centerX, centerY, radius, color, thickness) { const steps = 20; for (let i = 0; i <= steps; i++) { if (isStopped) break; const angle1 = (Math.PI * 2 * i) / steps; const angle2 = (Math.PI * 2 * (i + 1)) / steps; const x1 = centerX + Math.cos(angle1) * radius; const y1 = centerY + Math.sin(angle1) * radius; const x2 = centerX + Math.cos(angle2) * radius; const y2 = centerY + Math.sin(angle2) * radius; await drawLineLocalAndServer(x1, y1, x2, y2, color, thickness, 25); } } async function drawArc(arcCoords, color, thickness, startAngle, endAngle) { const steps = 24; for (let i = 0; i < steps; i++) { if (isStopped) break; const progress1 = i / steps; const progress2 = (i + 1) / steps; const angle1 = startAngle + (endAngle - startAngle) * progress1; const angle2 = startAngle + (endAngle - startAngle) * progress2; const x1 = arcCoords.centerX + Math.cos(angle1) * arcCoords.radius; const y1 = arcCoords.centerY + Math.sin(angle1) * arcCoords.radius; const x2 = arcCoords.centerX + Math.cos(angle2) * arcCoords.radius; const y2 = arcCoords.centerY + Math.sin(angle2) * arcCoords.radius; await drawLineLocalAndServer(x1, y1, x2, y2, color, thickness, 30); } } async function drawRectangleOutline(rectCoords, color, thickness) { await drawLineLocalAndServer(rectCoords.x, rectCoords.y, rectCoords.x + rectCoords.width, rectCoords.y, color, thickness, 35); await drawLineLocalAndServer(rectCoords.x + rectCoords.width, rectCoords.y, rectCoords.x + rectCoords.width, rectCoords.y + rectCoords.height, color, thickness, 35); await drawLineLocalAndServer(rectCoords.x + rectCoords.width, rectCoords.y + rectCoords.height, rectCoords.x, rectCoords.y + rectCoords.height, color, thickness, 35); await drawLineLocalAndServer(rectCoords.x, rectCoords.y + rectCoords.height, rectCoords.x, rectCoords.y, color, thickness, 35); } async function fillRectangle(rectCoords, color) { const steps = Math.floor(rectCoords.height / 3); for (let i = 0; i < steps; i++) { const y = rectCoords.y + (i * 3); await drawLineLocalAndServer(rectCoords.x + 1, y, rectCoords.x + rectCoords.width - 1, y, color, 2, 15); if (isStopped) break; } } // ✅ TEXTO BASEBALL EN PIXEL ART const BASEBALL_LETTERS = { 'B': [[1,1,1,1],[1,0,0,1],[1,1,1,1],[1,0,0,1],[1,1,1,1]], 'A': [[0,1,1,0],[1,0,0,1],[1,1,1,1],[1,0,0,1],[1,0,0,1]], 'S': [[1,1,1,1],[1,0,0,0],[1,1,1,1],[0,0,0,1],[1,1,1,1]], 'E': [[1,1,1,1],[1,0,0,0],[1,1,1,0],[1,0,0,0],[1,1,1,1]], 'L': [[1,0,0,0],[1,0,0,0],[1,0,0,0],[1,0,0,0],[1,1,1,1]], 'R': [[1,1,1,0],[1,0,0,1],[1,1,1,0],[1,0,1,0],[1,0,0,1]] }; async function drawBaseballPixelText(text, coords) { if (isStopped) return; const letterSpacing = coords.text.pixelSize * 6; const textWidth = text.length * letterSpacing; let currentX = coords.text.x - (textWidth / 2); for (let i = 0; i < text.length; i++) { if (isStopped) break; const letter = text[i].toUpperCase(); if (letter === ' ') { currentX += letterSpacing; continue; } const pattern = BASEBALL_LETTERS[letter]; if (!pattern) continue; for (let row = 0; row < pattern.length; row++) { for (let col = 0; col < pattern[row].length; col++) { if (pattern[row][col] === 1) { const pixelX = currentX + (col * coords.text.pixelSize); const pixelY = coords.text.y + (row * coords.text.pixelSize); const canvasSize = getCanvasSize(); if (pixelX >= 0 && pixelX < canvasSize.width && pixelY >= 0 && pixelY < canvasSize.height) { await drawPixel(pixelX, pixelY, BASEBALL_COLORS.textColor, coords.text.pixelSize); } } } } currentX += letterSpacing; await sleep(90); } } // ✅ FUNCIÓN PRINCIPAL: CANCHA DE BASEBALL COMPLETA async function drawCompleteBaseballField() { if (isDrawing) { alert('Ya está en curso un dibujo. Presiona "Parar" para cancelar.'); return; } if (!drawariaSocket || !drawariaCanvas || !drawariaCtx) { alert('No se detectó conexión o canvas. Asegúrate de estar en una sala de juego.'); return; } isDrawing = true; isStopped = false; const statusDiv = document.getElementById('baseball-status') || createStatusDiv(); try { const coords = calculateBaseballCoordinates(); const canvasSize = getCanvasSize(); console.log(`⚾ Campo de baseball MLB iniciado:`); console.log(`📏 Canvas: ${canvasSize.width}x${canvasSize.height}`); updateStatus(statusDiv, `⚾ CAMPO DE BASEBALL MLB: ${canvasSize.width}x${canvasSize.height}`, "#228B22"); await sleep(800); // FASE 1: CAMPO DE CÉSPED updateStatus(statusDiv, "⚾ FASE 1: Campo de césped MLB...", BASEBALL_COLORS.fieldColor); await drawBaseballField(); await sleep(300); if (isStopped) return; // FASE 2: BASES Y DIAMANTE updateStatus(statusDiv, "💎 FASE 2: Diamante y bases oficiales...", BASEBALL_COLORS.dirtColor); await drawBaseballBases(coords); await sleep(300); if (isStopped) return; // FASE 3: OUTFIELD Y CERCA updateStatus(statusDiv, "🏟️ FASE 3: Outfield y cerca perimetral...", BASEBALL_COLORS.fenceColor); await drawBaseballOutfield(coords); await sleep(300); if (isStopped) return; // FASE 4: TEXTO BASEBALL updateStatus(statusDiv, "🎮 FASE 4: Texto blanco 'BASEBALL'...", BASEBALL_COLORS.textColor); await drawBaseballPixelText("BASEBALL", coords); // CAMPO COMPLETO updateStatus(statusDiv, "🏆 ¡CAMPO DE BASEBALL MLB COMPLETO! ⚾🏆", "#006400"); setTimeout(() => { if (statusDiv && statusDiv.parentNode) { statusDiv.style.opacity = 0; setTimeout(() => statusDiv.remove(), 500); } }, 4000); } catch (error) { console.error("Error en campo de baseball:", error); updateStatus(statusDiv, `❌ Error: ${error.message}`, "#B22222"); } finally { isDrawing = false; } } function createStatusDiv() { const statusDiv = document.createElement('div'); statusDiv.id = 'baseball-status'; statusDiv.style.cssText = ` position: fixed; top: 20px; left: 50%; transform: translateX(-50%); background: linear-gradient(135deg, #228B22 0%, #32CD32 100%); color: white; padding: 20px 45px; border-radius: 35px; font-weight: bold; z-index: 10000; transition: opacity 0.5s; text-align: center; min-width: 480px; box-shadow: 0 15px 35px rgba(0,0,0,0.5); text-shadow: 1px 1px 3px rgba(0,0,0,0.4); border: 2px solid #FFFFFF; `; document.body.appendChild(statusDiv); return statusDiv; } function updateStatus(statusDiv, message, color) { if (!statusDiv) return; statusDiv.textContent = message; if (color) { statusDiv.style.background = color; } statusDiv.style.opacity = 1; } /* ---------- ADVANCED BASEBALL PHYSICS ENGINE ---------- */ class AdvancedDrawariaBaseball { constructor() { this.initialized = false; this.isActive = false; this.physicsObjects = new Map(); this.objectIdCounter = 0; this.lastRenderTime = 0; this.renderInterval = 1000 / 30; // Baseball batting system[5] this.battingSystem = { batLength: 80, batWidth: 6, swingCooldown: 500, lastSwingTime: 0 }; // Baseball game state this.baseballGame = { active: false, inning: 1, topBottom: 'top', // top or bottom outs: 0, strikes: 0, balls: 0, runs: { home: 0, away: 0 }, bases: { first: false, second: false, third: false } }; this.gameStats = { totalHits: 0, homeRuns: 0, strikeouts: 0, walks: 0, maxVelocityReached: 0, ballsCreated: 0 }; this.controls = { showDebug: false, defaultBallColor: BASEBALL_PHYSICS.BALL_COLOR, windDirection: 0, windStrength: 1 }; this.playerTracker = { players: new Map(), detectionRadius: BASEBALL_PHYSICS.BALL_RADIUS * 3, lastUpdateTime: 0 }; this.init(); } init() { if (this.initialized) return; const checkGameReady = () => { const gameCanvas = document.getElementById('canvas'); if (gameCanvas) { this.canvasElement = gameCanvas; drawariaCanvas = gameCanvas; this.canvasContext = gameCanvas.getContext('2d'); drawariaCtx = gameCanvas.getContext('2d'); this.initialized = true; this.createBaseballPanel(); console.log('✅ Advanced Baseball Physics Engine v1.0 initialized'); } else { setTimeout(checkGameReady, 100); } }; checkGameReady(); } createBaseballPanel() { const existingPanel = document.getElementById('baseball-physics-panel'); if (existingPanel) existingPanel.remove(); const panel = document.createElement('div'); panel.id = 'baseball-physics-panel'; panel.style.cssText = ` position: fixed !important; top: 20px !important; right: 20px !important; width: 400px !important; z-index: 2147483647 !important; background: linear-gradient(135deg, #0f2f0f, #1a4a1a) !important; border: 2px solid #228B22 !important; border-radius: 15px !important; color: white !important; font-family: 'Segoe UI', Arial, sans-serif !important; overflow: hidden !important; box-shadow: 0 0 30px rgba(34,139,34,0.4) !important; `; const header = document.createElement('div'); header.id = 'baseball-panel-header'; header.style.cssText = ` background: linear-gradient(45deg, #228B22, #32CD32) !important; padding: 12px 20px !important; font-weight: bold !important; text-align: center !important; font-size: 14px !important; cursor: move !important; user-select: none !important; display: flex !important; justify-content: space-between !important; align-items: center !important; `; const title = document.createElement('div'); title.innerHTML = '⚾ MLB BASEBALL ENGINE v1.0'; title.style.flex = '1'; const buttonContainer = document.createElement('div'); buttonContainer.style.cssText = `display: flex !important; gap: 8px !important;`; const minimizeBtn = document.createElement('button'); minimizeBtn.id = 'baseball-minimize-btn'; minimizeBtn.innerHTML = '−'; minimizeBtn.style.cssText = ` width: 25px !important; height: 25px !important; background: rgba(255,255,255,0.2) !important; border: none !important; border-radius: 4px !important; color: white !important; cursor: pointer !important; font-size: 16px !important; line-height: 1 !important; padding: 0 !important; `; const closeBtn = document.createElement('button'); closeBtn.id = 'baseball-close-btn'; closeBtn.innerHTML = '×'; closeBtn.style.cssText = ` width: 25px !important; height: 25px !important; background: rgba(255,0,0,0.6) !important; border: none !important; border-radius: 4px !important; color: white !important; cursor: pointer !important; font-size: 18px !important; line-height: 1 !important; padding: 0 !important; `; buttonContainer.appendChild(minimizeBtn); buttonContainer.appendChild(closeBtn); header.appendChild(title); header.appendChild(buttonContainer); const content = document.createElement('div'); content.id = 'baseball-panel-content'; content.style.cssText = `padding: 20px !important;`; content.innerHTML = ` <!-- CREATE BASEBALL FIELD --> <div style="margin-bottom: 15px; text-align: center;"> <button id="create-baseball-field-btn" style=" width: 100%; padding: 12px; background: linear-gradient(135deg, #228B22, #32CD32); color: white; border: none; border-radius: 8px; cursor: pointer; font-size: 14px; font-weight: bold; margin-bottom: 10px; box-shadow: 0 4px 15px rgba(34,139,34,0.3); ">⚾ Create MLB Baseball Field</button> </div> <!-- LAUNCH BASEBALL ENGINE --> <div style="margin-bottom: 15px; text-align: center;"> <button id="toggle-baseball-physics" style=" width: 100%; padding: 12px; background: linear-gradient(135deg, #87CEEB, #4682B4); color: white; border: none; border-radius: 8px; cursor: pointer; font-size: 14px; font-weight: bold; ">🚀 Launch Baseball Engine</button> </div> <!-- BASEBALL ACTIONS --> <div style="display: flex; gap: 8px; margin-bottom: 15px;"> <button id="add-baseball-btn" style=" flex: 1; padding: 8px; background: linear-gradient(135deg, #87CEEB, #4682B4); color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 12px; font-weight: bold; ">⚾ Add Baseball</button> <button id="pitch-ball-btn" style=" flex: 1; padding: 8px; background: linear-gradient(135deg, #FFD700, #FFA500); color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 12px; font-weight: bold; ">🥎 Pitch</button> </div> <!-- WEATHER CONDITIONS --> <div style="margin-bottom: 15px;"> <label style="display: block; margin-bottom: 5px; font-size: 12px; color: #32CD32;"> 🌬️ Wind Conditions: </label> <select id="wind-conditions" style=" width: 100%; padding: 8px; border: 1px solid #32CD32; border-radius: 6px; background: #1a4a1a; color: white; font-size: 12px; "> <option value="none">🌤️ No Wind</option> <option value="light">💨 Light Breeze</option> <option value="strong">🌪️ Strong Wind</option> <option value="tailwind">➡️ Tailwind (Home Run)</option> <option value="headwind">⬅️ Headwind (Pitcher)</option> </select> </div> <!-- ACTION BUTTONS --> <div style="display: flex; gap: 8px; margin-bottom: 15px;"> <button id="reset-baseball-btn" style=" flex: 1; padding: 8px; background: linear-gradient(135deg, #74b9ff, #0984e3); color: white; border: none; border-radius: 6px; cursor: pointer; font-weight: bold; font-size: 11px; ">🔄 Reset</button> <button id="stop-baseball-field-btn" style=" flex: 1; padding: 8px; background: linear-gradient(135deg, #e74c3c, #c0392b); color: white; border: none; border-radius: 6px; cursor: pointer; font-weight: bold; font-size: 11px; ">⛔ Stop Field</button> </div> <!-- BASEBALL MODES --> <div style="margin-bottom: 15px;"> <h4 style="margin: 0 0 10px 0; font-size: 13px; color: #32CD32; text-align: center;">⚾ Baseball Modes</h4> <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px;"> <button id="game-mode-toggle" class="baseball-mode-toggle" style=" padding: 8px; background: linear-gradient(135deg, #444, #666); color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 10px; font-weight: bold; ">🏆 Game Mode</button> <button id="clean-baseball-canvas-btn" style=" padding: 8px; background: linear-gradient(135deg, #e17055, #d63031); color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 10px; font-weight: bold; ">🧹 Clean Field</button> </div> </div> <!-- CLEAR ALL --> <div style="margin-bottom: 15px;"> <button id="clear-baseballs-btn" style=" width: 100%; padding: 10px; background: linear-gradient(135deg, #990000, #cc0000); color: white; border: none; border-radius: 8px; cursor: pointer; font-weight: bold; ">🗑️ Clear All Baseballs</button> </div> <!-- BASEBALL SCOREBOARD --> <div id="baseball-scoreboard" style=" display: none; background: rgba(0,0,0,0.4); padding: 15px; border-radius: 8px; text-align: center; margin-bottom: 15px; border: 2px solid #FFD700; "> <h4 style="margin: 0 0 10px 0; color: #FFD700; font-size: 14px;">⚾ MLB SCOREBOARD</h4> <div style="display: flex; justify-content: space-between; font-size: 14px; font-weight: bold; margin-bottom: 10px;"> <div style="color: #ff6b6b;"> Away: <span id="baseball-score-away">0</span> </div> <div style="color: #FFD700; font-size: 12px;"> Inning: <span id="current-inning">1</span> (<span id="inning-half">Top</span>) </div> <div style="color: #74b9ff;"> Home: <span id="baseball-score-home">0</span> </div> </div> <div style="display: flex; justify-content: space-between; font-size: 11px; color: #FFD700;"> <div>Outs: <span id="outs-count">0</span></div> <div>Balls: <span id="balls-count">0</span></div> <div>Strikes: <span id="strikes-count">0</span></div> </div> <div style="margin-top: 8px; font-size: 10px; color: #87CEEB;"> Bases: <span id="first-base" style="color: #ccc;">1st</span> | <span id="second-base" style="color: #ccc;">2nd</span> | <span id="third-base" style="color: #ccc;">3rd</span> </div> </div> <!-- BASEBALL STATS --> <div id="baseball-stats" style=" background: rgba(0,0,0,0.3); padding: 10px; border-radius: 6px; font-size: 10px; text-align: center; border: 1px solid rgba(34,139,34,0.3); "> <div>Baseballs: <span id="baseball-count">0</span> | Hits: <span id="baseball-hits-count">0</span></div> <div>Home Runs: <span id="home-runs-count">0</span> | Strikeouts: <span id="strikeouts-count">0</span></div> <div>Max Speed: <span id="baseball-max-speed">0</span> mph</div> <div>Wind: <span id="wind-info">No Wind</span></div> </div> <!-- HELP TEXT --> <div style=" text-align: center; margin-top: 15px; font-size: 9px; color: rgba(255,255,255,0.6); border-top: 1px solid rgba(255,255,255,0.1); padding-top: 10px; "> Professional MLB Diamond • Baseball Physics<br> <span style="color: #87CEEB;">Wind effects • Home run detection • Virtual batting</span> </div> `; panel.appendChild(header); panel.appendChild(content); document.body.appendChild(panel); this.makeBaseballPanelDraggable(); this.setupBaseballPanelButtons(); this.setupBaseballEventListeners(); this.startBaseballStatsMonitoring(); } setupBaseballEventListeners() { // Baseball field controls document.getElementById('create-baseball-field-btn')?.addEventListener('click', () => drawCompleteBaseballField()); document.getElementById('toggle-baseball-physics')?.addEventListener('click', () => this.toggleBaseballPhysics()); document.getElementById('stop-baseball-field-btn')?.addEventListener('click', () => this.stopBaseballFieldDrawing()); // Baseball creation document.getElementById('add-baseball-btn')?.addEventListener('click', () => this.addRandomBaseball()); document.getElementById('pitch-ball-btn')?.addEventListener('click', () => this.pitchBaseball()); // Actions document.getElementById('reset-baseball-btn')?.addEventListener('click', () => this.resetAllBaseballs()); document.getElementById('clear-baseballs-btn')?.addEventListener('click', () => this.clearAllBaseballs()); document.getElementById('game-mode-toggle')?.addEventListener('click', () => this.toggleBaseballGame()); document.getElementById('clean-baseball-canvas-btn')?.addEventListener('click', () => this.cleanBaseballField()); // Wind conditions document.getElementById('wind-conditions')?.addEventListener('change', (e) => { this.updateWindConditions(e.target.value); this.showBaseballFeedback(`🌬️ Wind: ${e.target.options[e.target.selectedIndex].text}`, '#87CEEB'); }); // Canvas click for baseball if (this.canvasElement) { this.canvasElement.addEventListener('click', (e) => this.createBaseball(e.clientX - this.canvasElement.getBoundingClientRect().left, e.clientY - this.canvasElement.getBoundingClientRect().top)); } } stopBaseballFieldDrawing() { isStopped = true; const statusDiv = document.getElementById('baseball-status'); if (statusDiv) { updateStatus(statusDiv, "⛔ Dibujo de campo detenido", "#B22222"); } this.showBaseballFeedback('⛔ Baseball field drawing stopped', '#B22222'); } /* ---------- BASEBALL PHYSICS ENGINE ---------- */ toggleBaseballPhysics() { const toggleBtn = document.getElementById('toggle-baseball-physics'); if (!this.isActive) { this.startBaseballPhysics(); if (toggleBtn) { toggleBtn.textContent = '🛑 Stop Baseball Engine'; toggleBtn.style.background = 'linear-gradient(135deg, #f56565, #e53e3e)'; } } else { this.stopBaseballPhysics(); if (toggleBtn) { toggleBtn.textContent = '🚀 Launch Baseball Engine'; toggleBtn.style.background = 'linear-gradient(135deg, #87CEEB, #4682B4)'; } } } startBaseballPhysics() { if (this.isActive) return; this.isActive = true; this.startBaseballGameLoop(); this.showBaseballFeedback('🚀 MLB Baseball Engine Started!', '#87CEEB'); } stopBaseballPhysics() { this.isActive = false; this.showBaseballFeedback('🛑 Baseball Engine Stopped', '#f56565'); } startBaseballGameLoop() { if (!this.isActive) return; const currentTime = performance.now(); if (currentTime - this.lastRenderTime >= this.renderInterval) { this.updateBaseballPhysics(); this.renderBaseballs(); this.lastRenderTime = currentTime; } requestAnimationFrame(() => this.startBaseballGameLoop()); } updateBaseballPhysics() { const dt = BASEBALL_PHYSICS.TIMESTEP; // Apply wind effects based on conditions let windX = 0, windY = 0; switch(this.controls.windDirection) { case 'light': windX = (Math.random() - 0.5) * 20; windY = (Math.random() - 0.5) * 10; break; case 'strong': windX = (Math.random() - 0.5) * 60; windY = (Math.random() - 0.5) * 30; break; case 'tailwind': windX = 0; windY = -40; // Viento a favor para home runs break; case 'headwind': windX = 0; windY = 20; // Viento en contra break; } // Update baseballs[1][2] this.physicsObjects.forEach(ball => { if (ball.type !== 'baseball') return; // Apply air resistance ball.vx *= (1 - BASEBALL_PHYSICS.AIR_RESISTANCE * dt); ball.vy *= (1 - BASEBALL_PHYSICS.AIR_RESISTANCE * dt); // Apply gravity (más fuerte para trayectorias parabólicas realistas) ball.vy += BASEBALL_PHYSICS.GRAVITY * dt; // Apply wind effects if (windX !== 0 || windY !== 0) { ball.vx += windX * BASEBALL_PHYSICS.WIND_EFFECT * dt; ball.vy += windY * BASEBALL_PHYSICS.WIND_EFFECT * dt; } // Update position ball.x += ball.vx * dt; ball.y += ball.vy * dt; this.handleBaseballBoundaryCollisions(ball); // Velocity limiting const speed = Math.sqrt(ball.vx * ball.vx + ball.vy * ball.vy); if (speed > this.gameStats.maxVelocityReached) { this.gameStats.maxVelocityReached = speed; } if (speed > BASEBALL_PHYSICS.MAX_VELOCITY) { ball.vx = (ball.vx / speed) * BASEBALL_PHYSICS.MAX_VELOCITY; ball.vy = (ball.vy / speed) * BASEBALL_PHYSICS.MAX_VELOCITY; } }); this.handleBaseballCollisions(); this.handleBaseballPlayerBatting(); if (this.baseballGame.active) { this.checkBaseballScoring(); } } updateWindConditions(windType) { this.controls.windDirection = windType; const windNames = { 'none': 'No Wind', 'light': 'Light Breeze', 'strong': 'Strong Wind', 'tailwind': 'Tailwind (Home Run Friendly)', 'headwind': 'Headwind (Pitcher Friendly)' }; document.getElementById('wind-info').textContent = windNames[windType] || 'Unknown'; } /* ---------- BASEBALL CREATION ---------- */ addRandomBaseball() { if (!this.canvasElement) return; const coords = calculateBaseballCoordinates(); // Spawn cerca del pitcher's mound const x = coords.pitcherMound.x + (Math.random() - 0.5) * 60; const y = coords.pitcherMound.y + (Math.random() - 0.5) * 40; this.createBaseball(x, y); } pitchBaseball() { if (!this.canvasElement) return; const coords = calculateBaseballCoordinates(); // Pitch desde el montículo hacia home plate const ball = this.createBaseball(coords.pitcherMound.x, coords.pitcherMound.y); // Aplicar fuerza de pitch hacia home plate const dx = coords.home.x - ball.x; const dy = coords.home.y - ball.y; const distance = Math.sqrt(dx * dx + dy * dy); if (distance > 0) { ball.vx = (dx / distance) * BASEBALL_PHYSICS.PITCH_FORCE; ball.vy = (dy / distance) * BASEBALL_PHYSICS.PITCH_FORCE; // Añadir variación de pitch ball.vx += (Math.random() - 0.5) * 100; ball.vy += (Math.random() - 0.5) * 50; } this.showBaseballFeedback('🥎 PITCH!', '#FFD700'); } createBaseball(x, y) { const id = `baseball_${this.objectIdCounter++}`; const ball = { id: id, type: 'baseball', x: x, y: y, vx: 0, vy: 0, radius: BASEBALL_PHYSICS.BALL_RADIUS, color: BASEBALL_PHYSICS.BALL_COLOR, mass: BASEBALL_PHYSICS.BALL_MASS, restitution: BASEBALL_PHYSICS.RESTITUTION_BALL, friction: BASEBALL_PHYSICS.FRICTION_DIRT, lastRenderX: -9999, lastRenderY: -9999, creationTime: performance.now(), lastCollisionTime: 0, // Baseball specific properties spin: 0, lastBounceTime: 0, bounceCount: 0, lastBatHit: 0, isHomeRun: false, inPlay: true }; this.physicsObjects.set(id, ball); this.gameStats.ballsCreated++; return ball; } /* ---------- BASEBALL COLLISION HANDLING ---------- */ handleBaseballBoundaryCollisions(ball) { if (!this.canvasElement) return; const coords = calculateBaseballCoordinates(); const ballHalfSize = ball.radius; // Límites del campo const boundaries = { left: 20 + ballHalfSize, right: this.canvasElement.width - 20 - ballHalfSize, top: 20 + ballHalfSize, bottom: this.canvasElement.height - 20 - ballHalfSize }; // Colisiones con paredes del campo if (ball.x < boundaries.left || ball.x > boundaries.right) { ball.x = ball.x < boundaries.left ? boundaries.left : boundaries.right; ball.vx = -ball.vx * BASEBALL_PHYSICS.RESTITUTION_WALL; ball.vy *= 0.8; } if (ball.y < boundaries.top) { ball.y = boundaries.top; ball.vy = -ball.vy * BASEBALL_PHYSICS.RESTITUTION_WALL; ball.vx *= 0.9; } else if (ball.y > boundaries.bottom) { ball.y = boundaries.bottom; ball.vy = -ball.vy * ball.restitution; ball.vx *= ball.friction; // Determinar superficie (césped vs tierra) const isInInfield = this.checkIfInInfield(ball, coords); if (isInInfield) { ball.friction = BASEBALL_PHYSICS.FRICTION_DIRT; ball.restitution = BASEBALL_PHYSICS.RESTITUTION_BALL * 0.7; // Menos rebote en tierra } else { ball.friction = BASEBALL_PHYSICS.FRICTION_GRASS; ball.restitution = BASEBALL_PHYSICS.RESTITUTION_BALL; // Rebote normal en césped } ball.bounceCount++; ball.lastBounceTime = performance.now(); } } checkIfInInfield(ball, coords) { // Verificar si la pelota está dentro del diamante de tierra const homeToSecond = Math.sqrt(Math.pow(coords.secondBase.x - coords.home.x, 2) + Math.pow(coords.secondBase.y - coords.home.y, 2)); const ballToHome = Math.sqrt(Math.pow(ball.x - coords.home.x, 2) + Math.pow(ball.y - coords.home.y, 2)); return ballToHome < homeToSecond * 1.2; } handleBaseballCollisions() { const ballsArray = Array.from(this.physicsObjects.values()).filter(obj => obj.type === 'baseball'); for (let i = 0; i < ballsArray.length; i++) { const ballA = ballsArray[i]; for (let j = i + 1; j < ballsArray.length; j++) { const ballB = ballsArray[j]; const dx = ballB.x - ballA.x; const dy = ballB.y - ballA.y; const distance = Math.sqrt(dx * dx + dy * dy); const minDistance = ballA.radius + ballB.radius; if (distance < minDistance && distance !== 0) { // Separar pelotas const normalX = dx / distance; const normalY = dy / distance; const overlap = minDistance - distance; ballA.x -= normalX * overlap * 0.5; ballA.y -= normalY * overlap * 0.5; ballB.x += normalX * overlap * 0.5; ballB.y += normalY * overlap * 0.5; // Physics collision response const relativeVelocityX = ballB.vx - ballA.vx; const relativeVelocityY = ballB.vy - ballA.vy; const velAlongNormal = relativeVelocityX * normalX + relativeVelocityY * normalY; if (velAlongNormal > 0) continue; const restitution = (ballA.restitution + ballB.restitution) * 0.5; const impulse = -(1 + restitution) * velAlongNormal; const impulseScalar = impulse / (ballA.mass + ballB.mass); ballA.vx -= impulseScalar * ballB.mass * normalX; ballA.vy -= impulseScalar * ballB.mass * normalY; ballB.vx += impulseScalar * ballA.mass * normalX; ballB.vy += impulseScalar * ballA.mass * normalY; } } } } /* ---------- SISTEMA DE BATEO VIRTUAL ---------- */ handleBaseballPlayerBatting() { const players = this.getBaseballPlayerPositions(); if (players.length === 0) return; this.physicsObjects.forEach(ball => { if (ball.type !== 'baseball') return; players.forEach(player => { const dx = ball.x - player.x; const dy = ball.y - player.y; const distance = Math.sqrt(dx * dx + dy * dy); const batReach = this.battingSystem.batLength; if (distance < batReach && distance > 0) { const currentTime = performance.now(); if (currentTime - ball.lastBatHit > this.battingSystem.swingCooldown) { this.executeBaseballSwing(ball, player, dx, dy, distance); ball.lastBatHit = currentTime; this.gameStats.totalHits++; } } }); }); } executeBaseballSwing(ball, player, dx, dy, distance) { const normalX = dx / distance; const normalY = dy / distance; // Fuerza del swing basada en proximidad[3][4] const swingIntensity = Math.max(0.2, (this.battingSystem.batLength - distance) / this.battingSystem.batLength); const baseSwingForce = BASEBALL_PHYSICS.BAT_FORCE * swingIntensity; // Aplicar fuerza del bateo (trayectoria parabólica) const launchAngle = -Math.PI/6; // Ángulo de elevación típico de baseball const forceX = Math.cos(launchAngle) * baseSwingForce; const forceY = Math.sin(launchAngle) * baseSwingForce; ball.vx = normalX * forceX; ball.vy = normalY * forceY + forceY; // Combinar dirección + elevación // Añadir velocidad del jugador si se está moviendo if (player.vx || player.vy) { const playerSpeed = Math.sqrt((player.vx || 0) ** 2 + (player.vy || 0) ** 2); if (playerSpeed > 20) { ball.vx += (player.vx || 0) * 1.2; ball.vy += (player.vy || 0) * 0.8; } } // Generar spin en el bateo ball.spin = (Math.random() - 0.5) * 30 * swingIntensity; // Determinar tipo de hit const hitSpeed = Math.sqrt(ball.vx ** 2 + ball.vy ** 2); this.categorizeBaseballHit(ball, hitSpeed, swingIntensity); // Efecto visual del swing this.showBaseballSwingEffect(ball.x, ball.y, player.type, swingIntensity); } categorizeBaseballHit(ball, speed, intensity) { if (speed > 600 && intensity > 0.8) { ball.isHomeRun = true; this.showBaseballFeedback('🏆 HOME RUN!', '#FFD700'); } else if (speed > 400) { this.showBaseballFeedback('💥 HARD HIT!', '#FF6347'); } else if (speed > 200) { this.showBaseballFeedback('⚾ BASE HIT!', '#32CD32'); } else { this.showBaseballFeedback('🏃 WEAK CONTACT', '#FFA500'); } } showBaseballSwingEffect(x, y, playerType, intensity) { const hitColor = playerType === 'self' ? '#48bb78' : '#f56565'; const intensityText = intensity > 0.7 ? 'POWER' : intensity > 0.4 ? 'GOOD' : 'WEAK'; this.showBaseballFeedback(`⚾ ${playerType === 'self' ? 'YOUR' : 'OPPONENT'} ${intensityText} SWING!`, hitColor); } getBaseballPlayerPositions() { const currentTime = performance.now(); const players = []; if (!drawariaCanvas) return players; const canvasRect = drawariaCanvas.getBoundingClientRect(); // Self player const selfPlayer = document.querySelector('div.spawnedavatar.spawnedavatar-self'); if (selfPlayer) { const rect = selfPlayer.getBoundingClientRect(); const currentPos = { type: 'self', id: 'self', x: rect.left - canvasRect.left + rect.width / 2, y: rect.top - canvasRect.top + rect.height / 2, radius: Math.max(rect.width, rect.height) / 2, vx: 0, vy: 0 }; players.push(currentPos); } // Other players const otherPlayers = document.querySelectorAll('div.spawnedavatar.spawnedavatar-otherplayer'); otherPlayers.forEach((player, index) => { const rect = player.getBoundingClientRect(); const currentPos = { type: 'other', id: `other_${index}`, x: rect.left - canvasRect.left + rect.width / 2, y: rect.top - canvasRect.top + rect.height / 2, radius: Math.max(rect.width, rect.height) / 2, vx: 0, vy: 0 }; players.push(currentPos); }); return players; } /* ---------- BASEBALL SCORING SYSTEM ---------- */ checkBaseballScoring() { if (!this.baseballGame.active || !this.canvasElement) return; const coords = calculateBaseballCoordinates(); this.physicsObjects.forEach(ball => { if (ball.type !== 'baseball' || !ball.inPlay) return; // Verificar home run (pelota sale del outfield) if (this.checkHomeRun(ball, coords)) { this.scoreHomeRun(ball); return; } // Verificar si la pelota aterrizó en territorio foul if (this.checkFoulBall(ball, coords)) { this.handleFoulBall(ball); return; } // Verificar out por fly ball if (ball.bounceCount === 0 && ball.vy > 0 && this.checkCatchablePosition(ball, coords)) { this.handleCatch(ball); return; } }); } checkHomeRun(ball, coords) { const distanceFromHome = Math.sqrt( Math.pow(ball.x - coords.home.x, 2) + Math.pow(ball.y - coords.home.y, 2) ); return distanceFromHome > BASEBALL_PHYSICS.HOME_RUN_DISTANCE && ball.vy > 0; } checkFoulBall(ball, coords) { // Verificar si está fuera de las líneas de foul const homeX = coords.home.x; const homeY = coords.home.y; // Lado izquierdo (línea de tercera base) const leftSlope = (coords.foulLines.leftEnd.y - homeY) / (coords.foulLines.leftEnd.x - homeX); const leftYAtBallX = homeY + leftSlope * (ball.x - homeX); // Lado derecho (línea de primera base) const rightSlope = (coords.foulLines.rightEnd.y - homeY) / (coords.foulLines.rightEnd.x - homeX); const rightYAtBallX = homeY + rightSlope * (ball.x - homeX); return ball.y < leftYAtBallX || ball.y < rightYAtBallX; } checkCatchablePosition(ball, coords) { // Verificar si está en posición de ser atrapada por un fielder return ball.y < coords.home.y - 100 && Math.abs(ball.vy) < 50; } async scoreHomeRun(ball) { ball.inPlay = false; this.gameStats.homeRuns++; // Añadir carrera if (this.baseballGame.topBottom === 'top') { this.baseballGame.runs.away++; } else { this.baseballGame.runs.home++; } await this.updateBaseballScore(); this.showBaseballFeedback('🏆 HOME RUN! ⚾🏠', '#FFD700'); setTimeout(() => { this.clearAllBaseballs(false); this.nextAtBat(); }, 3000); } async handleFoulBall(ball) { ball.inPlay = false; this.baseballGame.strikes++; if (this.baseballGame.strikes >= BASEBALL_GAME.STRIKES_FOR_OUT) { this.gameStats.strikeouts++; this.handleStrikeout(); } else { this.showBaseballFeedback(`❌ FOUL BALL! Strike ${this.baseballGame.strikes}`, '#FFA500'); } await this.updateBaseballScore(); setTimeout(() => { this.clearAllBaseballs(false); if (this.baseballGame.active) { this.pitchBaseball(); } }, 1500); } async handleCatch(ball) { ball.inPlay = false; this.baseballGame.outs++; this.showBaseballFeedback('🥎 CAUGHT! OUT!', '#f56565'); if (this.baseballGame.outs >= BASEBALL_GAME.OUTS_PER_INNING) { this.nextInning(); } else { this.nextAtBat(); } await this.updateBaseballScore(); } handleStrikeout() { this.baseballGame.outs++; this.showBaseballFeedback('⚾ STRIKEOUT! K!', '#f56565'); if (this.baseballGame.outs >= BASEBALL_GAME.OUTS_PER_INNING) { this.nextInning(); } else { this.nextAtBat(); } } nextAtBat() { this.baseballGame.strikes = 0; this.baseballGame.balls = 0; setTimeout(() => { if (this.baseballGame.active) { this.pitchBaseball(); } }, 1000); } nextInning() { if (this.baseballGame.topBottom === 'top') { this.baseballGame.topBottom = 'bottom'; } else { this.baseballGame.topBottom = 'top'; this.baseballGame.inning++; } this.baseballGame.outs = 0; this.baseballGame.strikes = 0; this.baseballGame.balls = 0; this.showBaseballFeedback(`⚾ INNING ${this.baseballGame.inning} - ${this.baseballGame.topBottom.toUpperCase()}`, '#87CEEB'); if (this.baseballGame.inning > BASEBALL_GAME.INNINGS) { this.endBaseballGame(); } else { setTimeout(() => { if (this.baseballGame.active) { this.pitchBaseball(); } }, 2000); } } /* ---------- BASEBALL GAME MODE ---------- */ toggleBaseballGame() { const button = document.getElementById('game-mode-toggle'); const scoreboard = document.getElementById('baseball-scoreboard'); this.baseballGame.active = !this.baseballGame.active; if (this.baseballGame.active) { button.style.background = 'linear-gradient(135deg, #FFD700, #FFA500)'; button.setAttribute('data-active', 'true'); scoreboard.style.display = 'block'; this.setupBaseballGame(); this.showBaseballFeedback('🏆 MLB GAME MODE ACTIVATED!', '#FFD700'); } else { button.style.background = 'linear-gradient(135deg, #444, #666)'; button.removeAttribute('data-active'); scoreboard.style.display = 'none'; this.resetBaseballGame(); this.showBaseballFeedback('🏆 Game Mode Deactivated', '#666'); } } async setupBaseballGame() { await drawCompleteBaseballField(); this.resetBaseballGameScores(); await this.updateBaseballScore(); setTimeout(() => { this.pitchBaseball(); }, 2000); } resetBaseballGameScores() { this.baseballGame = { active: true, inning: 1, topBottom: 'top', outs: 0, strikes: 0, balls: 0, runs: { home: 0, away: 0 }, bases: { first: false, second: false, third: false } }; } async updateBaseballScore() { document.getElementById('baseball-score-away').textContent = this.baseballGame.runs.away; document.getElementById('baseball-score-home').textContent = this.baseballGame.runs.home; document.getElementById('current-inning').textContent = this.baseballGame.inning; document.getElementById('inning-half').textContent = this.baseballGame.topBottom.charAt(0).toUpperCase() + this.baseballGame.topBottom.slice(1); document.getElementById('outs-count').textContent = this.baseballGame.outs; document.getElementById('balls-count').textContent = this.baseballGame.balls; document.getElementById('strikes-count').textContent = this.baseballGame.strikes; // Update base indicators document.getElementById('first-base').style.color = this.baseballGame.bases.first ? '#32CD32' : '#ccc'; document.getElementById('second-base').style.color = this.baseballGame.bases.second ? '#32CD32' : '#ccc'; document.getElementById('third-base').style.color = this.baseballGame.bases.third ? '#32CD32' : '#ccc'; } resetBaseballGame() { this.resetBaseballGameScores(); if (this.baseballGame.active) { this.clearAllBaseballs(false); setTimeout(() => { drawCompleteBaseballField().then(() => { this.pitchBaseball(); }); }, 500); } } async endBaseballGame() { const winner = this.baseballGame.runs.home > this.baseballGame.runs.away ? 'HOME' : 'AWAY'; const finalScore = `${this.baseballGame.runs.away}-${this.baseballGame.runs.home}`; this.showBaseballFeedback(`🏆 ${winner} TEAM WINS ${finalScore}!`, '#FFD700'); setTimeout(() => { this.resetBaseballGame(); }, 5000); } /* ---------- RENDERING ---------- */ renderBaseballs() { this.physicsObjects.forEach(obj => { if (obj.type !== 'baseball') return; const dx = Math.abs(obj.x - obj.lastRenderX); const dy = Math.abs(obj.y - obj.lastRenderY); const needsServerRedraw = dx > MOVEMENT_THRESHOLD || dy > MOVEMENT_THRESHOLD; if (needsServerRedraw) { // Borrar posición anterior if (obj.lastRenderX !== -9999 || obj.lastRenderY !== -9999) { this.drawBaseball(obj.lastRenderX, obj.lastRenderY, obj.radius, '#FFFFFF'); } // Dibujar en nueva posición this.drawBaseball(obj.x, obj.y, obj.radius, obj.color); obj.lastRenderX = obj.x; obj.lastRenderY = obj.y; } }); } drawBaseball(x, y, radius, color) { const effectiveThickness = radius * 2.0; // Pelota pequeña de baseball enqueueDrawCommand(x, y, x + 0.1, y + 0.1, color, effectiveThickness); } /* ---------- UTILITY FUNCTIONS ---------- */ clearAllBaseballs(showFeedback = true) { this.physicsObjects.clear(); positionCache.clear(); if (drawariaCtx && drawariaCanvas) { drawariaCtx.clearRect(0, 0, drawariaCanvas.width, drawariaCanvas.height); } if (showFeedback) { this.showBaseballFeedback('🗑️ ALL BASEBALLS CLEARED!', '#cc0000'); } } resetAllBaseballs() { if (this.canvasElement) { const coords = calculateBaseballCoordinates(); this.physicsObjects.forEach(obj => { obj.x = coords.pitcherMound.x + (Math.random() - 0.5) * 50; obj.y = coords.pitcherMound.y + (Math.random() - 0.5) * 30; obj.vx = 0; obj.vy = 0; obj.spin = 0; obj.lastRenderX = -9999; obj.lastRenderY = -9999; obj.bounceCount = 0; obj.inPlay = true; obj.isHomeRun = false; }); this.showBaseballFeedback('🔄 All baseballs reset to pitcher mound!', '#74b9ff'); } } async cleanBaseballField() { if (!drawariaCanvas) return; this.showBaseballFeedback('🧹 Cleaning MLB Field...', '#e17055'); const canvasWidth = drawariaCanvas.width; const canvasHeight = drawariaCanvas.height; for (let y = 0; y < canvasHeight; y += 70) { for (let x = 0; x < canvasWidth; x += 70) { const width = Math.min(70, canvasWidth - x); const height = Math.min(70, canvasHeight - y); enqueueDrawCommand(x, y, x + width, y + height, '#FFFFFF', Math.max(width, height)); await sleep(2); } } if (drawariaCtx) { drawariaCtx.clearRect(0, 0, canvasWidth, canvasHeight); } this.showBaseballFeedback('🧹 MLB Field Cleaned!', '#00d084'); } /* ---------- PANEL FUNCTIONALITY ---------- */ makeBaseballPanelDraggable() { const panel = document.getElementById('baseball-physics-panel'); const header = document.getElementById('baseball-panel-header'); let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; if (header) { header.onmousedown = dragMouseDown; } else { panel.onmousedown = dragMouseDown; } function dragMouseDown(e) { e = e || window.event; e.preventDefault(); pos3 = e.clientX; pos4 = e.clientY; document.onmouseup = closeDragElement; document.onmousemove = elementDrag; } function elementDrag(e) { e = e || window.event; e.preventDefault(); pos1 = pos3 - e.clientX; pos2 = pos4 - e.clientY; pos3 = e.clientX; pos4 = e.clientY; const newTop = panel.offsetTop - pos2; const newLeft = panel.offsetLeft - pos1; panel.style.top = Math.max(0, newTop) + "px"; panel.style.left = Math.max(0, newLeft) + "px"; panel.style.right = 'auto'; } function closeDragElement() { document.onmouseup = null; document.onmousemove = null; } } setupBaseballPanelButtons() { const minimizeBtn = document.getElementById('baseball-minimize-btn'); const closeBtn = document.getElementById('baseball-close-btn'); const content = document.getElementById('baseball-panel-content'); const panel = document.getElementById('baseball-physics-panel'); let isMinimized = false; minimizeBtn?.addEventListener('click', (e) => { e.stopPropagation(); if (!isMinimized) { content.style.display = 'none'; minimizeBtn.innerHTML = '+'; isMinimized = true; } else { content.style.display = 'block'; minimizeBtn.innerHTML = '−'; isMinimized = false; } }); closeBtn?.addEventListener('click', (e) => { e.stopPropagation(); if (confirm('¿Cerrar el motor de baseball?')) { if (this.isActive) this.stopBaseballPhysics(); isStopped = true; panel.remove(); this.showBaseballFeedback('❌ Baseball Engine Closed', '#ff4757'); } }); } startBaseballStatsMonitoring() { setInterval(() => { document.getElementById('baseball-count').textContent = this.physicsObjects.size; document.getElementById('baseball-hits-count').textContent = this.gameStats.totalHits; document.getElementById('home-runs-count').textContent = this.gameStats.homeRuns; document.getElementById('strikeouts-count').textContent = this.gameStats.strikeouts; document.getElementById('baseball-max-speed').textContent = Math.round(this.gameStats.maxVelocityReached * 2.237); // Convert to mph }, 1000); } showBaseballFeedback(message, color) { const feedback = document.createElement('div'); feedback.style.cssText = ` position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: ${color}; color: white; padding: 15px 25px; border-radius: 10px; font-weight: bold; z-index: 2147483648; font-size: 16px; box-shadow: 0 4px 20px rgba(0,0,0,0.3); opacity: 0; transition: opacity 0.3s ease-in-out; text-shadow: 1px 1px 3px rgba(0,0,0,0.5); border: 2px solid #FFD700; `; feedback.innerHTML = message; document.body.appendChild(feedback); setTimeout(() => feedback.style.opacity = '1', 10); setTimeout(() => feedback.style.opacity = '0', 2500); setTimeout(() => feedback.remove(), 2800); } } /* ---------- GLOBAL INITIALIZATION ---------- */ let baseballEngine = null; const initBaseballEngine = () => { if (!baseballEngine) { console.log('⚾ Initializing MLB Baseball Physics Engine v1.0...'); baseballEngine = new AdvancedDrawariaBaseball(); setTimeout(() => { const confirmMsg = document.createElement('div'); confirmMsg.style.cssText = ` position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: linear-gradient(45deg, #228B22, #32CD32); color: white; padding: 25px 35px; border-radius: 20px; font-size: 16px; font-weight: bold; z-index: 2147483648; text-align: center; box-shadow: 0 0 40px rgba(34,139,34,0.6); opacity: 0; transition: opacity 0.3s ease-in-out; border: 3px solid #FFD700; `; confirmMsg.innerHTML = ` ⚾ MLB BASEBALL ENGINE v1.0 LOADED! ⚾<br> <div style="font-size: 12px; margin-top: 10px; color: #E0FFE0;"> ✅ Professional MLB Diamond • Baseball Physics • Wind Effects<br> ✅ Home Run Detection • Virtual Batting • Innings System<br> ✅ Foul Ball Detection • Game Mode • Realistic Ball Physics </div> `; document.body.appendChild(confirmMsg); setTimeout(() => confirmMsg.style.opacity = '1', 10); setTimeout(() => confirmMsg.style.opacity = '0', 4000); setTimeout(() => confirmMsg.remove(), 4300); }, 1000); } }; // Enhanced CSS for Baseball styling const baseballStyle = document.createElement('style'); baseballStyle.textContent = ` @keyframes baseball-pitch { 0% { transform: scale(0) rotate(0deg); opacity: 1; } 50% { transform: scale(1.1) rotate(90deg); opacity: 0.9; } 100% { transform: scale(0) rotate(180deg); opacity: 0; } } @keyframes diamond-glow { 0% { box-shadow: 0 0 15px rgba(34, 139, 34, 0.3); } 50% { box-shadow: 0 0 25px rgba(135, 206, 235, 0.6); } 100% { box-shadow: 0 0 15px rgba(34, 139, 34, 0.3); } } .baseball-mode-toggle[data-active="true"] { animation: diamond-glow 2s infinite; } #baseball-physics-panel { transition: none !important; } #baseball-panel-header:hover { background: linear-gradient(45deg, #228B22, #32CD32) !important; } /* Baseball specific styling */ .mlb-diamond { background: linear-gradient(45deg, #228B22, #DEB887); } .baseball-spinning { animation: baseball-rotation 0.6s linear infinite; } @keyframes baseball-rotation { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } /* Home run animation */ @keyframes home-run-flight { 0% { transform: translateY(0) scale(1); } 50% { transform: translateY(-30px) scale(1.3); } 100% { transform: translateY(0) scale(1); } } .home-run-ball { animation: home-run-flight 2s ease-out; } /* Wind effect visualization */ @keyframes wind-effect { 0%, 100% { transform: translateX(0); } 50% { transform: translateX(5px); } } .wind-active { animation: wind-effect 1.5s infinite ease-in-out; } /* Status div baseball styling */ #baseball-status { font-family: 'Arial Black', Arial, sans-serif !important; animation: diamond-glow 3s infinite; } /* Baseball color scheme */ .baseball-green { color: #228B22; } .baseball-blue { color: #87CEEB; } .baseball-white { color: #FFFFFF; } .baseball-dirt { color: #DEB887; } .baseball-brown { color: #8B4513; } /* Baseball field animations */ @keyframes field-draw { 0% { opacity: 0.3; transform: scale(0.8); } 50% { opacity: 1; transform: scale(1.1); } 100% { opacity: 0.9; transform: scale(1); } } #baseball-status { animation: field-draw 2.5s ease-in-out infinite; } `; document.head.appendChild(baseballStyle); // Initialize when DOM is ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initBaseballEngine); } else { initBaseballEngine(); } setTimeout(initBaseballEngine, 2000); console.log('⚾ Advanced MLB Baseball Physics Engine v1.0 loaded successfully! ⚾'); console.log('🏟️ Features: Professional MLB Diamond • Baseball Physics • Wind Effects • Home Run Detection'); console.log('🏆 Ready for MLB-style baseball games in Drawaria!'); })();