Files
ai-qpid-pvz/game/doom/js/hud.js
T

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;