feat: boss-room loading overlay, drop HUD pressure multiplier, elite Brood Sac 4-6 minis
This commit is contained in:
Quintine committed 2026-08-15 00:24:05 +10:00
1 parent 2cf8dc89fe
commit 8991bde943
13 files changed
+101 -34

No files matched your search

+1 -1
View File
@@ -551,7 +551,7 @@ function updateEnemyAI(e, dt) {
e.summonT -= dt;
if (e.summonT <= 0) {
e.summonT = e.elite ? 3 : 4;
spawnBroodMinis(e, 2, 0.35);
spawnBroodMinis(e, e.elite ? irand(4, 6) : 2, 0.35);
e.attackT = 0.5; e.actionT = 0;
}
} else if (t.ranged) {
-2
View File
@@ -167,8 +167,6 @@ function drawHUD(ctx) {
ctx.fillText('KILLS ' + String(G.kills).padStart(3, '0'), W - 18, H - 67);
ctx.fillStyle = '#f0e5d7'; ctx.font = 'bold 11px monospace';
ctx.fillText('SCORE ' + String(G.score).padStart(6, '0'), W - 18, H - 46);
ctx.fillStyle = '#8f7770'; ctx.font = '8px monospace';
ctx.fillText('×' + (G.pressure || 1).toFixed(2), W - 168, H - 46);
ctx.textAlign = 'left';
const dialTxt = ' D' + (G.pressureDial > 0 ? '+' : '') + G.pressureDial;
ctx.fillStyle = G.pressure > 1 ? '#d6aa39' : '#69b8bd'; ctx.font = 'bold 8px monospace';
+24
View File
@@ -246,6 +246,11 @@ function update(dt) {
updateParticles(dt);
drawToastsUpdate(dt);
break;
case 'bossload':
updateParticles(dt);
if (G.bossLoadT > 0) G.bossLoadT -= dt;
if (G.bossLoadT <= 0 && Sprites.warm(G.bossLoadSheet)) G.mode = 'play';
break;
case 'levelup':
updateLevelup(dt);
updateParticles(dt);
@@ -328,6 +333,7 @@ function draw() {
ctx.fillRect(0, 0, W, H);
if (G.mode === 'menu') { drawMenu(ctx); return; }
if (G.mode === 'bossload') { drawBossLoad(ctx); return; }
updateCamera();
@@ -378,6 +384,24 @@ function draw() {
if (G.devMode) { if (G.debugPin && G.mode !== 'debug') drawDebugPin(ctx); }
}
function drawBossLoad(ctx) {
ctx.fillStyle = '#0a0506';
ctx.fillRect(0, 0, W, H);
ctx.textAlign = 'center';
const name = G.boss && G.boss.name ? G.boss.name : 'FRESH MEAT';
ctx.fillStyle = '#d9c9bc'; ctx.font = 'bold 28px monospace';
ctx.fillText(name, W / 2, H / 2 - 6);
ctx.fillStyle = '#8f1f2e'; ctx.font = 'bold 11px monospace';
ctx.fillText('FRESH MEAT APPROACHES', W / 2, H / 2 + 16);
const pr = Math.min(1, Math.max(0, G.bossLoadT / BOSS_LOAD_MIN));
const tier = Math.floor((1 - pr) * 5) % 3;
ctx.fillStyle = '#3a1a1e';
for (let i = 0; i < 3; i++) ctx.fillRect(W / 2 - 22 + i * 14, H / 2 + 30, 8, 8);
ctx.fillStyle = '#d7a934';
ctx.fillRect(W / 2 - 22 + tier * 14, H / 2 + 30, 8, 8);
ctx.textAlign = 'left';
}
function drawMenu(ctx) {
const t = performance.now() / 1000;
const bg = ctx.createRadialGradient && ctx.createRadialGradient(W / 2, H * 0.4, 30, W / 2, H * 0.45, 590);
+4
View File
@@ -187,6 +187,10 @@ function enterRoom(gx, gy) {
// boss fights are a pure ammo drain, so stock the room on the way in
spawnPickup('ammo', a.cx, a.cy + 140);
Music.playBoss(boss.bossKind);
G.mode = 'bossload';
G.bossLoadSheet = boss.sprite + '_sheet';
G.bossLoadT = BOSS_LOAD_MIN;
Sprites.warm(G.bossLoadSheet);
} else if (room.type === 'item') {
spawnItemPedestal(a.cx - 45, a.cy, null, 'room');
// item rooms sometimes also stock a weapon
+1 -1
View File
@@ -111,7 +111,7 @@ const SfxBank = {
if (this.buffers.has(name) || this.pending.has(name) || this.failed.has(name)) return;
const file = this.FILES[name];
if (!file || !this.ctx || typeof fetch !== 'function') return;
const job = fetch('/assets/sfx/' + encodeURIComponent(file) + '?v=59')
const job = fetch('/assets/sfx/' + encodeURIComponent(file) + '?v=60')
.then(r => { if (!r.ok) throw new Error('HTTP ' + r.status); return r.arrayBuffer(); })
.then(bytes => this.ctx.decodeAudioData(bytes))
.then(buffer => this.buffers.set(name, this.trim(buffer)))
+19 -2
View File
@@ -88,15 +88,32 @@ const Sprites = {
const finish = () => { if (--pending === 0) G.imagesLoaded = true; };
img.onload = () => { this.imgs[name] = img; if (typeof img.decode === 'function') img.decode().then(finish, finish); else finish(); };
img.onerror = () => {
if (hd && !img._sdRetry) { img._sdRetry = true; img.src = 'assets/' + name + '.png?v=59'; return; }
if (hd && !img._sdRetry) { img._sdRetry = true; img.src = 'assets/' + name + '.png?v=60'; return; }
this.imgs[name] = null;
finish();
};
img.src = hd ? 'assets/hd/' + name + '.webp?v=59' : 'assets/' + name + '.png?v=59';
img.src = hd ? 'assets/hd/' + name + '.webp?v=60' : 'assets/' + name + '.png?v=60';
}
},
get(name) { return this.imgs[name] || null; },
forceUpload(img) {
if (typeof document === 'undefined' || !document.createElement) return;
if (!document.createElement('canvas').getContext) return;
if (!this._warmCtx) {
const c = document.createElement('canvas');
c.width = 1; c.height = 1;
this._warmCtx = c.getContext('2d');
}
if (this._warmCtx) this._warmCtx.drawImage(img, 0, 0, 1, 1);
},
warm(name) {
if (this.imgs[name]) { this.forceUpload(this.imgs[name]); return true; }
return this.imgs[name] !== undefined;
},
// Compact player-only strip: eight forward-facing stride frames.
// Runtime rotation is smooth; distance-driven phase keeps contacts from skating.
legs(ctx, x, y, facing, phase, targetW) {
+3
View File
@@ -51,6 +51,8 @@ const G = {
hudAlpha: 1,
hdRemaster: false,
imagesLoaded: false,
bossLoadT: 0,
bossLoadSheet: null,
roomLayer: null,
roomLayerKey: '',
atmosphereLayer: null,
@@ -72,6 +74,7 @@ const PRESSURE_GAIN = PRESSURE_UNIT; // retained: dial 0 == 1 point
const PRESSURE_DROP_BASE = 3; // dial 0 == 0.03 relief base
const PRESSURE_RELIEF_MUL = 0.5; // global brake: all pressure drops (hit relief + passive decay) are halved
const DEATH_LOCK = 3;
const BOSS_LOAD_MIN = 1.0;
const PRESSURE_DIAL_MIN = -10, PRESSURE_DIAL_MAX = 10;
function hdScale() { return G.hdRemaster ? 4 : 1; }