Let the tour be a choice, and give her a body of her own
The court screen picks WHO as well as WHERE now: two tiles above the court rows, men's or women's singles, remembered like the court is. No new stage — three rows and a pair of buttons already fill a phone, so the tiles go side by side and the tap count from splash to play does not change. Everything downstream is one line: drawChar already built every frame name off a single prefix, so SK() appends '_f' when the women's tour is picked and falls straight through to the men's frame for anything without a twin. The horde, the officials and the ball boys are shared — they are staff, not the draw — but the turned CPU is not: a male corpse opposite a female player reads as a bug. Her frames came back as a different woman in every batch — blonde in one, auburn in the next, greyscale line art in a third — because each was anchored on the MAN whose pose it was. "newart" repaints the character and "state" is welded to the corpse palette, so neither could say "the same living woman, a new pose". That mode now exists, and every batch anchors on her own settled frame. The death is drawn to frame 3 and recoloured after it. The generator will not draw a dead woman in any wording worth using; frames 4 and 5 survive that because by then she is flat and then a heap of cloth, so they are the men's drawing with the blue shorts painted into her white (see assets/_die_f.py). The collapse still falls 224/159/117/103/43 straight through the handoff. Her first bounce frames reached to the shoulder, which is a catching pose, not patting a ball — redrawn low rather than bending BOUNCE_IDLE to fit a wrong drawing, so both tours share one apex and the ball meets her palm. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c75bd5b9ba
commit
14a1e5d261
46 files changed
+295
-24
No files matched your search
@@ -2,7 +2,7 @@
|
||||
|
||||
import { cv, ctx } from './dom.js';
|
||||
import { TUNE, UI, EATEN_GRAB, EATEN_BURST, EATEN_SUMMARY, EATEN_RESTART, COURT, P_BOUNDS, PLAYER_H, BOY_H, UMP_H, UMP_X, MACH_H, BOSS_ART, ZWOUND, woundStage, BOUNCE_IDLE, SPRITE_H, zombieH , WEAPONS, RACK_ROW } from './config.js';
|
||||
import { P, O, ball, score, crowd, G, court } from './state.js';
|
||||
import { P, O, ball, score, crowd, G, court, tour } from './state.js';
|
||||
import { predictLanding, previewServe, slotId, slotStock } from './gameplay.js';
|
||||
import { zombies, gore, piles, parts, pellets, extras, shots } from './zombies.js';
|
||||
|
||||
@@ -198,7 +198,7 @@ function drawRunOff() {
|
||||
|
||||
// ---------------------------------------------------------------- sprites ----
|
||||
// assets/<name>.png; game falls back to code-drawn figures until sprites load.
|
||||
const SPRV = 82; // asset cache-buster — bump with main.js?v=N (see the img.src note below)
|
||||
const SPRV = 83; // asset cache-buster — bump with main.js?v=N (see the img.src note below)
|
||||
const SPR = {};
|
||||
for (const n of ['p_idle1','p_idle2','p_bounce','p_bounce2','p_toss','p_smash','p_run1','p_run2','p_run3','p_run4','p_swing1','p_swing2','p_die1','p_die2','p_die3','p_die4','p_die5',
|
||||
'p_pull', // the serve wind-up: the racket dropping behind him while the pull is drawn
|
||||
@@ -234,6 +234,21 @@ for (const n of ['p_idle1','p_idle2','p_bounce','p_bounce2','p_toss','p_smash','
|
||||
'w_ball','w_bomb','w_triple','w_fire','w_gun', // weapon-rack icons
|
||||
'w_beacon','w_laser','w_rocket','w_flamer', // ...and the four that come out of a barrel
|
||||
'z_barrel1','z_barrel2', // the weapon barrel, two frames so it ROLLS
|
||||
// THE WOMEN'S TOUR. Same poses, same 224 px anchor, a different body — see SK().
|
||||
// Only the two players and the turned CPU have a twin; everything above is shared.
|
||||
// No o_run3_f/o_run4_f on purpose: runN reads the MEN'S table, where o_run4 is not
|
||||
// preloaded, so the opponent runs on two frames whichever tour you picked.
|
||||
'p_idle1_f','p_idle2_f','p_bounce_f','p_bounce2_f','p_toss_f','p_smash_f','p_pull_f',
|
||||
'p_run1_f','p_run2_f','p_run3_f','p_run4_f','p_swing1_f','p_swing2_f',
|
||||
// The death is DRAWN for frames 1-3 and RECOLOURED for 4-5. The generator refuses to
|
||||
// draw a dead woman in any wording worth using, and frames 4-5 survive that fine —
|
||||
// she is flat and face down, then a heap of torn cloth — so those two are the men's
|
||||
// drawing with the blue shorts painted over in her skirt's white. The one thing you
|
||||
// could actually see was blue popping in after three frames of white.
|
||||
'p_die1_f','p_die2_f','p_die3_f','p_die4_f','p_die5_f',
|
||||
'o_idle1_f','o_idle2_f','o_bounce_f','o_bounce2_f','o_toss_f','o_smash_f',
|
||||
'o_run1_f','o_run2_f','o_swing1_f','o_swing2_f',
|
||||
'z_c1_f','z_c2_f','z_c3_f','z_c4_f','z_cw1_f','z_cw2_f','z_cw1b_f','z_cw2b_f',
|
||||
'scoreboard']) {
|
||||
const img = new Image();
|
||||
img.onload = () => SPR[n] = img;
|
||||
@@ -495,6 +510,9 @@ function drawZombie(zb) {
|
||||
// project's oldest trap) is enough to cause it. Hold frame 1 instead: a body that stops animating
|
||||
// is a bug you can see and diagnose; one that flickers in and out reads as the game breaking.
|
||||
if (!SPR[name] && SPR['z_' + v + '1']) name = 'z_' + v + '1';
|
||||
// ...and only NOW the tour swap, so the height read below comes off the frame actually drawn. The
|
||||
// branches above all test the men's names (which always exist), so the wound logic is untouched.
|
||||
name = SK(name);
|
||||
// Every frame is drawn at ITS OWN height. drawStatic forces the height it is given, so a crouched
|
||||
// frame sliced 13 px shorter than its neighbours was being stretched back to full size — the body
|
||||
// grew and shrank once per stride. Oliver: "the brute is still pulsating while walking". The rule
|
||||
@@ -852,6 +870,13 @@ function drawPiles(near) { // split at the net like zombies so height occludes c
|
||||
drawGorePile(pl);
|
||||
}
|
||||
|
||||
// THE TOUR SWAP. Everything above builds a sprite NAME the same way it always did; this is the one
|
||||
// place the women's set exists. Her frames are the men's name + '_f', and a name with no twin — or
|
||||
// one whose PNG has not loaded yet — falls straight through to the men's frame. That fallback is
|
||||
// deliberate: a cached 404 on one female frame degrades to a man for a frame, it does not blink the
|
||||
// body out of existence (the same rule drawZombie's missing-frame guard is built on).
|
||||
const SK = n => (tour.sfx && SPR[n + tour.sfx]) ? n + tour.sfx : n;
|
||||
|
||||
function drawChar(ch) {
|
||||
// No split-step. The receiver used to be slid 0.12 m side to side while waiting on a serve, on top
|
||||
// of a swap to the run frames; both are gone. Sliding a sprite sideways is not footwork — it reads
|
||||
@@ -884,7 +909,7 @@ function drawChar(ch) {
|
||||
if (ch.pose === 'swing') name = pre + (ch.poseT < 0.15 ? 'swing1' : 'swing2');
|
||||
if (ch.pose === 'serve') name = pre + (G.phase === 'toss' ? 'toss' : 'smash');
|
||||
if (ch.pose === 'run') p.y -= Math.floor(ch.runT * 18) % 2; // 1px run bob
|
||||
const img = SPR[name];
|
||||
const img = SPR[SK(name)];
|
||||
drawShadow(p.x, p.y, hpx * 0.22, hpx * 0.06);
|
||||
if (img) {
|
||||
const k = hpx / SPRITE_H; // sprites share one scale; the idle pose defines it
|
||||
@@ -915,7 +940,7 @@ function drawDying() {
|
||||
if (t >= EATEN_BURST) return; // he is the pile now
|
||||
const k = t / EATEN_BURST;
|
||||
let i = 0; while (i + 1 < DIE_AT.length && k >= DIE_AT[i + 1]) i++;
|
||||
const img = SPR['p_die' + (i + 1)];
|
||||
const img = SPR[SK('p_die' + (i + 1))];
|
||||
const p = proj(at.x, 0, at.z);
|
||||
const j = i < 2 ? 1.6 : i < 4 ? 0.7 : 0; // he stops fighting on the way down
|
||||
ctx.save();
|
||||
|
||||
Reference in new issue
Block a user