|
|
@@ -12,6 +12,10 @@ export interface Pose {
|
|
|
attack: boolean;
|
|
|
special: boolean;
|
|
|
stunned: boolean;
|
|
|
+ /** Busy with something else (the cat licking its paw). */
|
|
|
+ busy: boolean;
|
|
|
+ /** Underground (Gégé swimming in the cake). */
|
|
|
+ underground: boolean;
|
|
|
/** Unit look direction in local space. */
|
|
|
lookX: number;
|
|
|
lookY: number;
|
|
|
@@ -280,9 +284,144 @@ export const paintPyromanchot: Painter = (g, r, pose) => {
|
|
|
stars(g, r, pose);
|
|
|
};
|
|
|
|
|
|
+
|
|
|
+/** Gégé le Ver: a pink earthworm in segments with a headband; underground, only a mound and his eyes. */
|
|
|
+export const paintGege: Painter = (g, r, pose) => {
|
|
|
+ const pink = 0xff9ab0;
|
|
|
+ const dark = 0xe06a88;
|
|
|
+ if (pose.underground) {
|
|
|
+ // A heap of crumbs moving along, two eyes peeking out.
|
|
|
+ g.ellipse(0, 0.55 * r, 1.1 * r, 0.5 * r).fill(0xc8964b).stroke({ width: pose.ink, color: INK });
|
|
|
+ for (let i = 0; i < 5; i++) g.circle(-0.7 * r + i * 0.35 * r, 0.3 * r + (i % 2) * 0.1 * r, 0.12 * r).fill(0xf2c46d);
|
|
|
+ eyes(g, pose, [[0.1 * r, 0.05 * r], [0.45 * r, 0.07 * r]], 0.17 * r);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const wiggle = pose.moving ? Math.sin(pose.walk) * 0.12 * r : Math.sin(pose.time * 3) * 0.05 * r;
|
|
|
+ // Tail segments behind.
|
|
|
+ for (let i = 3; i >= 1; i--) {
|
|
|
+ const sr = r * (0.75 - i * 0.12);
|
|
|
+ g.circle(-i * 0.55 * r, 0.35 * r + (i % 2 ? wiggle : -wiggle), sr).fill(i % 2 ? pink : dark).stroke({ width: pose.ink, color: INK });
|
|
|
+ }
|
|
|
+ const lunge = pose.attack ? 0.25 * r : 0;
|
|
|
+ g.circle(lunge, 0, r).fill(pink).stroke({ width: pose.ink, color: INK });
|
|
|
+ for (let i = -1; i <= 1; i++) g.moveTo(lunge - 0.6 * r, i * 0.25 * r).lineTo(lunge - 0.85 * r, i * 0.3 * r).stroke({ width: pose.ink * 0.5, color: dark });
|
|
|
+ g.rect(lunge - 0.95 * r, -0.62 * r, 1.9 * r, 0.18 * r).fill(0x4dc4ff).stroke({ width: pose.ink * 0.6, color: INK });
|
|
|
+ eyes(g, pose, [[lunge + 0.25 * r, -0.2 * r], [lunge + 0.6 * r, -0.18 * r]], 0.18 * r);
|
|
|
+ g.moveTo(lunge + 0.3 * r, 0.3 * r).quadraticCurveTo(lunge + 0.5 * r, 0.45 * r, lunge + 0.7 * r, 0.28 * r).stroke({ width: pose.ink * 0.7, color: INK, cap: 'round' });
|
|
|
+ stars(g, r, pose);
|
|
|
+};
|
|
|
+
|
|
|
+/** Bébé Pompier: a baby in a red helmet and yellow coat, pacifier in mouth, hose in hand. */
|
|
|
+export const paintPompier: Painter = (g, r, pose) => {
|
|
|
+ const step = pose.moving && pose.grounded ? Math.sin(pose.walk) : 0;
|
|
|
+ foot(g, -0.35 * r - step * 0.18 * r, 0.93 * r, r, 0x5a3a22, pose.ink);
|
|
|
+ g.circle(0, 0, r).fill(0xffd27a).stroke({ width: pose.ink, color: INK });
|
|
|
+ g.ellipse(0.2 * r, -0.15 * r, 0.62 * r, 0.55 * r).fill(0xffdcc4);
|
|
|
+ // Helmet with a badge.
|
|
|
+ g.poly(ellipsePoly(0, -0.62 * r, 1.0 * r, 0.42 * r, 0)).fill(0xe0123a).stroke({ width: pose.ink, color: INK });
|
|
|
+ g.rect(-1.1 * r, -0.5 * r, 2.2 * r, 0.14 * r).fill(0xc00f30).stroke({ width: pose.ink * 0.6, color: INK });
|
|
|
+ g.circle(0.15 * r, -0.72 * r, 0.14 * r).fill(0xffe14d).stroke({ width: pose.ink * 0.5, color: INK });
|
|
|
+ eyes(g, pose, [[0.15 * r, -0.2 * r], [0.5 * r, -0.18 * r]], 0.15 * r);
|
|
|
+ // Pacifier.
|
|
|
+ g.circle(0.48 * r, 0.22 * r, 0.16 * r).fill(0x4dc4ff).stroke({ width: pose.ink * 0.6, color: INK });
|
|
|
+ g.circle(0.48 * r, 0.22 * r, 0.06 * r).fill(0xffffff);
|
|
|
+ // Hose nozzle, spraying when attacking.
|
|
|
+ g.roundRect(0.6 * r, 0.32 * r, 0.6 * r, 0.16 * r, 0.05 * r).fill(0x8899aa).stroke({ width: pose.ink * 0.6, color: INK });
|
|
|
+ if (pose.attack) {
|
|
|
+ for (let i = 0; i < 4; i++) {
|
|
|
+ const y = 0.4 * r + (i - 1.5) * 0.12 * r;
|
|
|
+ g.moveTo(1.25 * r, y).lineTo((1.9 + (i % 2) * 0.4) * r, y + (i - 1.5) * 0.1 * r).stroke({ width: pose.ink * 0.9, color: 0x4dc4ff, cap: 'round' });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ foot(g, 0.35 * r + step * 0.18 * r, 0.93 * r, r, 0x5a3a22, pose.ink);
|
|
|
+ stars(g, r, pose);
|
|
|
+};
|
|
|
+
|
|
|
+/** Captain Prout: a superhero with a purple cape, a mask and a big P on the chest. */
|
|
|
+export const paintProut: Painter = (g, r, pose) => {
|
|
|
+ const step = pose.moving && pose.grounded ? Math.sin(pose.walk) : 0;
|
|
|
+ const flap = Math.sin(pose.time * 8) * 0.15 * r + (pose.special ? 0.3 * r : 0);
|
|
|
+ // Cape behind, flapping.
|
|
|
+ g.poly([-0.3 * r, -0.6 * r, -1.35 * r - flap, 0.5 * r, -1.1 * r - flap, 0.9 * r, 0.1 * r, 0.6 * r]).fill(0x8a3fc9).stroke({ width: pose.ink, color: INK });
|
|
|
+ foot(g, -0.35 * r - step * 0.18 * r, 0.93 * r, r, 0x2a2a3a, pose.ink);
|
|
|
+ g.circle(0, 0, r).fill(0x9be15d).stroke({ width: pose.ink, color: INK });
|
|
|
+ // Mask.
|
|
|
+ g.rect(-0.1 * r, -0.42 * r, 1.0 * r, 0.32 * r).fill(0x2a2a3a);
|
|
|
+ eyes(g, pose, [[0.25 * r, -0.26 * r], [0.62 * r, -0.24 * r]], 0.13 * r);
|
|
|
+ // Emblem.
|
|
|
+ g.circle(0.15 * r, 0.35 * r, 0.3 * r).fill(0xffe14d).stroke({ width: pose.ink * 0.7, color: INK });
|
|
|
+ g.moveTo(0.06 * r, 0.52 * r).lineTo(0.06 * r, 0.2 * r).quadraticCurveTo(0.32 * r, 0.2 * r, 0.06 * r, 0.38 * r).stroke({ width: pose.ink * 0.8, color: 0x8a3fc9 });
|
|
|
+ if (pose.attack) g.poly([0.6 * r, -0.3 * r, 1.6 * r, 0.1 * r, 0.7 * r, 0.4 * r]).fill(0x8a3fc9).stroke({ width: pose.ink * 0.8, color: INK });
|
|
|
+ foot(g, 0.35 * r + step * 0.18 * r, 0.93 * r, r, 0x2a2a3a, pose.ink);
|
|
|
+ stars(g, r, pose);
|
|
|
+};
|
|
|
+
|
|
|
+/** Le Chat: a ginger cat with pointy ears, whiskers and a curling tail; licks its paw when busy. */
|
|
|
+export const paintChat: Painter = (g, r, pose) => {
|
|
|
+ const ginger = 0xff9a2e;
|
|
|
+ const dark = 0xd9741a;
|
|
|
+ const step = pose.moving && pose.grounded ? Math.sin(pose.walk) : 0;
|
|
|
+ const curl = Math.sin(pose.time * 2.5) * 0.2;
|
|
|
+ g.moveTo(-0.8 * r, 0.4 * r).quadraticCurveTo(-1.5 * r, 0.2 * r, -1.3 * r - curl * r, -0.5 * r).stroke({ width: pose.ink * 3.2, color: INK, cap: 'round' });
|
|
|
+ g.moveTo(-0.8 * r, 0.4 * r).quadraticCurveTo(-1.5 * r, 0.2 * r, -1.3 * r - curl * r, -0.5 * r).stroke({ width: pose.ink * 2, color: ginger, cap: 'round' });
|
|
|
+ foot(g, -0.35 * r - step * 0.2 * r, 0.93 * r, r, dark, pose.ink);
|
|
|
+ for (const ex of [-0.45, 0.35]) {
|
|
|
+ g.poly([ex * r - 0.25 * r, -0.65 * r, ex * r, -1.2 * r, ex * r + 0.28 * r, -0.62 * r]).fill(ginger).stroke({ width: pose.ink, color: INK });
|
|
|
+ g.poly([ex * r - 0.12 * r, -0.7 * r, ex * r, -1.0 * r, ex * r + 0.14 * r, -0.68 * r]).fill(0xffb6c8);
|
|
|
+ }
|
|
|
+ g.circle(0, 0, r).fill(ginger).stroke({ width: pose.ink, color: INK });
|
|
|
+ for (let i = 0; i < 3; i++) g.moveTo(-0.6 * r + i * 0.25 * r, -0.85 * r).lineTo(-0.5 * r + i * 0.25 * r, -0.6 * r).stroke({ width: pose.ink * 0.6, color: dark });
|
|
|
+ g.ellipse(0.25 * r, 0.3 * r, 0.45 * r, 0.4 * r).fill(0xfff3e0);
|
|
|
+ if (pose.busy) {
|
|
|
+ // Eyes closed, paw up at the mouth.
|
|
|
+ g.moveTo(0.1 * r, -0.2 * r).lineTo(0.35 * r, -0.2 * r).moveTo(0.48 * r, -0.18 * r).lineTo(0.72 * r, -0.18 * r).stroke({ width: pose.ink * 0.8, color: INK });
|
|
|
+ g.circle(0.6 * r, 0.25 * r, 0.22 * r).fill(ginger).stroke({ width: pose.ink * 0.8, color: INK });
|
|
|
+ g.ellipse(0.55 * r, 0.12 * r, 0.08 * r, 0.1 * r).fill(0xff6f91);
|
|
|
+ } else {
|
|
|
+ eyes(g, pose, [[0.22 * r, -0.2 * r], [0.6 * r, -0.18 * r]], 0.15 * r);
|
|
|
+ g.poly([0.62 * r, 0.02 * r, 0.74 * r, 0.02 * r, 0.68 * r, 0.1 * r]).fill(0xff6f91);
|
|
|
+ }
|
|
|
+ g.moveTo(0.8 * r, 0.08 * r).lineTo(1.3 * r, 0).moveTo(0.8 * r, 0.14 * r).lineTo(1.3 * r, 0.2 * r).stroke({ width: pose.ink * 0.4, color: INK });
|
|
|
+ if (pose.attack || pose.special) g.circle(1.05 * r, 0.1 * r, 0.24 * r).fill(ginger).stroke({ width: pose.ink * 0.8, color: INK });
|
|
|
+ foot(g, 0.35 * r + step * 0.2 * r, 0.93 * r, r, dark, pose.ink);
|
|
|
+ stars(g, r, pose);
|
|
|
+};
|
|
|
+
|
|
|
+/** Le Magicien Raté: blue starry robe, pointy hat, long white beard, a wand that sparkles on casting. */
|
|
|
+export const paintMagicien: Painter = (g, r, pose) => {
|
|
|
+ const step = pose.moving && pose.grounded ? Math.sin(pose.walk) : 0;
|
|
|
+ foot(g, -0.35 * r - step * 0.18 * r, 0.93 * r, r, 0x2a2a6a, pose.ink);
|
|
|
+ g.circle(0, 0, r).fill(0x3d5adb).stroke({ width: pose.ink, color: INK });
|
|
|
+ for (const [sx, sy] of [[-0.5, 0.2], [-0.2, 0.6], [-0.6, -0.3]]) g.circle(sx * r, sy * r, 0.07 * r).fill(0xffe14d);
|
|
|
+ g.ellipse(0.25 * r, -0.2 * r, 0.55 * r, 0.45 * r).fill(0xffdcc4);
|
|
|
+ // Hat.
|
|
|
+ const tilt = Math.sin(pose.time * 2) * 0.05 * r;
|
|
|
+ g.poly([-0.75 * r, -0.55 * r, 0.75 * r, -0.55 * r, 0.1 * r + tilt, -1.75 * r]).fill(0x2a2a6a).stroke({ width: pose.ink, color: INK });
|
|
|
+ g.circle(0.05 * r + tilt, -1.0 * r, 0.12 * r).fill(0xffe14d);
|
|
|
+ eyes(g, pose, [[0.15 * r, -0.28 * r], [0.48 * r, -0.26 * r]], 0.13 * r);
|
|
|
+ // Beard.
|
|
|
+ g.poly([0.0 * r, 0.0 * r, 0.75 * r, 0.0 * r, 0.42 * r, 0.85 * r]).fill(0xffffff).stroke({ width: pose.ink * 0.8, color: INK });
|
|
|
+ // Wand.
|
|
|
+ const raise = pose.special || pose.attack ? -0.6 : 0;
|
|
|
+ g.moveTo(0.7 * r, 0.25 * r).lineTo(1.35 * r, (0.05 + raise) * r).stroke({ width: pose.ink * 1.1, color: 0x5a3a22, cap: 'round' });
|
|
|
+ if (pose.special) {
|
|
|
+ for (let i = 0; i < 6; i++) {
|
|
|
+ const a = pose.time * 9 + (i * Math.PI) / 3;
|
|
|
+ g.circle(1.35 * r + Math.cos(a) * 0.3 * r, (0.05 + raise) * r + Math.sin(a) * 0.3 * r, 0.07 * r).fill(i % 2 ? 0xffe14d : 0xff6f91);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ foot(g, 0.35 * r + step * 0.18 * r, 0.93 * r, r, 0x2a2a6a, pose.ink);
|
|
|
+ stars(g, r, pose);
|
|
|
+};
|
|
|
+
|
|
|
export const PAINTERS: Record<string, Painter> = {
|
|
|
castor: paintCastor,
|
|
|
mamie: paintMamie,
|
|
|
pyromanchot: paintPyromanchot,
|
|
|
+ gege: paintGege,
|
|
|
+ pompier: paintPompier,
|
|
|
+ prout: paintProut,
|
|
|
+ chat: paintChat,
|
|
|
+ magicien: paintMagicien,
|
|
|
generic: paintGeneric,
|
|
|
};
|