Give the machine, fire, gun and boss their missing sounds
All four synthesized in audio.js like every other weapon: a squeaking castor, a court-wide crackle clock plus a throttled catch, a real gun crack, and one shared boss voice (stomp per stride, wind-up breath, bark, pain, death roar) pitched by body size. Closes OPEN 13 pending the phone verdict. v355. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d05b49909e
commit
74aa209753
5 files changed
+248
-14
No files matched your search
+24
-11
@@ -5,7 +5,7 @@
|
||||
|
||||
import { TUNE, UI, COURT, CONTACT, BOSS_ART, bossArt, WEAPONS, BARREL_WEAPONS, TIN_LEAGUE, woundStage, zombieH, EATEN_BURST, EATEN_SUMMARY, EATEN_FEED_R } from './config.js';
|
||||
import { G, P, O, ball, score, sendBoy, setupPoint } from './state.js';
|
||||
import { splat, moan, pock, thud, zap, voice, boom, swish, shriek, aww, sfx , whoosh, flame } from './audio.js';
|
||||
import { splat, moan, pock, thud, zap, voice, boom, swish, shriek, aww, sfx , whoosh, flame, squeak, crackle, whump, gunshot, stomp, growl } from './audio.js';
|
||||
import { haptic } from './haptics.js';
|
||||
import { drillKill, drillCaught } from './drill.js';
|
||||
import { splatPoint, cheer, spentBall, aside, grantWeapon, endBurst, sliceCarom } from './gameplay.js';
|
||||
@@ -34,13 +34,13 @@ export function popText(x, y, z, text, col) {
|
||||
if (pops.length > 10) pops.shift();
|
||||
}
|
||||
|
||||
let spawnT = 1.5, moanT = 3;
|
||||
let spawnT = 1.5, moanT = 3, crackleT = 0, whumpT = 0, bossHurtT = 0; // the last three throttle the fire and boss sounds
|
||||
|
||||
// Every body, decal and projectile on the court, plus this module's own timers. Called by
|
||||
// gameplay.restartRun — a restart is in-place now, not a page reload.
|
||||
export function resetZombies() {
|
||||
for (const a of [zombies, gore, piles, parts, pellets, extras, shots, pops]) a.length = 0;
|
||||
spawnT = 1.5; moanT = 3; mgTick = 0; lastBarrel = null;
|
||||
spawnT = 1.5; moanT = 3; crackleT = whumpT = bossHurtT = 0; mgTick = 0; lastBarrel = null;
|
||||
}
|
||||
|
||||
export function spawnZombie(x, z, variant) {
|
||||
@@ -253,7 +253,9 @@ function bossAI(zb, dt) {
|
||||
zb.x = Math.max(-TUNE.bossPaceX - 0.6, Math.min(TUNE.bossPaceX + 0.6, zb.x)); // never off the sides
|
||||
// The stride steps off GROUND COVERED (the brute's rule). A strafing boss read off zb.z alone
|
||||
// would slide sideways with both feet frozen — the exact defect the distance rule exists to stop.
|
||||
zb.dist = (zb.dist || 0) + Math.hypot(zb.x - x0, zb.z - z0);
|
||||
const d0 = zb.dist || 0;
|
||||
zb.dist = d0 + Math.hypot(zb.x - x0, zb.z - z0);
|
||||
if (Math.floor(zb.dist / TUNE.bruteStride) !== Math.floor(d0 / TUNE.bruteStride)) stomp(0.35, bodySize(zb)); // one per drawn stride
|
||||
}
|
||||
|
||||
// HE CALLS SECURITY. A design with `adds` puts bodies on the court while you fight it — the one
|
||||
@@ -487,7 +489,7 @@ function bossDeath(zb) {
|
||||
G.zoom = { x: zb.x, z: zb.z, k: 1.22, t: 0, T: 0.8 };
|
||||
G.bossBurst = { x: zb.x, z: zb.z, t: 0.75 }; // the shockwave ring render.js draws
|
||||
G.shake = { t: 0.5, mag: 7 }; G.boomFlash = 0.12;
|
||||
boom(); splat(1, bodySize(zb)); cheer(4, 1); voice('unbelievable', 0.5); // the biggest body in the game, at its lowest
|
||||
boom(); splat(1, bodySize(zb)); growl(0.45, bodySize(zb), 1.4, 0.4); cheer(4, 1); voice('unbelievable', 0.5); // the biggest body in the game, at its lowest
|
||||
const h = 1.8 * (zb.scale || 1.5);
|
||||
for (let k = 0; k < 90; k++) parts.push({ // on top of killZombie's own burst: a full body's worth
|
||||
x: zb.x, y: h * (0.2 + Math.random() * 0.7), z: zb.z,
|
||||
@@ -595,7 +597,10 @@ function ignite(x, z, r) {
|
||||
// so fire is a real answer to a big body instead of a no-op
|
||||
// remember WHAT lit it: a body that burns to death is credited to the flamer or the fire ball
|
||||
// that started it, not to whatever happens to be firing when it finally goes down
|
||||
if (Math.hypot(zb.x - x, zb.z - z) < r) { zb.burnT = TUNE.burnTime; zb.burnBy = CAUSE; if (zb.hp === undefined) zb.hp = 1; }
|
||||
if (Math.hypot(zb.x - x, zb.z - z) < r) {
|
||||
zb.burnT = TUNE.burnTime; zb.burnBy = CAUSE; if (zb.hp === undefined) zb.hp = 1;
|
||||
if (whumpT <= 0) { whump(); whumpT = 0.25; } // an epidemic lights many a frame: say it once
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -612,6 +617,8 @@ function wound(zb, amount, fx = true) {
|
||||
let took = 0;
|
||||
while (zb.chip >= 1 && zb.hp > 0) { zb.chip -= 1; zb.hp--; took++; }
|
||||
if (took && fx) { splat(0.5, bodySize(zb)); woundBurst(zb); }
|
||||
// the boss FEELS it — throttled, because the gun chips it a dozen times a second
|
||||
if (took && zb.variant === 'B' && bossHurtT <= 0) { growl(0.3, bodySize(zb), 0.45, 0.6); bossHurtT = 0.9; }
|
||||
// A PHASED BOSS TURNING IS AN EVENT, not a silent change of numbers. Same body, new fight: the
|
||||
// crowd comes up, the court shakes, and he says which set this is. Without the beat the champion's
|
||||
// second gear would arrive as "the boss suddenly got faster" and read as the game glitching.
|
||||
@@ -811,7 +818,7 @@ export function firePellet(w = WEAPONS.gun) {
|
||||
// rate 30 = 10 overlapping 0.26 s puffs a second, which sums to a continuous roar; firing one per
|
||||
// round just stacks 30 gains on top of each other and clips.
|
||||
else if (w.sfx === 'flame') { if (++mgTick % 3 === 0) flame(0.5); }
|
||||
else if (++mgTick % 2 === 0) pock(0.25, w.ignite ? 300 : 1400);
|
||||
else if (++mgTick % 2 === 0) gunshot(); // the one ignite weapon is the flamer, which has its own case
|
||||
}
|
||||
const HUES = ['#ff6b6b', '#ffd166', '#7bffa0', '#6bd5ff', '#c08bff', '#ff8bd5'];
|
||||
// The next body a slice is struck on to: nearest live thing to the kill it just made, and it may
|
||||
@@ -1007,7 +1014,12 @@ export function updateParts(dt) {
|
||||
}
|
||||
|
||||
export function updateZombies(dt) {
|
||||
moanT -= dt;
|
||||
moanT -= dt; whumpT -= dt; bossHurtT -= dt;
|
||||
// FIRE HAS A SOUND OF ITS OWN: one crackle clock for the whole court, faster and a little louder
|
||||
// the more is burning (sqrt, so a lit wave is dense, not deafening) — never one loop per body.
|
||||
let lit = 0;
|
||||
for (const zb of zombies) if (zb && zb.burnT > 0) lit++;
|
||||
if (lit && (crackleT -= dt) <= 0) { crackle(Math.min(0.26, 0.12 + 0.035 * lit)); crackleT = Math.max(0.07, 0.2 / Math.sqrt(lit)); }
|
||||
if (moanT <= 0) { moan(); moanT = 2.5 + Math.random() * 4; }
|
||||
if (G.net && G.net.death && G.match) bleedO(); // the friend's damage, read as blood (see bleedO)
|
||||
|
||||
@@ -1113,7 +1125,7 @@ export function updateZombies(dt) {
|
||||
} else if (zb.windT > 0) {
|
||||
zb.windT -= dt;
|
||||
if (zb.windT <= 0) {
|
||||
if (boss) { zb.salvo = A?.volley ?? TUNE.bossVolley; zb.salvoT = 0; } // the volley starts next frame
|
||||
if (boss) { zb.salvo = A?.volley ?? TUNE.bossVolley; zb.salvoT = 0; growl(0.32, bodySize(zb), 0.18, 0.6); } // the volley starts next frame, on a bark
|
||||
else fireAtPlayer(zb.x, zb.z, 0, TUNE.machSpeed);
|
||||
}
|
||||
} else if ((zb.fireT -= dt) <= 0) {
|
||||
@@ -1123,7 +1135,8 @@ export function updateZombies(dt) {
|
||||
// with wind > 0.5 spent the first stretch at an invalid negative alpha — full brightness,
|
||||
// no ramp, on the one effect documented as the fairness contract.
|
||||
zb.windT = zb.windMax = boss ? (A?.wind ?? TUNE.bossWind) : TUNE.machWind;
|
||||
pock(0.3, 1500); // the tell: a mechanical click as it loads
|
||||
if (boss) growl(0.32, bodySize(zb), zb.windT, 1.35); // the tell: he draws breath for the whole wind-up
|
||||
else pock(0.3, 1500); // the machine's tell: a mechanical click as it loads
|
||||
}
|
||||
}
|
||||
if (G.mode === 'eaten' && G.eatenAt && zb.variant !== 'm' && G.eatenT < EATEN_SUMMARY) {
|
||||
@@ -1171,7 +1184,7 @@ export function updateZombies(dt) {
|
||||
if (Math.abs(d) < 0.06) { zb.rollTo = undefined; zb.repoT = TUNE.machRepo * (0.7 + Math.random() * 0.6); }
|
||||
else {
|
||||
zb.x += Math.sign(d) * Math.min(Math.abs(d), TUNE.machRoll * dt);
|
||||
if ((zb.rollT -= dt) <= 0) { pock(0.16, 150); zb.rollT = 0.4; } // castors grinding on the clay
|
||||
if ((zb.rollT -= dt) <= 0) { pock(0.16, 150); if (Math.random() < 0.6) squeak(); zb.rollT = 0.4; } // castors grinding, and one of them squeaks: it announces itself
|
||||
}
|
||||
} else if ((zb.repoT -= dt) <= 0) { zb.rollTo = (Math.random() * 2 - 1) * 3.6; zb.rollT = 0; }
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user