Make the bosses move, and put six of them and three new bodies on court
A chapter boss had no AI at all: it matched no branch in the variant chain and fell through to the crowd's aimless wobble, so "the boss AI owns its x" was a comment describing code that did not exist. Movement is now data on the BOSS_ART row -- pace, shadow, surge, charge, bounce -- and bossAI is the one place it happens. Every behaviour field is optional, so a row carrying only art behaves exactly as a boss did before. Three earlier drafts of those styles failed the same way: written as "walk toward the player's x", which in splatter means arrive in four seconds and stand still, because the player is rooted in pretoss for nearly all of a boss fight. Every style now moves under its own power and only biases toward him. Measured over a simulated minute each: 50-136 m travelled, none static. Three new designs -- THE HEAD COACH (a 5-shot drill 0.14s apart), THE PHYSIO (charges, and withdraws less than it charges, so it reaches you at 38s and that is the fight's clock), THE SPONSOR MASCOT (gap 0, so the whole wall of five leaves in ONE frame and is dodged rather than parried). Harder to kill: 16 -> 20 hp, 9 -> 10 per chapter, plus per-design multipliers. Three new bodies: the ball boy (3 hp, 2 skins, from game 3), the bouncer (6/3, game 8) and the roller man (9/4, game 15), each with a 4-frame walk and a drawn two-frame lurch for every wound stage. Three wound rules became one. The brute had a hardcoded stage array, everything else a single skin behind 'abd'.includes(...), and the boss a fraction of hpMax. The boss's rule reproduces the other two exactly -- verified: the brute still draws z_gw/gw2/gw3/gw4 at hp 4/3/2/1 -- and keeps working when hp is not one-per-skin, which all three new bodies need. So a multi-hit body is now a row in config.ZWOUND and no code. bleed() reads the same call, which also gave the steward the trickle his wound skin had always implied. Fixes a real crash on the way past: killPlayer empties shots[] while the shots loop is mid-iteration, so every remaining index read undefined and the frame threw. The mascot's five-at-once volley makes it easy to hit; the shipped 3-shot volley could always have hit it. _regen.py gains two style blocks and a check tier. One constant used to say "keep every colour exactly as the reference" -- right for a remaster, and it fights every word of the subject otherwise. newart is for a new character, state for the same character wounded or mid-throw; using newart for the wound sheets licensed a repaint and got one, five came back with living tan skin. And the detail gate ratchets against the file it replaces, so check: any exists for re-rolling already-fine art to fix something the gate cannot see, such as colour. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0774a2a31d
commit
4cda6bde76
64 files changed
+694
-78
No files matched your search
@@ -101,9 +101,11 @@ export const TUNE = {
|
||||
// ---- chapter bosses: one every bossEvery games, escalating. The racket zombie is demoted to an
|
||||
// occasional opponent (racketEvery) so the boss is the only thing that reads as a wall.
|
||||
bossEvery: 10, // games between chapter bosses
|
||||
bossHp: 16, // hp of the FIRST one; each later chapter adds bossHpPerChapter. 10 was far too
|
||||
// soft — three banked flame balls took chapter 1 down (2 impact + 2.4 burn each)
|
||||
bossHpPerChapter: 9,
|
||||
bossHp: 20, // hp of the FIRST one; each later chapter adds bossHpPerChapter. 10 was far too
|
||||
// soft — three banked flame balls took chapter 1 down (2 impact + 2.4 burn each).
|
||||
// 16 -> 20 alongside the movement below: a boss that holds its ground and dodges
|
||||
// is only harder if it also lives long enough for the dodging to matter.
|
||||
bossHpPerChapter: 10,
|
||||
bossScale: 1.5, // body height multiplier at chapter 1 (a shambler is 1.8 m)
|
||||
bossScalePer: 0.18, // ...growing per chapter, so chapter 4 towers
|
||||
bossSpeed: 0.42, // m/s it advances — slow, deliberate, unstoppable
|
||||
@@ -121,6 +123,20 @@ export const TUNE = {
|
||||
bossSlowT: 1.2, // s of REAL time the world runs slow while a boss comes apart
|
||||
bossSlowMo: 0.35, // ...and how slow
|
||||
bossShotSpeed: 15.5, // m/s a boss throws at (the machine's is machSpeed 13)
|
||||
// ---- boss MOVEMENT. A boss used to have no AI at all: it fell through to the crowd's aimless
|
||||
// ±0.15 m/s wobble and crawled forward until it simply arrived on top of you. Each design now owns
|
||||
// a movement style (BOSS_ART.move) and these are the shared numbers it spends.
|
||||
bossStrafe: 1.7, // m/s sideways — bossSpeed above is only the FORWARD crawl
|
||||
bossPaceX: 3.4, // m either side of the centre line a pacing boss turns at
|
||||
bossShadowX: 1.7, // m either side of YOUR lane a 'shadow' boss turns at — its beat follows you
|
||||
bossZMin: -1.0, // the band a boss holds. Without a clamp a fight ends by the boss walking into
|
||||
bossZMax: 5.6, // you (~57 s at bossSpeed) rather than by you killing it — and every extra hp
|
||||
// above just made that MORE likely, not less. Only 'charge' breaks the band.
|
||||
bossChargeV: 4.2, // m/s the physio rushes at once it commits
|
||||
bossChargeT: 1.5, // s a charge lasts before it withdraws
|
||||
bossChargeRest: 2.2, // s of held ground between charges — the window you get to hit it
|
||||
bossHopT: 0.55, // s per mascot hop (it moves in arcs, not in a slide)
|
||||
bossHopX: 2.0, // m of ground one hop covers (the eased arc lands it at about 2/pi of this)
|
||||
racketEvery: 3, // games between racket-zombie (plain opponent) spawns — was EVERY won game, which
|
||||
// is why he read as "the boss" and why a real boss would not have stood out
|
||||
asideEvery: 55, // s between the umpire's sarcastic asides in splatter (jittered +-40%). He never
|
||||
@@ -223,6 +239,14 @@ export const TUNE = {
|
||||
// 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)
|
||||
// ---- the late roster. Three bodies that arrive as the run goes on, filling the gap between the
|
||||
// 2 hp shamblers and the 5 hp brute and then extending past it. Each has one wound skin per hit it
|
||||
// can still take, so the damage is readable without a bar (the rule the boss's missing bar set).
|
||||
ballboyHp: 3, // the ball boy ('e') — a stock-sized body that takes three, with two wound skins
|
||||
bouncerHp: 6, // the bouncer ('h') — 2.4 m, shoves through the crowd instead of shambling round it
|
||||
rollerHp: 9, // the roller man ('k') — 3.2 m, hauling a court roller. The heaviest thing on court
|
||||
rollerStride: 0.62, // m he covers per lurch frame — longer than the brute's 0.5: a bigger body,
|
||||
// a longer stride, or his feet slide the way the brute's used to
|
||||
// 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
|
||||
@@ -324,16 +348,66 @@ export const PLAYER_H = 1.88;
|
||||
// throws: the same states mid-throw, used while windT > 0. Index is clamped, so a design with
|
||||
// fewer throw frames than wound stages simply reuses its last one.
|
||||
// Every name must be ALL LOWERCASE and a whole word — see the case-collision note in the pipeline.
|
||||
//
|
||||
// BEHAVIOUR is data too, and every field below is OPTIONAL — anything left out falls back to the
|
||||
// shared TUNE.boss* number, so a row with only art in it behaves exactly like a boss did before:
|
||||
// move 'pace' | 'mirror' | 'advance' | 'charge' | 'bounce' (spawnChapterBoss -> bossAI)
|
||||
// hpMul / speedMul scale this design's hp and its forward crawl
|
||||
// volley shots per attack · gap s between them (0 = the whole volley leaves in ONE frame,
|
||||
// which is a WALL to dodge rather than a rhythm to parry) · spread m of scatter
|
||||
// cycle s between attacks · wind s of telegraph · shot m/s it throws at
|
||||
export const BOSS_ART = [
|
||||
{ name: 'THE GROUNDSKEEPER', walk: ['z_boss1', 'z_boss2'],
|
||||
wounds: ['z_bossw1', 'z_bossw2', 'z_bossw3'],
|
||||
throws: ['z_bosst0', 'z_bosst1', 'z_bosst2', 'z_bosst3'] },
|
||||
throws: ['z_bosst0', 'z_bosst1', 'z_bosst2', 'z_bosst3'],
|
||||
move: 'pace', speedMul: 0.7 },
|
||||
{ name: 'THE LINE JUDGE', walk: ['z_judge1', 'z_judge2'],
|
||||
wounds: ['z_judgew'], throws: ['z_judget', 'z_judgewt'] },
|
||||
wounds: ['z_judgew'], throws: ['z_judget', 'z_judgewt'],
|
||||
move: 'shadow' }, // he walks the line, and the line is wherever you are
|
||||
{ name: 'THE CHAIR UMPIRE', walk: ['z_chair1', 'z_chair2'],
|
||||
wounds: ['z_chairw'], throws: ['z_chairt', 'z_chairwt'] },
|
||||
wounds: ['z_chairw'], throws: ['z_chairt', 'z_chairwt'],
|
||||
move: 'surge', hpMul: 1.1 }, // the immovable one — he just keeps coming, and coming back
|
||||
{ name: 'THE HEAD COACH', walk: ['z_coach1', 'z_coach2'],
|
||||
wounds: ['z_coachw1', 'z_coachw2', 'z_coachw3'],
|
||||
throws: ['z_coacht0', 'z_coacht1', 'z_coacht2', 'z_coacht3'],
|
||||
move: 'pace', paceMul: 1.9, hpMul: 0.9, // never where he was: drills you while running the line
|
||||
volley: 5, gap: 0.14, cycle: 2.2, wind: 0.4, shot: 19 },
|
||||
{ name: 'THE PHYSIO', walk: ['z_physio1', 'z_physio2'],
|
||||
wounds: ['z_physiow1', 'z_physiow2', 'z_physiow3'],
|
||||
throws: ['z_physiot0', 'z_physiot1', 'z_physiot2', 'z_physiot3'],
|
||||
move: 'charge', hpMul: 0.8, // fast and fragile: the BODY is the weapon, and contact
|
||||
volley: 1, cycle: 3.4, wind: 0.35, shot: 21, spread: 0.15 }, // is still instant death
|
||||
{ name: 'THE SPONSOR MASCOT', walk: ['z_mascot1', 'z_mascot2'],
|
||||
wounds: ['z_mascotw1', 'z_mascotw2', 'z_mascotw3'],
|
||||
throws: ['z_mascott0', 'z_mascott1', 'z_mascott2', 'z_mascott3'],
|
||||
move: 'bounce', hpMul: 1.3, speedMul: 0.6, // soaks, and answers with a WALL: five at once, so
|
||||
volley: 5, gap: 0, spread: 1.6, cycle: 3.2, wind: 0.65, shot: 14 }, // there is nothing to parry
|
||||
];
|
||||
|
||||
// Which wound stage a body is in: 0 = unhurt, 1..n = the skins, spread evenly over its full health.
|
||||
// This is the rule the chapter boss already used, and it turns out to reproduce the brute's old
|
||||
// hardcoded thresholds EXACTLY (5 hp over 4 skins = one skin per hit) and the shamblers' single skin
|
||||
// too — so there is now one rule for damage readout instead of three, and it is the one that keeps
|
||||
// working when a body's hp is not one-per-skin. Every caller passes its own stage count.
|
||||
export const woundStage = (zb, n) => {
|
||||
const f = zb.hp / (zb.hpMax || 1);
|
||||
return f >= 1 ? 0 : Math.min(n, Math.max(1, Math.ceil((1 - f) * n)));
|
||||
};
|
||||
// Wound skins by variant, HEALTHIEST FIRST — the same ordering BOSS_ART.wounds uses, indexed by
|
||||
// woundStage above. This one table replaced two branches in drawZombie that did the same job against
|
||||
// different constants (the brute had a hardcoded stage array, everything else a single skin behind
|
||||
// `'abd'.includes(...)`), which is why a new multi-stage body is a row here and no code at all.
|
||||
// Each name's `…b` twin — the stride swapped — is looked up automatically, so a wounded body keeps
|
||||
// WALKING instead of freezing and sliding. 'r' and 'B' carry their skins elsewhere ('B' on its
|
||||
// design row) but read them through the very same woundStage call.
|
||||
export const ZWOUND = {
|
||||
a: ['z_aw'], b: ['z_bw'], d: ['z_dw'],
|
||||
e: ['z_ew', 'z_ew2'],
|
||||
h: ['z_hw', 'z_hw2', 'z_hw3'],
|
||||
g: ['z_gw', 'z_gw2', 'z_gw3', 'z_gw4'],
|
||||
k: ['z_kw', 'z_kw2', 'z_kw3', 'z_kw4'],
|
||||
};
|
||||
|
||||
export const MACH_H = 1.55; // was 1.15 — waist high read as a prop you could ignore. Still furniture
|
||||
// (a body is PLAYER_H 1.88), but big enough to be a thing on the court.
|
||||
|
||||
@@ -341,6 +415,7 @@ export const MACH_H = 1.55; // was 1.15 — waist high read as a prop you could
|
||||
// fraction of it, so the two cannot drift — a shot at a brute has to arrive at the brute's chest,
|
||||
// not at the height a shambler's chest happens to be.
|
||||
export const zombieH = zb => zb.variant === 'g' ? 2.7 : zb.variant === 'd' ? 2.15
|
||||
: zb.variant === 'k' ? 3.2 : zb.variant === 'h' ? 2.4
|
||||
: zb.variant === 'r' ? 1.85 : zb.variant === 'm' ? MACH_H
|
||||
: zb.variant === 'B' ? 1.8 * (zb.scale || 1.5) : 1.8;
|
||||
// Where on that body the ball is aimed, and where the aim mark is drawn. Solving the shot to the
|
||||
|
||||
Reference in new issue
Block a user