261 lines
6.6 KiB
JavaScript
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 };
|
|
} |