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:
wuwiandClaude Opus 5 committed 2026-08-03 15:05:56 +02:00
1 parent 91053b4c35
commit 6e88fd5bb9
6 files changed
+128 -30

No files matched your search

+26 -4
View File
@@ -599,6 +599,11 @@ function drawBeamWedge(dirX, dirZ, ox, oy, oz, alpha, from) {
oz + uz * (u * TUNE.beamLen) + pz * w);
const u0 = Math.max(0, from);
const rgb = RGB(WEAPONS.laser.col);
// WHERE THE FAR EDGE SITS is a screen-space decision, not a world one. Run out to beamLen on the
// ground and it lands on the ads' TOP line; the beam should die at their MIDDLE. Pinning the far
// corners' y here keeps that exact in both orientations and leaves beamLen free to be the KILL
// range rather than a number tuned to one camera.
const farY = proj(0, 0, -6).y - AD_H / 2; // proj(0,0,-6) is the same line the ad strip sits on
// THREE NESTED WEDGES: a wide violet-blue bloom, a cyan body, and a white-hot core down the
// middle. One flat quad with a gradient along its length was a coloured shape; layers that share
// an axis and get brighter toward the centre are what read as sci-fi light.
@@ -613,6 +618,7 @@ function drawBeamWedge(dirX, dirZ, ox, oy, oz, alpha, from) {
for (const L of layers) {
const w0 = wAt(u0) * L.k, w1 = wAt(1) * L.k;
const c1 = corner(u0, -w0), c2 = corner(u0, w0), c3 = corner(1, w1), c4 = corner(1, -w1);
c3.y = c4.y = farY; // ...the x's keep their perspective, the y is pinned
// ...and it fades ALONG its length too, so the far end dissolves into the ads instead of
// stopping at a hard edge
const g = ctx.createLinearGradient((c1.x + c2.x) / 2, (c1.y + c2.y) / 2,
@@ -676,13 +682,18 @@ function mulberry32(a) {
return ((t ^ t >>> 14) >>> 0) / 4294967296; };
}
const PILE_COLS = ['#6e1414', '#8c1212', '#4e0e0e', '#7a2a18'];
// ...and the same mound for something the fire got: cold, dry, grey. A body that burns has nothing
// left to spray, so it should not leave a wet red heap behind it.
const ASH_COLS = ['#4a4744', '#3a3835', '#5c5854', '#2e2c2a'];
function buildBlobs(pl) {
const rnd = mulberry32((pl.seed | 0) + 1), n = Math.round(6 + pl.size * 5);
pl.blobs = [];
for (let k = 0; k < n; k++) {
const ang = rnd() * 6.2832, rad = Math.sqrt(rnd()); // uniform over the disk
pl.blobs.push({ nx: Math.cos(ang) * rad, nz: Math.sin(ang) * rad,
lift: rnd(), pr: 1 + rnd() * 2, color: rnd() < 0.1 ? '#cfc9b0' : PILE_COLS[(rnd() * 4) | 0] });
lift: rnd(), pr: 1 + rnd() * 2,
color: pl.ash ? (rnd() < 0.12 ? '#8a8580' : ASH_COLS[(rnd() * 4) | 0]) // a few pale flecks of bone
: (rnd() < 0.1 ? '#cfc9b0' : PILE_COLS[(rnd() * 4) | 0]) });
}
pl.blobs.sort((a, b) => (b.nz - 1.25 * b.lift) - (a.nz - 1.25 * a.lift)); // back/low first, peak on top
pl.builtSize = pl.size;
@@ -799,8 +810,12 @@ function drawFallbackChar(ch, p, h) { // shown only until sprites load
ctx.fillStyle = skin; ctx.fillRect(x - 1.8 * u, y - 3 * u, 1.4 * u, 3 * u); ctx.fillRect(x + 0.4 * u, y - 3 * u, 1.4 * u, 3 * u);
}
function drawMgRacket() { // horde: the player braces the racket forward and up-court, machine-gun style
const hip = proj(P.x + 0.15, 1.05, P.z);
const tip = proj(P.x, 0.95, P.z - 1.35); // pointing up-court, where the pellets stream out
// HELD OUT TO ONE SIDE. Straight up-court (P.x, P.z - 1.35) projects to almost exactly where the
// player sprite already is, so the racket head landed as an oval painted on his back — Oliver saw
// it every time. Offset by facing, it reads as a racket held out beside him.
const f = P.facing || 1;
const hip = proj(P.x + 0.34 * f, 1.15, P.z);
const tip = proj(P.x + 0.86 * f, 1.02, P.z - 1.25); // pointing up-court, where the pellets stream out
ctx.strokeStyle = '#d8d8d8'; ctx.lineWidth = 2;
ctx.beginPath(); ctx.moveTo(hip.x, hip.y); ctx.lineTo(tip.x, tip.y); ctx.stroke();
ctx.strokeStyle = '#b07030'; ctx.lineWidth = 1.5; // the head, aimed forward
@@ -1072,7 +1087,9 @@ export function render() {
// gore decals: what remains of the visiting team (flat ground layer, then far-side mounds)
for (const d of gore) {
const p = proj(d.x, 0, d.z);
ctx.fillStyle = d.scorch ? `rgba(28,22,16,${d.a})` : `rgba(140,18,18,${d.a})`; // craters burn black-brown
ctx.fillStyle = d.scorch ? `rgba(28,22,16,${d.a})`
: d.ash ? `rgba(58,56,54,${d.a})` // burnt out: dark grey ash, not blood
: `rgba(140,18,18,${d.a})`; // craters burn black-brown
ctx.beginPath(); ctx.ellipse(p.x, p.y, p.s * d.r * 0.4, p.s * d.r * 0.16, 0, 0, 7); ctx.fill();
}
drawPiles(false); // far-side piles (occluded by the net); near-side piles drawn after the net
@@ -1197,6 +1214,11 @@ export function render() {
ctx.globalAlpha = 1;
continue;
}
if (pt.ash) { // settling ash from a burnt body
ctx.fillStyle = pt.dark ? 'rgba(46,44,42,0.9)' : 'rgba(92,88,84,0.9)';
ctx.fillRect(pp.x | 0, pp.y | 0, 2, 2);
continue;
}
ctx.fillStyle = pt.spark ? pt.spark // a thrown round dragging its colour
: pt.pink ? (pt.t > 0.2 ? UI.pink : '#ff9df6') // a barrel bursting: debris, not gore
: pt.fire ? (pt.t > 0.12 ? '#ffc030' : '#ff8010')