Phase 4: bomb balls
Ball boys deliver a bomb ball every TUNE.bombEvery (8) splatter kills, up to TUNE.bombMax (3) in stock (G.bombs / G.killMeter in state). The next serve auto-consumes one (hitServe, splatter-only) — no extra input, keeping one-handed play. A bomb detonates on first zombie contact (regardless of pace) or, if it misses everyone, on its first bounce: explode() splatters everything within TUNE.bombRadius (2.5m), each kill feeding ball.kills so the Phase-2 fanfare escalates naturally, and leaves a crater — big stain, size-3 pile, 40 particles, 0.1s hitstop, boom(). New synth boom() in audio.js. Bomb ball renders black with a flickering orange fuse; the HUD shows stocked bombs as black/orange pips by the YOU line. Both explode sites set phase='over' so the serve loop doesn't stall on the killed ball. Alternative delivery ideas Oliver floated but not built: umpire throws it, or court pickup. Verified via window.T: 8th kill earns a bomb (meter resets); a serve consumes one and renders #111 black + orange fuse; a pow-0 bomb amid 4 clustered zombies kills all 4 with 'MASSACRE ×4', boom, hitstop 0.1, crater stain (r1.4) + size-4 pile; a bomb that misses still craters on first bounce; bombs never leak into tennis (mode guard); 3 HUD pips; no console errors. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
ad4c8041b9
commit
06025bdccf
6 files changed
+67
-5
No files matched your search
+8
-1
@@ -4,7 +4,7 @@ import { TUNE, COURT, CONTACT, VOLLEY, ZONE_DEPTH, HOME, P_BOUNDS, TOSS, SHOT_DE
|
||||
import { P, O, ball, score, crowd, G, other, deuceSide, setupPoint, sendBoy } from './state.js';
|
||||
import { audio, pock, swish, applause, voice } from './audio.js';
|
||||
import { axisX, axisZ, takeFlickAim } from './input.js';
|
||||
import { updateZombies, spawnZombie, zombies } from './zombies.js';
|
||||
import { updateZombies, spawnZombie, zombies, explode } from './zombies.js';
|
||||
|
||||
// ------------------------------------------------------------- ballistics ----
|
||||
// Launch ball from (x0,y0,z0) so it lands at (tx, tz) with horizontal speed s under gravity g.
|
||||
@@ -79,6 +79,7 @@ function doHit(ch, quality, aim) {
|
||||
G.wasServe = false; // a serve volleyed back pre-bounce must not fault-check the return
|
||||
ball.pow = quality; // carried pace: zombies splatter only above zSplatQ
|
||||
ball.kills = 0; // fresh shot: multi-kill streak restarts
|
||||
ball.bomb = false; // only a bomb-consuming serve is a bomb; any other hit clears it
|
||||
if (ch.side === 'P') { // shot marker: where this will land — red means it's going out
|
||||
const ok = G.mode !== 'tennis' || (Math.abs(tx) <= COURT.W / 2 + 0.02 && tz >= -0.02 && tz <= COURT.netZ);
|
||||
G.outMarker = { x: tx, z: tz, out: !ok, t: 1.3 };
|
||||
@@ -120,6 +121,8 @@ function hitServe(sv, quality, aim = null) {
|
||||
ball.lastHit = sv.side; ball.bounces = 0;
|
||||
ball.pow = quality;
|
||||
ball.kills = 0; // fresh serve: multi-kill streak restarts
|
||||
ball.bomb = G.mode === 'splatter' && G.bombs > 0; // auto-consume a stocked bomb ball
|
||||
if (ball.bomb) G.bombs--;
|
||||
if (sv.side === 'P') G.outMarker = { x: boxX, z: tzs, out: G.mode === 'tennis' && !inServeBox(boxX, tzs), t: 1.3 };
|
||||
G.phase = 'rally'; pock(1, 1100);
|
||||
sv.pose = 'serve'; sv.poseT = 0;
|
||||
@@ -229,6 +232,10 @@ export function inServeBox(x, z) {
|
||||
function onBounce() {
|
||||
ball.bounces++;
|
||||
pock(0.35, 500);
|
||||
if (G.mode === 'splatter' && ball.bomb && ball.bounces === 1) { // a bomb that missed everyone still blows up
|
||||
explode(ball.x, ball.z); ball.active = false; ball.bomb = false;
|
||||
G.phase = 'over'; G.overT = TUNE.splatterOverT; return;
|
||||
}
|
||||
if (G.mode !== 'tennis') return; // no lines, no faults — the umpire has other concerns
|
||||
if (ball.bounces === 1) {
|
||||
ball.firstLand = { x: ball.x, z: ball.z };
|
||||
|
||||
Reference in new issue
Block a user