diffs / #47
feat(#47): boss throws pizza
Requested-by: demo replay (synthetic wallet)
+ BUILD+ UNIT TESTS+ LINT+ DIFF GATE+ PAGE CLASSIFIER+ PLAYWRIGHT
src/boss-registry.js+1 −1
| 3 | // Boss classes are discovered from src/bosses/*.js by export name, so a new boss needs no import here. | 3 | // Boss classes are discovered from src/bosses/*.js by export name, so a new boss needs no import here. |
| 4 | const B = Object.assign({}, ...Object.values(import.meta.glob("./bosses/*.js", { eager: true }))); | 4 | const B = Object.assign({}, ...Object.values(import.meta.glob("./bosses/*.js", { eager: true }))); |
| 5 | const registry = () => ({ | 5 | const registry = () => ({ |
| 6 | − rooftop: null, | 6 | + rooftop: B.PizzaBoss, |
| 7 | lobby: null, | 7 | lobby: null, |
| 8 | alley: null, | 8 | alley: null, |
| 9 | }); | 9 | }); |
3 // Boss classes are discovered from src/bosses/*.js by export name, so a new boss needs no import here.
4 const B = Object.assign({}, ...Object.values(import.meta.glob("./bosses/*.js", { eager: true })));
5 const registry = () => ({
6− rooftop: null,
6+ rooftop: B.PizzaBoss,
7 lobby: null,
8 alley: null,
9 });
src/bosses/pizzaBoss.jsNEW+31 −0
| 1 | +export class PizzaBoss extends Boss { | ||
| 2 | + hp = 12; | ||
| 3 | + throwEvery = 1.4; | ||
| 4 | + projectile = "pizza"; | ||
| 5 | + art = "ogre"; | ||
| 6 | + volley = 0; | ||
| 7 | + | ||
| 8 | + // every third throw is a fan of three slices | ||
| 9 | + throw(w) { | ||
| 10 | + this.volley = (this.volley + 1) % 3; | ||
| 11 | + if (this.volley !== 0) return super.throw(w); | ||
| 12 | + for (const spread of [-24, 0, 24]) { | ||
| 13 | + // aim left of, at, and right of the cat | ||
| 14 | + const tx = (w.player ? w.player.x : 40) + spread; | ||
| 15 | + w.projectiles.push({ | ||
| 16 | + kind: "pizza", | ||
| 17 | + x: this.x + 4, | ||
| 18 | + y: this.y - 4, | ||
| 19 | + vx: (tx - this.x) / 1.5, | ||
| 20 | + vy: -80 - w.rand() * 20, | ||
| 21 | + r: 0, | ||
| 22 | + spin: 8, | ||
| 23 | + life: 4, | ||
| 24 | + }); | ||
| 25 | + } | ||
| 26 | + if (w.level.shake) w.shake = Math.max(w.shake, w.level.shake); | ||
| 27 | + } | ||
| 28 | +} | ||
| 29 | + | ||
| 30 | +// imports are hoisted, so the class reads first | ||
| 31 | +import { Boss } from "./boss.js"; |
1+export class PizzaBoss extends Boss {
2+ hp = 12;
3+ throwEvery = 1.4;
4+ projectile = "pizza";
5+ art = "ogre";
6+ volley = 0;
7+
8+ // every third throw is a fan of three slices
9+ throw(w) {
10+ this.volley = (this.volley + 1) % 3;
11+ if (this.volley !== 0) return super.throw(w);
12+ for (const spread of [-24, 0, 24]) {
13+ // aim left of, at, and right of the cat
14+ const tx = (w.player ? w.player.x : 40) + spread;
15+ w.projectiles.push({
16+ kind: "pizza",
17+ x: this.x + 4,
18+ y: this.y - 4,
19+ vx: (tx - this.x) / 1.5,
20+ vy: -80 - w.rand() * 20,
21+ r: 0,
22+ spin: 8,
23+ life: 4,
24+ });
25+ }
26+ if (w.level.shake) w.shake = Math.max(w.shake, w.level.shake);
27+ }
28+}
29+
30+// imports are hoisted, so the class reads first
31+import { Boss } from "./boss.js";
src/levels/rooftop.js+6 −3
| 2 | // Roof tops are one-way platforms. The door on the left roof leads down to the lobby; walk off the left edge for the alley. | 2 | // Roof tops are one-way platforms. The door on the left roof leads down to the lobby; walk off the left edge for the alley. |
| 3 | import { PAL } from "../palette.js"; | 3 | import { PAL } from "../palette.js"; |
| 4 | import { blit } from "../sprites.js"; | 4 | import { blit } from "../sprites.js"; |
| 5 | +import { PizzaBoss } from "../bosses/pizzaBoss.js"; | ||
| 6 | 6 | ||
| 7 | const BLOCKS = [ | 7 | const BLOCKS = [ |
| 8 | { x: 0, y: 96, w: 80, h: 84, rows: 5 }, | 8 | { x: 0, y: 96, w: 80, h: 84, rows: 5 }, |
| export default { | export default { | ||
| 19 | bossAt: { x: 262, y: 92 }, | 19 | bossAt: { x: 262, y: 92 }, |
| 20 | exits: { left: { to: "alley", at: { x: 300, y: 150 } } }, | 20 | exits: { left: { to: "alley", at: { x: 300, y: 150 } } }, |
| 21 | doors: [{ x: 52, y: 84, to: "lobby", at: { x: 40, y: 150 } }], | 21 | doors: [{ x: 52, y: 84, to: "lobby", at: { x: 40, y: 150 } }], |
| 21 | − boss: null, | 22 | + boss: new PizzaBoss({ x: 262, y: 92 }), |
| 22 | − spawn: [], | 23 | + spawn: ["pizza"], |
| 23 | − music: "rooftop-a", | 24 | + music: "rooftop-boss", |
| 25 | + bossIntro: true, | ||
| 26 | + shake: 0.2, | ||
| 27 | draw(g) { | 27 | draw(g) { |
| 28 | for (const b of BLOCKS) { | 28 | for (const b of BLOCKS) { |
| 29 | g.fillStyle = PAL.wall; g.fillRect(b.x, b.y, b.w, b.h); | 29 | g.fillStyle = PAL.wall; g.fillRect(b.x, b.y, b.w, b.h); |
2 // Roof tops are one-way platforms. The door on the left roof leads down to the lobby; walk off the left edge for the alley.
3 import { PAL } from "../palette.js";
4 import { blit } from "../sprites.js";
5+import { PizzaBoss } from "../bosses/pizzaBoss.js";
6
7 const BLOCKS = [
8 { x: 0, y: 96, w: 80, h: 84, rows: 5 },
export default {
19 bossAt: { x: 262, y: 92 },
20 exits: { left: { to: "alley", at: { x: 300, y: 150 } } },
21 doors: [{ x: 52, y: 84, to: "lobby", at: { x: 40, y: 150 } }],
21− boss: null,
22− spawn: [],
23− music: "rooftop-a",
22+ boss: new PizzaBoss({ x: 262, y: 92 }),
23+ spawn: ["pizza"],
24+ music: "rooftop-boss",
25+ bossIntro: true,
26+ shake: 0.2,
27 draw(g) {
28 for (const b of BLOCKS) {
29 g.fillStyle = PAL.wall; g.fillRect(b.x, b.y, b.w, b.h);


CI log tail
$ ci:build (760 ms, exit 0) $ ci:test (199 ms, exit 0) TAP version 13 # Subtest: palette: valid hex, at most 40 colours ok 1 - palette: valid hex, at most 40 colours --- duration_ms: 1.547037 type: 'test' ... # Subtest: sprites: rectangular-ish rows of known keys ok 2 - sprites: rectangular-ish rows of known keys --- duration_ms: 4.60926 type: 'test' ... # Subtest: levels: every room has a name, platforms and a draw function ok 3 - levels: every room has a name, platforms and a draw function --- duration_ms: 6.938871 type: 'test' ... # Subtest: bosses: every file exports a class with update/draw ok 4 - bosses: every file exports a class with update/draw --- duration_ms: 1.359145 type: 'test' ... # Subtest: features: default export with a unique id and only known hooks ok 5 - features: default export with a unique id and only known hooks --- duration_ms: 7.652769 type: 'test' ... # Subtest: no network, storage or parent access anywhere in src ok 6 - no network, storage or parent access anywhere in src --- duration_ms: 1.830637 type: 'test' ... 1..6 # tests 6 # suites 0 # pass 6 # fail 0 # cancelled 0 # skipped 0 # todo 0 # duration_ms 129.118903 $ ci:lint (717 ms, exit 0) $ playwright 960x540 fps 60 long tasks 0 console errors 0