From 1f22d3d5a09441444a1424907fd4c0198f0004a9 Mon Sep 17 00:00:00 2001 From: Oliver Rieger Date: Wed, 5 Aug 2026 10:07:28 +0200 Subject: [PATCH] Say the weapon's name once A first-seen tin announced itself twice after a won duel: the modal that teaches its rules, and then the delivery card right behind it, same title same icon. weaponModal now reports whether it spoke, and the card (and the barrel's flash) only speak when it didn't. Co-Authored-By: Claude Opus 5 --- gameplay.js | 18 +++++++++++------- zombies.js | 6 +++--- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/gameplay.js b/gameplay.js index 9362334..c1ce0b5 100644 --- a/gameplay.js +++ b/gameplay.js @@ -594,24 +594,28 @@ export function deliverTin() { aside('ump_new_balls_dark', 2.0); // "...and a new opponent, apparently." // THE PRIZE GETS ITS OWN CARD, with the tin's own icon on it. It used to borrow G.flash — the // same channel as PERFECT and DOUBLE KILL — so the one thing you played the duel FOR arrived as a - // word in the praise slot and read as another compliment. weaponModal still fires the first time - // a tin is seen (it has rules to teach); every time after, this card is the announcement, and it + // word in the praise slot and read as another compliment. The first time a tin is seen the modal + // announces it (and teaches its rules), so the card would be the SAME news twice, back to back — + // it only speaks when the modal doesn't. Every time after, the card is the announcement, and it // does not want a tap: you have just won a point, not been asked a question. - G.card = { title: WEAPONS[id].label, sub1: 'DELIVERED TO THE RACK', - sub2: (WEAPONS[id].desc && WEAPONS[id].desc[0]) || '', - icon: WEAPONS[id].icon, color: UI.warm, t: TUNE.tinCardT }; - weaponModal(id); + if (!weaponModal(id)) + G.card = { title: WEAPONS[id].label, sub1: 'DELIVERED TO THE RACK', + sub2: (WEAPONS[id].desc && WEAPONS[id].desc[0]) || '', + icon: WEAPONS[id].icon, color: UI.warm, t: TUNE.tinCardT }; } // The one-per-run "what this thing does" popup, shared by the duel ladder and the barrels. // THE GATE LIVES HERE, not at the call sites. The barrels gated themselves and the tin ladder did // not, so the four delivered weapons re-explained themselves every three games for a whole run // while a barrel weapon explained itself once — the same popup behaving two different ways. +// Returns whether it actually announced, so a caller with its own announcement (the tin card, the +// barrel's flash) can stay quiet rather than say the same thing twice. export function weaponModal(id) { - if (G.seen['w' + id]) return; + if (G.seen['w' + id]) return false; G.seen['w' + id] = true; const w = WEAPONS[id]; showModal({ title: w.label, body: w.desc, icon: w.icon, iconH: 40, color: UI.warm }); + return true; } // ---- TEST MODE. Triple-tap the scoreboard. A sandbox for trying the new content without playing diff --git a/zombies.js b/zombies.js index 6cef2fc..a0894cf 100644 --- a/zombies.js +++ b/zombies.js @@ -359,9 +359,9 @@ function popBarrel(zb) { vx: (Math.random() * 2 - 1) * 4, vy: 1.2 + Math.random() * 3.5, vz: (Math.random() * 2 - 1) * 4, t: 0.35 + Math.random() * 0.5, pink: true }); grantWeapon(zb.wid); - const w = WEAPONS[zb.wid]; - G.flash = { text: w.label, t: 1.1 }; - weaponModal(zb.wid); // first sight only — the gate lives in weaponModal, for both callers + // first sight only — the gate lives in weaponModal, for both callers — and the flash keeps quiet + // when the modal spoke, so the same weapon isn't named twice in the same beat + if (!weaponModal(zb.wid)) G.flash = { text: WEAPONS[zb.wid].label, t: 1.1 }; } // dark smoke: one puff, drifting up and spreading. Not gravity-bound like gore (see the parts loop).