/** * DOOM 3D - Authentic Retro Status Bar & HUD * LED segmented numbers, animated Doomguy face canvas reacting to HP/pain/godmode, * weapon slot matrix, keycard inventory, ticker notifications, and damage screen flashes. */ class DoomHUD { constructor(player) { this.player = player; // Container elements this.hudContainer = document.getElementById('doom-hud'); this.damageOverlay = document.getElementById('damage-flash'); this.ticker = document.getElementById('hud-ticker'); this.faceCanvas = document.getElementById('doomguy-face'); this.faceCtx = this.faceCanvas ? this.faceCanvas.getContext('2d') : null; // Doomguy Face animation states this.faceLookDir = 0; // -1 = left, 0 = forward, 1 = right this.faceTimer = 0; this.painTimer = 0; this.grinTimer = 0; // Notification ticker this.tickerTimer = 0; // Flash states this.flashAlpha = 0; this.flashColor = 'rgba(255, 0, 0, '; this.init(); } init() { if (this.faceCanvas) { this.faceCanvas.width = 64; this.faceCanvas.height = 72; } } showNotification(text) { if (!this.ticker) return; this.ticker.innerText = text.toUpperCase(); this.ticker.style.opacity = '1'; this.tickerTimer = 3.5; } triggerDamageFlash(color = 'red') { this.flashAlpha = 0.55; this.painTimer = 0.5; if (color === 'green') { this.flashColor = 'rgba(50, 255, 0, '; } else if (color === 'yellow') { this.flashColor = 'rgba(255, 220, 0, '; } else { this.flashColor = 'rgba(220, 0, 0, '; } } triggerGrin() { this.grinTimer = 1.2; } showGameOver() { const overlay = document.getElementById('game-over-modal'); if (overlay) overlay.classList.remove('hidden'); } hideGameOver() { const overlay = document.getElementById('game-over-modal'); if (overlay) overlay.classList.add('hidden'); } // ---------------------------------------------------- // DOOMGUY FACE RENDERING (Dynamic Procedural Pixel Art) // ---------------------------------------------------- renderDoomguyFace(delta) { if (!this.faceCtx) return; const ctx = this.faceCtx; ctx.clearRect(0, 0, 64, 72); const hp = this.player.health; const isDead = this.player.isDead; const godMode = this.player.godMode; // Update look direction timer this.faceTimer += delta; if (this.faceTimer > 1.8) { this.faceTimer = 0; const r = Math.random(); this.faceLookDir = r < 0.3 ? -1 : (r > 0.7 ? 1 : 0); } if (this.painTimer > 0) this.painTimer -= delta; if (this.grinTimer > 0) this.grinTimer -= delta; // 1. Background / Skin Base let skinTone = '#d49b78'; if (hp <= 40) skinTone = '#c28766'; if (hp <= 20) skinTone = '#b07255'; if (isDead) skinTone = '#808a70'; // Pale zombie/corpse skin // Jaw / Head Contour ctx.fillStyle = skinTone; ctx.fillRect(12, 10, 40, 52); ctx.fillRect(16, 62, 32, 6); // Brown Hair & Crewcut ctx.fillStyle = '#4a2c18'; ctx.fillRect(10, 4, 44, 14); ctx.fillRect(8, 12, 6, 20); ctx.fillRect(50, 12, 6, 20); if (isDead) { // Dead Doomguy: X-eyes or blank white eyes + blood ctx.fillStyle = '#111'; ctx.fillRect(18, 30, 10, 4); ctx.fillRect(36, 30, 10, 4); ctx.fillStyle = '#aa0000'; ctx.fillRect(26, 44, 12, 18); ctx.fillRect(16, 52, 32, 6); return; } // 2. Eyes const eyeOffset = this.faceLookDir * 2; ctx.fillStyle = '#ffffff'; ctx.fillRect(18, 28, 10, 8); ctx.fillRect(36, 28, 10, 8); // Pupils if (godMode) { // Glowing Yellow God Mode Eyes ctx.fillStyle = '#ffff00'; ctx.fillRect(18, 28, 10, 8); ctx.fillRect(36, 28, 10, 8); } else { ctx.fillStyle = '#3a6688'; // Blue eyes ctx.fillRect(21 + eyeOffset, 30, 4, 4); ctx.fillRect(39 + eyeOffset, 30, 4, 4); } // Eyebrows ctx.fillStyle = '#3a2010'; if (this.painTimer > 0) { // Pain / Anguished eyebrows ctx.fillRect(16, 24, 12, 3); ctx.fillRect(36, 24, 12, 3); } else if (this.grinTimer > 0) { // Evil smirk eyebrows ctx.fillRect(16, 26, 12, 3); ctx.fillRect(36, 23, 12, 3); } else { // Normal stern eyebrows ctx.fillRect(16, 25, 12, 3); ctx.fillRect(36, 25, 12, 3); } // 3. Nose ctx.fillStyle = '#a66e4e'; ctx.fillRect(29, 36, 6, 12); if (hp <= 60) { // Bloody nose ctx.fillStyle = '#aa0000'; ctx.fillRect(29, 44, 6, 6); ctx.fillRect(31, 50, 3, 6); } // 4. Mouth / Expressions if (this.painTimer > 0) { // Ouch open screaming mouth ctx.fillStyle = '#111'; ctx.fillRect(24, 52, 16, 10); ctx.fillStyle = '#fff'; ctx.fillRect(26, 52, 12, 3); } else if (this.grinTimer > 0) { // Evil grin ctx.fillStyle = '#111'; ctx.fillRect(22, 53, 20, 6); ctx.fillStyle = '#fff'; ctx.fillRect(24, 54, 16, 3); } else if (hp <= 30) { // Bruised grimace ctx.fillStyle = '#441111'; ctx.fillRect(24, 54, 16, 4); } else { // Classic Doomguy smirk ctx.fillStyle = '#6b3e28'; ctx.fillRect(24, 54, 16, 4); } // 5. Blood wounds for low health if (hp <= 80) { ctx.fillStyle = '#aa0000'; ctx.fillRect(44, 18, 4, 8); // Forehead cut } if (hp <= 40) { ctx.fillStyle = '#aa0000'; ctx.fillRect(14, 38, 8, 10); // Cheek wound ctx.fillRect(40, 56, 6, 8); } if (hp <= 20) { ctx.fillStyle = '#880000'; ctx.fillRect(16, 28, 14, 10); // Black eye } } // ---------------------------------------------------- // UPDATE STATUS BAR METRICS // ---------------------------------------------------- update(delta) { // 1. Ticker fadeout if (this.tickerTimer > 0) { this.tickerTimer -= delta; if (this.tickerTimer <= 0 && this.ticker) { this.ticker.style.opacity = '0'; } } // 2. Damage Screen Flash fadeout if (this.flashAlpha > 0) { this.flashAlpha = Math.max(0, this.flashAlpha - delta * 2.0); if (this.damageOverlay) { this.damageOverlay.style.backgroundColor = `${this.flashColor}${this.flashAlpha})`; } } // 3. Render Animated Doomguy Face this.renderDoomguyFace(delta); // 4. Update HUD Numerical Values const currentWeapon = (window.game && window.game.weapons) ? window.game.weapons.current : null; const ammoCount = (currentWeapon && currentWeapon.ammoType) ? this.player.ammo[currentWeapon.ammoType] : '---'; const ammoElem = document.getElementById('stat-ammo'); const hpElem = document.getElementById('stat-health'); const armorElem = document.getElementById('stat-armor'); if (ammoElem) ammoElem.innerText = ammoCount; if (hpElem) hpElem.innerText = `${Math.max(0, this.player.health)}%`; if (armorElem) armorElem.innerText = `${this.player.armor}%`; // 5. Update ARMS Numbers (1-7) if (window.game && window.game.weapons) { const weapons = window.game.weapons; for (let i = 0; i < 7; i++) { const armSlot = document.getElementById(`arm-slot-${i + 1}`); if (armSlot) { const isUnlocked = weapons.unlocked[i]; const isCurrent = (weapons.currentIndex === i); armSlot.className = `arm-num ${isUnlocked ? 'active' : 'inactive'} ${isCurrent ? 'selected' : ''}`; } } } // 6. Update KEYS Indicators (Blue, Red, Yellow) const blueKeyElem = document.getElementById('key-blue'); const redKeyElem = document.getElementById('key-red'); const yellowKeyElem = document.getElementById('key-yellow'); if (blueKeyElem) blueKeyElem.className = `key-slot ${this.player.keysInventory.blue ? 'has-key' : ''}`; if (redKeyElem) redKeyElem.className = `key-slot ${this.player.keysInventory.red ? 'has-key' : ''}`; if (yellowKeyElem) yellowKeyElem.className = `key-slot ${this.player.keysInventory.yellow ? 'has-key' : ''}`; } } window.DoomHUD = DoomHUD;