Files
siege-protocol/js/renderer-hud.js
T
44r0n7 622a9fd170 Initial commit: Siege Protocol
Inverted tower-defense browser game — deploy enemies yourself, tower auto-kills them, pocket credits, upgrade weapons. HTML + Canvas + vanilla JS, no build step.

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-06-16 11:36:53 -04:00

233 lines
9.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ═══ renderer-hud.js ═══
// ============================================================
// RENDERER HUD — top HUD, warnings
// ============================================================
// ============================================================
// RENDERER HUD — top HUD, warnings
// ============================================================
// ── HUD ───────────────────────────────────────────────────────
const HUD_H = 64;
// Right-section column centers (canvas x coords, 1600px wide)
const _HUD_KILLS_CX = 1542;
const _HUD_SCORE_CX = 1468;
const _HUD_DIV1_X = 1424;
const _HUD_RSV_CX = 1318;
const _HUD_DIV2_X = 1204;
const _HUD_CRED_CX = 1118;
function drawHUD() {
const W = canvas.width;
const cheapest = cheapestEnemyCost();
// ── Background strip ─────────────────────────────────────────
ctx.fillStyle = 'rgba(6,14,22,0.95)';
ctx.fillRect(0, 0, W, HUD_H);
ctx.strokeStyle = '#122030';
ctx.lineWidth = 1;
ctx.beginPath(); ctx.moveTo(0, HUD_H); ctx.lineTo(W, HUD_H); ctx.stroke();
ctx.save();
// ── LEFT: title ──────────────────────────────────────────────
ctx.font = '900 15px Orbitron, "Share Tech Mono", monospace';
ctx.letterSpacing = '5px';
ctx.textAlign = 'left';
ctx.textBaseline = 'middle';
ctx.fillStyle = '#00d4ff';
ctx.shadowColor = 'rgba(0,212,255,0.35)';
ctx.shadowBlur = 14;
ctx.fillText('SIEGE PROTOCOL', 20, HUD_H / 2);
ctx.shadowBlur = 0;
const titleW = ctx.measureText('SIEGE PROTOCOL').width;
ctx.letterSpacing = '0px';
// ── LEFT: shop button ────────────────────────────────────────
const SBX = 20 + titleW + 16;
const SBY = 14;
const SBH = 36;
ctx.font = '11px Orbitron, "Share Tech Mono", monospace';
const shopLabel = '⚙ SHOP [Space]';
const SBW = ctx.measureText(shopLabel).width + 32;
const shopHov = isHovered(SBX, SBY, SBW, SBH);
ctx.fillStyle = shopHov ? '#00d4ff' : 'transparent';
ctx.strokeStyle = '#00d4ff';
ctx.lineWidth = 1;
ctx.beginPath(); ctx.rect(SBX, SBY, SBW, SBH); ctx.fill(); ctx.stroke();
if (shopHov) { ctx.shadowColor = '#00d4ff'; ctx.shadowBlur = 16; }
ctx.fillStyle = shopHov ? '#000000' : '#00d4ff';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(shopLabel, SBX + SBW / 2, SBY + SBH / 2);
ctx.shadowBlur = 0;
addHitRegion(SBX, SBY, SBW, SBH, () => G.shopOpen ? closeShop() : openShop());
// ── CENTER: HP bar ───────────────────────────────────────────
const CX = W / 2;
const hpPct = G.tower.hp / G.tower.maxHp;
const hpColor = hpPct > 0.5 ? '#00ff88' : hpPct > 0.25 ? '#ffd700' : '#ff3355';
ctx.font = '11px "Share Tech Mono", monospace';
ctx.textAlign = 'center';
ctx.textBaseline = 'top';
ctx.fillStyle = '#3a6080';
ctx.fillText('TOWER INTEGRITY', CX, 7);
const BAR_X = CX - 180, BAR_Y = 24, BAR_W = 360, BAR_H = 14;
ctx.fillStyle = '#0a1520';
ctx.strokeStyle = '#1a3048';
ctx.lineWidth = 1;
ctx.beginPath(); ctx.rect(BAR_X, BAR_Y, BAR_W, BAR_H); ctx.fill(); ctx.stroke();
if (hpPct > 0) {
const grad = ctx.createLinearGradient(BAR_X, 0, BAR_X + BAR_W, 0);
if (hpPct > 0.5) { grad.addColorStop(0, '#00ff88'); grad.addColorStop(1, '#00ffaa'); }
else if (hpPct > 0.25) { grad.addColorStop(0, '#ffd700'); grad.addColorStop(1, '#ffaa00'); }
else { grad.addColorStop(0, '#ff3355'); grad.addColorStop(1, '#ff0033'); }
ctx.fillStyle = grad;
ctx.shadowColor = hpColor;
ctx.shadowBlur = 8;
ctx.fillRect(BAR_X, BAR_Y, BAR_W * Math.max(0, hpPct), BAR_H);
ctx.shadowBlur = 0;
}
ctx.font = '11px Orbitron, "Share Tech Mono", monospace';
ctx.textAlign = 'center';
ctx.textBaseline = 'bottom';
ctx.fillStyle = hpColor;
ctx.fillText(`${G.tower.hp} / ${G.tower.maxHp} HP`, CX, HUD_H - 6);
// ── RIGHT helpers ────────────────────────────────────────────
function hudStat(label, value, valColor, cx) {
ctx.textAlign = 'center';
ctx.font = '11px "Share Tech Mono", monospace';
ctx.textBaseline = 'top';
ctx.fillStyle = '#3a6080';
ctx.fillText(label, cx, 7);
ctx.font = '700 18px Orbitron, "Share Tech Mono", monospace';
ctx.textBaseline = 'bottom';
ctx.fillStyle = valColor;
ctx.fillText(String(value), cx, HUD_H - 6);
}
function hudDivider(x) {
ctx.strokeStyle = '#1a3048';
ctx.lineWidth = 1;
ctx.beginPath(); ctx.moveTo(x, 12); ctx.lineTo(x, HUD_H - 12); ctx.stroke();
}
// KILLS / SCORE
hudStat('KILLS', G.totalKills, '#00d4ff', _HUD_KILLS_CX);
hudStat('SCORE', G.score, '#b8d8e8', _HUD_SCORE_CX);
hudDivider(_HUD_DIV1_X);
// ── RESERVE ──────────────────────────────────────────────────
const RSV = _HUD_RSV_CX;
ctx.font = '11px "Share Tech Mono", monospace';
ctx.textAlign = 'center';
ctx.textBaseline = 'top';
ctx.fillStyle = '#3a6080';
ctx.fillText('RESERVE', RSV, 7);
const RBW = 22, RBH = 22, RBY = 22;
const rDownX = RSV - 49;
const rUpX = RSV + 27;
const rDownDis = G.creditReserve <= cheapest;
const rUpDis = G.creditReserve >= G.credits;
function reserveBtn(bx, label, disabled) {
const hov = isHovered(bx, RBY, RBW, RBH);
ctx.strokeStyle = disabled ? '#aaaaff22' : (hov ? '#aaaaff' : '#aaaaff44');
ctx.fillStyle = (hov && !disabled) ? '#aaaaff22' : 'transparent';
ctx.lineWidth = 1;
ctx.beginPath(); ctx.rect(bx, RBY, RBW, RBH); ctx.fill(); ctx.stroke();
ctx.font = '13px Orbitron, monospace';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillStyle = disabled ? '#aaaaff40' : '#aaaaff';
ctx.fillText(label, bx + RBW / 2, RBY + RBH / 2);
}
reserveBtn(rDownX, '', rDownDis);
reserveBtn(rUpX, '+', rUpDis);
ctx.font = '700 14px Orbitron, "Share Tech Mono", monospace';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillStyle = '#aaaaff';
ctx.fillText(G.creditReserve + '¢', RSV, RBY + RBH / 2);
// Spendable line (two-color)
const sp = Math.max(0, G.credits - G.creditReserve);
const pfx = 'spendable: ';
const spv = sp + '¢';
ctx.font = '10px "Share Tech Mono", monospace';
const pfxW = ctx.measureText(pfx).width;
const spvW = ctx.measureText(spv).width;
const spX = RSV - (pfxW + spvW) / 2;
ctx.textAlign = 'left';
ctx.textBaseline = 'bottom';
ctx.fillStyle = '#3a6080';
ctx.fillText(pfx, spX, HUD_H - 6);
ctx.fillStyle = sp <= 0 ? '#ff3355' : '#ffd700';
ctx.fillText(spv, spX + pfxW, HUD_H - 6);
if (!rDownDis) addHitRegion(rDownX, RBY, RBW, RBH, () => adjustReserve(-10));
if (!rUpDis) addHitRegion(rUpX, RBY, RBW, RBH, () => adjustReserve(10));
hudDivider(_HUD_DIV2_X);
// ── CREDITS ──────────────────────────────────────────────────
const CRED = _HUD_CRED_CX;
ctx.font = '11px "Share Tech Mono", monospace';
ctx.textAlign = 'center';
ctx.textBaseline = 'top';
ctx.fillStyle = '#3a6080';
ctx.fillText('CREDITS', CRED, 7);
const credColor = G.credits === 0 ? '#ff3355' : G.credits < 50 ? '#ff8844' : '#ffd700';
ctx.font = '900 26px Orbitron, "Share Tech Mono", monospace';
ctx.textAlign = 'center';
ctx.textBaseline = 'bottom';
ctx.fillStyle = credColor;
ctx.shadowColor = 'rgba(255,215,0,0.6)';
ctx.shadowBlur = 16;
ctx.fillText(G.credits + '¢', CRED, HUD_H - 6);
ctx.shadowBlur = 0;
ctx.restore();
}
// ── BROKE WARNING ─────────────────────────────────────────────
function drawBrokeWarning() {
const cheapest = cheapestEnemyCost();
if (G.credits >= cheapest || G.gameOver) return;
const W = canvas.width, H = canvas.height;
const msg = '⚠ LOW CREDITS — if enemies breach you may go bankrupt';
ctx.save();
ctx.font = '11px Orbitron, "Share Tech Mono", monospace';
const msgW = ctx.measureText(msg).width;
const BW = msgW + 44, BH = 30;
const BX = (W - BW) / 2;
const BY = H - 90;
const alpha = 0.35 + 0.65 * (0.5 + 0.5 * Math.sin(G.frame * 0.08));
ctx.globalAlpha = alpha;
ctx.fillStyle = 'rgba(4,10,16,0.92)';
ctx.strokeStyle = '#ffd700';
ctx.lineWidth = 1;
ctx.beginPath(); ctx.rect(BX, BY, BW, BH); ctx.fill(); ctx.stroke();
ctx.fillStyle = '#ffd700';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(msg, W / 2, BY + BH / 2);
ctx.restore();
}