271 lines
8.9 KiB
JavaScript
Executable File
271 lines
8.9 KiB
JavaScript
Executable File
/**
|
|
* 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;
|