diff options
author | elioat <elioat@tilde.institute> | 2024-12-18 15:52:05 -0500 |
---|---|---|
committer | elioat <elioat@tilde.institute> | 2024-12-18 15:52:05 -0500 |
commit | ee38675b78dc7f6a49af2a7004822565086df476 (patch) | |
tree | 0c33c4a3d675a5dda7d54e2943a6c76b622bc399 | |
parent | f66730b7aeb9494c266260df7eeba8a8e26beb4e (diff) | |
download | tour-ee38675b78dc7f6a49af2a7004822565086df476.tar.gz |
*
-rw-r--r-- | html/plains/enemies.js | 87 | ||||
-rw-r--r-- | html/plains/game.js | 193 |
2 files changed, 226 insertions, 54 deletions
diff --git a/html/plains/enemies.js b/html/plains/enemies.js index c0e3daa..ad457b2 100644 --- a/html/plains/enemies.js +++ b/html/plains/enemies.js @@ -90,6 +90,17 @@ const handleEnemyDamage = (enemy, damage, knockbackForce = 0, angle = 0) => { createdAt: animationTime }); } + + // Generate diamonds when enemy is defeated + const diamondCount = Math.floor(Math.random() * 5); // 0 to 4 diamonds + for (let i = 0; i < diamondCount; i++) { + state.diamonds.push({ + x: enemy.x + (Math.random() - 0.5) * 20, + y: enemy.y + (Math.random() - 0.5) * 20, + size: 6, // Smaller size (was 10) + collected: false + }); + } } return damage > 0 && enemy.hp <= 0; @@ -356,7 +367,14 @@ const updateEnemies = (enemies, deltaTime) => { }; const renderEnemies = (ctx, enemies) => { + // Pre-create the health circle gradient once + const healthGradient = ctx.createRadialGradient(0, 0, 0, 0, 0, 2); + healthGradient.addColorStop(0, 'rgba(255, 0, 0, 0.8)'); + healthGradient.addColorStop(0.7, 'rgba(200, 0, 0, 0.6)'); + healthGradient.addColorStop(1, 'rgba(150, 0, 0, 0.4)'); + enemies.forEach(enemy => { + // Draw enemy body ctx.beginPath(); ctx.arc(enemy.x, enemy.y, enemy.size, 0, Math.PI * 2); ctx.fillStyle = enemy.stunned ? 'rgb(150, 150, 150)' : enemy.color; @@ -365,35 +383,68 @@ const renderEnemies = (ctx, enemies) => { // Add a glowing effect const glowSize = enemy.stunned ? 1.1 : (enemy.attacking ? 1.6 : enemy.isChasing ? 1.4 : 1.2); const glowIntensity = enemy.stunned ? 0.1 : (enemy.attacking ? 0.5 : enemy.isChasing ? 0.3 : 0.2); - const gradient = ctx.createRadialGradient( + + // Create glow gradient only once per enemy + const glowGradient = ctx.createRadialGradient( enemy.x, enemy.y, enemy.size * 0.5, enemy.x, enemy.y, enemy.size * glowSize ); - gradient.addColorStop(0, `rgba(255, 0, 0, ${glowIntensity})`); - gradient.addColorStop(1, 'rgba(255, 0, 0, 0)'); + glowGradient.addColorStop(0, `rgba(255, 0, 0, ${glowIntensity})`); + glowGradient.addColorStop(1, 'rgba(255, 0, 0, 0)'); ctx.beginPath(); ctx.arc(enemy.x, enemy.y, enemy.size * glowSize, 0, Math.PI * 2); - ctx.fillStyle = gradient; + ctx.fillStyle = glowGradient; ctx.fill(); - // Draw HP bar - const maxHP = 10; - const barWidth = enemy.size * 2; - const barHeight = 4; - const barX = enemy.x - barWidth / 2; - const barY = enemy.y - enemy.size - barHeight - 5; - - // Background - ctx.fillStyle = 'rgba(0, 0, 0, 0.5)'; - ctx.fillRect(barX, barY, barWidth, barHeight); - - // HP - ctx.fillStyle = `rgb(${Math.floor(255 * (1 - enemy.hp/maxHP))}, ${Math.floor(255 * enemy.hp/maxHP)}, 0)`; - ctx.fillRect(barX, barY, barWidth * (enemy.hp/maxHP), barHeight); + // Draw HP circles + if (enemy.hp > 0) { + const circleRadius = 3; + const circleSpacing = 8; + const totalCircles = enemy.hp; + const startX = enemy.x - ((totalCircles - 1) * circleSpacing) / 2; + const circleY = enemy.y - enemy.size - 15; + + // Batch draw the circle borders + ctx.beginPath(); + for (let i = 0; i < totalCircles; i++) { + const circleX = startX + (i * circleSpacing); + ctx.moveTo(circleX + circleRadius, circleY); + ctx.arc(circleX, circleY, circleRadius, 0, Math.PI * 2); + } + ctx.strokeStyle = 'rgba(255, 255, 255, 0.3)'; + ctx.lineWidth = 1; + ctx.stroke(); + + // Batch draw the filled circles + ctx.beginPath(); + for (let i = 0; i < totalCircles; i++) { + const circleX = startX + (i * circleSpacing); + ctx.moveTo(circleX + circleRadius - 1, circleY); + ctx.arc(circleX, circleY, circleRadius - 1, 0, Math.PI * 2); + } + ctx.fillStyle = 'rgba(255, 0, 0, 0.6)'; + ctx.fill(); + } }); }; +const generateDiamonds = () => { + const diamondCount = Math.floor(Math.random() * 5); // 0 to 4 diamonds + const diamonds = []; + + for (let i = 0; i < diamondCount; i++) { + diamonds.push({ + x: enemy.x + (Math.random() - 0.5) * 20, // Random position around the enemy + y: enemy.y + (Math.random() - 0.5) * 20, + size: 10, // Size of the diamond + collected: false // Track if collected + }); + } + + return diamonds; +}; + // Export the functions window.enemySystem = { generateEnemies, diff --git a/html/plains/game.js b/html/plains/game.js index 5923b16..d25daaf 100644 --- a/html/plains/game.js +++ b/html/plains/game.js @@ -366,7 +366,9 @@ const createInitialState = () => ({ maxHp: 15, isInvulnerable: false, invulnerableUntil: 0, - isDead: false + isDead: false, + diamonds: 0, // Track number of collected diamonds + lastRenderedCircles: 4, // Add this line }, particles: [], footprints: [], @@ -381,7 +383,8 @@ const createInitialState = () => ({ villagers: [], gameComplete: false, gameCompleteMessageShown: false, - enemies: [] + enemies: [], + diamonds: [] // Array to hold diamond objects }); let state = createInitialState(); @@ -1080,46 +1083,101 @@ const renderPlayerHUD = (ctx) => { // Switch to screen coordinates ctx.setTransform(1, 0, 0, 1, 0, 0); - // HP Bar container - const barWidth = 200; - const barHeight = 20; - const barX = 20; - const barY = 20; - const borderWidth = 2; - - // Draw border - ctx.fillStyle = 'rgba(0, 0, 0, 0.8)'; - ctx.fillRect( - barX - borderWidth, - barY - borderWidth, - barWidth + borderWidth * 2, - barHeight + borderWidth * 2 - ); + const circleRadius = 15; + const circleSpacing = 40; + const startX = 30; + const startY = 30; + const totalCircles = 4; + const hpPerCircle = state.player.maxHp / totalCircles; + + // Track if we need to create explosion particles + const currentFilledCircles = Math.ceil(state.player.hp / hpPerCircle); + if (currentFilledCircles < state.player.lastRenderedCircles) { + // Create explosion particles for the depleted circle + const circleIndex = currentFilledCircles; + const particleX = startX + circleIndex * circleSpacing; + createHealthCircleExplosion(particleX, startY); + } + state.player.lastRenderedCircles = currentFilledCircles; - // Background - ctx.fillStyle = 'rgba(50, 50, 50, 0.8)'; - ctx.fillRect(barX, barY, barWidth, barHeight); - - // HP - const hpRatio = state.player.hp / state.player.maxHp; - ctx.fillStyle = `rgb(${Math.floor(255 * (1 - hpRatio))}, ${Math.floor(255 * hpRatio)}, 0)`; - ctx.fillRect(barX, barY, barWidth * hpRatio, barHeight); - - // HP Text - ctx.fillStyle = 'white'; - ctx.font = '14px Arial'; - ctx.textAlign = 'center'; - ctx.textBaseline = 'middle'; - ctx.fillText( - `HP: ${state.player.hp}/${state.player.maxHp}`, - barX + barWidth/2, - barY + barHeight/2 - ); + // Draw circles + for (let i = 0; i < totalCircles; i++) { + const circleX = startX + i * circleSpacing; + const circleY = startY; + + // Calculate how full this circle should be + const circleStartHp = i * hpPerCircle; + const circleEndHp = (i + 1) * hpPerCircle; + let fillAmount = 1; + + if (state.player.hp <= circleStartHp) { + fillAmount = 0; + } else if (state.player.hp < circleEndHp) { + fillAmount = (state.player.hp - circleStartHp) / hpPerCircle; + } + + // Draw outer circle (border) + ctx.beginPath(); + ctx.arc(circleX, circleY, circleRadius, 0, Math.PI * 2); + ctx.strokeStyle = 'rgba(255, 255, 255, 0.5)'; + ctx.lineWidth = 2; + ctx.stroke(); + + // Draw fill + if (fillAmount > 0) { + // Create gradient for filled portion + const gradient = ctx.createRadialGradient( + circleX, circleY, 0, + circleX, circleY, circleRadius + ); + gradient.addColorStop(0, 'rgba(255, 0, 0, 0.8)'); + gradient.addColorStop(0.7, 'rgba(200, 0, 0, 0.6)'); + gradient.addColorStop(1, 'rgba(150, 0, 0, 0.4)'); + + ctx.beginPath(); + ctx.arc(circleX, circleY, circleRadius * 0.9, 0, Math.PI * 2 * fillAmount); + ctx.lineTo(circleX, circleY); + ctx.fillStyle = gradient; + ctx.fill(); + } + } // Restore the context state ctx.restore(); }; +// Add this new function to create the explosion effect +const createHealthCircleExplosion = (screenX, screenY) => { + const numParticles = 20; + const colors = [ + 'rgba(255, 0, 0, 0.8)', + 'rgba(200, 0, 0, 0.6)', + 'rgba(150, 0, 0, 0.4)' + ]; + + // Convert screen coordinates to world coordinates + const worldX = screenX - state.camera.x; + const worldY = screenY - state.camera.y; + + for (let i = 0; i < numParticles; i++) { + const angle = (i / numParticles) * Math.PI * 2; + const speed = 2 + Math.random() * 3; + const size = 2 + Math.random() * 3; + const color = colors[Math.floor(Math.random() * colors.length)]; + + state.particles.push({ + x: worldX, + y: worldY, + dx: Math.cos(angle) * speed, + dy: Math.sin(angle) * speed, + size: size, + color: color, + lifetime: 1000, + createdAt: animationTime + }); + } +}; + const render = () => { ctx.clearRect(0, 0, GAME_WIDTH, GAME_HEIGHT); @@ -1489,6 +1547,9 @@ const render = () => { state.player.isInvulnerable = false; } + // Render diamonds + renderDiamonds(); + ctx.restore(); }; @@ -1598,6 +1659,7 @@ const gameLoop = (currentTime) => { updatePlayer(); state.enemies = enemySystem.updateEnemies(state.enemies, deltaTime); + collectDiamonds(); // Add this line to check for diamond collection render(); lastFrameTime = currentTime; @@ -1851,4 +1913,63 @@ const showGameOver = () => { <p>Press Space to restart</p> `; document.body.appendChild(gameOverDiv); +}; + +// Update the player's HP when collecting diamonds +const collectDiamonds = () => { + state.diamonds.forEach(diamond => { + const dx = state.player.x - diamond.x; + const dy = state.player.y - diamond.y; + const distance = Math.sqrt(dx * dx + dy * dy); + + if (distance < (CONFIG.player.size / 2 + diamond.size / 2) && !diamond.collected) { + diamond.collected = true; // Mark diamond as collected + state.player.hp = Math.min(state.player.hp + 1, state.player.maxHp); // Restore 1 HP, max out at maxHp + } + }); + + // Remove collected diamonds from the state + state.diamonds = state.diamonds.filter(diamond => !diamond.collected); +}; + +const renderDiamonds = () => { + state.diamonds.forEach(diamond => { + ctx.save(); + + // Add pulsing glow effect + const pulseIntensity = 0.5 + Math.sin(animationTime * 0.005) * 0.3; // Pulsing between 0.2 and 0.8 + + // Outer glow + const gradient = ctx.createRadialGradient( + diamond.x, diamond.y, diamond.size * 0.2, + diamond.x, diamond.y, diamond.size * 1.5 + ); + gradient.addColorStop(0, `rgba(255, 215, 0, ${pulseIntensity})`); // Gold center + gradient.addColorStop(0.6, 'rgba(255, 215, 0, 0.2)'); // Fading gold + gradient.addColorStop(1, 'rgba(255, 215, 0, 0)'); // Transparent edge + + ctx.beginPath(); + ctx.arc(diamond.x, diamond.y, diamond.size * 1.5, 0, Math.PI * 2); + ctx.fillStyle = gradient; + ctx.fill(); + + // Inner diamond + ctx.beginPath(); + ctx.arc(diamond.x, diamond.y, diamond.size * 0.4, 0, Math.PI * 2); // Make core smaller + ctx.fillStyle = `rgba(255, 223, 0, ${0.8 + pulseIntensity * 0.2})`; // Brighter gold + ctx.fill(); + + // Shine effect + ctx.beginPath(); + ctx.arc( + diamond.x - diamond.size * 0.2, + diamond.y - diamond.size * 0.2, + diamond.size * 0.15, + 0, Math.PI * 2 + ); + ctx.fillStyle = `rgba(255, 255, 255, ${0.6 + pulseIntensity * 0.4})`; + ctx.fill(); + + ctx.restore(); + }); }; \ No newline at end of file |