diff --git a/config.js b/config.js index faebe0d..1c2a654 100644 --- a/config.js +++ b/config.js @@ -28,8 +28,9 @@ export const TUNE = { zPushback: 1.4, // m a staggered zombie is knocked back splatterOverT: 0.1, // s the spent-ball 'over' beat lasts before the next serve is armed zPierce: 0.75, // pace a ball keeps after a splatter — hot balls plow through lined-up pairs - bombEvery: 8, // splatter kills between bomb-ball rewards - bombMax: 3, // bomb balls you can stock at once + ballChangeFirst: 3, // game of the first new-balls delivery (real rule: 7, then every 9 — too sparse here) + ballChangeEvery: 4, // games between deliveries after the first + bombMax: 3, // bomb balls in a fresh tin bombRadius: 2.5, // m blast radius of a bomb ball hordeLen: 12, // s a horde wave lasts hordeRate: 2.5, // zombies spawned per second during a horde diff --git a/gameplay.js b/gameplay.js index 9483778..8164b9e 100644 --- a/gameplay.js +++ b/gameplay.js @@ -186,7 +186,11 @@ export function splatPoint(side, quiet = false) { const gameOver = advancePoint(side); if (gameOver) { G.runGames++; - if (side === 'O') G.shameGame = true; // they took a game — Oliver pays at the next new-balls delivery + if (side === 'O') G.shameGame = true; // they took a game — the next new-balls delivery is withheld + // the new-balls rule arms you: deliveries land at the changeover that follows the scheduled game + if (G.runGames === TUNE.ballChangeFirst || + (G.runGames > TUNE.ballChangeFirst && (G.runGames - TUNE.ballChangeFirst) % TUNE.ballChangeEvery === 0)) + G.tinDue = true; voice(side === 'P' ? 'game_set_massacre' : 'game_opponent', 0.5); // game-win lines always speak cheer(2.2, 0.5); const i = side === 'P' ? 0 : 1; @@ -206,7 +210,12 @@ function startChangeover() { G.changeover = true; G.phase = 'over'; G.overT = TUNE.changeoverT; sendBoy(); - G.flash = { text: 'CHANGEOVER', t: 1.4 }; + if (G.tinDue) { // the boys arm you — unless they took a game since the last delivery + G.tinDue = false; + if (G.shameGame) G.flash = { text: 'NO NEW BALLS', t: 1.6 }; // the umpire withholds + else { G.bombs = TUNE.bombMax; voice('new_balls', 0.6); G.flash = { text: 'NEW BALLS', t: 1.6 }; } + G.shameGame = false; // the slate is clean either way + } else G.flash = { text: 'CHANGEOVER', t: 1.4 }; } // retire the splatter ball; a running changeover's long beat must not be clobbered short diff --git a/state.js b/state.js index 2a6876d..8758b0e 100644 --- a/state.js +++ b/state.js @@ -33,7 +33,7 @@ export const G = { wasServe: false, // the ball in flight is a serve (first bounce must hit the box) splatT: 0, // time survived in splatter mode — drives the difficulty ramp bombs: 0, // bomb-ball stock (max TUNE.bombMax); the next serve auto-consumes one - killMeter: 0, // splatter kills toward the next bomb reward (every TUNE.bombEvery) + tinDue: false, // the rules called for new balls — deliver at the next changeover horde: null, // active horde wave {t, spawnT, mgT} — machine-gun racket, no serving runGames: 0, // games finished this run (either side) — the difficulty director sets: [0, 0], // splatter sets won [you, them]; 6 games = a set = a horde diff --git a/zombies.js b/zombies.js index 661ea90..0aa909c 100644 --- a/zombies.js +++ b/zombies.js @@ -62,11 +62,6 @@ function splatter(i, fanfare = true) { // the MAIN ball hit a zombie if (ball.kills === 2) { 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); } - // bomb-ball reward: every bombEvery kills a boy delivers one (up to bombMax in stock) - if (++G.killMeter >= TUNE.bombEvery) { - G.killMeter = 0; - if (G.bombs < TUNE.bombMax) { G.bombs++; sendBoy(); voice('new_balls', 0.2); G.flash = { text: 'BOMB BALL', t: 0.9 }; } - } } // ---- horde wave: dense spawns + an auto-firing machine-gun racket (pellets, not the match ball)