diff --git a/assets/_sheet_dwound.png b/assets/_sheet_dwound.png new file mode 100644 index 0000000..2860e2b Binary files /dev/null and b/assets/_sheet_dwound.png differ diff --git a/assets/_sheet_woundab.png b/assets/_sheet_woundab.png new file mode 100644 index 0000000..9ad4531 Binary files /dev/null and b/assets/_sheet_woundab.png differ diff --git a/assets/_sheet_woundc.png b/assets/_sheet_woundc.png new file mode 100644 index 0000000..bffdf6b Binary files /dev/null and b/assets/_sheet_woundc.png differ diff --git a/assets/z_awb.png b/assets/z_awb.png new file mode 100644 index 0000000..8661392 Binary files /dev/null and b/assets/z_awb.png differ diff --git a/assets/z_bwb.png b/assets/z_bwb.png new file mode 100644 index 0000000..9e028da Binary files /dev/null and b/assets/z_bwb.png differ diff --git a/assets/z_cw1b.png b/assets/z_cw1b.png new file mode 100644 index 0000000..119b189 Binary files /dev/null and b/assets/z_cw1b.png differ diff --git a/assets/z_cw2b.png b/assets/z_cw2b.png new file mode 100644 index 0000000..4d14598 Binary files /dev/null and b/assets/z_cw2b.png differ diff --git a/assets/z_dw.png b/assets/z_dw.png new file mode 100644 index 0000000..2c7cf5a Binary files /dev/null and b/assets/z_dw.png differ diff --git a/assets/z_dwb.png b/assets/z_dwb.png new file mode 100644 index 0000000..90721a1 Binary files /dev/null and b/assets/z_dwb.png differ diff --git a/config.js b/config.js index cde0c64..69af11e 100644 --- a/config.js +++ b/config.js @@ -205,8 +205,9 @@ export const TUNE = { runnerSpeed: 3.2, // runner ('f') speed multiplier over the shamble runnerVMax: 4.2, // hard runner speed cap — the late-game ramp must stay humanly reactable shamblerHp: 2, // splat-quality hits a classic shambler ('a'/'b') soaks before it dies — only the runner ('f') is still one-shot glass - stewardHp: 3, // the steward ('d'): one hit tougher than the crowd he walks in. Deliberately NOT - // a brute — no wound skins, no special rule, just a bigger body that takes one more + stewardHp: 5, // the steward ('d'). Was 3 and died like a shambler, which made a bigger body read + // as decoration; at 5 he is a brute's worth of hp on a faster, ordinary-looking + // walker, and he has a wound skin (z_dw) so the second half of that shows hordeSteward: 0.2, // share of horde spawns that come out as stewards (from game 4) // fire: the flame ball no longer just kills what it touches, it starts an epidemic. burnTime: 3.0, // s a body burns before the fire goes out diff --git a/index.html b/index.html index cb2241f..ea3b06e 100644 --- a/index.html +++ b/index.html @@ -97,6 +97,6 @@

- + diff --git a/render.js b/render.js index 3bf87a4..399a5a0 100644 --- a/render.js +++ b/render.js @@ -155,7 +155,7 @@ export function proj(x, y, z) { // ---------------------------------------------------------------- sprites ---- // assets/.png; game falls back to code-drawn figures until sprites load. -const SPRV = 53; // asset cache-buster — bump with main.js?v=N (see the img.src note below) +const SPRV = 55; // 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', 'o_idle1','o_idle2','o_bounce','o_bounce2','o_toss','o_smash','o_run1','o_run2','o_swing1','o_swing2', @@ -170,6 +170,7 @@ for (const n of ['p_idle1','p_idle2','p_bounce','p_bounce2','p_toss','p_smash',' 'z_chair1','z_chair2','z_chairw','z_chairt','z_chairwt', // boss design 3 (chapter 3, 6, ...) 'z_a1','z_a2','z_a3','z_a4','z_b1','z_b2','z_b3','z_b4','z_c1','z_c2', 'z_d1','z_d2','z_d3','z_d4', // the steward: bigger body, hi-vis vest, 4-frame walk + 'z_awb','z_bwb','z_cw1b','z_cw2b','z_dw','z_dwb', // ...and every wound skin's second frame: wounded bodies walk too 'z_f1','z_f2','z_f3','z_f4','z_g1','z_g2','z_g3','z_g4','z_gw','z_s1','z_s2','z_s3', 'z_cw1','z_cw2','z_aw','z_bw', // wounded skins for multi-hit enemies (racket boss, brute, and now the classic shamblers) 'w_ball','w_bomb','w_triple','w_fire','w_gun', // weapon-rack icons @@ -316,7 +317,13 @@ function drawZombie(zb) { h *= SPR[stage].height / SPRITE_H; // height ALWAYS off the A frame, or the size pops each step } else if (SPR.z_gw) name = 'z_gw'; // a missing asset degrades to the last skin that loaded } - else if ((zb.variant === 'a' || zb.variant === 'b') && zb.hp < TUNE.shamblerHp && SPR['z_' + zb.variant + 'w']) name = 'z_' + zb.variant + 'w'; // gashed shambler + else if ('abd'.includes(zb.variant) && zb.hp < (zb.hpMax || 0) && SPR['z_' + zb.variant + 'w']) { + // A wounded body used to freeze on ONE frame and slide across the court — the wound skins were + // single stills. Every skin has its …b twin now (the stride swapped), stepped off the same clock + // as the healthy walk, so losing an arm changes how he walks and not whether he walks. + const w = 'z_' + zb.variant + 'w'; + name = SPR[w + 'b'] && stepFrame(zb.walkT * 2.6, 2) ? w + 'b' : w; // gashed shambler, still lurching + } // The boss's damage is read as a FRACTION of full health — his hp scales with the run // (TUNE.racketHpPerGame), so the old `hp === 2` / `hp <= 1` thresholds would have left a late-run // boss looking untouched until the killing blow. Keep these in step with bleed()'s anchors. @@ -324,6 +331,8 @@ function drawZombie(zb) { const f = zb.hp / (zb.hpMax || TUNE.racketHp); if (f <= 0.34 && 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 (f <= 0.67 && SPR.z_cw1) name = 'z_cw1'; + const b = name + 'b'; // ...and he limps through his wounds too, instead of gliding + if (name !== 'z_' + v + frame && SPR[b] && stepFrame(zb.walkT * 2.6, 2)) name = b; } // The chapter boss comes apart through the SAME fraction-of-hpMax rule as the racket zombie, over // three stages, so a 34 hp chapter-4 boss visibly degrades all the way down: arm off, then ribs diff --git a/zombies.js b/zombies.js index 6ef279d..df920e9 100644 --- a/zombies.js +++ b/zombies.js @@ -33,7 +33,11 @@ export function spawnZombie(x, z, variant) { if (variant === 'f') { zb.v = Math.min(TUNE.runnerVMax, zb.v * TUNE.runnerSpeed); shriek(); } // shriek = the fairness contract; capped so late runners stay reactable if (variant === 'a' || variant === 'b') zb.hp = TUNE.shamblerHp; // classic shamblers take a wound before they drop — only the runner is one-shot glass if (variant === 'g') { zb.v *= 0.55; zb.hp = TUNE.bruteHp; } // slow, but soaks hits - if (variant === 'd') { zb.v *= 0.8; zb.hp = TUNE.stewardHp; } // the steward: a head taller and a hit tougher than the crowd, nothing more exotic than that + if (variant === 'd') { zb.v *= 0.8; zb.hp = TUNE.stewardHp; } // the steward: a head taller and several hits tougher than the crowd, nothing more exotic than that + // Every body with hp remembers its FULL hp. The wound skins used to compare against the variant's + // TUNE constant, which meant every new body needed its own branch to look hurt; one field here and + // the rule is "hp below full = show the wound", for anything that has a wound skin. + if (zb.hp) zb.hpMax = zb.hp; if (variant === 's') zb.shieldT = 0; // chair up; > 0 = seconds of stagger left if ('fgsd'.includes(variant) && !G.seen['v' + variant]) { G.seen['v' + variant] = true; aside('ump_dress_code', 0.8); } if (variant === 'm') { // the rogue ball machine: furniture, not a body. It does not walk at all.