Splatter: ball economy — one ball, fetch it back, no more toss-spam
The slingshot serve didn't fix the gameplay because four things were free at once: pure-hold auto-target with predictive lead, a hardcoded 0.62 serve quality, infinite ammo with an instant mid-rally re-toss, and a melee swish on every single tap that cleaved the whole cluster. Perfecting the pretoss/toss rhythm beat the game; no skill required. The ball is scarce now. A spent ball LIES where it died (G.loose, ringed on court) and there is no serve until it's back in your hand: walk it down (instant, and the only reason to leave the baseline), let a boy scoop it, wait out TUNE.looseMax, or watch a shambler swallow it — the one real punishment. Fire rate is a positioning decision instead of a thumb-tempo one. Also: the far backstop rebounds a ball with pace left, still lethal on the way home and volleyable (trySwing lets P re-hit their own ball in splatter); melee drops to nearest-one-only on a 1s cooldown; and the mid-rally re-toss is gone. With no ball in hand the thumb-drag steers your legs instead of the reticle — without that, walking your ball down is impossible on touch. TUNE.looseMax is the fire-rate knob. Measured: ~4s per serve standing still, ~0.4s if you go and get it. 600 taps in 10s now yield exactly one serve. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
013fe8152d
commit
2787778ad2
6 files changed
+95
-35
No files matched your search
+14
-10
@@ -6,7 +6,7 @@
|
||||
import { TUNE, COURT } from './config.js';
|
||||
import { G, P, ball, sendBoy, setupPoint } from './state.js';
|
||||
import { splat, moan, pock, voice, boom, swish, shriek, aww, sfx } from './audio.js';
|
||||
import { splatPoint, cheer, spentBall, resolveInterlude } from './gameplay.js';
|
||||
import { splatPoint, cheer, spentBall, resolveInterlude, giveBall } from './gameplay.js';
|
||||
|
||||
export const zombies = []; // {x, z, v, walkT, variant: 'a'|'b'|'c'}
|
||||
export const gore = []; // lawn decals {x, z, r, a} — the flat ground layer
|
||||
@@ -80,16 +80,20 @@ function woundBurst(zb) {
|
||||
t: 0.35 + Math.random() * 0.3, dark: Math.random() < 0.6 });
|
||||
}
|
||||
|
||||
// swishing the racket cleaves any shambler in reach (the racket-zombie boss shrugs melee off)
|
||||
// swishing the racket takes out the NEAREST shambler in reach — one swing, one body. It used to
|
||||
// cleave the whole cluster, which made standing still and tapping better than playing tennis.
|
||||
// (the racket-zombie boss shrugs melee off entirely)
|
||||
export function meleeSwipe(x, z, range) {
|
||||
let hit = 0;
|
||||
for (let i = zombies.length - 1; i >= 0; i--) {
|
||||
let best = -1, bd = range;
|
||||
for (let i = 0; i < zombies.length; i++) {
|
||||
const zb = zombies[i];
|
||||
if (!zb || zb.variant === 'r') continue; // !zb: killZombie can sweep the court mid-loop
|
||||
if (Math.hypot(zb.x - x, zb.z - z) < range) { killZombie(i, !!G.horde); hit++; }
|
||||
if (zb.variant === 'r') continue;
|
||||
const d = Math.hypot(zb.x - x, zb.z - z);
|
||||
if (d < bd) { bd = d; best = i; }
|
||||
}
|
||||
if (hit) swish(0.35);
|
||||
return hit;
|
||||
if (best < 0) return 0;
|
||||
killZombie(best, !!G.horde); swish(0.35);
|
||||
return 1;
|
||||
}
|
||||
|
||||
function splatter(i, fanfare = true) { // the MAIN ball hit a zombie
|
||||
@@ -119,7 +123,7 @@ export function spawnRacketZombie() { // one takes up residence on the far court
|
||||
function endHorde() {
|
||||
G.horde = null; pellets.length = 0;
|
||||
if (!zombies.some(z => z.variant === 'r')) spawnRacketZombie(); // survive the wave → a racket zombie stays
|
||||
setupPoint(); // serve loop resumes; any zombies still standing are yours to mop up
|
||||
giveBall(); setupPoint(); // the wave is over: a ball in hand, and any zombies still standing are yours to mop up
|
||||
voice('quiet_dark', 0.5, 0.85);
|
||||
}
|
||||
function tickHorde(dt) {
|
||||
@@ -180,7 +184,7 @@ export function updateZombies(dt) {
|
||||
else if (G.mgT > 0 && G.phase === 'rally') { // machine-gun tin: horde firepower, regular rules
|
||||
G.mgT -= dt;
|
||||
if ((G.mgCool -= dt) <= 0) { firePellet(); G.mgCool = 1 / TUNE.mgRate; }
|
||||
if (G.mgT <= 0) setupPoint(); // ammo dry — the serve loop resumes
|
||||
if (G.mgT <= 0) { giveBall(); setupPoint(); } // ammo dry — the serve loop resumes, ball in hand
|
||||
}
|
||||
if (!G.horde && !G.changeover && !G.interlude && !G.clearing) { // breathers, duels AND the mop-up after a won game get a quiet court (no new spawns)
|
||||
spawnT -= dt;
|
||||
|
||||
Reference in new issue
Block a user