'use strict'; // === ENEMY DRAWING FUNCTIONS === function renderEnemies() { for (const enemy of gameState.enemies) { drawEnemy(enemy); } } function drawEnemy(enemy) { const px = enemy.x * TILE; const py = enemy.y * TILE; const isHit = enemy.hitTimer > 0; const isStunned = enemy.stunned; const isFrozen = enemy.frozen; const isOnFire = enemy.onFire; ctx.save(); if (isHit) { ctx.globalAlpha = 0.5 + Math.sin(Date.now() * 0.02) * 0.5; } if (isStunned) { ctx.globalAlpha = 0.6; } if (isFrozen) { ctx.filter = 'hue-rotate(180deg) brightness(1.2)'; } if (isOnFire) { ctx.filter = 'saturate(2) hue-rotate(-30deg)'; } // Draw based on enemy type const def = ENEMY_TYPES.find(e => e.id === enemy.id || e.name === enemy.name); if (def && def.drawFunction && typeof window[def.drawFunction] === 'function') { window[def.drawFunction](ctx, px, py, enemy, isHit); } else { // Default goblin-style drawing const bodyColor = isHit ? '#FFFFFF' : (enemy.color || COLORS.goblin); ctx.fillStyle = bodyColor; ctx.beginPath(); ctx.roundRect(px + 3, py + 4, TILE - 6, TILE - 8, 3); ctx.fill(); ctx.fillStyle = enemy.colorLight || COLORS.goblinLight; ctx.beginPath(); ctx.roundRect(px + 4, py + 5, (TILE - 6) * 0.6, (TILE - 8) * 0.4, 2); ctx.fill(); ctx.fillStyle = enemy.eyeColor || COLORS.goblinEye; ctx.beginPath(); ctx.arc(px + TILE / 2 - 4, py + 10, 2.5, 0, Math.PI * 2); ctx.fill(); ctx.beginPath(); ctx.arc(px + TILE / 2 + 4, py + 10, 2.5, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = '#000'; ctx.beginPath(); ctx.arc(px + TILE / 2 - 4, py + 10, 1.2, 0, Math.PI * 2); ctx.fill(); ctx.beginPath(); ctx.arc(px + TILE / 2 + 4, py + 10, 1.2, 0, Math.PI * 2); ctx.fill(); } // HP bar const barW = TILE - 4; const hpRatio = enemy.hp / enemy.maxHp; ctx.fillStyle = '#333333'; ctx.fillRect(px + 2, py - 4, barW, 4); ctx.fillStyle = hpRatio > 0.5 ? '#00FF00' : hpRatio > 0.25 ? '#FFAA00' : '#FF0000'; ctx.fillRect(px + 2, py - 4, barW * hpRatio, 4); // Stun indicator if (isStunned) { ctx.fillStyle = '#FFFF00'; ctx.font = '8px monospace'; ctx.textAlign = 'center'; ctx.fillText('?', px + TILE / 2, py - 6); ctx.textAlign = 'left'; } // Freeze indicator if (isFrozen) { ctx.fillStyle = '#00BFFF'; ctx.font = '8px monospace'; ctx.textAlign = 'center'; ctx.fillText('❄', px + TILE / 2, py - 6); ctx.textAlign = 'left'; } ctx.restore(); } // Individual enemy draw functions function drawSlime(ctx, px, py, enemy, isHit) { const bounce = Math.sin(Date.now() * 0.008 + enemy.x * 2) * 2; const color = isHit ? '#FFFFFF' : (enemy.color || '#00CC44'); ctx.fillStyle = color; ctx.beginPath(); ctx.ellipse(px + TILE / 2, py + TILE / 2 + bounce, TILE * 0.35, TILE * 0.25, 0, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = isHit ? '#FFFFFF' : '#00FF66'; ctx.beginPath(); ctx.ellipse(px + TILE / 2, py + TILE / 2 + bounce - 2, TILE * 0.15, TILE * 0.1, 0, 0, Math.PI * 2); ctx.fill(); // Eyes ctx.fillStyle = '#000'; ctx.beginPath(); ctx.arc(px + TILE / 2 - 3, py + TILE / 2 + bounce, 1.5, 0, Math.PI * 2); ctx.fill(); ctx.beginPath(); ctx.arc(px + TILE / 2 + 3, py + TILE / 2 + bounce, 1.5, 0, Math.PI * 2); ctx.fill(); } function drawKeese(ctx, px, py, enemy, isHit) { const flap = Math.sin(Date.now() * 0.015 + enemy.x * 3) * 3; const color = isHit ? '#FFFFFF' : (enemy.color || '#4A3040'); // Body ctx.fillStyle = color; ctx.beginPath(); ctx.ellipse(px + TILE / 2, py + TILE / 2 + flap, TILE * 0.2, TILE * 0.15, 0, 0, Math.PI * 2); ctx.fill(); // Wings ctx.fillStyle = isHit ? '#FFFFFF' : '#3A2030'; ctx.beginPath(); ctx.ellipse(px + TILE / 2 - 8, py + TILE / 2 + flap, 8, 4, -0.3, 0, Math.PI * 2); ctx.fill(); ctx.beginPath(); ctx.ellipse(px + TILE / 2 + 8, py + TILE / 2 + flap, 8, 4, 0.3, 0, Math.PI * 2); ctx.fill(); // Eyes ctx.fillStyle = enemy.eyeColor || '#FF0000'; ctx.beginPath(); ctx.arc(px + TILE / 2 - 2, py + TILE / 2 + flap, 1.5, 0, Math.PI * 2); ctx.fill(); ctx.beginPath(); ctx.arc(px + TILE / 2 + 2, py + TILE / 2 + flap, 1.5, 0, Math.PI * 2); ctx.fill(); } function drawOctorok(ctx, px, py, enemy, isHit) { const color = isHit ? '#FFFFFF' : (enemy.color || '#8B008B'); // Body ctx.fillStyle = color; ctx.beginPath(); ctx.arc(px + TILE / 2, py + TILE / 2, TILE * 0.35, 0, Math.PI * 2); ctx.fill(); // Legs ctx.fillStyle = isHit ? '#FFFFFF' : (enemy.colorLight || '#A020A0'); for (let i = 0; i < 8; i++) { const angle = (i / 8) * Math.PI * 2 + Date.now() * 0.001; const lx = px + TILE / 2 + Math.cos(angle) * TILE * 0.4; const ly = py + TILE / 2 + Math.sin(angle) * TILE * 0.4; ctx.fillRect(lx - 1, ly - 1, 3, 3); } // Eyes ctx.fillStyle = enemy.eyeColor || '#FFFF00'; ctx.beginPath(); ctx.arc(px + TILE / 2 - 4, py + TILE / 2 - 2, 2.5, 0, Math.PI * 2); ctx.fill(); ctx.beginPath(); ctx.arc(px + TILE / 2 + 4, py + TILE / 2 - 2, 2.5, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = '#000'; ctx.beginPath(); ctx.arc(px + TILE / 2 - 4, py + TILE / 2 - 2, 1, 0, Math.PI * 2); ctx.fill(); ctx.beginPath(); ctx.arc(px + TILE / 2 + 4, py + TILE / 2 - 2, 1, 0, Math.PI * 2); ctx.fill(); } function drawMoblin(ctx, px, py, enemy, isHit) { const color = isHit ? '#FFFFFF' : (enemy.color || '#2F4F2F'); // Body (larger than goblin) ctx.fillStyle = color; ctx.beginPath(); ctx.roundRect(px + 2, py + 2, TILE - 4, TILE - 4, 4); ctx.fill(); ctx.fillStyle = enemy.colorLight || '#3F5F3F'; ctx.beginPath(); ctx.roundRect(px + 3, py + 3, (TILE - 4) * 0.5, (TILE - 4) * 0.4, 2); ctx.fill(); // Snout ctx.fillStyle = isHit ? '#FFFFFF' : '#5F3F1F'; ctx.fillRect(px + TILE / 2 - 3, py + 14, 6, 6); // Eyes ctx.fillStyle = enemy.eyeColor || '#FF4400'; ctx.beginPath(); ctx.arc(px + TILE / 2 - 5, py + 8, 2.5, 0, Math.PI * 2); ctx.fill(); ctx.beginPath(); ctx.arc(px + TILE / 2 + 5, py + 8, 2.5, 0, Math.PI * 2); ctx.fill(); // Spear ctx.fillStyle = '#8B4513'; ctx.fillRect(px + TILE - 4, py + 6, 2, TILE - 10); ctx.fillStyle = '#808080'; ctx.fillRect(px + TILE - 6, py + 4, 6, 4); } function drawStalfos(ctx, px, py, enemy, isHit) { const color = isHit ? '#FFFFFF' : (enemy.color || '#E8E8E8'); // Skeleton body ctx.fillStyle = color; ctx.fillRect(px + TILE / 2 - 2, py + 4, 4, TILE - 10); // Skull ctx.beginPath(); ctx.arc(px + TILE / 2, py + 6, 5, 0, Math.PI * 2); ctx.fill(); // Eye sockets ctx.fillStyle = '#000'; ctx.beginPath(); ctx.arc(px + TILE / 2 - 2, py + 5, 1.5, 0, Math.PI * 2); ctx.fill(); ctx.beginPath(); ctx.arc(px + TILE / 2 + 2, py + 5, 1.5, 0, Math.PI * 2); ctx.fill(); // Eye glow ctx.fillStyle = enemy.eyeColor || '#FF0000'; ctx.beginPath(); ctx.arc(px + TILE / 2 - 2, py + 5, 0.8, 0, Math.PI * 2); ctx.fill(); ctx.beginPath(); ctx.arc(px + TILE / 2 + 2, py + 5, 0.8, 0, Math.PI * 2); ctx.fill(); // Arms ctx.fillStyle = color; ctx.fillRect(px + 2, py + 10, TILE - 4, 2); // Sword ctx.fillStyle = '#C0C0C0'; ctx.fillRect(px + TILE - 4, py + 4, 2, 12); } function drawGoriya(ctx, px, py, enemy, isHit) { const color = isHit ? '#FFFFFF' : (enemy.color || '#4169AA'); // Body ctx.fillStyle = color; ctx.beginPath(); ctx.roundRect(px + 3, py + 4, TILE - 6, TILE - 8, 3); ctx.fill(); // Mask ctx.fillStyle = enemy.colorLight || '#5F9EA0'; ctx.fillRect(px + TILE / 2 - 5, py + 6, 10, 8); // Eyes ctx.fillStyle = enemy.eyeColor || '#FFFF00'; ctx.fillRect(px + TILE / 2 - 3, py + 8, 2, 2); ctx.fillRect(px + TILE / 2 + 1, py + 8, 2, 2); // Boomerang ctx.fillStyle = '#DAA520'; ctx.beginPath(); ctx.arc(px + TILE - 2, py + 10, 4, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = '#B8860B'; ctx.fillRect(px + TILE - 4, py + 8, 2, 6); } function drawArmos(ctx, px, py, enemy, isHit) { const color = isHit ? '#FFFFFF' : (enemy.color || '#DAA520'); const activated = enemy.activated || false; // Statue body ctx.fillStyle = activated ? '#FFD700' : color; ctx.fillRect(px + 4, py + 2, TILE - 8, TILE - 4); // Triangle eyes ctx.fillStyle = activated ? '#FF0000' : '#B8860B'; ctx.beginPath(); ctx.moveTo(px + TILE / 2 - 4, py + 8); ctx.lineTo(px + TILE / 2 - 2, py + 12); ctx.lineTo(px + TILE / 2 - 6, py + 12); ctx.closePath(); ctx.fill(); ctx.beginPath(); ctx.moveTo(px + TILE / 2 + 4, py + 8); ctx.lineTo(px + TILE / 2 + 2, py + 12); ctx.lineTo(px + TILE / 2 + 6, py + 12); ctx.closePath(); ctx.fill(); // Sword ctx.fillStyle = '#C0C0C0'; ctx.fillRect(px + TILE - 2, py + 4, 2, 10); } function drawLynel(ctx, px, py, enemy, isHit) { const color = isHit ? '#FFFFFF' : (enemy.color || '#8B0000'); // Large body ctx.fillStyle = color; ctx.beginPath(); ctx.roundRect(px, py + 2, TILE, TILE - 4, 4); ctx.fill(); // Mane ctx.fillStyle = enemy.colorLight || '#CC2222'; ctx.fillRect(px + 2, py + 2, TILE - 4, 6); // Eyes ctx.fillStyle = enemy.eyeColor || '#FFD700'; ctx.beginPath(); ctx.arc(px + TILE / 2 - 5, py + 8, 3, 0, Math.PI * 2); ctx.fill(); ctx.beginPath(); ctx.arc(px + TILE / 2 + 5, py + 8, 3, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = '#000'; ctx.beginPath(); ctx.arc(px + TILE / 2 - 5, py + 8, 1.5, 0, Math.PI * 2); ctx.fill(); ctx.beginPath(); ctx.arc(px + TILE / 2 + 5, py + 8, 1.5, 0, Math.PI * 2); ctx.fill(); } // Register draw functions const ENEMY_DRAW_FUNCTIONS = { slime: drawSlime, keese: drawKeese, octorok: drawOctorok, moblin: drawMoblin, stalfos: drawStalfos, goriya: drawGoriya, armos: drawArmos, lynel: drawLynel, }; if (typeof module !== 'undefined' && module.exports) { module.exports = { renderEnemies, drawEnemy, drawSlime, drawKeese, drawOctorok, drawMoblin, drawStalfos, drawGoriya, drawArmos, drawLynel }; }