Sound: recorded SFX layer + a touch-easier CPU
Wire the new assets/audio/*.wav samples over the synth SFX: - Ball hits: per-side tennis-player / tennis-opponent on serve & return, now with a stadium reverb tail (same convolver as umpire/aww). - Crowd: tennis-applaus on your points/games (louder on big moments); tennis-aww when the opponent scores or you're eaten (reverb tail). - Ambient bed: tennis-ambient-loop from first tap, 500Hz lowcut, low vol. - Zombie music: zombie-mode loop when splatter kicks in (800Hz lowcut), fades out for the 1:1 sudden-death duel and back in after. - Headshots: random headshot-1/-2 on every zombie kill (killZombie). audio.js: sfx() one-shot player w/ rate+reverb opts, loopBed() beds with duck/fade, aww() wrapper, hotter reverb send on the umpire voice. Also: normal opponent aiFlub 0.09 -> 0.14 (a little easier to beat). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
88e46a62b8
commit
eacad1ffca
13 files changed
+75
-13
No files matched your search
+10
-7
@@ -2,7 +2,7 @@
|
||||
|
||||
import { TUNE, COURT, CONTACT, VOLLEY, ZONE_DEPTH, HOME, P_BOUNDS, TOSS, SHOT_DEPTH, POSE_TIME, BOUNCE_IDLE } from './config.js';
|
||||
import { P, O, ball, score, crowd, G, other, deuceSide, setupPoint, sendBoy } from './state.js';
|
||||
import { audio, pock, swish, applause, voice } from './audio.js';
|
||||
import { audio, pock, swish, applause, voice, sfx, aww, zombieMusic, zombieDuck } from './audio.js';
|
||||
import { axisX, axisZ, takeFlickAim, touchPX } from './input.js';
|
||||
import { W } from './render.js'; // runtime-only cycle (render imports gameplay), same pattern as input/zombies
|
||||
import { updateZombies, spawnZombie, spawnRacketZombie, sweepCourt, zombies, explode, meleeSwipe, extras, parts } from './zombies.js';
|
||||
@@ -106,7 +106,7 @@ function doHit(ch, quality, aim) {
|
||||
G.outMarker = { x: tx, z: tz, out: !ok, t: 1.3 };
|
||||
G.aiReactT = 0; // CPU hasn't read the shot yet
|
||||
}
|
||||
pock(0.9, 950);
|
||||
sfx(ch.side === 'P' ? 'tennis-player' : 'tennis-opponent', 0.9, { reverb: true }); // recorded racket strike, per side
|
||||
if (quality > 0.999) { G.hitstop = 0.05; G.flash = { text: 'PERFECT', t: 0.7 }; pock(0.5, 1600); }
|
||||
ch.pose = 'swing'; ch.poseT = 0;
|
||||
}
|
||||
@@ -162,7 +162,7 @@ function hitServe(sv, quality, aim = null) {
|
||||
}
|
||||
}
|
||||
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);
|
||||
G.phase = 'rally'; sfx(sv.side === 'P' ? 'tennis-player' : 'tennis-opponent', 0.9, { reverb: true }); // recorded serve strike, per side
|
||||
sv.pose = 'serve'; sv.poseT = 0;
|
||||
if (quality > 0.999) { G.flash = { text: 'PERFECT', t: 0.7 }; G.hitstop = 0.05; }
|
||||
}
|
||||
@@ -180,7 +180,7 @@ function scoreKey() {
|
||||
}
|
||||
|
||||
export function cheer(seconds, vol) {
|
||||
applause(seconds, vol);
|
||||
sfx('tennis-applaus', Math.min(1.5, vol * 2.2)); // recorded crowd; vol scales with the size of the moment (gain > 1 amplifies)
|
||||
crowd.cheer = Math.max(crowd.cheer, seconds);
|
||||
}
|
||||
|
||||
@@ -203,6 +203,7 @@ function pointTo(side, reason) {
|
||||
const gameOver = advancePoint(side);
|
||||
G.phase = 'over'; G.overT = gameOver ? 3.2 : 2.2;
|
||||
sendBoy();
|
||||
if (side === 'O') aww(0.8); // you dropped the point — the crowd sighs
|
||||
if (reason) voice(reason);
|
||||
if (gameOver) {
|
||||
if (G.mode === 'tennis' && side === 'P' && !G.everTransitioned) return startTransition();
|
||||
@@ -232,7 +233,7 @@ export function splatPoint(side, quiet = false) {
|
||||
(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);
|
||||
if (side === 'P') cheer(2.2, 0.5); else aww(0.85); // your game = applause, theirs = a groan
|
||||
sweepCourt(); // the wave is OVER: survivors collapse where they stand — a game ends visibly
|
||||
ball.active = false; // the beat owns the court
|
||||
G.pendingGame = true; // 'GAME N' announces the next wave when play resumes
|
||||
@@ -282,12 +283,13 @@ function deliverTin() {
|
||||
export function resolveInterlude(side) {
|
||||
if (!G.interlude) return;
|
||||
G.interlude = false;
|
||||
zombieDuck(false); // duel over — bring the music back up
|
||||
ball.active = false; G.phase = 'over'; G.overT = 2.2;
|
||||
if (side === 'P') {
|
||||
cheer(3, 0.7);
|
||||
deliverTin(); // win the point, win the next tin — on the spot
|
||||
} else {
|
||||
voice('game_opponent', 0.4);
|
||||
voice('game_opponent', 0.4); aww(0.85); // lost the sudden-death duel
|
||||
score.games[1]++; G.runGames++; G.shameGame = true; // it goes on the scoreboard
|
||||
if (score.games[1] >= 6) { G.sets[1]++; score.games = [0, 0]; }
|
||||
G.flash = { text: 'GAME. THEM.', t: 1.6 };
|
||||
@@ -339,7 +341,7 @@ function transition(dt) {
|
||||
once('turn', 3.4, () => { spawnZombie(O.x, O.z, 'c'); G.hideO = true; }); // the opponent... turns
|
||||
once('mob', 4.8, () => { spawnZombie(-3.5, -1.6, 'a'); spawnZombie(2.5, -2.1, 'b'); });
|
||||
once('seated', 5.4, () => voice('remain_seated', 0, 0.95));
|
||||
once('go', 8.2, () => { G.mode = 'splatter'; score.server = 'P'; setupPoint(); voice('play', 0.4, 0.9); });
|
||||
once('go', 8.2, () => { G.mode = 'splatter'; score.server = 'P'; setupPoint(); voice('play', 0.4, 0.9); zombieMusic(); });
|
||||
}
|
||||
|
||||
function fault() {
|
||||
@@ -653,6 +655,7 @@ export function update(dt) {
|
||||
&& G.phase === 'pretoss' && zombies.length === 1 && zombies[0].variant === 'r' && !zombies[0].dueled) {
|
||||
zombies[0].dueled = true; // one duel per opponent — after that it just rallies
|
||||
G.interlude = { introT: 2.4 }; // the intro beat: serving blocked, centered card, colors return
|
||||
zombieDuck(true); // fade the zombie music out for the 1:1 duel
|
||||
voice('play_resume', 0.2); voice('sudden_death', 2.4);
|
||||
}
|
||||
if (G.interlude && G.interlude.introT > 0) G.interlude.introT -= dt;
|
||||
|
||||
Reference in new issue
Block a user