Zombie town: the match continues
Win your first game and the applause cuts off mid-clap. The palette drains,
the umpire lowers his voice ('Quiet... please'), your opponent turns — torn
red polo, dragging his racket — and shamblers come over the billboard wall.
The scoreboard rewrites CPU to THEM. 'Play.'
- zombies.js: shamblers walk your way; quality shots splatter them (gore
decal + particle burst + wet synth splat), weak sitters only stagger them
back; one crossing your baseline is a point for THEM
- tennis scoring machinery keeps running untouched: fifteen, thirty, deuce,
and 'Game, set... massacre' on a game win, all in recorded umpire English
- ball boys mop the gore they pass (their destiny fulfilled)
- transition: palette lerp, scripted spawns incl. the turned opponent,
new recorded lines (quiet_dark, remain_seated, play, new_balls)
- moan ambience, drained splatter palette, all zombie params in TUNE
Verified: transition triggers on first game win, splat/stagger/baseline
scoring all correct, dead-ball flow feeds fresh serves, tennis mode
regression clean (6/6 returns, 6/6 points resolved).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f221ff84df
commit
69e3a0c310
20 files changed
+247
-25
No files matched your search
+80
@@ -0,0 +1,80 @@
|
||||
// The splatter phase: shamblers, gore decals, particles. The ball is the weapon;
|
||||
// tennis scoring keeps running via gameplay.splatPoint. Zombies advance toward
|
||||
// your baseline — one crossing it is a point for THEM.
|
||||
// (zombies <-> gameplay is a runtime-only import cycle, same pattern as input.)
|
||||
|
||||
import { TUNE, COURT } from './config.js';
|
||||
import { G, ball, sendBoy } from './state.js';
|
||||
import { splat, moan, pock } from './audio.js';
|
||||
import { splatPoint } from './gameplay.js';
|
||||
|
||||
export const zombies = []; // {x, z, v, walkT, variant: 'a'|'b'|'c'}
|
||||
export const gore = []; // lawn decals {x, z, r, a}
|
||||
export const parts = []; // splatter particles {x, y, z, vx, vy, vz, t}
|
||||
|
||||
let spawnT = 1.5, moanT = 3;
|
||||
|
||||
export function spawnZombie(x, z, variant) {
|
||||
zombies.push({ x, z, v: TUNE.zSpeed * (0.8 + Math.random() * 0.4), walkT: Math.random() * 9, variant });
|
||||
}
|
||||
|
||||
function splatter(i) {
|
||||
const zb = zombies[i];
|
||||
zombies.splice(i, 1);
|
||||
gore.push({ x: zb.x, z: zb.z, r: 0.45 + Math.random() * 0.35, a: 0.85 });
|
||||
if (gore.length > 40) gore.shift();
|
||||
for (let k = 0; k < 14; k++) parts.push({
|
||||
x: zb.x, y: 0.9, z: zb.z,
|
||||
vx: (Math.random() * 2 - 1) * 3.5, vy: 1 + Math.random() * 3.5, vz: (Math.random() * 2 - 1) * 3.5,
|
||||
t: 0.4 + Math.random() * 0.4,
|
||||
});
|
||||
splat();
|
||||
ball.vx *= 0.5; ball.vz *= 0.5; // the kill takes pace off the ball
|
||||
sendBoy(zb.x, zb.z); // a boy sprints out to mop
|
||||
splatPoint('P');
|
||||
}
|
||||
|
||||
export function updateZombies(dt) {
|
||||
moanT -= dt;
|
||||
if (moanT <= 0) { moan(); moanT = 2.5 + Math.random() * 4; }
|
||||
|
||||
if (G.mode === 'splatter') { // transition spawns are scripted, not timed
|
||||
spawnT -= dt;
|
||||
if (spawnT <= 0 && zombies.length < TUNE.zMax) {
|
||||
spawnZombie((Math.random() * 2 - 1) * 4.8, -1.5 - Math.random(), Math.random() < 0.5 ? 'a' : 'b');
|
||||
spawnT = TUNE.zSpawnEvery;
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = zombies.length - 1; i >= 0; i--) {
|
||||
const zb = zombies[i];
|
||||
zb.walkT += dt;
|
||||
zb.z += zb.v * dt;
|
||||
zb.x += Math.sin(zb.walkT * 1.7) * 0.15 * dt; // shamblers don't walk straight lines
|
||||
if (ball.active && ball.lastHit === 'P' && ball.y < 1.9) {
|
||||
const d = Math.hypot(ball.x - zb.x, ball.z - zb.z);
|
||||
if (d < 0.55) {
|
||||
if ((ball.pow || 0) >= TUNE.zSplatQ) { splatter(i); continue; }
|
||||
zb.z -= TUNE.zPushback; // weak sitters only stagger them
|
||||
pock(0.6, 300);
|
||||
ball.vx *= 0.6; ball.vz *= 0.6; ball.lastHit = 'X'; // spent — one stagger per ball
|
||||
}
|
||||
}
|
||||
if (zb.z >= COURT.L + 0.7) { zombies.splice(i, 1); splatPoint('O'); } // it reached your baseline
|
||||
}
|
||||
|
||||
for (let i = parts.length - 1; i >= 0; i--) {
|
||||
const p = parts[i];
|
||||
p.t -= dt; p.vy -= 14 * dt;
|
||||
p.x += p.vx * dt; p.y += p.vy * dt; p.z += p.vz * dt;
|
||||
if (p.y < 0) { p.y = 0; p.vy = 0; p.vx *= 0.5; p.vz *= 0.5; }
|
||||
if (p.t <= 0) parts.splice(i, 1);
|
||||
}
|
||||
|
||||
const boy = G.boy; // the boy mops any gore he passes over
|
||||
for (let i = gore.length - 1; i >= 0; i--) {
|
||||
const d = gore[i];
|
||||
if (boy && Math.abs(boy.x - d.x) < 0.7 && Math.abs(boy.z - d.z) < 1.2) d.a -= dt * 1.6;
|
||||
if (d.a <= 0) gore.splice(i, 1);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user