Burn them to ashes, and give the burst back its bar
A burnt body does not burst. Fire kills by cooking, so there is nothing left to spray: a body that dies alight -- flamethrower or flame ball -- leaves dark grey ash instead of blood. Same decal, mound and particles, recoloured and smaller, with a puff of smoke and a dull thud instead of the wet splat and the headshot crack. The flame BALL has to mark itself, because that kill lands before the body ever catches. The flamethrower is a lighter, not a gun: no impact damage at all, it pierces, and it only IGNITES -- the burn does the killing, so a lit crowd walks a few steps and goes down together. 26 m of reach against a 22.8 m court, so it crosses to the far baseline. The guard matters: hp -= 0 fell straight through to killZombie, which made the cone delete the crowd on contact. Every burst weapon had no progress bar and the cause was the spend. Arming spent the tin at once, so the rack entry vanished -- and the bar draws on the slot holding G.gunId, which no longer existed. The tin is spent when the burst ENDS now, so icon, count and bar are all there while it runs, and every path that ends a burst routes through endBurst or it would hand out free ammo. Auto-fire movement again: the previous fix read moving as mx||mz||assist, and finger-steering zeroes mx/mz, so sliding left and right still counted as standing still. And the oval on his back was the mg racket -- its head projected almost exactly onto the sprite; offset by facing it reads as held out beside him. The laser's far edge is pinned in SCREEN space to the middle of the ad strip rather than running out to beamLen on the ground, which landed on their top line. beamLen goes back to being the kill range instead of a number tuned to one camera. Beacon thumps, rocket swooshes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
91053b4c35
commit
6e88fd5bb9
6 files changed
+128
-30
No files matched your search
+21
-7
@@ -592,7 +592,7 @@ export function startDuel() {
|
||||
if (!zombies.some(z => z.variant === 'r')) spawnRacketZombie(); // the schedule guarantees an opponent
|
||||
for (const z of zombies) if (z.variant === 'r') z.dueled = true; // a veteran can be challenged again — this marks the fresh duel
|
||||
G.weapon = 0; // tennis rules for the duel: plain balls, no tins
|
||||
G.mgT = 0; pellets.length = 0; // ...and a burst in progress is over. The umpire has restored order.
|
||||
G.mgT = 0; endBurst(); pellets.length = 0; // ...and a burst in progress is over. The umpire has restored order.
|
||||
G.interlude = { introT: 2.4 }; // the intro beat: serving blocked while the umpire announces
|
||||
G.boy = null; // ...and the mop boy is waved off the court (sendBoy is barred for the duration)
|
||||
ball.active = false; setupPoint();
|
||||
@@ -614,7 +614,7 @@ export function startBossFight(chapter) {
|
||||
if (G.bossFight) return;
|
||||
G.bossFight = chapter;
|
||||
zombies.length = 0; pellets.length = 0; extras.length = 0; shots.length = 0; // a clean stage
|
||||
G.horde = null; G.mgT = 0; G.clearing = false;
|
||||
G.horde = null; G.mgT = 0; endBurst(); G.clearing = false;
|
||||
ball.active = false; setupPoint();
|
||||
spawnChapterBoss(chapter);
|
||||
zombieDuck(true); // the music drops out for it
|
||||
@@ -934,7 +934,10 @@ function tryArmGun() {
|
||||
if (!w || w.kind !== 'burst' || slotStock(G.weapon) <= 0) return false;
|
||||
G.gunId = id;
|
||||
G.mgT = w.len ?? TUNE.mgPickupLen;
|
||||
spendSlot(G.weapon);
|
||||
// The tin is spent when the burst ENDS, not when it starts. Spending on commit removed the rack
|
||||
// entry immediately, so the countdown bar — which draws on the slot holding G.gunId — had no slot
|
||||
// to draw on and every burst weapon ran with no progress bar at all. Oliver spotted it on the
|
||||
// flamethrower; it was true of all four.
|
||||
G.mgCool = 0;
|
||||
ball.active = false; G.phase = 'rally';
|
||||
P.pose = 'idle'; P.poseT = 0; P.swingT = -1;
|
||||
@@ -953,6 +956,15 @@ export function serveToss() {
|
||||
if (score.server === 'P' && G.phase === 'pretoss' && !ball.active) tossBall(P);
|
||||
}
|
||||
|
||||
// The burst is over: NOW the tin is spent. Called from updateZombies when mgT runs out.
|
||||
export function endBurst() {
|
||||
const id = G.gunId;
|
||||
G.gunId = null;
|
||||
if (!id) return;
|
||||
const slot = [0, 1, 2, 3, 4].find(i => slotId(i) === id);
|
||||
if (slot !== undefined) spendSlot(slot);
|
||||
}
|
||||
|
||||
// The laser fired from a TAP (the keyboard/two-stage path). The drag path solves its own direction
|
||||
// and goes through hitServe; this one has no arc to read a lane off, so it takes the held flick aim
|
||||
// if there is one and otherwise fires straight up-court.
|
||||
@@ -1168,12 +1180,11 @@ export function update(dt) {
|
||||
// to what the camera can see (portrait's world bounds are wider than its viewport)
|
||||
const mgMode = G.horde || G.mgT > 0;
|
||||
const xVis = Math.min(P_BOUNDS.x, (W / 2 - 14) / (TUNE.camF / (TUNE.camZ - P.z)));
|
||||
let mgSteer = false;
|
||||
let mgSteer = false, mgMoving = false;
|
||||
if (mgMode && touchPX() !== null) {
|
||||
mgSteer = true;
|
||||
const tx = Math.max(-xVis, Math.min(xVis, touchPX() * xVis));
|
||||
if (Math.abs(tx - P.x) > 0.04) { P.facing = tx > P.x ? 1 : -1; P.runT += dt; P.pose = 'run'; }
|
||||
else P.pose = 'idle';
|
||||
if (Math.abs(tx - P.x) > 0.04) { P.facing = tx > P.x ? 1 : -1; mgMoving = true; }
|
||||
P.x = tx;
|
||||
}
|
||||
const slow = P.swingT >= 0 ? 0.35 : 1;
|
||||
@@ -1202,7 +1213,10 @@ export function update(dt) {
|
||||
const xLim = Math.max(xVis, COURT.W / 2);
|
||||
P.x = Math.max(-xLim, Math.min(xLim, P.x));
|
||||
P.z = Math.max(P_BOUNDS.zMin, Math.min(P_BOUNDS.zMax, P.z));
|
||||
const moving = mx || mz || assist !== 0;
|
||||
// mgMoving MUST be in here. Finger-steering zeroes mx/mz (they are the keyboard axes), so a
|
||||
// player sliding left and right under auto-fire counted as standing still and never ran —
|
||||
// "when he's moving left and right the run animation is not applied".
|
||||
const moving = mgMoving || mx || mz || assist !== 0;
|
||||
P.runT += moving ? dt : 0;
|
||||
// Auto-fire used to be excluded here, which left the pose stuck on 'idle' — and render then
|
||||
// faked a run cycle off G.idleT so he was not a statue. That fake is what read as "this hectic
|
||||
|
||||
Reference in new issue
Block a user