Splatter feel pass: baseline turret, tennis duel, rapid serves, bleeding wounds

- Stand at the baseline: in splatter the player is pinned to the baseline
  (a turret, side-to-side only) — the duel is exempt so it keeps free depth.
- Zombie 1:1 duel now plays like real tennis: no free-aim flick, no drag
  reticle; lane aim + the toss-then-hit serve (gate on !G.interlude in
  doHit/hitServe/keyPressed/canAimServe).
- Rapid serves: the two-stage reticle serve now fires mid-rally too — a ball
  still in flight is banished to a ballistic clone so the next serve launches
  at once. Serve as fast as you can drag-release.
- Wounded skins for multi-hit foes: brute (arm off) and racket boss (arm off,
  then headless) swap by remaining hp, with a graceful fallback to the healthy
  sprite. The headless boss is drawn a head shorter so the shoulders line up.
- Dynamic gore: the blood is NOT painted in — open wounds gush live particles
  every frame (arm stumps + a neck geyser on the headless boss), absurdly, and
  bounded so it self-regulates.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
wuwiandClaude Opus 4.8 committed 2026-07-17 20:39:07 +02:00
1 parent 7936d83f0f
commit df4eaf6e39
10 files changed
+53 -21

No files matched your search

+12 -4
View File
@@ -67,7 +67,8 @@ for (const n of ['p_idle1','p_idle2','p_bounce','p_bounce2','p_toss','p_smash','
'o_idle1','o_idle2','o_bounce','o_bounce2','o_toss','o_smash','o_run1','o_run2','o_swing1','o_swing2',
'x_ump','x_boyrun1','x_boyrun2','x_boyrun3','x_boyrun4','x_crowd','x_ads',
'z_a1','z_a2','z_a3','z_a4','z_b1','z_b2','z_b3','z_b4','z_c1','z_c2',
'z_f1','z_f2','z_f3','z_f4','z_g1','z_g2','z_s1','z_s2','z_s3',
'z_f1','z_f2','z_f3','z_f4','z_g1','z_g2','z_gw','z_s1','z_s2','z_s3',
'z_cw1','z_cw2', // wounded skins for multi-hit enemies (brute, racket boss)
'w_ball','w_bomb','w_triple','w_fire','w_gun', // weapon-rack icons
'scoreboard']) {
const img = new Image();
@@ -146,8 +147,15 @@ function drawZombie(zb) {
// same CYCLE frequency as before — more frames subdivide the stride, they don't speed it up
let frame = rk && zb.pose === 'swing' ? 2 : Math.floor(zb.walkT * (zb.variant === 'f' ? 6 : 2.6) * N / 2) % N + 1;
if (zb.variant === 's' && zb.shieldT > 0) frame = 3; // chair dropped — hit it NOW
const h = zb.variant === 'g' ? 2.7 : rk ? 1.85 : 1.8; // the brute towers half again over the crowd
drawStatic('z_' + v + frame, zb.x, zb.z, h, Math.floor(zb.walkT * 5.2) % 2);
let h = zb.variant === 'g' ? 2.7 : rk ? 1.85 : 1.8; // the brute towers half again over the crowd
// wounded skins: multi-hit enemies show their damage as hp drops (arm off → head gone, still
// shambling). The blood is NOT painted in — open wounds gush live particles (see bleed() in
// zombies.js). Fall back to the healthy sprite until the wounded PNG has loaded.
let name = 'z_' + v + frame;
if (zb.variant === 'g' && zb.hp < TUNE.bruteHp && SPR.z_gw) name = 'z_gw';
else if (rk && zb.hp <= 1 && SPR.z_cw2) { name = 'z_cw2'; h = 1.52; } // headless: a head shorter, so the shoulders land where a whole zombie's do
else if (rk && zb.hp === 2 && SPR.z_cw1) name = 'z_cw1';
drawStatic(name, zb.x, zb.z, h, Math.floor(zb.walkT * 5.2) % 2);
if (rk) { // HP bar floating above the mini-boss
const p = proj(zb.x, 2.05, zb.z), w = Math.max(10, p.s * 0.5), x = p.x - w / 2;
ctx.fillStyle = '#300'; ctx.fillRect(x, p.y, w, 3);
@@ -377,7 +385,7 @@ export function render() {
}
if (G.outMarker) drawRing(G.outMarker.x, G.outMarker.z, G.outMarker.out ? '#ff5050' : '#f8e048');
// two-stage serve aim: the reticle tracks the thumb; red = the magnet has a zombie locked
if (G.aim && G.mode === 'splatter' && G.phase === 'pretoss') {
if (G.aim && G.mode === 'splatter' && !G.interlude && (G.phase === 'pretoss' || G.phase === 'rally')) {
const ft = flickTarget(P, G.aim);
if (ft.zt) { // locked: the ring rides the zombie itself, plus a marker over its head
drawRing(ft.zt.x, ft.zt.z, '#ff5050');