zelda-dungeon/src/rendering/draw-bosses.js
2026-07-03 01:08:48 +00:00

261 lines
6.6 KiB
JavaScript

'use strict';
// === BOSS DRAWING ===
function renderBoss(boss) {
if (!boss) return;
const px = boss.x * TILE;
const py = boss.y * TILE;
const isHit = boss.hitTimer > 0;
ctx.save();
if (isHit) {
ctx.globalAlpha = 0.5 + Math.sin(Date.now() * 0.02) * 0.5;
}
// Boss is drawn larger (2x2 tiles)
const def = BOSS_TYPES.find(b => b.id === boss.id || b.name === boss.name);
if (def && def.drawFunction && typeof window[def.drawFunction] === 'function') {
window[def.drawFunction](ctx, px, py, boss, isHit);
} else {
drawGenericBoss(ctx, px, py, boss, isHit);
}
// Boss HP bar (wider, at top of screen)
const barW = 200;
const barH = 8;
const barX = (CANVAS_W - barW) / 2;
const barY = 4;
const hpRatio = boss.hp / boss.maxHp;
ctx.fillStyle = '#333333';
ctx.fillRect(barX, barY, barW, barH);
ctx.fillStyle = hpRatio > 0.5 ? '#00FF00' : hpRatio > 0.25 ? '#FFAA00' : '#FF0000';
ctx.fillRect(barX, barY, barW * hpRatio, barH);
ctx.strokeStyle = '#555';
ctx.lineWidth = 1;
ctx.strokeRect(barX, barY, barW, barH);
// Boss name
ctx.fillStyle = '#FFD700';
ctx.font = 'bold 10px monospace';
ctx.textAlign = 'center';
ctx.fillText(boss.name, CANVAS_W / 2, barY + barH + 10);
ctx.textAlign = 'left';
// Phase indicator
if (boss.phases && boss.phase > 0) {
ctx.fillStyle = '#FF4444';
ctx.font = '8px monospace';
ctx.textAlign = 'center';
ctx.fillText(`Phase ${boss.phase + 1}`, CANVAS_W / 2, barY + barH + 20);
ctx.textAlign = 'left';
}
ctx.restore();
}
function drawGenericBoss(ctx, px, py, boss, isHit) {
const size = TILE * 2;
const color = isHit ? '#FFFFFF' : (boss.color || '#8B0000');
// Large body
ctx.fillStyle = color;
ctx.beginPath();
ctx.roundRect(px - TILE / 2, py - TILE / 2, size, size, 6);
ctx.fill();
// Details
ctx.fillStyle = boss.colorLight || '#CC2222';
ctx.beginPath();
ctx.roundRect(px - TILE / 2 + 4, py - TILE / 2 + 4, size - 8, size * 0.4, 4);
ctx.fill();
// Eyes
ctx.fillStyle = boss.eyeColor || '#FFD700';
ctx.beginPath();
ctx.arc(px - 6, py - 4, 4, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.arc(px + 6, py - 4, 4, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#000';
ctx.beginPath();
ctx.arc(px - 6, py - 4, 2, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.arc(px + 6, py - 4, 2, 0, Math.PI * 2);
ctx.fill();
}
// Specific boss draw functions
function drawGleeok(ctx, px, py, boss, isHit) {
const size = TILE * 2;
const color = isHit ? '#FFFFFF' : (boss.color || '#2F4F4F');
const heads = boss.heads || 3;
const aliveHeads = boss.aliveHeads || heads;
// Body
ctx.fillStyle = color;
ctx.beginPath();
ctx.ellipse(px, py, size * 0.45, size * 0.35, 0, 0, Math.PI * 2);
ctx.fill();
// Scales pattern
ctx.fillStyle = boss.colorLight || '#3F5F5F';
for (let i = 0; i < 6; i++) {
const angle = (i / 6) * Math.PI * 2;
ctx.beginPath();
ctx.ellipse(px + Math.cos(angle) * 12, py + Math.sin(angle) * 8, 4, 3, angle, 0, Math.PI * 2);
ctx.fill();
}
// Heads
for (let i = 0; i < heads; i++) {
const angle = (i / heads) * Math.PI * 2 - Math.PI / 2;
const hx = px + Math.cos(angle) * 24;
const hy = py + Math.sin(angle) * 18 - 8;
const alive = i < aliveHeads;
if (!alive) {
ctx.fillStyle = '#333';
ctx.beginPath();
ctx.arc(hx, hy, 5, 0, Math.PI * 2);
ctx.fill();
continue;
}
const headBob = Math.sin(Date.now() * 0.005 + i) * 2;
ctx.fillStyle = boss.headColor || '#006400';
ctx.beginPath();
ctx.ellipse(hx, hy + headBob, 6, 5, 0, 0, Math.PI * 2);
ctx.fill();
// Eyes
ctx.fillStyle = '#FFD700';
ctx.beginPath();
ctx.arc(hx - 2, hy + headBob, 1.5, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.arc(hx + 2, hy + headBob, 1.5, 0, Math.PI * 2);
ctx.fill();
// Fire breath
if (boss.attacking && i < aliveHeads) {
ctx.fillStyle = '#FF4500';
const dirX = Math.cos(angle);
const dirY = Math.sin(angle);
ctx.beginPath();
ctx.moveTo(hx + dirX * 6, hy + headBob + dirY * 6);
ctx.lineTo(hx + dirX * 14, hy + headBob + dirY * 14);
ctx.lineTo(hx + dirX * 6 + dirY * 3, hy + headBob + dirY * 6 - dirX * 3);
ctx.closePath();
ctx.fill();
}
}
}
function drawDarknut(ctx, px, py, boss, isHit) {
const size = TILE * 2;
const color = isHit ? '#FFFFFF' : (boss.color || '#708090');
const armorBroken = boss.armorBroken || 0;
// Armor body
ctx.fillStyle = color;
ctx.fillRect(px - 10, py - 12, 20, 28);
// Helmet
ctx.fillStyle = isHit ? '#FFFFFF' : '#606060';
ctx.beginPath();
ctx.arc(px, py - 8, 8, 0, Math.PI * 2);
ctx.fill();
// Visor
ctx.fillStyle = boss.eyeColor || '#FF0000';
ctx.fillRect(px - 5, py - 10, 10, 3);
// Armor plates (break with phases)
ctx.fillStyle = isHit ? '#FFFFFF' : '#808080';
if (armorBroken < 1) {
ctx.fillRect(px - 12, py - 4, 24, 6); // Chest plate
}
if (armorBroken < 2) {
ctx.fillRect(px - 12, py + 8, 24, 6); // Waist plate
}
// Weapon (two-handed)
ctx.fillStyle = '#C0C0C0';
ctx.fillRect(px + 12, py - 16, 3, 32);
ctx.fillStyle = '#8B4513';
ctx.fillRect(px + 10, py + 6, 7, 4);
// Speed increases with armor broken
if (armorBroken > 0) {
ctx.fillStyle = 'rgba(255,0,0,0.2)';
ctx.fillRect(px - 14, py - 14, 28, 32);
}
}
function drawWizzrobe(ctx, px, py, boss, isHit) {
const robeColor = isHit ? '#FFFFFF' : (boss.color || '#4B0082');
const teleporting = boss.teleporting || false;
if (teleporting) {
ctx.globalAlpha = 0.3 + Math.sin(Date.now() * 0.05) * 0.3;
}
// Robe
ctx.fillStyle = robeColor;
ctx.beginPath();
ctx.moveTo(px, py - 14);
ctx.lineTo(px - 10, py + 16);
ctx.lineTo(px + 10, py + 16);
ctx.closePath();
ctx.fill();
// Hood
ctx.beginPath();
ctx.arc(px, py - 10, 8, 0, Math.PI * 2);
ctx.fill();
// Face opening
ctx.fillStyle = '#FFD700';
ctx.beginPath();
ctx.arc(px, py - 10, 4, 0, Math.PI * 2);
ctx.fill();
// Eyes
ctx.fillStyle = '#FF00FF';
ctx.beginPath();
ctx.arc(px - 2, py - 11, 1.5, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.arc(px + 2, py - 11, 1.5, 0, Math.PI * 2);
ctx.fill();
// Magic orb
if (boss.castSpell) {
ctx.fillStyle = '#FF00FF';
ctx.globalAlpha = 0.7;
ctx.beginPath();
ctx.arc(px + 14, py - 4, 4, 0, Math.PI * 2);
ctx.fill();
ctx.globalAlpha = 1;
}
}
// Register boss draw functions
const BOSS_DRAW_FUNCTIONS = {
gleeok: drawGleeok,
darknut: drawDarknut,
wizzrobe: drawWizzrobe,
generic: drawGenericBoss,
};
if (typeof module !== 'undefined' && module.exports) {
module.exports = { renderBoss, drawGenericBoss, drawGleeok, drawDarknut, drawWizzrobe };
}