Files
siege-protocol/js/renderer-overlays.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

263 lines
9.5 KiB
JavaScript

// ═══ renderer-overlays.js ═══
// ============================================================
// RENDERER OVERLAYS — game over, pause, mount drag UI
// ============================================================
// ── GAME OVER / BANKRUPT OVERLAY ─────────────────────────────
function drawGameOverPanel() {
const W = canvas.width, H = canvas.height;
// Game over takes over hit detection — clear HUD regions
clearHitRegions();
// Full-screen dark tint
ctx.fillStyle = 'rgba(0,0,0,0.88)';
ctx.fillRect(0, 0, W, H);
const BW = 460, BH = 270;
const BX = (W - BW) / 2, BY = (H - BH) / 2;
ctx.fillStyle = '#060e16';
ctx.strokeStyle = '#1a3048';
ctx.lineWidth = 1;
ctx.beginPath(); ctx.rect(BX, BY, BW, BH); ctx.fill(); ctx.stroke();
const isBankrupt = G.isBankrupt;
const titleColor = isBankrupt ? '#ffd700' : '#ff3355';
const title = isBankrupt ? 'BANKRUPT' : 'TOWER FALLEN';
const subText = isBankrupt
? 'You ran out of credits with no way to recover.'
: 'The defense has been breached.';
ctx.save();
ctx.textAlign = 'center';
ctx.textBaseline = 'top';
ctx.font = '900 30px Orbitron, "Share Tech Mono", monospace';
ctx.fillStyle = titleColor;
ctx.shadowColor = titleColor;
ctx.shadowBlur = 24;
ctx.fillText(title, W / 2, BY + 28);
ctx.shadowBlur = 0;
ctx.font = '14px Orbitron, "Share Tech Mono", monospace';
ctx.fillStyle = '#ffd700';
ctx.fillText(`Score: ${G.score} — Kills: ${G.totalKills}`, W / 2, BY + 78);
ctx.font = '12px "Share Tech Mono", monospace';
if (G._isNewBest) {
ctx.fillStyle = '#ffd700';
ctx.fillText('★ NEW BEST!', W / 2, BY + 108);
} else if (_best) {
ctx.fillStyle = '#3a6080';
ctx.fillText(`Best: ${_best.score}${_best.kills} kills`, W / 2, BY + 108);
}
ctx.font = '11px "Share Tech Mono", monospace';
ctx.fillStyle = '#3a6080';
ctx.fillText(subText, W / 2, BY + 134);
// RESTART button
const RBW = 220, RBH = 44;
const RBX = W / 2 - RBW / 2;
const RBY = BY + 192;
const restHov = isHovered(RBX, RBY, RBW, RBH);
ctx.fillStyle = restHov ? '#00d4ff' : 'transparent';
ctx.strokeStyle = '#00d4ff';
ctx.lineWidth = 1;
ctx.beginPath(); ctx.rect(RBX, RBY, RBW, RBH); ctx.fill(); ctx.stroke();
if (restHov) { ctx.shadowColor = 'rgba(0,212,255,0.35)'; ctx.shadowBlur = 20; }
ctx.font = '12px Orbitron, "Share Tech Mono", monospace';
ctx.textBaseline = 'middle';
ctx.fillStyle = restHov ? '#000000' : '#00d4ff';
ctx.fillText('RESTART MISSION', W / 2, RBY + RBH / 2);
ctx.shadowBlur = 0;
ctx.restore();
addHitRegion(RBX, RBY, RBW, RBH, restartGame);
}
// ── PAUSE OVERLAY ─────────────────────────────────────────────
function drawPauseOverlay() {
if (!document.body.classList.contains('paused')) return;
const W = canvas.width, H = canvas.height;
ctx.fillStyle = 'rgba(0,0,0,0.58)';
ctx.fillRect(0, 0, W, H);
const BW = 420, BH = 118;
const BX = (W - BW) / 2, BY = (H - BH) / 2;
ctx.save();
ctx.fillStyle = 'rgba(6,14,22,0.94)';
ctx.strokeStyle = '#1a3048';
ctx.lineWidth = 1;
ctx.beginPath(); ctx.rect(BX, BY, BW, BH); ctx.fill(); ctx.stroke();
ctx.shadowColor = 'rgba(0,212,255,0.18)';
ctx.shadowBlur = 34;
ctx.strokeRect(BX, BY, BW, BH);
ctx.shadowBlur = 0;
ctx.textAlign = 'center';
ctx.textBaseline = 'top';
ctx.font = '900 34px Orbitron, "Share Tech Mono", monospace';
ctx.fillStyle = '#ffd700';
ctx.shadowColor = '#ffd700';
ctx.shadowBlur = 22;
ctx.fillText('PAUSED', W / 2, BY + 18);
ctx.shadowBlur = 0;
ctx.font = '11px Orbitron, "Share Tech Mono", monospace';
ctx.fillStyle = '#3a6080';
ctx.fillText('Esc resumes · Space opens armory · I opens inventory', W / 2, BY + 78);
ctx.restore();
}
// ── MOUNT POINT TOOLTIP ───────────────────────────────────────
function _drawMountTooltip(mx, my, weapon, socketR) {
const def = getWeaponDef(weapon);
if (!def) return;
const TW = 168, TH = 58;
let tx = mx + socketR + 10;
let ty = my - TH / 2;
if (tx + TW > 1330) tx = mx - socketR - 10 - TW;
ty = Math.max(68, Math.min(900 - TH - 4, ty));
ctx.save();
ctx.fillStyle = '#050d18';
ctx.strokeStyle = '#00d4ff';
ctx.lineWidth = 1;
ctx.fillRect(tx, ty, TW, TH);
ctx.strokeRect(tx, ty, TW, TH);
ctx.font = '10px Orbitron, monospace';
ctx.letterSpacing = '1px';
ctx.textAlign = 'left';
ctx.textBaseline = 'top';
ctx.fillStyle = '#00d4ff';
ctx.fillText((def.icon || '') + ' ' + def.name, tx + 8, ty + 8);
ctx.letterSpacing = '0px';
const parts = [];
if (typeof def.damage === 'number') parts.push(`DMG ${def.damage}`);
if (typeof def.fireRate === 'number') parts.push(`RPM ${Math.round(3600 / def.fireRate)}`);
const el = weapon.elements?.[0] ? ELEMENTS[weapon.elements[0]] : null;
if (el) parts.push(el.icon + ' ' + el.name);
ctx.font = '9px "Share Tech Mono", monospace';
ctx.fillStyle = '#7aaabb';
ctx.fillText(parts.join(' '), tx + 8, ty + 26);
ctx.fillStyle = '#3a6080';
ctx.fillText('drag to move or bag', tx + 8, ty + 41);
ctx.restore();
}
// ── MOUNT POINT INTERACTION (drawn after overlays so it sits on top) ─────
function drawMountInteraction(cx, cy) {
if (G.shopOpen) return;
const invOpen = document.body.classList.contains('inventory-open');
const totalSlots = Math.max(1, G.tower.weaponSlots);
const hpRatio = G.tower.hp / G.tower.maxHp;
const hpColor = hpRatio > 0.5 ? '#00d4ff' : hpRatio > 0.25 ? '#ffd700' : '#ff3355';
for (let slotIndex = 0; slotIndex < totalSlots; slotIndex++) {
const weapon = G.weapons[slotIndex];
const installed = !!weapon;
const mountAngle = HARDPOINT_BASE_ANGLE + (slotIndex / totalSlots) * Math.PI * 2;
if (invOpen) {
const actual = getSlotHardpoint(slotIndex, cx, cy, totalSlots);
const ORBIT = Math.max(78, 64 + totalSlots * 6);
const mx = cx + Math.cos(mountAngle) * ORBIT;
const my = cy + Math.sin(mountAngle) * ORBIT;
const dropR = 20;
const hov = isHovered(mx - dropR, my - dropR, dropR * 2, dropR * 2);
const dragging = _dragWeapon !== null;
ctx.save();
ctx.strokeStyle = '#00aaff33';
ctx.lineWidth = 1;
ctx.setLineDash([5, 6]);
ctx.beginPath();
ctx.moveTo(actual.x, actual.y);
ctx.lineTo(mx, my);
ctx.stroke();
ctx.setLineDash([]);
ctx.restore();
if (dragging) {
ctx.save();
ctx.shadowBlur = hov ? 20 : 8;
ctx.shadowColor = '#ffd700';
ctx.strokeStyle = hov ? '#ffd700' : '#00aaff55';
ctx.lineWidth = 2;
ctx.beginPath(); ctx.arc(mx, my, dropR + 5, 0, Math.PI * 2); ctx.stroke();
ctx.shadowBlur = 0;
ctx.restore();
}
ctx.fillStyle = hov ? '#0c1820' : '#050d16';
ctx.strokeStyle = installed
? (hov ? '#ffd700' : hpColor + 'aa')
: (hov ? '#00aaff' : '#1a3240');
ctx.lineWidth = hov ? 2.5 : 1.5;
ctx.beginPath(); ctx.arc(mx, my, dropR, 0, Math.PI * 2); ctx.fill(); ctx.stroke();
ctx.font = '8px Orbitron, monospace';
ctx.letterSpacing = '1px';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillStyle = '#3a6080';
ctx.fillText(`S${slotIndex + 1}`, mx, my - (installed ? 8 : 0));
ctx.letterSpacing = '0px';
if (installed) {
_dragRegions.push({ x: mx - dropR, y: my - dropR, w: dropR * 2, h: dropR * 2, weapon, source: { type: 'slot', slotIndex } });
const def = getWeaponDef(weapon);
ctx.font = '14px monospace';
ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
ctx.fillStyle = '#ffffff';
ctx.fillText(def?.icon || '?', mx, my + 5);
} else {
ctx.font = '14px "Share Tech Mono", monospace';
ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
ctx.fillStyle = '#1a3240';
ctx.fillText('+', mx, my);
}
_mountDropZones.push({ x: mx, y: my, r: dropR, slotIndex });
addHitRegion(mx - dropR, my - dropR, dropR * 2, dropR * 2, () => openWeaponPicker(slotIndex));
if (hov && installed) _drawMountTooltip(mx, my, weapon, dropR);
} else {
// inventory closed: invisible hit region — click opens picker for this slot
const mount = getSlotHardpoint(slotIndex, cx, cy, totalSlots);
const r = 10;
addHitRegion(mount.x - r, mount.y - r, r * 2, r * 2, () => openWeaponPicker(slotIndex));
}
}
}
// ── DRAG GHOST ────────────────────────────────────────────────
function drawDragGhost() {
if (!_dragWeapon || !_hoverPt) return;
const def = getWeaponDef(_dragWeapon);
if (!def) return;
const GW = 90, GH = 68;
const gx = _hoverPt.x - GW / 2;
const gy = _hoverPt.y - GH / 2;
ctx.save();
ctx.globalAlpha = 0.85;
ctx.fillStyle = '#060e16';
ctx.strokeStyle = '#ffd700';
ctx.lineWidth = 2;
ctx.fillRect(gx, gy, GW, GH);
ctx.strokeRect(gx, gy, GW, GH);
ctx.globalAlpha = 1;
ctx.font = '22px monospace';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillStyle = '#ffffff';
ctx.fillText(def.icon || '?', gx + GW / 2, gy + GH / 2 - 8);
ctx.font = '8px Orbitron, monospace';
ctx.fillStyle = '#b8d8e8';
ctx.fillText(def.name, gx + GW / 2, gy + GH / 2 + 14);
ctx.restore();
}