Serve reads, the mop beat, the head-icon card, and Daniel benched
Oliver's batch, the code half: - The tennis serve lost its ghost fingers (idle tap demo + apex rider deleted with their counters; the rally-return hint survives). - The toss rises over the tossing HAND, measured off the toss frames' raised fingertips (TOSS.dx/oDx, women's tour override) — not over the head. - An uncaught toss returns to the bounce stance instead of flashing the smash frame the player never earned. - Between match games the court belongs to the mop boy: 5 s (TUNE.matchMopT), full job allowance, a hurry speed between stains, and the far-court reach limit lifted for the match (his stains are exactly where opponents die). The next opponent walks on only after — d.ending guards the empty court. - The walk-on card wears the opponent's own head: config.HEAD_CROP, straight crops measured off the family sheets, no new art. - The match intro is DEATH MATCH, counting the real blood pool. - Daniel is benched: every ump_* personality line routes through aside()'s early return; officiating calls keep speaking. Personality pass later. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0e01894f4d
commit
c084d53497
8 files changed
+141
-70
No files matched your search
+27
-12
@@ -1,7 +1,7 @@
|
||||
// Game logic: ballistics, swings, serves, rules/scoring, AI, and the per-frame update.
|
||||
|
||||
import { TUNE, UI, bossArt, APPLAUSE_T, EATEN_BURST, EATEN_SUMMARY, COURT, CONTACT, VOLLEY, ZONE_DEPTH, HOME, P_BOUNDS, TOSS, SHOT_DEPTH, POSE_TIME, BOUNCE_IDLE, zombieH, Z_CHEST , WEAPONS, RACK_MAX , BARREL_WEAPONS } from './config.js';
|
||||
import { P, O, ball, score, crowd, G, court, other, deuceSide, setupPoint, sendBoy, resetState } from './state.js';
|
||||
import { P, O, ball, score, crowd, G, court, tour, other, deuceSide, setupPoint, sendBoy, resetState } from './state.js';
|
||||
import { audio, pock, swish, whoosh, applause, voice, sfx, aww, zombieMusic, zombieDuck, menuDuck, splat, shriek, boom } from './audio.js';
|
||||
import { axisX, axisZ, takeFlickAim, touchPX, resetInput } from './input.js';
|
||||
import { W } from './render.js'; // runtime-only cycle (render imports gameplay), same pattern as input/zombies
|
||||
@@ -232,11 +232,11 @@ function doHit(ch, quality, aim) {
|
||||
// ----------------------------------------------------------------- serve ----
|
||||
function tossBall(sv) {
|
||||
ball.active = true; ball.bounces = 0; ball.collected = false;
|
||||
// The player is right-handed: he tosses with his LEFT hand (viewer's left) and hits with the
|
||||
// racket on the viewer's right, so the ball goes up almost over his head — a big offset either way
|
||||
// lands it on the wrong side of one of those two frames. The opponent is drawn from the front and
|
||||
// keeps the reach he was drawn with.
|
||||
ball.x = sv.x + (sv.side === 'P' ? 0.12 : -0.35); ball.z = sv.z; ball.y = TOSS.y0;
|
||||
// The ball rises over the tossing HAND, not the head — TOSS.dx/oDx are measured off the toss
|
||||
// frames' raised fingertips (see config). `* facing` because drawChar mirrors the whole sprite
|
||||
// when the server faces left, exactly like the idle bounce's offset.
|
||||
ball.x = sv.x + sv.facing * (sv.side === 'P' ? (tour.tossDx ?? TOSS.dx) : TOSS.oDx);
|
||||
ball.z = sv.z; ball.y = TOSS.y0;
|
||||
ball.vx = 0; ball.vz = 0; ball.vy = TOSS.vy; ball.g = TUNE.gravity;
|
||||
// THE TOSS IS THE SERVER'S BALL. lastHit was left stale from the previous rally, and if the
|
||||
// OPPONENT had touched it last, the freshly tossed ball read as *their* ball in flight — which
|
||||
@@ -424,7 +424,6 @@ function hitServe(sv, quality, aim = null, perfect = quality > 0.999) {
|
||||
// (spendSlot already hands you back plain balls when the last one goes)
|
||||
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 (sv.side === 'P') G.tossDrops = 0; // a served toss ends the apex lesson
|
||||
if (perfect && sv.side === 'P') {
|
||||
G.hitstop = 0.05;
|
||||
// the word is ARMED here and pops at the IMPACT — first body the ball touches, or its first
|
||||
@@ -474,6 +473,13 @@ export function dismissModal() {
|
||||
// and not situation aware.. they seem random". `aside()` is the single entry point and it enforces
|
||||
// one shared cooldown (TUNE.asideEvery), so several triggers firing at once still yields one remark.
|
||||
export function aside(name, delay = 0.2) {
|
||||
// DANIEL IS BENCHED (2026-08-13). Oliver: "daniel is very repetetive. please let him just
|
||||
// announce the normal tennis game events for now.. this is a task for another time." So the
|
||||
// umpire keeps every OFFICIATING call (score, game/set, fault, out, let, time, play) and loses
|
||||
// ALL personality asides — this early return is the whole cut, since every ump_* remark routes
|
||||
// through here. The situation triggers and the cooldown model below survive intact for the
|
||||
// personality pass that will re-open him; do not delete them.
|
||||
return;
|
||||
// THE MATCH IS THE EXCEPTION to the interlude bar, exactly as sendBoy is: the match runs on a
|
||||
// PERMANENT interlude, so this line silently muted the umpire for an entire mode — the one mode
|
||||
// that is literally a tennis match he is sitting over. The shared asideEvery cooldown still holds,
|
||||
@@ -1294,7 +1300,10 @@ function mopRound(b, dt) {
|
||||
if (b.tx === undefined && b.jobs > 0) {
|
||||
let best = null, bs = -1;
|
||||
for (const d of gore) {
|
||||
if (d.a < 0.15 || d.z < COURT.netZ - 2) continue; // faded, or up the far end where he can't work
|
||||
// faded, or up the far end where he can't work — EXCEPT in the match, where the far end is
|
||||
// exactly where every opponent dies: the mop beat holds an empty court for him, so the
|
||||
// gore-mode reach limit would leave him jobless two steps from the mess he was sent for
|
||||
if (d.a < 0.15 || (!G.match && d.z < COURT.netZ - 2)) continue;
|
||||
const score = d.a * d.r - Math.hypot(d.x - b.x, d.z - b.z) * 0.04; // big and close first
|
||||
if (score > bs) { bs = score; best = d; }
|
||||
}
|
||||
@@ -1304,7 +1313,10 @@ function mopRound(b, dt) {
|
||||
const working = b.tx !== undefined && b.jobs > 0;
|
||||
const gx = working ? b.tx : b.dir * 14, gz = working ? b.tz : b.z; // no target left: walk off the wing
|
||||
const dx = gx - b.x, dz = gz - b.z, d = Math.hypot(dx, dz);
|
||||
const step = Math.min(TUNE.boyMopSpeed * dt, d);
|
||||
// during the match's mop beat he HURRIES between stains (the beat is his stage and it is 5 s
|
||||
// long; at walking pace he spent 4 of them getting into shot) — the push itself keeps its pace
|
||||
const spd = G.match && G.match.mopT > 0 ? TUNE.boyHurry : TUNE.boyMopSpeed;
|
||||
const step = Math.min(spd * dt, d);
|
||||
if (d > 0.001) { b.x += dx / d * step; b.z += dz / d * step; b.dist = (b.dist || 0) + step; }
|
||||
if (Math.abs(dx) > 0.05) b.dir = dx > 0 ? 1 : -1; // face the way he's walking
|
||||
if (working && d <= 0.12) { b.mopT = TUNE.boyMopDwell; b.tx = undefined; b.tz = undefined; } // arrived: get to work
|
||||
@@ -1671,10 +1683,13 @@ export function update(dt) {
|
||||
}
|
||||
if (G.phase === 'toss' && ball.y <= 1.4 && ball.vy < 0 && !G.aim) { // uncaught toss → retoss (a held slingshot aim keeps the toss up)
|
||||
ball.active = false; G.phase = 'pretoss';
|
||||
// an uncaught toss is NOT a swing. The 'serve' pose used to survive into pretoss, and
|
||||
// drawChar's pick (phase !== 'toss' → smash) flashed the SMASH frame at a ball the player
|
||||
// never hit — Oliver: "the smash frame is always played even if I did not tap a second
|
||||
// time". Back to idle, where the bounce stance picks him up.
|
||||
const sv = score.server === 'P' ? P : O;
|
||||
if (sv.pose === 'serve') { sv.pose = 'idle'; sv.poseT = 0; }
|
||||
if (score.server === 'O') G.aiServeT = 0.8;
|
||||
// dropping the toss twice running means the second tap isn't landing — the ghost hand then
|
||||
// rides the next toss and presses at the apex (render.drawApexHint)
|
||||
else if (G.mode === 'tennis') G.tossDrops++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user