Splatter feel pass: melee, homing, multi-ball, clearer MG mode
From Oliver's feedback round: - Racket melee: any swish (tap) in splatter cleaves shamblers within TUNE.meleeRange (1.7m) — a close-range survival tool. Works during a horde too (tap = swish only, still no serve). The racket-zombie boss (Phase 6, not built) is immune by design. - Zombies home in on you: on YOUR half (z > netZ) they now drift toward P.x instead of walking a straight lane; far half stays an aimless shamble. - Multi-ball / rapid fire: splatter is now a shooter — every tap/flick fires immediately (dropped the toss ritual there), and serving while a ball still flies stashes the old one into a new extraBalls array so it keeps arcing and killing (cap 6). Refactored the kill into a shared killZombie() with no ball coupling, used by ball hits, pellets, melee, and extra balls. - Machine-gun mode clarity: confirmed free-walk (drag/arrows move you, auto-assist off) and tap-doesn't-serve during a horde; added a code-drawn "rambo" racket held forward with a muzzle flash over the player, and a splatter-specific serve prompt. REVIEW on your phone — two opinionated calls, both easy to revert: 1. Dropped the toss in splatter (tap = instant quick-serve, pow 0.62). Rapid fire, but no apex-timing quality/pierce. Want the toss back as an option? 2. MG posture is a code-drawn forward racket, not a real body sprite — good enough, or worth a proper p_mg sprite later? Also: each rapid serve still auto-consumes a stocked bomb, so bombs deplete fast now — flag if that feels wrong. Verified via window.T: melee kills a 1.0m zombie and spares a 4.5m one; a near-half zombie drifts x 3→1.9 toward you; firing twice leaves 1 stashed ball + a fresh serve (barrage screenshot); extra balls kill and pierce; free-walk during a horde (x 0→2.7); horde-tap melees without serving; tennis toss-serve and Phase-4 bomb consume still work; no console errors. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
bf9553bd49
commit
9100bd5a23
5 files changed
+89
-28
No files matched your search
+52
-7
@@ -5,7 +5,7 @@
|
||||
|
||||
import { TUNE, COURT } from './config.js';
|
||||
import { G, P, ball, sendBoy, setupPoint } from './state.js';
|
||||
import { splat, moan, pock, voice, boom } from './audio.js';
|
||||
import { splat, moan, pock, voice, boom, swish } from './audio.js';
|
||||
import { splatPoint, cheer } from './gameplay.js';
|
||||
|
||||
export const zombies = []; // {x, z, v, walkT, variant: 'a'|'b'|'c'}
|
||||
@@ -13,6 +13,7 @@ export const gore = []; // lawn decals {x, z, r, a} — the flat ground laye
|
||||
export const piles = []; // 3D-ish gore mounds {x, z, size 1..4, seed} — grow where kills cluster
|
||||
export const parts = []; // splatter particles {x, y, z, vx, vy, vz, t, dark}
|
||||
export const pellets = []; // machine-gun rounds during a horde {x, y, z, vx, vz, t}
|
||||
export const extraBalls = []; // earlier serves still in flight while you fire the next {x,y,z,vx,vy,vz,g,pow,bomb,bounces}
|
||||
|
||||
let spawnT = 1.5, moanT = 3;
|
||||
|
||||
@@ -21,7 +22,9 @@ export function spawnZombie(x, z, variant) {
|
||||
zombies.push({ x, z, v: TUNE.zSpeed * ramp * (0.8 + Math.random() * 0.4), walkT: Math.random() * 9, variant });
|
||||
}
|
||||
|
||||
function splatter(i, fanfare = true) {
|
||||
// core kill: remove the zombie, spawn its gore/pile/particles, score. No ball coupling —
|
||||
// shared by ball hits, machine-gun pellets, melee swipes, and extra (multi-serve) balls.
|
||||
function killZombie(i, quiet) {
|
||||
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 });
|
||||
@@ -36,9 +39,32 @@ function splatter(i, fanfare = true) {
|
||||
t: dark ? 0.6 + Math.random() * 0.6 : 0.4 + Math.random() * 0.4, dark,
|
||||
}); }
|
||||
splat();
|
||||
ball.vx *= TUNE.zPierce; ball.vz *= TUNE.zPierce; // keep enough pace to plow through the next
|
||||
sendBoy(zb.x, zb.z); // a boy sprints out to mop
|
||||
splatPoint('P', !!G.horde); // horde kills rate-limit the umpire so he doesn't machine-gun scores
|
||||
splatPoint('P', quiet); // horde kills rate-limit the umpire so he doesn't machine-gun scores
|
||||
}
|
||||
|
||||
// swishing the racket cleaves any shambler in reach (the racket-zombie boss shrugs melee off)
|
||||
export function meleeSwipe(x, z, range) {
|
||||
let hit = 0;
|
||||
for (let i = zombies.length - 1; i >= 0; i--) {
|
||||
if (zombies[i].variant === 'r') continue;
|
||||
if (Math.hypot(zombies[i].x - x, zombies[i].z - z) < range) { killZombie(i, !!G.horde); hit++; }
|
||||
}
|
||||
if (hit) swish(0.35);
|
||||
return hit;
|
||||
}
|
||||
|
||||
// serving again while a ball still flies: stash the current one so it keeps killing (a barrage)
|
||||
export function stashExtraBall() {
|
||||
if (!ball.active) return;
|
||||
extraBalls.push({ x: ball.x, y: ball.y, z: ball.z, vx: ball.vx, vy: ball.vy, vz: ball.vz,
|
||||
g: ball.g, pow: ball.pow, bomb: ball.bomb, bounces: ball.bounces });
|
||||
if (extraBalls.length > 6) extraBalls.shift(); // cap the barrage
|
||||
}
|
||||
|
||||
function splatter(i, fanfare = true) { // the MAIN ball hit a zombie
|
||||
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
|
||||
ball.kills++; // this shot's running streak
|
||||
// escalating multi-kill fanfare — one shot, ≥2 zombies
|
||||
@@ -111,7 +137,10 @@ export function updateZombies(dt) {
|
||||
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 (G.mode === 'splatter' && zb.variant !== 'r' && zb.z > COURT.netZ) // on YOUR half they home in on you
|
||||
zb.x += Math.sign(P.x - zb.x) * Math.min(Math.abs(P.x - zb.x), zb.v * 0.7 * dt);
|
||||
else
|
||||
zb.x += Math.sin(zb.walkT * 1.7) * 0.15 * dt; // far half / racket boss: aimless shamble
|
||||
if (G.mode === 'splatter' && Math.hypot(zb.x - P.x, zb.z - P.z) < 0.7) { // it got you
|
||||
gore.push({ x: P.x, z: P.z, r: 0.9, a: 0.9 });
|
||||
for (let k = 0; k < 30; k++) parts.push({
|
||||
@@ -122,7 +151,7 @@ export function updateZombies(dt) {
|
||||
splat(1);
|
||||
voice('game_set_massacre', 0.8);
|
||||
G.mode = 'eaten'; ball.active = false; G.flash = null; // zombies keep shambling for ambience
|
||||
G.horde = null; pellets.length = 0; // the swarm stops; the eaten screen takes over
|
||||
G.horde = null; pellets.length = 0; extraBalls.length = 0; // the swarm stops; the eaten screen takes over
|
||||
}
|
||||
if (ball.active && ball.lastHit === 'P' && ball.y < 1.9) {
|
||||
const d = Math.hypot(ball.x - zb.x, ball.z - zb.z);
|
||||
@@ -145,10 +174,26 @@ export function updateZombies(dt) {
|
||||
pe.t -= dt; pe.x += pe.vx * dt; pe.z += pe.vz * dt;
|
||||
let hit = false;
|
||||
for (let j = zombies.length - 1; j >= 0; j--)
|
||||
if (Math.hypot(zombies[j].x - pe.x, zombies[j].z - pe.z) < 0.55) { splatter(j, false); hit = true; break; }
|
||||
if (zombies[j].variant !== 'r' && Math.hypot(zombies[j].x - pe.x, zombies[j].z - pe.z) < 0.55) { killZombie(j, true); hit = true; break; }
|
||||
if (hit || pe.t <= 0) pellets.splice(i, 1);
|
||||
}
|
||||
|
||||
for (let i = extraBalls.length - 1; i >= 0; i--) { // earlier serves keep arcing and killing
|
||||
const b = extraBalls[i];
|
||||
b.vy -= (b.g || TUNE.gravity) * dt;
|
||||
b.x += b.vx * dt; b.y += b.vy * dt; b.z += b.vz * dt;
|
||||
let done = false;
|
||||
if (b.y <= 0 && b.vy < 0) { b.y = 0; b.vy = -b.vy * TUNE.bounceY; b.vx *= TUNE.bounceXZ; b.vz *= TUNE.bounceXZ; b.bounces++;
|
||||
if (b.bomb) { explode(b.x, b.z); done = true; } }
|
||||
if (!done && b.y < 1.9) for (let j = zombies.length - 1; j >= 0; j--) {
|
||||
if (zombies[j].variant === 'r' || Math.hypot(zombies[j].x - b.x, zombies[j].z - b.z) >= 0.55) continue;
|
||||
if (b.bomb) { explode(b.x, b.z); done = true; }
|
||||
else if ((b.pow || 0) >= TUNE.zSplatQ) { killZombie(j, !!G.horde); b.vx *= TUNE.zPierce; b.vz *= TUNE.zPierce; }
|
||||
break;
|
||||
}
|
||||
if (done || b.bounces >= 2 || b.z < -2 || b.z > 25.5 || Math.abs(b.x) > 7.5) extraBalls.splice(i, 1);
|
||||
}
|
||||
|
||||
for (let i = parts.length - 1; i >= 0; i--) {
|
||||
const p = parts[i];
|
||||
p.t -= dt; p.vy -= 14 * dt;
|
||||
|
||||
Reference in new issue
Block a user