// ==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!');
})();