|
@@ -44,23 +44,99 @@ function ellipsePoly(cx: number, cy: number, rx: number, ry: number, angle: numb
|
|
|
return pts;
|
|
return pts;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+function darken(color: number, k: number): number {
|
|
|
|
|
+ return (Math.round((color >> 16) * k) << 16) | (Math.round(((color >> 8) & 0xff) * k) << 8) | Math.round((color & 0xff) * k);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * A round body part with volume: own shadow on the back and underside, a lit face, a soft
|
|
|
|
|
+ * highlight and a glint up front, crayon hatching in the shadow, then the felt outline.
|
|
|
|
|
+ * Light comes from the top-front (facing right, up).
|
|
|
|
|
+ */
|
|
|
|
|
+function body(g: Graphics, cx: number, cy: number, rx: number, ry: number, color: number, pose: Pose, angle = 0): void {
|
|
|
|
|
+ const shadow = darken(color, 0.72);
|
|
|
|
|
+ g.poly(ellipsePoly(cx, cy, rx, ry, angle, 28)).fill(shadow);
|
|
|
|
|
+ g.poly(ellipsePoly(cx + rx * 0.07, cy - ry * 0.09, rx * 0.9, ry * 0.88, angle, 28)).fill(color);
|
|
|
|
|
+ // Crayon hatching in the shadow (back-bottom).
|
|
|
|
|
+ for (let i = 0; i < 6; i++) {
|
|
|
|
|
+ const a = 1.95 + i * 0.2;
|
|
|
|
|
+ const x0 = cx + Math.cos(a) * rx * 0.62;
|
|
|
|
|
+ const y0 = cy + Math.sin(a) * ry * 0.62;
|
|
|
|
|
+ g.moveTo(x0, y0)
|
|
|
|
|
+ .lineTo(x0 - rx * 0.16, y0 + ry * 0.2)
|
|
|
|
|
+ .stroke({ width: pose.ink * 0.45, color: darken(color, 0.55), alpha: 0.45, cap: 'round' });
|
|
|
|
|
+ }
|
|
|
|
|
+ g.poly(ellipsePoly(cx - rx * 0.28, cy - ry * 0.5, rx * 0.34, ry * 0.17, -0.45, 16)).fill({ color: 0xffffff, alpha: 0.32 });
|
|
|
|
|
+ g.circle(cx + rx * 0.5, cy - ry * 0.55, Math.min(rx, ry) * 0.08).fill({ color: 0xffffff, alpha: 0.85 });
|
|
|
|
|
+ g.poly(ellipsePoly(cx, cy, rx, ry, angle, 28)).stroke({ width: pose.ink, color: INK, join: 'round' });
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** Upper half of an ellipse (helmets, hats), as a closed polygon. */
|
|
|
|
|
+function dome(cx: number, cy: number, rx: number, ry: number): number[] {
|
|
|
|
|
+ const pts: number[] = [];
|
|
|
|
|
+ for (let i = 0; i <= 16; i++) {
|
|
|
|
|
+ const a = Math.PI + (i / 16) * Math.PI;
|
|
|
|
|
+ pts.push(cx + Math.cos(a) * rx, cy + Math.sin(a) * ry);
|
|
|
|
|
+ }
|
|
|
|
|
+ return pts;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** Pink cheek. */
|
|
|
|
|
+function blush(g: Graphics, x: number, y: number, r: number): void {
|
|
|
|
|
+ g.ellipse(x, y, r, r * 0.65).fill({ color: 0xff5c8a, alpha: 0.35 });
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** Blinks every few seconds, out of step between players. */
|
|
|
|
|
+function blinking(pose: Pose): boolean {
|
|
|
|
|
+ const period = 3.1 + ((pose.color >> 4) & 7) * 0.23;
|
|
|
|
|
+ return (pose.time + (pose.color & 0xff) / 97) % period < 0.13;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Big expressive eyes: white with a shaded lid, a pupil following the aim with two glints,
|
|
|
|
|
+ * eyebrows giving the mood (angry when attacking, raised during the special), crosses when
|
|
|
|
|
+ * stunned, a blink now and then.
|
|
|
|
|
+ */
|
|
|
function eyes(g: Graphics, pose: Pose, spots: [number, number][], size: number): void {
|
|
function eyes(g: Graphics, pose: Pose, spots: [number, number][], size: number): void {
|
|
|
- for (const [ex, ey] of spots) {
|
|
|
|
|
|
|
+ spots.forEach(([ex, ey], i) => {
|
|
|
if (pose.stunned) {
|
|
if (pose.stunned) {
|
|
|
- const d = size * 0.6;
|
|
|
|
|
- g.moveTo(ex - d, ey - d).lineTo(ex + d, ey + d).moveTo(ex + d, ey - d).lineTo(ex - d, ey + d).stroke({ width: pose.ink, color: INK, cap: 'round' });
|
|
|
|
|
- continue;
|
|
|
|
|
|
|
+ // Spinning spiral eyes.
|
|
|
|
|
+ const turn = pose.time * 10 * (i ? -1 : 1);
|
|
|
|
|
+ g.moveTo(ex, ey);
|
|
|
|
|
+ for (let k = 1; k <= 14; k++) {
|
|
|
|
|
+ const a = turn + k * 0.75;
|
|
|
|
|
+ const rr = (k / 14) * size;
|
|
|
|
|
+ g.lineTo(ex + Math.cos(a) * rr, ey + Math.sin(a) * rr);
|
|
|
|
|
+ }
|
|
|
|
|
+ g.stroke({ width: pose.ink * 0.7, color: INK, cap: 'round' });
|
|
|
|
|
+ return;
|
|
|
}
|
|
}
|
|
|
- g.ellipse(ex, ey, size, size * 1.2).fill(0xffffff).stroke({ width: pose.ink * 0.8, color: INK });
|
|
|
|
|
- const px = ex + pose.lookX * size * 0.4;
|
|
|
|
|
- const py = ey + pose.lookY * size * 0.45;
|
|
|
|
|
- g.circle(px, py, size * 0.5).fill(INK);
|
|
|
|
|
- g.circle(px - size * 0.18, py - size * 0.2, size * 0.17).fill(0xffffff);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if (blinking(pose)) {
|
|
|
|
|
+ g.moveTo(ex - size, ey).quadraticCurveTo(ex, ey + size * 0.6, ex + size, ey).stroke({ width: pose.ink * 0.9, color: INK, cap: 'round' });
|
|
|
|
|
+ } else {
|
|
|
|
|
+ g.ellipse(ex, ey, size, size * 1.22).fill(0xffffff);
|
|
|
|
|
+ g.ellipse(ex, ey - size * 0.75, size * 0.9, size * 0.45).fill({ color: 0x9fb4d0, alpha: 0.35 });
|
|
|
|
|
+ g.ellipse(ex, ey, size, size * 1.22).stroke({ width: pose.ink * 0.8, color: INK });
|
|
|
|
|
+ const px = ex + pose.lookX * size * 0.42;
|
|
|
|
|
+ const py = ey + pose.lookY * size * 0.5;
|
|
|
|
|
+ g.circle(px, py, size * 0.55).fill(INK);
|
|
|
|
|
+ g.circle(px - size * 0.2, py - size * 0.22, size * 0.2).fill(0xffffff);
|
|
|
|
|
+ g.circle(px + size * 0.18, py + size * 0.2, size * 0.08).fill(0xffffff);
|
|
|
|
|
+ }
|
|
|
|
|
+ // Eyebrow: the inner end (toward the other eye) drops when angry, both rise when excited.
|
|
|
|
|
+ const inner = spots.length > 1 ? (i === 0 ? 1 : -1) : 1;
|
|
|
|
|
+ const by = ey - size * 1.65 - (pose.special ? size * 0.35 : 0);
|
|
|
|
|
+ const tilt = pose.attack ? size * 0.55 : pose.special ? -size * 0.15 : -size * 0.1;
|
|
|
|
|
+ g.moveTo(ex - inner * size * 0.85, by - (pose.attack ? size * 0.1 : 0))
|
|
|
|
|
+ .lineTo(ex + inner * size * 0.75, by + tilt)
|
|
|
|
|
+ .stroke({ width: pose.ink * 0.9, color: INK, cap: 'round' });
|
|
|
|
|
+ });
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function foot(g: Graphics, x: number, y: number, r: number, color: number, ink: number): void {
|
|
function foot(g: Graphics, x: number, y: number, r: number, color: number, ink: number): void {
|
|
|
- g.ellipse(x, y, r * 0.24, r * 0.13).fill(color).stroke({ width: ink, color: INK });
|
|
|
|
|
|
|
+ g.ellipse(x, y, r * 0.26, r * 0.14).fill(color);
|
|
|
|
|
+ g.ellipse(x + r * 0.04, y - r * 0.04, r * 0.14, r * 0.05).fill({ color: 0xffffff, alpha: 0.3 });
|
|
|
|
|
+ g.ellipse(x, y, r * 0.26, r * 0.14).stroke({ width: ink, color: INK });
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function stars(g: Graphics, r: number, pose: Pose): void {
|
|
function stars(g: Graphics, r: number, pose: Pose): void {
|
|
@@ -99,8 +175,10 @@ export const paintCastor: Painter = (g, r, pose) => {
|
|
|
foot(g, -0.38 * r - step * 0.18 * r, 0.93 * r - (step < 0 ? lift : 0), r, dark, pose.ink);
|
|
foot(g, -0.38 * r - step * 0.18 * r, 0.93 * r - (step < 0 ? lift : 0), r, dark, pose.ink);
|
|
|
|
|
|
|
|
// Body + belly.
|
|
// Body + belly.
|
|
|
- g.circle(0, 0, r).fill(fur).stroke({ width: pose.ink, color: INK });
|
|
|
|
|
|
|
+ body(g, 0, 0, r, r, fur, pose);
|
|
|
g.ellipse(0.22 * r, 0.32 * r, 0.52 * r, 0.5 * r).fill(0xe0b07a);
|
|
g.ellipse(0.22 * r, 0.32 * r, 0.52 * r, 0.5 * r).fill(0xe0b07a);
|
|
|
|
|
+ g.ellipse(0.3 * r, 0.22 * r, 0.3 * r, 0.22 * r).fill({ color: 0xffffff, alpha: 0.2 });
|
|
|
|
|
+ for (let i = 0; i < 4; i++) g.moveTo(0.02 * r + i * 0.14 * r, 0.62 * r).lineTo(0.08 * r + i * 0.14 * r, 0.7 * r).stroke({ width: pose.ink * 0.4, color: 0xb88550 });
|
|
|
// Fur tufts.
|
|
// Fur tufts.
|
|
|
g.moveTo(-0.55 * r, -0.2 * r).lineTo(-0.42 * r, -0.12 * r).moveTo(-0.6 * r, 0.05 * r).lineTo(-0.46 * r, 0.1 * r).stroke({ width: pose.ink * 0.5, color: dark });
|
|
g.moveTo(-0.55 * r, -0.2 * r).lineTo(-0.42 * r, -0.12 * r).moveTo(-0.6 * r, 0.05 * r).lineTo(-0.46 * r, 0.1 * r).stroke({ width: pose.ink * 0.5, color: dark });
|
|
|
|
|
|
|
@@ -158,9 +236,8 @@ export const paintGeneric: Painter = (g, r, pose) => {
|
|
|
g.moveTo(0, -r).quadraticCurveTo(0.05 * r, -1.25 * r, sway, -1.4 * r).stroke({ width: pose.ink, color: INK });
|
|
g.moveTo(0, -r).quadraticCurveTo(0.05 * r, -1.25 * r, sway, -1.4 * r).stroke({ width: pose.ink, color: INK });
|
|
|
g.poly(ellipsePoly(sway + 0.18 * r, -1.42 * r, 0.2 * r, 0.1 * r, -0.4)).fill(0x7ccf5a).stroke({ width: pose.ink * 0.7, color: INK });
|
|
g.poly(ellipsePoly(sway + 0.18 * r, -1.42 * r, 0.2 * r, 0.1 * r, -0.4)).fill(0x7ccf5a).stroke({ width: pose.ink * 0.7, color: INK });
|
|
|
|
|
|
|
|
- g.circle(0, 0, r).fill(pose.color).stroke({ width: pose.ink, color: INK });
|
|
|
|
|
- g.ellipse(-0.35 * r, -0.45 * r, 0.25 * r, 0.15 * r).fill({ color: 0xffffff, alpha: 0.55 });
|
|
|
|
|
- g.circle(0.55 * r, 0.25 * r, 0.14 * r).fill({ color: 0xff3b6b, alpha: 0.35 });
|
|
|
|
|
|
|
+ body(g, 0, 0, r, r, pose.color, pose);
|
|
|
|
|
+ blush(g, 0.6 * r, 0.22 * r, 0.16 * r);
|
|
|
|
|
|
|
|
eyes(
|
|
eyes(
|
|
|
g,
|
|
g,
|
|
@@ -178,10 +255,6 @@ export const paintGeneric: Painter = (g, r, pose) => {
|
|
|
stars(g, r, pose);
|
|
stars(g, r, pose);
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-function darken(color: number, k: number): number {
|
|
|
|
|
- return (Math.round((color >> 16) * k) << 16) | (Math.round(((color >> 8) & 0xff) * k) << 8) | Math.round((color & 0xff) * k);
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
/** Mamie Tricot: lilac dress, pink shawl, grey bun skewered with knitting needles, round glasses. */
|
|
/** Mamie Tricot: lilac dress, pink shawl, grey bun skewered with knitting needles, round glasses. */
|
|
|
export const paintMamie: Painter = (g, r, pose) => {
|
|
export const paintMamie: Painter = (g, r, pose) => {
|
|
|
const step = pose.moving && pose.grounded ? Math.sin(pose.walk) : 0;
|
|
const step = pose.moving && pose.grounded ? Math.sin(pose.walk) : 0;
|
|
@@ -194,14 +267,23 @@ export const paintMamie: Painter = (g, r, pose) => {
|
|
|
g.circle(-0.32 * r, -1.08 * r, 0.3 * r).fill(0xe8e8f0).stroke({ width: pose.ink, color: INK });
|
|
g.circle(-0.32 * r, -1.08 * r, 0.3 * r).fill(0xe8e8f0).stroke({ width: pose.ink, color: INK });
|
|
|
|
|
|
|
|
// Dress, face, hair cap.
|
|
// Dress, face, hair cap.
|
|
|
- g.circle(0, 0, r).fill(0xb18cff).stroke({ width: pose.ink, color: INK });
|
|
|
|
|
|
|
+ body(g, 0, 0, r, r, 0xb18cff, pose);
|
|
|
g.ellipse(0.18 * r, -0.2 * r, 0.68 * r, 0.6 * r).fill(0xffd9b8);
|
|
g.ellipse(0.18 * r, -0.2 * r, 0.68 * r, 0.6 * r).fill(0xffd9b8);
|
|
|
g.ellipse(-0.1 * r, -0.72 * r, 0.85 * r, 0.38 * r).fill(0xe8e8f0).stroke({ width: pose.ink * 0.7, color: INK });
|
|
g.ellipse(-0.1 * r, -0.72 * r, 0.85 * r, 0.38 * r).fill(0xe8e8f0).stroke({ width: pose.ink * 0.7, color: INK });
|
|
|
for (let i = 0; i < 4; i++) g.moveTo(-0.7 * r + i * 0.3 * r, -0.8 * r).quadraticCurveTo(-0.6 * r + i * 0.3 * r, -0.6 * r, -0.5 * r + i * 0.3 * r, -0.75 * r).stroke({ width: pose.ink * 0.4, color: 0xa0a8b0 });
|
|
for (let i = 0; i < 4; i++) g.moveTo(-0.7 * r + i * 0.3 * r, -0.8 * r).quadraticCurveTo(-0.6 * r + i * 0.3 * r, -0.6 * r, -0.5 * r + i * 0.3 * r, -0.75 * r).stroke({ width: pose.ink * 0.4, color: 0xa0a8b0 });
|
|
|
|
|
|
|
|
// Shawl draped over the shoulders.
|
|
// Shawl draped over the shoulders.
|
|
|
g.poly([-0.95 * r, 0.05 * r, 0.9 * r, 0.25 * r, 0.3 * r, 0.75 * r, -0.6 * r, 0.6 * r]).fill(0xff8fab).stroke({ width: pose.ink * 0.8, color: INK });
|
|
g.poly([-0.95 * r, 0.05 * r, 0.9 * r, 0.25 * r, 0.3 * r, 0.75 * r, -0.6 * r, 0.6 * r]).fill(0xff8fab).stroke({ width: pose.ink * 0.8, color: INK });
|
|
|
- for (let i = 0; i < 3; i++) g.circle(-0.4 * r + i * 0.35 * r, 0.42 * r, 0.06 * r).fill(0xffffff);
|
|
|
|
|
|
|
+ // Knitted stitches.
|
|
|
|
|
+ for (let row = 0; row < 2; row++) {
|
|
|
|
|
+ for (let i = 0; i < 5; i++) {
|
|
|
|
|
+ const kx = -0.6 * r + i * 0.28 * r + row * 0.14 * r;
|
|
|
|
|
+ const ky = 0.28 * r + row * 0.18 * r;
|
|
|
|
|
+ g.moveTo(kx - 0.06 * r, ky - 0.05 * r).lineTo(kx, ky + 0.04 * r).lineTo(kx + 0.06 * r, ky - 0.05 * r).stroke({ width: pose.ink * 0.4, color: 0xd9547a });
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ // Pearl necklace.
|
|
|
|
|
+ for (let i = 0; i < 6; i++) g.circle(-0.05 * r + i * 0.13 * r, 0.12 * r + Math.sin(i * 0.6) * 0.05 * r, 0.055 * r).fill(0xfffaf0).stroke({ width: pose.ink * 0.3, color: INK });
|
|
|
|
|
|
|
|
eyes(
|
|
eyes(
|
|
|
g,
|
|
g,
|
|
@@ -213,9 +295,12 @@ export const paintMamie: Painter = (g, r, pose) => {
|
|
|
0.12 * r,
|
|
0.12 * r,
|
|
|
);
|
|
);
|
|
|
// Round glasses.
|
|
// Round glasses.
|
|
|
- for (const ex of [0.2, 0.56]) g.circle(ex * r, -0.21 * r, 0.19 * r).stroke({ width: pose.ink * 0.6, color: INK });
|
|
|
|
|
|
|
+ for (const ex of [0.2, 0.56]) {
|
|
|
|
|
+ g.circle(ex * r, -0.21 * r, 0.19 * r).fill({ color: 0xbfe6ff, alpha: 0.2 }).stroke({ width: pose.ink * 0.6, color: INK });
|
|
|
|
|
+ g.moveTo(ex * r + 0.02 * r, -0.33 * r).lineTo(ex * r + 0.11 * r, -0.26 * r).stroke({ width: pose.ink * 0.4, color: 0xffffff, alpha: 0.9 });
|
|
|
|
|
+ }
|
|
|
g.moveTo(0.39 * r, -0.22 * r).lineTo(0.37 * r, -0.22 * r).stroke({ width: pose.ink * 0.6, color: INK });
|
|
g.moveTo(0.39 * r, -0.22 * r).lineTo(0.37 * r, -0.22 * r).stroke({ width: pose.ink * 0.6, color: INK });
|
|
|
- g.circle(0.68 * r, 0.02 * r, 0.11 * r).fill({ color: 0xff3b6b, alpha: 0.4 });
|
|
|
|
|
|
|
+ blush(g, 0.68 * r, 0.02 * r, 0.12 * r);
|
|
|
if (pose.attack) {
|
|
if (pose.attack) {
|
|
|
g.circle(0.42 * r, 0.1 * r, 0.08 * r).fill(0x5a1e1e);
|
|
g.circle(0.42 * r, 0.1 * r, 0.08 * r).fill(0x5a1e1e);
|
|
|
// Needles poking forward.
|
|
// Needles poking forward.
|
|
@@ -258,8 +343,10 @@ export const paintPyromanchot: Painter = (g, r, pose) => {
|
|
|
g.circle(-0.85 * r, -1.28 * r, 0.06 * r).fill(0xffffff);
|
|
g.circle(-0.85 * r, -1.28 * r, 0.06 * r).fill(0xffffff);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- g.ellipse(0, 0, r * (1 - waddle * 0.3), r * 1.05).fill(black).stroke({ width: pose.ink, color: INK });
|
|
|
|
|
- g.ellipse(0.25 * r, 0.18 * r, 0.6 * r, 0.75 * r).fill(0xffffff);
|
|
|
|
|
|
|
+ body(g, 0, 0, r * (1 - waddle * 0.3), r * 1.05, black, pose);
|
|
|
|
|
+ g.ellipse(0.25 * r, 0.18 * r, 0.6 * r, 0.75 * r).fill(0xf2f4f8);
|
|
|
|
|
+ g.ellipse(0.32 * r, 0.05 * r, 0.42 * r, 0.55 * r).fill(0xffffff);
|
|
|
|
|
+ blush(g, 0.62 * r, 0.02 * r, 0.12 * r);
|
|
|
// Flippers.
|
|
// Flippers.
|
|
|
const raise = pose.attack ? -0.6 : 0;
|
|
const raise = pose.attack ? -0.6 : 0;
|
|
|
g.poly(ellipsePoly(0.75 * r, (0.15 + raise * 0.4) * r, 0.18 * r, 0.42 * r, 0.5 + raise)).fill(black).stroke({ width: pose.ink * 0.8, color: INK });
|
|
g.poly(ellipsePoly(0.75 * r, (0.15 + raise * 0.4) * r, 0.18 * r, 0.42 * r, 0.5 + raise)).fill(black).stroke({ width: pose.ink * 0.8, color: INK });
|
|
@@ -270,6 +357,10 @@ export const paintPyromanchot: Painter = (g, r, pose) => {
|
|
|
}
|
|
}
|
|
|
// Headband.
|
|
// Headband.
|
|
|
g.rect(-0.95 * r, -0.68 * r, 1.85 * r, 0.2 * r).fill(0xe0123a).stroke({ width: pose.ink * 0.6, color: INK });
|
|
g.rect(-0.95 * r, -0.68 * r, 1.85 * r, 0.2 * r).fill(0xe0123a).stroke({ width: pose.ink * 0.6, color: INK });
|
|
|
|
|
+ g.moveTo(-0.95 * r, -0.62 * r).lineTo(0.9 * r, -0.62 * r).stroke({ width: pose.ink * 0.35, color: 0xff6f6f });
|
|
|
|
|
+ // Knot flapping behind.
|
|
|
|
|
+ const flap = Math.sin(pose.time * 9) * 0.08 * r;
|
|
|
|
|
+ g.poly([-0.9 * r, -0.6 * r, -1.35 * r, -0.75 * r + flap, -1.25 * r, -0.5 * r + flap]).fill(0xe0123a).stroke({ width: pose.ink * 0.6, color: INK });
|
|
|
eyes(
|
|
eyes(
|
|
|
g,
|
|
g,
|
|
|
pose,
|
|
pose,
|
|
@@ -294,20 +385,30 @@ export const paintGege: Painter = (g, r, pose) => {
|
|
|
g.ellipse(0, 0.55 * r, 1.1 * r, 0.5 * r).fill(0xc8964b).stroke({ width: pose.ink, color: INK });
|
|
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);
|
|
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);
|
|
eyes(g, pose, [[0.1 * r, 0.05 * r], [0.45 * r, 0.07 * r]], 0.17 * r);
|
|
|
|
|
+ // The lamp of his helmet glows out of the crumbs.
|
|
|
|
|
+ g.circle(0.3 * r, -0.28 * r, 0.11 * r).fill(0xfff7b0).stroke({ width: pose.ink * 0.5, color: INK });
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
const wiggle = pose.moving ? Math.sin(pose.walk) * 0.12 * r : Math.sin(pose.time * 3) * 0.05 * r;
|
|
const wiggle = pose.moving ? Math.sin(pose.walk) * 0.12 * r : Math.sin(pose.time * 3) * 0.05 * r;
|
|
|
// Tail segments behind.
|
|
// Tail segments behind.
|
|
|
for (let i = 3; i >= 1; i--) {
|
|
for (let i = 3; i >= 1; i--) {
|
|
|
const sr = r * (0.75 - i * 0.12);
|
|
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 sy = 0.35 * r + (i % 2 ? wiggle : -wiggle);
|
|
|
|
|
+ body(g, -i * 0.55 * r, sy, sr, sr, i % 2 ? pink : dark, pose);
|
|
|
|
|
+ g.moveTo(-i * 0.55 * r - sr * 0.2, sy - sr * 0.9).quadraticCurveTo(-i * 0.55 * r - sr * 0.45, sy, -i * 0.55 * r - sr * 0.2, sy + sr * 0.9).stroke({ width: pose.ink * 0.45, color: darken(pink, 0.7) });
|
|
|
}
|
|
}
|
|
|
const lunge = pose.attack ? 0.25 * r : 0;
|
|
const lunge = pose.attack ? 0.25 * r : 0;
|
|
|
- g.circle(lunge, 0, r).fill(pink).stroke({ width: pose.ink, color: INK });
|
|
|
|
|
|
|
+ body(g, lunge, 0, r, r, pink, pose);
|
|
|
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 });
|
|
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 });
|
|
|
|
|
|
|
+ // Miner's helmet with a lamp.
|
|
|
|
|
+ g.poly(dome(lunge - 0.05 * r, -0.55 * r, 0.85 * r, 0.5 * r)).fill(0xffd23f).stroke({ width: pose.ink, color: INK });
|
|
|
|
|
+ g.rect(lunge - 0.98 * r, -0.6 * r, 1.9 * r, 0.14 * r).fill(0xe0a800).stroke({ width: pose.ink * 0.6, color: INK });
|
|
|
|
|
+ g.roundRect(lunge + 0.38 * r, -0.98 * r, 0.3 * r, 0.24 * r, 0.06 * r).fill(0x8899aa).stroke({ width: pose.ink * 0.6, color: INK });
|
|
|
|
|
+ g.circle(lunge + 0.68 * r, -0.86 * r, 0.11 * r).fill(0xfff7b0).stroke({ width: pose.ink * 0.5, color: INK });
|
|
|
|
|
+ g.poly([lunge + 0.75 * r, -0.92 * r, lunge + 1.9 * r, -1.25 * r, lunge + 1.9 * r, -0.45 * r, lunge + 0.75 * r, -0.8 * r]).fill({ color: 0xfff7b0, alpha: 0.28 });
|
|
|
eyes(g, pose, [[lunge + 0.25 * r, -0.2 * r], [lunge + 0.6 * r, -0.18 * r]], 0.18 * r);
|
|
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' });
|
|
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' });
|
|
|
|
|
+ blush(g, lunge + 0.72 * r, 0.1 * r, 0.13 * r);
|
|
|
stars(g, r, pose);
|
|
stars(g, r, pose);
|
|
|
};
|
|
};
|
|
|
|
|
|
|
@@ -315,12 +416,19 @@ export const paintGege: Painter = (g, r, pose) => {
|
|
|
export const paintPompier: Painter = (g, r, pose) => {
|
|
export const paintPompier: Painter = (g, r, pose) => {
|
|
|
const step = pose.moving && pose.grounded ? Math.sin(pose.walk) : 0;
|
|
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);
|
|
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 });
|
|
|
|
|
|
|
+ body(g, 0, 0, r, r, 0xffd27a, pose);
|
|
|
|
|
+ // Reflective stripes on the coat.
|
|
|
|
|
+ g.rect(-0.85 * r, 0.42 * r, 1.7 * r, 0.12 * r).fill(0xd8e0e8).stroke({ width: pose.ink * 0.4, color: INK });
|
|
|
g.ellipse(0.2 * r, -0.15 * r, 0.62 * r, 0.55 * r).fill(0xffdcc4);
|
|
g.ellipse(0.2 * r, -0.15 * r, 0.62 * r, 0.55 * r).fill(0xffdcc4);
|
|
|
|
|
+ blush(g, 0.66 * r, 0.02 * r, 0.12 * r);
|
|
|
|
|
+ blush(g, 0.0 * r, 0.02 * r, 0.1 * r);
|
|
|
// Helmet with a badge.
|
|
// 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.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.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 });
|
|
g.circle(0.15 * r, -0.72 * r, 0.14 * r).fill(0xffe14d).stroke({ width: pose.ink * 0.5, color: INK });
|
|
|
|
|
+ g.ellipse(-0.35 * r, -0.82 * r, 0.25 * r, 0.1 * r).fill({ color: 0xffffff, alpha: 0.4 });
|
|
|
|
|
+ // A curl of hair escaping the helmet.
|
|
|
|
|
+ g.moveTo(0.55 * r, -0.42 * r).quadraticCurveTo(0.75 * r, -0.62 * r, 0.62 * r, -0.72 * r).stroke({ width: pose.ink * 0.6, color: 0x8a5a2a, cap: 'round' });
|
|
|
eyes(g, pose, [[0.15 * r, -0.2 * r], [0.5 * r, -0.18 * r]], 0.15 * r);
|
|
eyes(g, pose, [[0.15 * r, -0.2 * r], [0.5 * r, -0.18 * r]], 0.15 * r);
|
|
|
// Pacifier.
|
|
// 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.16 * r).fill(0x4dc4ff).stroke({ width: pose.ink * 0.6, color: INK });
|
|
@@ -343,10 +451,14 @@ export const paintProut: Painter = (g, r, pose) => {
|
|
|
const flap = Math.sin(pose.time * 8) * 0.15 * r + (pose.special ? 0.3 * r : 0);
|
|
const flap = Math.sin(pose.time * 8) * 0.15 * r + (pose.special ? 0.3 * r : 0);
|
|
|
// Cape behind, flapping.
|
|
// 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 });
|
|
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 });
|
|
|
|
|
+ g.poly([-0.3 * r, -0.5 * r, -1.0 * r - flap * 0.7, 0.45 * r, -0.85 * r - flap * 0.7, 0.75 * r]).fill({ color: 0x5e2290, alpha: 0.6 });
|
|
|
foot(g, -0.35 * r - step * 0.18 * r, 0.93 * r, r, 0x2a2a3a, pose.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 });
|
|
|
|
|
|
|
+ body(g, 0, 0, r, r, 0x9be15d, pose);
|
|
|
// Mask.
|
|
// Mask.
|
|
|
- g.rect(-0.1 * r, -0.42 * r, 1.0 * r, 0.32 * r).fill(0x2a2a3a);
|
|
|
|
|
|
|
+ g.roundRect(-0.15 * r, -0.44 * r, 1.07 * r, 0.36 * r, 0.12 * r).fill(0x8a3fc9).stroke({ width: pose.ink * 0.6, color: INK });
|
|
|
|
|
+ g.poly([-0.15 * r, -0.36 * r, -0.45 * r, -0.5 * r - Math.sin(pose.time * 8) * 0.05 * r, -0.42 * r, -0.25 * r]).fill(0x8a3fc9).stroke({ width: pose.ink * 0.5, color: INK });
|
|
|
|
|
+ // Belt.
|
|
|
|
|
+ g.rect(-0.95 * r, 0.62 * r, 1.9 * r, 0.14 * r).fill(0xffe14d).stroke({ width: pose.ink * 0.5, color: INK });
|
|
|
eyes(g, pose, [[0.25 * r, -0.26 * r], [0.62 * r, -0.24 * r]], 0.13 * r);
|
|
eyes(g, pose, [[0.25 * r, -0.26 * r], [0.62 * r, -0.24 * r]], 0.13 * r);
|
|
|
// Emblem.
|
|
// Emblem.
|
|
|
g.circle(0.15 * r, 0.35 * r, 0.3 * r).fill(0xffe14d).stroke({ width: pose.ink * 0.7, color: INK });
|
|
g.circle(0.15 * r, 0.35 * r, 0.3 * r).fill(0xffe14d).stroke({ width: pose.ink * 0.7, color: INK });
|
|
@@ -369,9 +481,14 @@ export const paintChat: Painter = (g, r, pose) => {
|
|
|
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.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.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 });
|
|
|
|
|
|
|
+ body(g, 0, 0, r, r, ginger, pose);
|
|
|
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 });
|
|
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);
|
|
g.ellipse(0.25 * r, 0.3 * r, 0.45 * r, 0.4 * r).fill(0xfff3e0);
|
|
|
|
|
+ for (let i = 0; i < 3; i++) g.moveTo(-0.92 * r, -0.1 * r + i * 0.25 * r).quadraticCurveTo(-0.7 * r, -0.05 * r + i * 0.25 * r, -0.55 * r, -0.12 * r + i * 0.25 * r).stroke({ width: pose.ink * 0.7, color: dark, cap: 'round' });
|
|
|
|
|
+ // Collar and its bell.
|
|
|
|
|
+ g.moveTo(-0.6 * r, 0.62 * r).quadraticCurveTo(0.1 * r, 0.82 * r, 0.75 * r, 0.55 * r).stroke({ width: pose.ink * 1.6, color: 0xe0123a, cap: 'round' });
|
|
|
|
|
+ g.circle(0.2 * r, 0.8 * r, 0.12 * r).fill(0xffd23f).stroke({ width: pose.ink * 0.5, color: INK });
|
|
|
|
|
+ blush(g, 0.72 * r, 0.12 * r, 0.11 * r);
|
|
|
if (pose.busy) {
|
|
if (pose.busy) {
|
|
|
// Eyes closed, paw up at the mouth.
|
|
// 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.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 });
|
|
@@ -391,16 +508,21 @@ export const paintChat: Painter = (g, r, pose) => {
|
|
|
export const paintMagicien: Painter = (g, r, pose) => {
|
|
export const paintMagicien: Painter = (g, r, pose) => {
|
|
|
const step = pose.moving && pose.grounded ? Math.sin(pose.walk) : 0;
|
|
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);
|
|
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 });
|
|
|
|
|
|
|
+ body(g, 0, 0, r, r, 0x3d5adb, pose);
|
|
|
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);
|
|
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);
|
|
g.ellipse(0.25 * r, -0.2 * r, 0.55 * r, 0.45 * r).fill(0xffdcc4);
|
|
|
// Hat.
|
|
// Hat.
|
|
|
const tilt = Math.sin(pose.time * 2) * 0.05 * r;
|
|
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.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);
|
|
|
|
|
|
|
+ g.circle(0.05 * r + tilt, -1.0 * r, 0.14 * r).fill(0xffe14d);
|
|
|
|
|
+ g.circle(0.11 * r + tilt, -1.04 * r, 0.12 * r).fill(0x2a2a6a);
|
|
|
|
|
+ for (const [hx, hy] of [[-0.3, -0.7], [0.3, -1.3], [0.4, -0.75]]) g.circle(hx * r + tilt * 0.5, hy * r, 0.05 * r).fill(0xffffff);
|
|
|
|
|
+ g.poly([-0.82 * r, -0.5 * r, 0.82 * r, -0.5 * r, 0.75 * r, -0.62 * r, -0.75 * r, -0.62 * r]).fill(0x1e1e55).stroke({ width: pose.ink * 0.6, color: INK });
|
|
|
eyes(g, pose, [[0.15 * r, -0.28 * r], [0.48 * r, -0.26 * r]], 0.13 * r);
|
|
eyes(g, pose, [[0.15 * r, -0.28 * r], [0.48 * r, -0.26 * r]], 0.13 * r);
|
|
|
// Beard.
|
|
// 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 });
|
|
|
|
|
|
|
+ g.poly([-0.05 * r, -0.02 * r, 0.8 * r, -0.02 * r, 0.65 * r, 0.45 * r, 0.42 * r, 0.9 * r, 0.2 * r, 0.45 * r]).fill(0xffffff).stroke({ width: pose.ink * 0.8, color: INK });
|
|
|
|
|
+ g.moveTo(0.2 * r, 0.15 * r).lineTo(0.32 * r, 0.5 * r).moveTo(0.5 * r, 0.12 * r).lineTo(0.45 * r, 0.55 * r).stroke({ width: pose.ink * 0.4, color: 0xc8d0dc });
|
|
|
|
|
+ g.ellipse(0.38 * r, 0.02 * r, 0.12 * r, 0.07 * r).fill(0xffb39a).stroke({ width: pose.ink * 0.4, color: INK });
|
|
|
// Wand.
|
|
// Wand.
|
|
|
const raise = pose.special || pose.attack ? -0.6 : 0;
|
|
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' });
|
|
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' });
|