Put a steward on the court
A new ordinary body between the shambler and the brute: a stadium security steward in a hi-vis vest, 2.15 m, three hp, a touch slower. Deliberately the mildest thing in the advanced pool — no wound skins, no special rule to learn — so he can unlock early (game 4) and thicken the ordinary crowd, which a mix of two shamblers never did. A fifth of horde spawns come out as stewards too, so a grown wave is not one body dying at one range. Sheet generated against the existing shambler walk (assets/_sheet_dwalk.png), sliced to a 4-frame cycle, so he shambles as smoothly as the crowd he walks in. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
7803c83564
commit
d62478e8b1
9 files changed
+19
-7
No files matched your search
Binary file not shown.
|
After Width: | Height: | Size: 378 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 27 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
@@ -205,6 +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
|
||||
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
|
||||
burnDps: 2.0, // hp lost per second while burning — burnTime*burnDps must exceed bruteHp or tanks walk out of the fire
|
||||
|
||||
+1
-1
@@ -97,6 +97,6 @@
|
||||
<p id="build"></p>
|
||||
</div>
|
||||
</div>
|
||||
<script type="module" src="main.js?v=55"></script>
|
||||
<script type="module" src="main.js?v=56"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -155,7 +155,7 @@ export function proj(x, y, z) {
|
||||
|
||||
// ---------------------------------------------------------------- sprites ----
|
||||
// assets/<name>.png; game falls back to code-drawn figures until sprites load.
|
||||
const SPRV = 50; // asset cache-buster — bump with main.js?v=N (see the img.src note below)
|
||||
const SPRV = 51; // 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_swing1','p_swing2',
|
||||
'o_idle1','o_idle2','o_bounce','o_bounce2','o_toss','o_smash','o_run1','o_run2','o_swing1','o_swing2',
|
||||
@@ -169,6 +169,7 @@ for (const n of ['p_idle1','p_idle2','p_bounce','p_bounce2','p_toss','p_smash','
|
||||
'z_judge1','z_judge2','z_judgew','z_judget','z_judgewt', // boss design 2 (chapter 2, 5, ...)
|
||||
'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_f1','z_f2','z_f3','z_f4','z_g1','z_g2','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
|
||||
@@ -257,7 +258,7 @@ function drawBoy(b) {
|
||||
ctx.drawImage(img, -w / 2, -h, w, h);
|
||||
ctx.restore();
|
||||
}
|
||||
const ZFRAMES = { a: 4, b: 4, f: 4 }; // 4-frame walk cycles where sheets exist; the rest stay 2
|
||||
const ZFRAMES = { a: 4, b: 4, f: 4, d: 4 }; // 4-frame walk cycles where sheets exist; the rest stay 2
|
||||
// The rogue ball machine, on its own generated sheet: a dented hopper cart on wheels with a barrel
|
||||
// aimed down-court, and a second frame with the muzzle flashing. Oliver called the first, procedural
|
||||
// version "naively painted" — it was four rects, which is fine for fire but not for furniture the
|
||||
@@ -288,7 +289,7 @@ 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
|
||||
let h = zb.variant === 'g' ? 2.7 : rk ? 1.85 : 1.8; // the brute towers half again over the crowd
|
||||
let h = zb.variant === 'g' ? 2.7 : zb.variant === 'd' ? 2.15 : rk ? 1.85 : 1.8; // the brute towers half again over the crowd; the steward stands between him and it
|
||||
if (zb.variant === 'B') h = 1.8 * (zb.scale || 1.5); // ...and a chapter boss towers over the brute
|
||||
// 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
|
||||
|
||||
+11
-3
@@ -33,8 +33,9 @@ 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 === 's') zb.shieldT = 0; // chair up; > 0 = seconds of stagger left
|
||||
if ('fgs'.includes(variant) && !G.seen['v' + variant]) { G.seen['v' + variant] = true; aside('ump_dress_code', 0.8); }
|
||||
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.
|
||||
zb.v = 0; zb.hp = TUNE.machHp; zb.fireT = TUNE.machCycle; zb.windT = 0;
|
||||
}
|
||||
@@ -130,6 +131,10 @@ function pickVariant() {
|
||||
if (g >= 2 && !zombies.some(z => z.variant === 'f')) pool.push('f'); // at most one runner alive
|
||||
if (g >= 6) pool.push('g');
|
||||
if (g >= 12) pool.push('s');
|
||||
// The steward is the mildest of the pool on purpose — bigger and one hit tougher, no special rule
|
||||
// to learn — so he can arrive EARLY and thicken the ordinary crowd, which the shambler mix alone
|
||||
// never did. He is also the only advanced body that shows up in a horde (see spawnHordeZombie).
|
||||
if (g >= 4) pool.push('d');
|
||||
const nasty = Math.min(TUNE.nastyMax, g * TUNE.nastyPerGame);
|
||||
if (pool.length && Math.random() < nasty) return pool[Math.floor(Math.random() * pool.length)];
|
||||
return Math.random() < 0.5 ? 'a' : 'b';
|
||||
@@ -140,7 +145,7 @@ function pickVariant() {
|
||||
// A BOSS BODY: takes wound damage from everything instead of being deleted by it. The brute ('g') is
|
||||
// deliberately NOT one — melee/bomb/fire one-shotting a brute is a settled "power exception".
|
||||
const isBoss = zb => zb.variant === 'r' || zb.variant === 'B';
|
||||
const hitR = zb => zb.variant === 'g' ? 0.75 : zb.variant === 'r' ? 0.7
|
||||
const hitR = zb => zb.variant === 'g' ? 0.75 : zb.variant === 'd' ? 0.64 : zb.variant === 'r' ? 0.7
|
||||
: zb.variant === 'B' ? 0.55 * (zb.scale || 1) * 1.4 // a chapter boss is a barn door, as it should be
|
||||
: zb.variant === 'm' ? 0.8 // the machine is a wide low box
|
||||
: 0.55;
|
||||
@@ -302,7 +307,10 @@ function splatter(i, fanfare = true) { // the MAIN ball hit a zombie
|
||||
|
||||
// ---- horde wave: dense spawns + an auto-firing machine-gun racket (pellets, not the match ball)
|
||||
function spawnHordeZombie() { // all from the far baseline, across the court — never off-screen at the sides
|
||||
spawnZombie((Math.random() * 2 - 1) * 5, -1.5 - Math.random(), Math.random() < 0.5 ? 'a' : 'b');
|
||||
// a few stewards in the mix: the wave needs bodies that survive one pellet, or a grown horde is a
|
||||
// uniform wall of the same thing dying at the same range
|
||||
const v = Math.random() < TUNE.hordeSteward && G.runGames >= 4 ? 'd' : Math.random() < 0.5 ? 'a' : 'b';
|
||||
spawnZombie((Math.random() * 2 - 1) * 5, -1.5 - Math.random(), v);
|
||||
}
|
||||
let mgTick = 0;
|
||||
function firePellet() {
|
||||
|
||||
Reference in new issue
Block a user