Gore opens in the gore, the theme comes back, and every mode gets a board
Splatter gore no longer makes you win a game of tennis first — the mode you pick is the mode you get, with the man you came to play already turned. The transition itself is untouched and still reachable; it just isn't a toll booth. The menu theme could only ever be started, never brought back, so the first run of a session silenced it for good while the court's ambience followed you into the menu. Starting a bed now un-ducks it too. Three world boards instead of one: kills, opponents felled, and games taken off the CPU, each with its own name prompt and its own row per player. ...and the gore economy stops paying a barrel for every game won past game 16, while the shamblers, the mix and the ball machines keep escalating past the point they all used to stop. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
9cc3ac54b3
commit
3cf3e029a4
11 files changed
+440
-88
No files matched your search
+52
-10
@@ -535,6 +535,16 @@ export function cheer(seconds, vol) {
|
||||
crowd.cheer = Math.max(crowd.cheer, seconds, APPLAUSE_T);
|
||||
}
|
||||
|
||||
// Games between weapon barrels. Exported for the suite, because the shape of this curve IS the
|
||||
// gore mode's economy and it is one line of arithmetic that once quietly became "one per game".
|
||||
// The floor is the point: however long a run gets, a barrel stays something you waited for.
|
||||
export const barrelGap = () => Math.max(TUNE.barrelFloor,
|
||||
TUNE.barrelEvery - Math.floor(G.runGames / TUNE.barrelRamp));
|
||||
// ...and how many ball machines the late court is allowed to hold at once. Same idea from the other
|
||||
// end: the barrels stop growing, this one starts.
|
||||
export const machCap = () => Math.min(TUNE.machMaxCap,
|
||||
TUNE.machMax + Math.floor(G.runGames / TUNE.machMaxPer));
|
||||
|
||||
export function advancePoint(side) { // shared scoring math; returns whether a game was won
|
||||
const i = side === 'P' ? 0 : 1, j = 1 - i;
|
||||
score.pts[i]++;
|
||||
@@ -574,7 +584,11 @@ export function pointTo(side, reason) { // exported for net.js: a remote verdict
|
||||
voice(side === 'P' ? 'game_player' : 'game_opponent', 0.7);
|
||||
cheer(2.8, 0.6);
|
||||
score.server = other(score.server);
|
||||
if (G.classic) { G.runGames++; if (classicGame(side)) return; } // ...then a set, then the match
|
||||
if (G.classic) {
|
||||
G.runGames++;
|
||||
if (side === 'P') G.classicWon++; // the number its board ranks
|
||||
if (classicGame(side)) return; // ...then a set, then the match
|
||||
}
|
||||
} else {
|
||||
voice(scoreKey(), 0.7);
|
||||
if (side === 'P') cheer(1.2, 0.3);
|
||||
@@ -612,11 +626,15 @@ export function endClassic(won) {
|
||||
ball.active = false; G.flash = null; G.modal = null; G.card = null;
|
||||
if (won) { cheer(4, 1); applause(1, 0.2); voice('unbelievable', 0.4); }
|
||||
else { aww(1); voice('game_opponent', 0.3); }
|
||||
// g = GAMES PLAYED, s = SETS YOU WON — this mode's two headline numbers in the record's two slots.
|
||||
// g = GAMES PLAYED (what the stats screen calls LONGEST MATCH), s = GAMES YOU WON — this mode's
|
||||
// two headline numbers in the record's two slots. `s` was SETS until the classic board arrived,
|
||||
// and nothing ever displayed it: the sets line on the death screen is gore's, and read() has
|
||||
// never looked at `s` at all. Games won is the number worth ranking, because every classic win is
|
||||
// two sets and a board of nothing but 2s is a wall of ties.
|
||||
// `dead` carries the RESULT: a match you lost is the run ending badly, a match you won is not,
|
||||
// so the stats screen's DEATHS column reads as matches lost and (runs − deaths) as matches won,
|
||||
// with no extra field to keep in step.
|
||||
const b = endRun(G.runGames, G.sets[0], G.splatT, !won, MODE_CLASSIC);
|
||||
const b = endRun(G.runGames, G.classicWon, G.splatT, !won, MODE_CLASSIC);
|
||||
G.best = b; G.newBest = b.newBest;
|
||||
}
|
||||
|
||||
@@ -671,12 +689,12 @@ export function splatPoint(side, quiet = false) {
|
||||
spawnRacketZombie();
|
||||
// the rogue ball machine gets wheeled out on the same kind of schedule, capped on court
|
||||
if (side === 'P' && G.runGames >= TUNE.machEvery && G.runGames % TUNE.machEvery === 0
|
||||
&& zombies.filter(z => z.variant === 'm').length < TUNE.machMax && !G.bossFight)
|
||||
&& zombies.filter(z => z.variant === 'm').length < machCap() && !G.bossFight)
|
||||
spawnMachine();
|
||||
// ...and a weapon barrel on its own schedule — which TIGHTENS as the run goes on (barrelRamp),
|
||||
// because the late game is where the rack has to carry you. Still one at a time: two glowing
|
||||
// pink things on court would out-shout everything the palette uses to say "this one is dangerous".
|
||||
if (side === 'P' && G.runGames % Math.max(1, TUNE.barrelEvery - Math.floor(G.runGames / TUNE.barrelRamp)) === 0
|
||||
if (side === 'P' && G.runGames % barrelGap() === 0
|
||||
&& !zombies.some(z => z.variant === 'o') && !G.bossFight)
|
||||
spawnBarrel();
|
||||
// The wave does NOT auto-collapse anymore: the survivors stay and must be cleared BY HAND before
|
||||
@@ -924,16 +942,40 @@ function transition(dt) {
|
||||
once('mob', 4.8, () => { spawnZombie(-3.5, -1.6, 'a'); spawnZombie(2.5, -2.1, 'b'); G.shake = { t: 0.5, mag: 6 }; });
|
||||
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); zombieMusic();
|
||||
enterSplatter();
|
||||
G.shake = { t: 0.7, mag: 9 }; // the floor goes out from under the match
|
||||
// NO modal here anymore — nobody read it (Oliver's testteam, verbatim). The ghost hand teaches
|
||||
// the pull instead: prime the idle clock so the apron demo starts miming IMMEDIATELY rather
|
||||
// than after the usual beat of inaction, and it keeps coming back whenever the player stalls.
|
||||
G.idleHintT = TUNE.pullHintAfter;
|
||||
G.flash = { text: 'THE MATCH HAS CHANGED', t: 2.0 };
|
||||
});
|
||||
}
|
||||
|
||||
// WHERE THE SPLATTER MODE ACTUALLY BEGINS. Shared by the transition's last beat and by the gore
|
||||
// mode's own start, so the two doors cannot land the player in two slightly different games.
|
||||
// NO modal here — nobody read it (Oliver's testteam, verbatim). The ghost hand teaches the pull
|
||||
// instead: the idle clock is primed so the apron demo starts miming IMMEDIATELY rather than after
|
||||
// the usual beat of inaction, and it keeps coming back whenever the player stalls.
|
||||
function enterSplatter() {
|
||||
G.mode = 'splatter'; G.everTransitioned = true;
|
||||
score.server = 'P'; setupPoint();
|
||||
voice('play', 0.4, 0.9); zombieMusic();
|
||||
G.idleHintT = TUNE.pullHintAfter;
|
||||
}
|
||||
|
||||
// THE GORE MODE STARTS IN THE GORE (Oliver, 2026-08-15: "the splatter gore should not have the
|
||||
// normal tennis intro anymore, we should just start right in splatter mode"). The prologue was a
|
||||
// game of real tennis you had to win before the mode you picked would begin — fine as a reveal the
|
||||
// first time, a toll booth every time after it. The transition itself is NOT deleted: it is still
|
||||
// what `forceZombies` plays, it is still the cinematic, and `everTransitioned` above keeps it from
|
||||
// firing behind us. What is gone is it standing between the menu and the mode.
|
||||
// The turned opponent still opens the run — he is the man you came to play, and the mode's whole
|
||||
// premise is that he is already one of them.
|
||||
export function startGore() {
|
||||
if (G.mode !== 'tennis') return;
|
||||
enterSplatter();
|
||||
spawnZombie(O.x, O.z, 'c'); G.hideO = true;
|
||||
G.shake = { t: 0.6, mag: 7 };
|
||||
G.flash = { text: 'THE CLUB IS DEAD', t: 2.0 };
|
||||
}
|
||||
|
||||
export function fault() { // exported for net.js: the receiver's fault call re-arms the server's second serve
|
||||
if (G.serveNum === 1) {
|
||||
if (netJudging()) netSendFault(); // netplay: only the first-serve branch wires — the double
|
||||
|
||||
Reference in new issue
Block a user