diff options
Diffstat (limited to 'html/space')
-rw-r--r-- | html/space/game.js | 45 | ||||
-rw-r--r-- | html/space/gameState.js | 169 | ||||
-rw-r--r-- | html/space/index.html | 22 | ||||
-rw-r--r-- | html/space/input.js | 83 | ||||
-rw-r--r-- | html/space/physics.js | 253 | ||||
-rw-r--r-- | html/space/renderer.js | 358 |
6 files changed, 930 insertions, 0 deletions
diff --git a/html/space/game.js b/html/space/game.js new file mode 100644 index 0000000..ecd7abc --- /dev/null +++ b/html/space/game.js @@ -0,0 +1,45 @@ +// Main game entry point +import { initRenderer, render } from './renderer.js'; +import { initInput, updateInput } from './input.js'; +import { initPhysics, updatePhysics } from './physics.js'; +import { initGameState, updateGameState } from './gameState.js'; + +// Game state +let lastTime = 0; +let isRunning = true; + +// Initialize all systems +function init() { + console.log('Initializing game...'); + initRenderer(); + initInput(); + initPhysics(); + initGameState(); + console.log('Game initialized'); +} + +// Main game loop using requestAnimationFrame +function gameLoop(timestamp) { + if (!isRunning) return; + + const deltaTime = timestamp - lastTime; + lastTime = timestamp; + + // Update game systems + updateInput(); + updatePhysics(deltaTime); + updateGameState(deltaTime); + render(); + + // Debug output + if (Math.random() < 0.01) { // Only log occasionally to avoid spam + console.log('Game loop running, deltaTime:', deltaTime); + } + + requestAnimationFrame(gameLoop); +} + +// Start the game +console.log('Starting game...'); +init(); +requestAnimationFrame(gameLoop); \ No newline at end of file diff --git a/html/space/gameState.js b/html/space/gameState.js new file mode 100644 index 0000000..620bc1f --- /dev/null +++ b/html/space/gameState.js @@ -0,0 +1,169 @@ +// Game state module for managing game objects and logic +import { inputState } from './input.js'; +import { getPlayerState } from './physics.js'; + +// Game objects +const planets = []; +const enemyShips = []; +const projectiles = []; +let lastEnemySpawn = 0; + +// Space dimensions +const SPACE_SIZE = 10000; // Increased from implicit 2000 +const PLANET_DISTANCE = 5000; // Increased from 1000 +const ENEMY_SPAWN_DISTANCE = 3000; // Increased from 500 +const ENEMY_SPAWN_INTERVAL = 5000; // 5 seconds +const MAX_ENEMIES = 5; + +// Initialize game state +export function initGameState() { + // Create planets + planets.push({ + position: { x: PLANET_DISTANCE, y: 0, z: 0 }, + radius: 500, // Increased from 200 + color: '#3498db' + }); + + planets.push({ + position: { x: -PLANET_DISTANCE, y: PLANET_DISTANCE/2, z: -PLANET_DISTANCE }, + radius: 400, // Increased from 150 + color: '#e74c3c' + }); + + // Reset other state + enemyShips.length = 0; + projectiles.length = 0; + lastEnemySpawn = Date.now(); + + // Create initial enemy ships + for (let i = 0; i < 5; i++) { + createEnemyShip(); + } +} + +// Create a new enemy ship +function createEnemyShip() { + const distance = ENEMY_SPAWN_DISTANCE + Math.random() * ENEMY_SPAWN_DISTANCE; + const angle = Math.random() * Math.PI * 2; + const height = (Math.random() - 0.5) * ENEMY_SPAWN_DISTANCE; + + enemyShips.push({ + position: { + x: Math.cos(angle) * distance, + y: height, + z: Math.sin(angle) * distance + }, + velocity: { + x: (Math.random() - 0.5) * 0.5, // Reduced from 2 + y: (Math.random() - 0.5) * 0.5, // Reduced from 2 + z: (Math.random() - 0.5) * 0.5 // Reduced from 2 + }, + health: 100 + }); +} + +// Update game state +export function updateGameState(deltaTime) { + const currentTime = Date.now(); + const player = getPlayerState(); + + // Spawn enemies + if (currentTime - lastEnemySpawn > ENEMY_SPAWN_INTERVAL && + enemyShips.length < MAX_ENEMIES) { + spawnEnemy(); + lastEnemySpawn = currentTime; + } + + // Update projectiles + projectiles.forEach((projectile, index) => { + projectile.position.x += projectile.velocity.x * deltaTime; + projectile.position.y += projectile.velocity.y * deltaTime; + projectile.position.z += projectile.velocity.z * deltaTime; + + // Remove if too old + if (currentTime - projectile.createdAt > 5000) { + projectiles.splice(index, 1); + } + }); + + // Update enemy ships + enemyShips.forEach((ship, index) => { + // Move ships + ship.position.x += ship.velocity.x * deltaTime; + ship.position.y += ship.velocity.y * deltaTime; + ship.position.z += ship.velocity.z * deltaTime; + + // Check if ship is too far away + const distance = Math.sqrt( + Math.pow(ship.position.x - player.position.x, 2) + + Math.pow(ship.position.y - player.position.y, 2) + + Math.pow(ship.position.z - player.position.z, 2) + ); + + if (distance > SPACE_SIZE) { + enemyShips.splice(index, 1); + createEnemyShip(); + } + }); + + // Handle firing + if (inputState.firePrimary) { + createProjectile('primary'); + } + if (inputState.fireSecondary) { + createProjectile('secondary'); + } +} + +// Create a new projectile +export function createProjectile(type) { + const player = getPlayerState(); + const speed = type === 'primary' ? 10 : 7.5; // Reduced from 20/15 + const damage = type === 'primary' ? 25 : 10; + + const cosY = Math.cos(player.rotation.y); + const sinY = Math.sin(player.rotation.y); + const cosX = Math.cos(player.rotation.x); + const sinX = Math.sin(player.rotation.x); + + projectiles.push({ + type, + position: { ...player.position }, + velocity: { + x: sinY * cosX * speed, + y: sinX * speed, + z: cosY * cosX * speed + }, + damage, + createdAt: Date.now() + }); +} + +// Spawn a new enemy ship +function spawnEnemy() { + const angle = Math.random() * Math.PI * 2; + const distance = ENEMY_SPAWN_DISTANCE; + + enemyShips.push({ + position: { + x: Math.cos(angle) * distance, + y: 0, + z: Math.sin(angle) * distance + }, + velocity: { + x: (Math.random() - 0.5) * 0.5, + y: (Math.random() - 0.5) * 0.5, + z: (Math.random() - 0.5) * 0.5 + }, + health: 100 + }); +} + +// Get game state for rendering +export function getGameState() { + return { + planets: [...planets], + enemyShips: [...enemyShips], + projectiles: [...projectiles] + }; +} \ No newline at end of file diff --git a/html/space/index.html b/html/space/index.html new file mode 100644 index 0000000..9db977d --- /dev/null +++ b/html/space/index.html @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Space</title> + <style> + body { + margin: 0; + overflow: hidden; + background: black; + } + canvas { + display: block; + } + </style> +</head> +<body> + <canvas id="gameCanvas"></canvas> + <script type="module" src="game.js"></script> +</body> +</html> \ No newline at end of file diff --git a/html/space/input.js b/html/space/input.js new file mode 100644 index 0000000..19ea56c --- /dev/null +++ b/html/space/input.js @@ -0,0 +1,83 @@ +// Input handling module +import { updatePlayerControls } from './physics.js'; + +let keys = {}; +let mouseX = 0; +let mouseY = 0; + +// Input state that other modules can read +export const inputState = { + thrust: 0, // forward/backward thrust (W/S) + strafe: 0, // left/right strafe (A/D) + yaw: 0, // left/right rotation (arrow keys) + pitch: 0, // up/down rotation (arrow keys) + firePrimary: false, + fireSecondary: false +}; + +// Initialize input handlers +export function initInput() { + // Keyboard event listeners + document.addEventListener('keydown', (e) => { + keys[e.key.toLowerCase()] = true; + }); + + document.addEventListener('keyup', (e) => { + keys[e.key.toLowerCase()] = false; + }); + + // Mouse movement for heading + document.addEventListener('mousemove', (e) => { + // Calculate mouse position relative to center of canvas + const canvas = document.querySelector('canvas'); + const rect = canvas.getBoundingClientRect(); + const centerX = rect.left + rect.width / 2; + const centerY = rect.top + rect.height / 2; + + mouseX = (e.clientX - centerX) / (rect.width / 2); + mouseY = (e.clientY - centerY) / (rect.height / 2); + }); + + // Mouse click for primary weapon + document.addEventListener('mousedown', (e) => { + if (e.button === 0) { // Left click + keys['fire'] = true; + } + }); + + document.addEventListener('mouseup', (e) => { + if (e.button === 0) { // Left click + keys['fire'] = false; + } + }); + + // E key for secondary weapon + document.addEventListener('keydown', (e) => { + if (e.key.toLowerCase() === 'e') { + keys['secondary'] = true; + } + }); + + document.addEventListener('keyup', (e) => { + if (e.key.toLowerCase() === 'e') { + keys['secondary'] = false; + } + }); +} + +// Update controls based on current input state +export function updateInput() { + const controls = { + thrust: keys[' '] || false, // Space bar for thrust + up: keys['w'] || false, // W for upward strafe + down: keys['s'] || false, // S for downward strafe + left: keys['a'] || false, // A for left strafe + right: keys['d'] || false, // D for right strafe + fire: keys['fire'] || false, + secondary: keys['secondary'] || false, + mouseX, + mouseY + }; + + updatePlayerControls(controls); +} \ No newline at end of file diff --git a/html/space/physics.js b/html/space/physics.js new file mode 100644 index 0000000..d4dfe55 --- /dev/null +++ b/html/space/physics.js @@ -0,0 +1,253 @@ +// Physics module for handling movement and collisions +import { inputState } from './input.js'; +import { createProjectile } from './gameState.js'; + +// Constants +const MAX_THRUST = 0.5; // Reduced from 2 +const THRUST_ACCELERATION = 0.01; // Reduced from 0.05 +const DECELERATION = 0.001; // Reduced from 0.01 +const BASE_ROTATION_SPEED = 0.001; // Reduced from 0.005 +const ROTATION_ACCELERATION = 0.0005; // Reduced from 0.002 +const ROTATION_DECELERATION = 0.0002; // Reduced from 0.001 +const MOUSE_SENSITIVITY = 0.03; // Increased from 0.01 for sharper turns +const MAX_SPEED = 1.0; // Maximum speed in any direction + +// Weapon constants +export const PRIMARY_COOLDOWN = 100; // ms between primary shots +export const SECONDARY_COOLDOWN = 2000; // ms between secondary shots +export const PRIMARY_BURST_COUNT = 3; // Number of shots in primary burst +export const PRIMARY_BURST_DELAY = 50; // ms between burst shots + +// Player state +let playerState = { + position: { x: 0, y: 0, z: 0 }, + velocity: { x: 0, y: 0, z: 0 }, + rotation: { x: 0, y: 0 }, + thrust: 0, + strafe: 0, + weapons: { + primary: { + lastFired: 0, + burstCount: 0, + burstTimer: 0 + }, + secondary: { + lastFired: 0 + } + } +}; + +// Initialize physics +export function initPhysics() { + // Reset player state + playerState.position = { x: 0, y: 0, z: 0 }; + playerState.velocity = { x: 0, y: 0, z: 0 }; + playerState.rotation = { x: 0, y: 0 }; + playerState.thrust = 0; + playerState.strafe = 0; +} + +// Helper function to limit speed in a direction +function limitSpeed(velocity, maxSpeed) { + const speed = Math.sqrt(velocity.x * velocity.x + velocity.y * velocity.y + velocity.z * velocity.z); + if (speed > maxSpeed) { + const scale = maxSpeed / speed; + velocity.x *= scale; + velocity.y *= scale; + velocity.z *= scale; + } +} + +// Update player controls +export function updatePlayerControls(controls) { + // Handle thrust (space bar) + if (controls.thrust) { + playerState.thrust = Math.min(playerState.thrust + THRUST_ACCELERATION, MAX_THRUST); + } else { + // Apply deceleration when no thrust input + if (playerState.thrust > 0) { + playerState.thrust = Math.max(playerState.thrust - DECELERATION, 0); + } + } + + // Handle vertical strafing (W/S) + if (controls.up) { + playerState.verticalStrafe = Math.min(playerState.verticalStrafe + THRUST_ACCELERATION, MAX_THRUST); + } else if (controls.down) { + playerState.verticalStrafe = Math.max(playerState.verticalStrafe - THRUST_ACCELERATION, -MAX_THRUST); + } else { + // Apply deceleration when no vertical strafe input + if (playerState.verticalStrafe > 0) { + playerState.verticalStrafe = Math.max(playerState.verticalStrafe - DECELERATION, 0); + } else if (playerState.verticalStrafe < 0) { + playerState.verticalStrafe = Math.min(playerState.verticalStrafe + DECELERATION, 0); + } + } + + // Handle horizontal strafing (A/D) + if (controls.left) { + playerState.horizontalStrafe = Math.min(playerState.horizontalStrafe + THRUST_ACCELERATION, MAX_THRUST); + } else if (controls.right) { + playerState.horizontalStrafe = Math.max(playerState.horizontalStrafe - THRUST_ACCELERATION, -MAX_THRUST); + } else { + // Apply deceleration when no horizontal strafe input + if (playerState.horizontalStrafe > 0) { + playerState.horizontalStrafe = Math.max(playerState.horizontalStrafe - DECELERATION, 0); + } else if (playerState.horizontalStrafe < 0) { + playerState.horizontalStrafe = Math.min(playerState.horizontalStrafe + DECELERATION, 0); + } + } + + // Handle mouse-based rotation with smoothing + const targetRotationY = controls.mouseX * MOUSE_SENSITIVITY; + const targetRotationX = controls.mouseY * MOUSE_SENSITIVITY; + + // Smooth rotation using lerp with faster response + playerState.rotation.y += (targetRotationY - playerState.rotation.y) * 0.2; + playerState.rotation.x += (targetRotationX - playerState.rotation.x) * 0.2; + + // Clamp pitch rotation + playerState.rotation.x = Math.max(-Math.PI/2, Math.min(Math.PI/2, playerState.rotation.x)); + + // Handle weapons with cooldowns + const currentTime = Date.now(); + + // Primary weapon (burst fire) + if (controls.fire) { + const primary = playerState.weapons.primary; + if (currentTime - primary.lastFired >= PRIMARY_COOLDOWN && primary.burstCount === 0) { + primary.burstCount = PRIMARY_BURST_COUNT; + primary.burstTimer = currentTime; + firePrimaryWeapon(); + primary.lastFired = currentTime; + } + } + + // Secondary weapon (single shot with cooldown) + if (controls.secondary && currentTime - playerState.weapons.secondary.lastFired >= SECONDARY_COOLDOWN) { + fireSecondaryWeapon(); + playerState.weapons.secondary.lastFired = currentTime; + } + + // Handle burst fire timing + const primary = playerState.weapons.primary; + if (primary.burstCount > 0 && currentTime - primary.burstTimer >= PRIMARY_BURST_DELAY) { + firePrimaryWeapon(); + primary.burstCount--; + primary.burstTimer = currentTime; + } +} + +// Update physics +export function updatePhysics(deltaTime) { + // Calculate forward and right vectors based on rotation + const forward = { + x: Math.sin(playerState.rotation.y) * Math.cos(playerState.rotation.x), + y: -Math.sin(playerState.rotation.x), + z: Math.cos(playerState.rotation.y) * Math.cos(playerState.rotation.x) + }; + + const right = { + x: Math.cos(playerState.rotation.y), + y: 0, + z: -Math.sin(playerState.rotation.y) + }; + + const up = { x: 0, y: 1, z: 0 }; + + // Apply thrust in forward direction + const thrustVelocity = { + x: forward.x * playerState.thrust * deltaTime, + y: forward.y * playerState.thrust * deltaTime, + z: forward.z * playerState.thrust * deltaTime + }; + + // Apply horizontal strafe + const horizontalStrafeVelocity = { + x: right.x * playerState.horizontalStrafe * deltaTime, + y: 0, + z: right.z * playerState.horizontalStrafe * deltaTime + }; + + // Apply vertical strafe + const verticalStrafeVelocity = { + x: 0, + y: up.y * playerState.verticalStrafe * deltaTime, + z: 0 + }; + + // Add velocities + playerState.velocity.x += thrustVelocity.x + horizontalStrafeVelocity.x + verticalStrafeVelocity.x; + playerState.velocity.y += thrustVelocity.y + horizontalStrafeVelocity.y + verticalStrafeVelocity.y; + playerState.velocity.z += thrustVelocity.z + horizontalStrafeVelocity.z + verticalStrafeVelocity.z; + + // Limit total speed + limitSpeed(playerState.velocity, MAX_SPEED); + + // Apply velocity to position + playerState.position.x += playerState.velocity.x * deltaTime; + playerState.position.y += playerState.velocity.y * deltaTime; + playerState.position.z += playerState.velocity.z * deltaTime; + + // Apply friction/drag + const drag = 0.99; + playerState.velocity.x *= drag; + playerState.velocity.y *= drag; + playerState.velocity.z *= drag; +} + +// Weapon firing +function firePrimaryWeapon() { + const forward = { + x: Math.sin(playerState.rotation.y) * Math.cos(playerState.rotation.x), + y: -Math.sin(playerState.rotation.x), + z: Math.cos(playerState.rotation.y) * Math.cos(playerState.rotation.x) + }; + + createProjectile({ + position: { ...playerState.position }, + velocity: { + x: forward.x * 10 + playerState.velocity.x, + y: forward.y * 10 + playerState.velocity.y, + z: forward.z * 10 + playerState.velocity.z + }, + type: 'primary' + }); +} + +function fireSecondaryWeapon() { + const forward = { + x: Math.sin(playerState.rotation.y) * Math.cos(playerState.rotation.x), + y: -Math.sin(playerState.rotation.x), + z: Math.cos(playerState.rotation.y) * Math.cos(playerState.rotation.x) + }; + + createProjectile({ + position: { ...playerState.position }, + velocity: { + x: forward.x * 5 + playerState.velocity.x, + y: forward.y * 5 + playerState.velocity.y, + z: forward.z * 5 + playerState.velocity.z + }, + type: 'secondary' + }); +} + +// Get current player state +export function getPlayerState() { + return playerState; +} + +// Get weapon cooldown states +export function getWeaponStates() { + const currentTime = Date.now(); + return { + primary: { + cooldown: Math.max(0, PRIMARY_COOLDOWN - (currentTime - playerState.weapons.primary.lastFired)), + burstCount: playerState.weapons.primary.burstCount + }, + secondary: { + cooldown: Math.max(0, SECONDARY_COOLDOWN - (currentTime - playerState.weapons.secondary.lastFired)) + } + }; +} \ No newline at end of file diff --git a/html/space/renderer.js b/html/space/renderer.js new file mode 100644 index 0000000..04646cf --- /dev/null +++ b/html/space/renderer.js @@ -0,0 +1,358 @@ +// Renderer module using HTML5 Canvas +import { getPlayerState, getWeaponStates } from './physics.js'; +import { getGameState } from './gameState.js'; + +// Import weapon constants +import { + PRIMARY_COOLDOWN, + SECONDARY_COOLDOWN, + PRIMARY_BURST_COUNT, + PRIMARY_BURST_DELAY +} from './physics.js'; + +let canvas; +let ctx; +let width; +let height; + +// Star field +let starfield = []; // Declare starfield array +const NUM_STARS = 2000; // Increased from 1000 +const STAR_FIELD_DEPTH = 20000; // Increased from 2000 + +// HUD constants +const HUD_COLOR = '#00ff00'; +const HUD_ALPHA = 0.7; +const RADAR_RADIUS = 100; +const RADAR_CENTER_X = 100; +const RADAR_CENTER_Y = 100; +const RADAR_SCALE = 0.1; // Scale factor for radar display +const TARGET_LOCK_COLOR = '#ff0000'; +const TARGET_LOCK_THRESHOLD = 20; // Pixels from center to consider locked + +// Initialize the renderer +export function initRenderer() { + console.log('Initializing renderer...'); + canvas = document.getElementById('gameCanvas'); + ctx = canvas.getContext('2d'); + + // Set canvas size + width = canvas.width = window.innerWidth; + height = canvas.height = window.innerHeight; + + // Initialize starfield + console.log('Creating starfield with', NUM_STARS, 'stars...'); + starfield = Array.from({ length: NUM_STARS }, () => ({ + x: (Math.random() - 0.5) * STAR_FIELD_DEPTH, + y: (Math.random() - 0.5) * STAR_FIELD_DEPTH, + z: Math.random() * STAR_FIELD_DEPTH, + size: Math.random() * 2 + 1 + })); + console.log('Starfield initialized'); +} + +// Project 3D point to 2D screen coordinates +function projectPoint(x, y, z) { + if (z <= 0) return null; // Behind camera + + const scale = 2000 / z; // Increased scale factor + return { + x: width/2 + x * scale, + y: height/2 + y * scale, + scale + }; +} + +// Check if any enemy ship is in targeting range +function getTargetLock(player, gameState) { + const centerX = width / 2; + const centerY = height / 2; + + for (const ship of gameState.enemyShips) { + const projected = projectPoint(ship.x - player.x, ship.y - player.y, ship.z - player.z); + if (projected) { + const distance = Math.sqrt( + Math.pow(projected.x - centerX, 2) + + Math.pow(projected.y - centerY, 2) + ); + + if (distance < TARGET_LOCK_THRESHOLD) { + return { + ship, + distance + }; + } + } + } + return null; +} + +// Draw radar/minimap +function drawRadar(player, gameState, targetLock) { + // Save context + ctx.save(); + + // Set radar style + ctx.strokeStyle = targetLock ? TARGET_LOCK_COLOR : HUD_COLOR; + ctx.fillStyle = 'rgba(0, 0, 0, 0.5)'; + ctx.lineWidth = 1; + + // Draw radar background + ctx.beginPath(); + ctx.arc(RADAR_CENTER_X, RADAR_CENTER_Y, RADAR_RADIUS, 0, Math.PI * 2); + ctx.fill(); + ctx.stroke(); + + // Draw radar grid + ctx.beginPath(); + ctx.moveTo(RADAR_CENTER_X - RADAR_RADIUS, RADAR_CENTER_Y); + ctx.lineTo(RADAR_CENTER_X + RADAR_RADIUS, RADAR_CENTER_Y); + ctx.moveTo(RADAR_CENTER_X, RADAR_CENTER_Y - RADAR_RADIUS); + ctx.lineTo(RADAR_CENTER_X, RADAR_CENTER_Y + RADAR_RADIUS); + ctx.stroke(); + + // Draw objects on radar + ctx.fillStyle = HUD_COLOR; + + // Draw planets + gameState.planets.forEach(planet => { + const dx = (planet.x - player.x) * RADAR_SCALE; + const dy = (planet.z - player.z) * RADAR_SCALE; + const distance = Math.sqrt(dx * dx + dy * dy); + + if (distance < RADAR_RADIUS) { + ctx.beginPath(); + ctx.arc( + RADAR_CENTER_X + dx, + RADAR_CENTER_Y + dy, + 5, + 0, + Math.PI * 2 + ); + ctx.fill(); + } + }); + + // Draw enemy ships + gameState.enemyShips.forEach(ship => { + const dx = (ship.x - player.x) * RADAR_SCALE; + const dy = (ship.z - player.z) * RADAR_SCALE; + const distance = Math.sqrt(dx * dx + dy * dy); + + if (distance < RADAR_RADIUS) { + ctx.beginPath(); + ctx.arc( + RADAR_CENTER_X + dx, + RADAR_CENTER_Y + dy, + 4, + 0, + Math.PI * 2 + ); + ctx.fill(); + } + }); + + // Restore context + ctx.restore(); +} + +// Draw speed and direction indicators +function drawSpeedIndicator(player, targetLock) { + ctx.save(); + ctx.fillStyle = targetLock ? TARGET_LOCK_COLOR : HUD_COLOR; + ctx.font = '14px monospace'; + + // Calculate speed from velocity components + const speed = Math.sqrt( + player.vx * player.vx + + player.vy * player.vy + + player.vz * player.vz + ); + + // Draw speed + ctx.fillText(`Speed: ${speed.toFixed(2)}`, 20, height - 40); + + // Draw direction (using x and z components for heading) + const direction = Math.atan2(player.vx, player.vz); + ctx.fillText(`Heading: ${(direction * 180 / Math.PI).toFixed(1)}°`, 20, height - 20); + + ctx.restore(); +} + +// Draw enhanced targeting reticle +function drawTargetingReticle(player, gameState) { + ctx.save(); + + // Check for target lock + const targetLock = getTargetLock(player, gameState); + const currentColor = targetLock ? TARGET_LOCK_COLOR : HUD_COLOR; + + ctx.strokeStyle = currentColor; + ctx.lineWidth = 1; + + // Outer circle + ctx.beginPath(); + ctx.arc(width/2, height/2, 20, 0, Math.PI * 2); + ctx.stroke(); + + // Inner crosshair + ctx.beginPath(); + ctx.moveTo(width/2 - 10, height/2); + ctx.lineTo(width/2 + 10, height/2); + ctx.moveTo(width/2, height/2 - 10); + ctx.lineTo(width/2, height/2 + 10); + ctx.stroke(); + + // Target brackets + ctx.beginPath(); + ctx.moveTo(width/2 - 30, height/2 - 30); + ctx.lineTo(width/2 - 20, height/2 - 30); + ctx.lineTo(width/2 - 20, height/2 - 20); + ctx.moveTo(width/2 + 30, height/2 - 30); + ctx.lineTo(width/2 + 20, height/2 - 30); + ctx.lineTo(width/2 + 20, height/2 - 20); + ctx.moveTo(width/2 - 30, height/2 + 30); + ctx.lineTo(width/2 - 20, height/2 + 30); + ctx.lineTo(width/2 - 20, height/2 + 20); + ctx.moveTo(width/2 + 30, height/2 + 30); + ctx.lineTo(width/2 + 20, height/2 + 30); + ctx.lineTo(width/2 + 20, height/2 + 20); + ctx.stroke(); + + // Draw target lock indicator if locked + if (targetLock) { + // Draw pulsing circle around target + const pulseSize = 30 + Math.sin(Date.now() * 0.01) * 5; + ctx.beginPath(); + ctx.arc(width/2, height/2, pulseSize, 0, Math.PI * 2); + ctx.stroke(); + + // Draw target distance + ctx.fillStyle = currentColor; + ctx.font = '14px monospace'; + ctx.fillText(`Target Lock: ${targetLock.distance.toFixed(1)}`, width/2 - 50, height/2 + 50); + } + + ctx.restore(); +} + +// Draw weapon cooldown indicators +function drawWeaponCooldowns() { + const weaponStates = getWeaponStates(); + ctx.save(); + ctx.fillStyle = HUD_COLOR; + ctx.font = '14px monospace'; + + // Primary weapon cooldown (bottom left) + const primaryCooldown = weaponStates.primary.cooldown / PRIMARY_COOLDOWN; + ctx.fillText('Primary:', 20, height - 80); + ctx.fillStyle = `rgba(0, 255, 0, ${primaryCooldown})`; + ctx.fillRect(20, height - 70, 100, 10); + ctx.strokeStyle = HUD_COLOR; + ctx.strokeRect(20, height - 70, 100, 10); + + // Secondary weapon cooldown (bottom left, below primary) + const secondaryCooldown = weaponStates.secondary.cooldown / SECONDARY_COOLDOWN; + ctx.fillStyle = HUD_COLOR; + ctx.fillText('Secondary:', 20, height - 50); + ctx.fillStyle = `rgba(0, 255, 0, ${secondaryCooldown})`; + ctx.fillRect(20, height - 40, 100, 10); + ctx.strokeStyle = HUD_COLOR; + ctx.strokeRect(20, height - 40, 100, 10); + + // Draw burst indicator for primary weapon + if (weaponStates.primary.burstCount > 0) { + ctx.fillStyle = HUD_COLOR; + ctx.fillText(`Burst: ${weaponStates.primary.burstCount}`, 20, height - 20); + } + + ctx.restore(); +} + +// Main render function +export function render() { + const player = getPlayerState(); + const gameState = getGameState(); + const targetLock = getTargetLock(player, gameState); + + // Clear canvas + ctx.fillStyle = '#000000'; + ctx.fillRect(0, 0, canvas.width, canvas.height); + + // Draw starfield + let starsRendered = 0; + starfield.forEach(star => { + // Calculate star position relative to player + let relativeX = star.x - player.x; + let relativeY = star.y - player.y; + let relativeZ = star.z - player.z; + + // Apply player rotation + let rotatedX = relativeX * Math.cos(player.rotation) - relativeY * Math.sin(player.rotation); + let rotatedY = relativeX * Math.sin(player.rotation) + relativeY * Math.cos(player.rotation); + let rotatedZ = relativeZ; + + // Project to screen coordinates + if (rotatedZ > 0) { + const projected = projectPoint(rotatedX, rotatedY, rotatedZ); + if (projected) { + const brightness = Math.min(1, 2000 / rotatedZ); + ctx.fillStyle = `rgba(255, 255, 255, ${brightness})`; + ctx.beginPath(); + ctx.arc(projected.x, projected.y, star.size * brightness, 0, Math.PI * 2); + ctx.fill(); + starsRendered++; + } + } + }); + + // Debug output + if (Math.random() < 0.01) { // Only log occasionally to avoid spam + console.log('Stars rendered:', starsRendered); + } + + // Draw planets + gameState.planets.forEach(planet => { + const projected = projectPoint(planet.x - player.x, planet.y - player.y, planet.z - player.z); + if (projected) { + const radius = planet.radius * projected.scale; + ctx.fillStyle = planet.color; + ctx.beginPath(); + ctx.arc(projected.x, projected.y, radius, 0, Math.PI * 2); + ctx.fill(); + } + }); + + // Draw enemy ships + gameState.enemyShips.forEach(ship => { + const projected = projectPoint(ship.x - player.x, ship.y - player.y, ship.z - player.z); + if (projected) { + const size = 20 * projected.scale; + ctx.fillStyle = '#ff0000'; + ctx.beginPath(); + ctx.moveTo(projected.x, projected.y - size); + ctx.lineTo(projected.x + size, projected.y + size); + ctx.lineTo(projected.x - size, projected.y + size); + ctx.closePath(); + ctx.fill(); + } + }); + + // Draw projectiles + gameState.projectiles.forEach(projectile => { + const projected = projectPoint(projectile.x - player.x, projectile.y - player.y, projectile.z - player.z); + if (projected) { + const size = 3 * projected.scale; + ctx.fillStyle = projectile.type === 'primary' ? '#ffff00' : '#00ffff'; + ctx.beginPath(); + ctx.arc(projected.x, projected.y, size, 0, Math.PI * 2); + ctx.fill(); + } + }); + + // Draw HUD elements + drawRadar(player, gameState, targetLock); + drawSpeedIndicator(player, targetLock); + drawTargetingReticle(player, gameState); + drawWeaponCooldowns(); +} \ No newline at end of file |