The reaction lands where the ball does
- praise was firing at RELEASE, half a second before impact (measured: 32-frame flight, word on frame 0) — it now arms on the ball and pops on the first body it meets, or its first bounce - DOUBLE KILL / MASSACRE / RETURNED rise off the thing that earned them, as world-anchored pop texts — the announcement band is for milestones - the band got rows: a card can no longer print over the flash line - GAME N flash deleted (the scoreboard bar already says it), CHANGEOVER folded into the countdown panel Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
9476f354c2
commit
1337048a85
7 files changed
+91
-30
No files matched your search
+17
-5
@@ -21,13 +21,21 @@ export const extras = []; // triple-serve side balls {x, y, z, vx, vy, vz, pow,
|
||||
// theirs and they hurt YOU. Swat one with a swing and it becomes an `extras` entry flying back
|
||||
// up-court, which is how a parry turns into a real weapon for free.
|
||||
export const shots = []; // {x, y, z, vx, vy, vz, g, t}
|
||||
// world-anchored reaction pops: DOUBLE KILL rises off the corpse that earned it, PERFECT lands on
|
||||
// the impact — the answer to "what did I just do" appears where you are already looking, not in
|
||||
// the announcement band. Render draws them, gameplay's update ticks them, killed in resetZombies.
|
||||
export const pops = []; // {x, y, z, text, col, t, T}
|
||||
export function popText(x, y, z, text, col) {
|
||||
pops.push({ x, y, z, text, col, t: 0.85, T: 0.85 });
|
||||
if (pops.length > 10) pops.shift();
|
||||
}
|
||||
|
||||
let spawnT = 1.5, moanT = 3;
|
||||
|
||||
// Every body, decal and projectile on the court, plus this module's own timers. Called by
|
||||
// gameplay.restartRun — a restart is in-place now, not a page reload.
|
||||
export function resetZombies() {
|
||||
for (const a of [zombies, gore, piles, parts, pellets, extras, shots]) a.length = 0;
|
||||
for (const a of [zombies, gore, piles, parts, pellets, extras, shots, pops]) a.length = 0;
|
||||
spawnT = 1.5; moanT = 3; mgTick = 0; lastBarrel = null;
|
||||
}
|
||||
|
||||
@@ -598,6 +606,7 @@ export function meleeSwipe(x, z, range) {
|
||||
}
|
||||
|
||||
function splatter(i, fanfare = true) { // the MAIN ball hit a zombie
|
||||
const zx = zombies[i].x, zy = zombieH(zombies[i]) * 0.9, zz = zombies[i].z; // for the pops — killZombie splices
|
||||
killZombie(i, !!G.horde);
|
||||
ball.vx *= TUNE.zPierce; ball.vz *= TUNE.zPierce; // keep enough pace to plow through the next
|
||||
if (!fanfare) return; // machine-gun pellets splatter but skip the streak fanfare / bomb meter
|
||||
@@ -608,9 +617,9 @@ function splatter(i, fanfare = true) { // the MAIN ball hit a zombie
|
||||
G.hitstop = Math.max(G.hitstop, Math.min(0.09, 0.03 + 0.012 * ball.kills));
|
||||
// escalating multi-kill fanfare — one shot, ≥2 zombies
|
||||
// ...and the stat is banked on the TRANSITION to 2, so a massacre of six is one multi-kill
|
||||
if (ball.kills === 2) { multiKill(); G.flash = { text: 'DOUBLE KILL', t: 0.9 }; cheer(1.6, 0.45); voice('double_kill', 0.4); }
|
||||
else if (ball.kills === 3) { G.flash = { text: 'TRIPLE KILL', t: 0.9 }; cheer(2.2, 0.6); voice('triple_kill', 0.4); }
|
||||
else if (ball.kills >= 4) { G.flash = { text: `MASSACRE ×${ball.kills}`, t: 0.9 }; cheer(2.8, 0.7); voice('unbelievable', 0.4);
|
||||
if (ball.kills === 2) { multiKill(); popText(zx, zy, zz, 'DOUBLE KILL'); cheer(1.6, 0.45); voice('double_kill', 0.4); }
|
||||
else if (ball.kills === 3) { popText(zx, zy, zz, 'TRIPLE KILL'); cheer(2.2, 0.6); voice('triple_kill', 0.4); }
|
||||
else if (ball.kills >= 4) { popText(zx, zy, zz, `MASSACRE ×${ball.kills}`); cheer(2.8, 0.7); voice('unbelievable', 0.4);
|
||||
G.slowT = Math.max(G.slowT, 0.35); } // a massacre borrows the boss's slow-mo — the infra already runs every frame
|
||||
}
|
||||
|
||||
@@ -953,6 +962,9 @@ export function updateZombies(dt) {
|
||||
CAUSE = ball.wid; // re-stamped per body: burnTick above shares this loop and moves it
|
||||
const d = Math.hypot(ball.x - zb.x, ball.z - zb.z);
|
||||
if (d < hitR(zb) + ballSweep && !(zb.hitT > 0)) { // brute and boss are bigger targets
|
||||
// the serve's deferred praise pops on the FIRST body the ball meets — this is the impact
|
||||
// the word was waiting for (armed in hitServe, see praiseWord)
|
||||
if (ball.praise) { popText(zb.x, zombieH(zb) * 0.8, zb.z, ball.praise, UI.warm); ball.praise = null; }
|
||||
if (ball.bomb) { // detonate on first contact, whatever the pace
|
||||
explode(ball.x, ball.z); ball.bomb = false;
|
||||
spentBall(); break; // zombies mutated — stop this frame's loop
|
||||
@@ -1122,7 +1134,7 @@ export function updateZombies(dt) {
|
||||
const sp = Math.hypot(s.vx, s.vz) * 1.15;
|
||||
extras.push({ x: s.x, y: s.y, z: s.z, g: TUNE.gravity, pow: TUNE.parryPow, t: 2.5,
|
||||
vx: Math.sin(ang) * sp, vy: 4.2, vz: -Math.cos(ang) * sp });
|
||||
pock(0.85, 1300); G.flash = { text: 'RETURNED', t: 0.6 }; cheer(0.8, 0.3);
|
||||
pock(0.85, 1300); popText(s.x, s.y + 0.4, s.z, 'RETURNED'); cheer(0.8, 0.3);
|
||||
done = true;
|
||||
}
|
||||
if (!done && dHit < TUNE.shotHitR && s.y < TUNE.shotHitY) { hurtPlayer(TUNE.machDmg); done = true; }
|
||||
|
||||
Reference in new issue
Block a user