about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorelioat <elioat@tilde.institute>2024-12-18 15:52:05 -0500
committerelioat <elioat@tilde.institute>2024-12-18 15:52:05 -0500
commitee38675b78dc7f6a49af2a7004822565086df476 (patch)
tree0c33c4a3d675a5dda7d54e2943a6c76b622bc399
parentf66730b7aeb9494c266260df7eeba8a8e26beb4e (diff)
downloadtour-ee38675b78dc7f6a49af2a7004822565086df476.tar.gz
*
-rw-r--r--html/plains/enemies.js87
-rw-r--r--html/plains/game.js193
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