@fabi-play/sdk 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +84 -0
- package/dist/avatars.d.ts +26 -14
- package/dist/avatars.js +1 -1
- package/dist/chunks/{chunk-GFNOIOSX.js → chunk-H3BKYVPL.js} +301 -22
- package/dist/fabi-play.d.ts +193 -10
- package/dist/fabi-play.js +1 -1
- package/dist/fabi-play.schema.json +32 -3
- package/dist/fabi-play.umd.js +453 -26
- package/dist/fabi-play.umd.min.js +1 -1
- package/dist/index.d.ts +212 -11
- package/dist/index.js +166 -4
- package/dist/integrity.json +6 -6
- package/package.json +3 -3
|
@@ -2,6 +2,35 @@ var __defProp = Object.defineProperty;
|
|
|
2
2
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
3
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
4
|
|
|
5
|
+
// ../protocol/src/picture.ts
|
|
6
|
+
var MAX_PICTURE_BYTES = 3 * 1024 * 1024;
|
|
7
|
+
var MAX_PICTURE_SIDE = 1920;
|
|
8
|
+
var PICTURE_JPEG_QUALITY = 0.85;
|
|
9
|
+
var MAX_PICTURE_TITLE = 60;
|
|
10
|
+
var PICTURE_RESULTS = ["sent", "unavailable", "too-big", "error"];
|
|
11
|
+
var BASE64 = /^[A-Za-z0-9+/]*={0,2}$/;
|
|
12
|
+
var MAGIC = { "image/jpeg": "/9j/", "image/png": "iVBORw0KGg" };
|
|
13
|
+
var PREFIX = /^data:(image\/(?:jpeg|png));base64,/;
|
|
14
|
+
var decodedSize = (b64) => b64.length / 4 * 3 - (b64.endsWith("==") ? 2 : b64.endsWith("=") ? 1 : 0);
|
|
15
|
+
var MAX_BASE64 = Math.ceil(MAX_PICTURE_BYTES / 3) * 4;
|
|
16
|
+
var isImage = (mime, b64) => b64.length % 4 === 0 && b64.length > 0 && b64.startsWith(MAGIC[mime]) && BASE64.test(b64);
|
|
17
|
+
function checkPicture(v) {
|
|
18
|
+
if (typeof v !== "string") return { error: "invalid" };
|
|
19
|
+
const m = PREFIX.exec(v);
|
|
20
|
+
if (!m) return { error: "invalid" };
|
|
21
|
+
const mime = m[1];
|
|
22
|
+
const base64 = v.slice(m[0].length);
|
|
23
|
+
if (base64.length > MAX_BASE64) return { error: "too-big" };
|
|
24
|
+
if (!isImage(mime, base64)) return { error: "invalid" };
|
|
25
|
+
const size = decodedSize(base64);
|
|
26
|
+
return size > MAX_PICTURE_BYTES ? { error: "too-big" } : { ok: { mime, base64, size } };
|
|
27
|
+
}
|
|
28
|
+
function parsePictureTitle(v) {
|
|
29
|
+
if (typeof v !== "string") return void 0;
|
|
30
|
+
const t = Array.from(v.replace(/[\p{Cc}\p{Cf}]/gu, "").trim()).slice(0, MAX_PICTURE_TITLE).join("").trim();
|
|
31
|
+
return t || void 0;
|
|
32
|
+
}
|
|
33
|
+
|
|
5
34
|
// ../protocol/src/index.ts
|
|
6
35
|
var PROTOCOL_VERSION = 1;
|
|
7
36
|
var LANDMARKS = 33;
|
|
@@ -57,6 +86,15 @@ function parseGameStats(v) {
|
|
|
57
86
|
if (DEVICE_TIERS.includes(v.tier)) out.tier = v.tier;
|
|
58
87
|
return out;
|
|
59
88
|
}
|
|
89
|
+
function parseCursor(v) {
|
|
90
|
+
if (!isObject(v) || !isNumber(v.x) || !isNumber(v.y) || typeof v.down !== "boolean") return void 0;
|
|
91
|
+
const c = (n) => Math.min(Math.max(n, 0), 1);
|
|
92
|
+
return { type: "cursor", x: c(v.x), y: c(v.y), down: v.down };
|
|
93
|
+
}
|
|
94
|
+
function parsePictureResult(v) {
|
|
95
|
+
if (!isObject(v) || !PICTURE_RESULTS.includes(v.result)) return void 0;
|
|
96
|
+
return { type: "picture-result", ...Number.isInteger(v.id) ? { id: v.id } : {}, result: v.result };
|
|
97
|
+
}
|
|
60
98
|
var isPlayerId = (id) => Number.isInteger(id) && id >= 1 && id <= MAX_PLAYERS;
|
|
61
99
|
function parseTurnPlayers(v) {
|
|
62
100
|
return Array.isArray(v) && v.length === 1 && isPlayerId(v[0]) ? [v[0]] : void 0;
|
|
@@ -65,6 +103,7 @@ function parseTurnOrder(v) {
|
|
|
65
103
|
if (!Array.isArray(v) || v.length < 1 || v.length > MAX_PLAYERS || !v.every(isPlayerId) || new Set(v).size !== v.length) return void 0;
|
|
66
104
|
return [...v];
|
|
67
105
|
}
|
|
106
|
+
var COVER_ASPECT = 16 / 9;
|
|
68
107
|
var isObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
69
108
|
var isNumber = (v) => typeof v === "number" && Number.isFinite(v);
|
|
70
109
|
var POSE_HEADER = 12;
|
|
@@ -305,6 +344,7 @@ function headShapes(def, ow) {
|
|
|
305
344
|
if (has("locks")) {
|
|
306
345
|
out.push({ k: "path", d: smoothClosed(pts(-1.14, 0.58, -1.24, -0.2, -0.86, -0.98, 0, -1.2, 0.86, -0.98, 1.24, -0.2, 1.14, 0.58, 0.8, 0.66, 0, 0.2, -0.8, 0.66)), fill: p.hair ?? p.outline, ...ol });
|
|
307
346
|
}
|
|
347
|
+
if (has("bun")) out.push({ k: "circle", x: 0.04, y: -1.14, r: 0.4, fill: p.hair ?? p.outline, ...ol });
|
|
308
348
|
if (has("ninja-hood")) {
|
|
309
349
|
for (const [rot, len2] of [
|
|
310
350
|
[0.35, 0.5],
|
|
@@ -342,7 +382,15 @@ function headShapes(def, ow) {
|
|
|
342
382
|
if (muzzle === "disc") for (const s of SIDES) out.push({ k: "circle", x: s * 0.4, y: 0.02, r: 0.44, fill: mc });
|
|
343
383
|
if (muzzle === "cheeks") for (const s of SIDES) out.push({ k: "ellipse", x: s * 0.4, y: 0.44, rx: 0.44, ry: 0.32, fill: mc });
|
|
344
384
|
if (muzzle === "band") out.push({ k: "path", d: lowerFace(def, ow), fill: mc });
|
|
345
|
-
if (has("stripes")) {
|
|
385
|
+
if (has("stripes") && def.head === "snout-long") {
|
|
386
|
+
const c = extra(0, p.outline);
|
|
387
|
+
for (const [x0, y0, x1, y1] of [
|
|
388
|
+
[0.46, -0.94, 0.36, -0.66],
|
|
389
|
+
[0.8, -0.72, 0.62, -0.5],
|
|
390
|
+
[1, -0.36, 0.78, -0.24]
|
|
391
|
+
])
|
|
392
|
+
out.push({ k: "line", pts: [x0, y0, x1, y1], stroke: c, lw: 0.11 });
|
|
393
|
+
} else if (has("stripes")) {
|
|
346
394
|
const c = extra(0, p.outline);
|
|
347
395
|
out.push({ k: "line", pts: [0, -0.97, 0, -0.66], stroke: c, lw: 0.13 });
|
|
348
396
|
for (const s of SIDES) {
|
|
@@ -357,6 +405,13 @@ function headShapes(def, ow) {
|
|
|
357
405
|
out.push({ k: "circle", x: 0.58, y: -0.42, r: 0.11, fill: c });
|
|
358
406
|
out.push({ k: "circle", x: 0.22, y: -0.76, r: 0.09, fill: c });
|
|
359
407
|
}
|
|
408
|
+
if (def.head === "snout-long") {
|
|
409
|
+
for (const [x, y, r] of [
|
|
410
|
+
[-1.38, -0.14, 0.06],
|
|
411
|
+
[-1.14, -0.22, 0.055]
|
|
412
|
+
])
|
|
413
|
+
out.push({ k: "ellipse", x, y, rx: r, ry: r * 0.8, rot: -0.3, fill: p.outline, alpha: 0.85 });
|
|
414
|
+
}
|
|
360
415
|
if (has("forehead")) out.push({ k: "path", d: smoothClosed(pts(-0.62, -0.66, 0, -0.86, 0.62, -0.66, 0.26, -0.36, 0, -0.1, -0.26, -0.36), 0.9), fill: extra(0, p.accent) });
|
|
361
416
|
if (has("eye-spot")) out.push({ k: "ellipse", x: 0.42, y: -0.06, rx: 0.3, ry: 0.33, rot: 0.3, fill: p.ears ?? p.accent });
|
|
362
417
|
if (has("gills")) {
|
|
@@ -365,6 +420,7 @@ function headShapes(def, ow) {
|
|
|
365
420
|
const hair = p.hair ?? p.outline;
|
|
366
421
|
if (has("hair")) out.push({ k: "path", d: smoothClosed(pts(-1.02, -0.06, -1.02, -0.58, -0.62, -1.04, 0.08, -1.14, 0.76, -0.92, 1.05, -0.42, 1, -0.06, 0.84, -0.4, 0.42, -0.5, 0.06, -0.4, -0.34, -0.56, -0.74, -0.38)), fill: hair, ...ol });
|
|
367
422
|
if (has("locks")) out.push({ k: "path", d: smoothClosed(pts(-1, -0.08, -0.98, -0.62, -0.56, -1.02, 0, -1.1, 0.56, -1.02, 0.98, -0.62, 1, -0.08, 0.8, -0.38, 0.36, -0.44, 0, -0.4, -0.36, -0.44, -0.8, -0.38)), fill: hair, ...ol });
|
|
423
|
+
if (has("bun")) out.push({ k: "path", d: smoothClosed(pts(-1.02, -0.1, -0.98, -0.62, -0.56, -1.02, 0, -1.1, 0.56, -1.02, 0.98, -0.62, 1.02, -0.1, 0.84, -0.36, 0.5, -0.56, 0.12, -0.48, -0.22, -0.6, -0.6, -0.5, -0.86, -0.36)), fill: hair, ...ol });
|
|
368
424
|
if (has("curls")) {
|
|
369
425
|
for (let i = 0; i < 7; i++) {
|
|
370
426
|
const c = polar(-1.3 + i / 6 * 2.6, 0.86);
|
|
@@ -386,7 +442,7 @@ function headShapes(def, ow) {
|
|
|
386
442
|
}
|
|
387
443
|
if (def.face.brows) for (const s of SIDES) out.push({ k: "line", pts: [s * 0.2, -0.2, s * 0.54, -0.28], stroke: def.face.brows, lw: 0.09 });
|
|
388
444
|
out.push(...mouth(def, ow));
|
|
389
|
-
if (def.face.cheeks) for (const
|
|
445
|
+
if (def.face.cheeks) for (const c of cheekSpots(def)) out.push({ k: "ellipse", x: c.x, y: c.y, rx: 0.15, ry: 0.1, fill: def.face.cheeks, alpha: 0.8 });
|
|
390
446
|
if (has("whiskers")) {
|
|
391
447
|
for (const s of SIDES)
|
|
392
448
|
for (const dy of [-0.1, 0.06]) out.push({ k: "line", pts: [s * 0.62, 0.38 + dy, s * 1.06, 0.3 + dy * 2.2], stroke: p.outline, lw: ow * 0.7, alpha: 0.75 });
|
|
@@ -408,6 +464,24 @@ function headShapes(def, ow) {
|
|
|
408
464
|
out.push({ k: "line", pts: [-w, y + 0.04, w, y - 0.04], stroke: p.outline, lw: ow * 0.7, alpha: 0.6 });
|
|
409
465
|
}
|
|
410
466
|
}
|
|
467
|
+
if (has("flower-crown")) {
|
|
468
|
+
const cols = [extra(1, "#f7a8cf"), extra(2, "#fff3a0"), extra(3, WHITE)];
|
|
469
|
+
for (const a of [-0.84, -0.28, 0.28, 0.84]) {
|
|
470
|
+
const c = polar(a, 0.9);
|
|
471
|
+
out.push({ k: "ellipse", x: c.x, y: c.y, rx: 0.14, ry: 0.07, rot: a + 0.5, fill: "#7cc76a", stroke: p.outline, lw: ow * 0.7 });
|
|
472
|
+
}
|
|
473
|
+
;
|
|
474
|
+
[-1.12, -0.56, 0, 0.56, 1.12].forEach((a, i) => {
|
|
475
|
+
const c = polar(a, 0.9);
|
|
476
|
+
const r = i === 2 ? 0.13 : 0.11;
|
|
477
|
+
for (let k = 0; k < 5; k++) {
|
|
478
|
+
const t = a + k / 5 * Math.PI * 2;
|
|
479
|
+
out.push({ k: "circle", x: c.x + Math.sin(t) * r, y: c.y - Math.cos(t) * r, r: r * 0.82, fill: cols[i % cols.length], stroke: p.outline, lw: ow * 0.7 });
|
|
480
|
+
}
|
|
481
|
+
out.push({ k: "circle", x: c.x, y: c.y, r: r * 0.62, fill: cols[i % cols.length] });
|
|
482
|
+
out.push({ k: "circle", x: c.x, y: c.y, r: r * 0.42, fill: p.accent });
|
|
483
|
+
});
|
|
484
|
+
}
|
|
411
485
|
if (has("helmet")) {
|
|
412
486
|
for (const s of SIDES) out.push({ k: "rect", x: s * 1, y: 0.05, w: 0.26, h: 0.56, r: 0.12, fill: p.accent, ...ol });
|
|
413
487
|
out.push({ k: "path", d: "M-0.62 -0.62 Q-0.3 -0.92 0.12 -0.9", stroke: WHITE, lw: 0.1, alpha: 0.9 });
|
|
@@ -472,6 +546,8 @@ function headShape(def, ol) {
|
|
|
472
546
|
return { k: "rect", x: 0, y: 0, w: 2, h: 1.8, r: 0.46, fill, ...ol };
|
|
473
547
|
case "bucket":
|
|
474
548
|
return { k: "path", d: roundedPolygon(pts(-0.94, 0.96, -0.98, -0.36, -0.62, -1.04, 0.62, -1.04, 0.98, -0.36, 0.94, 0.96), 0.42), fill, ...ol };
|
|
549
|
+
case "snout-long":
|
|
550
|
+
return { k: "path", d: smoothClosed(SNOUT_HEAD, 0.95), fill, ...ol };
|
|
475
551
|
case "bulb":
|
|
476
552
|
return {
|
|
477
553
|
k: "path",
|
|
@@ -490,6 +566,19 @@ function headShape(def, ol) {
|
|
|
490
566
|
};
|
|
491
567
|
}
|
|
492
568
|
}
|
|
569
|
+
var SNOUT_HEAD = pts(0.7, -0.88, 0.12, -1.04, -0.3, -0.98, -0.72, -0.74, -1.2, -0.56, -1.5, -0.32, -1.6, 0.04, -1.44, 0.42, -0.9, 0.72, -0.2, 0.92, 0.5, 0.88, 0.96, 0.56, 1.08, 0, 0.98, -0.5);
|
|
570
|
+
function eyeSpots(def, ex, ey) {
|
|
571
|
+
if (def.head === "snout-long")
|
|
572
|
+
return [
|
|
573
|
+
{ x: -0.36, y: -0.5, k: 0.95 },
|
|
574
|
+
{ x: 0.26, y: -0.48, k: 1.1 }
|
|
575
|
+
];
|
|
576
|
+
return SIDES.map((s) => ({ x: s * ex, y: ey, k: 1 }));
|
|
577
|
+
}
|
|
578
|
+
function cheekSpots(def) {
|
|
579
|
+
if (def.head === "snout-long") return pts(-0.66, -0.14, 0.66, 0.1);
|
|
580
|
+
return SIDES.map((s) => ({ x: s * 0.64, y: 0.3 }));
|
|
581
|
+
}
|
|
493
582
|
function headEllipse(def) {
|
|
494
583
|
if (def.head === "round") return { cy: 0, rx: 1, ry: 1 };
|
|
495
584
|
if (def.head === "tall") return { cy: -0.02, rx: 0.94, ry: 1.04 };
|
|
@@ -570,6 +659,10 @@ function ears(def, ow) {
|
|
|
570
659
|
out.push({ k: "ellipse", x: s * 1.04, y: -0.22, rx: 0.4, ry: 0.18, rot: s * 0.32, fill: p.skin ?? p.body, ...ol });
|
|
571
660
|
out.push({ k: "ellipse", x: s * 1.08, y: -0.2, rx: 0.24, ry: 0.08, rot: s * 0.32, fill: p.accent });
|
|
572
661
|
break;
|
|
662
|
+
case "pixie":
|
|
663
|
+
out.push({ k: "path", d: roundedPolygon(pts(s * 0.86, -0.22, s * 1.36, -0.5, s * 1.02, 0.26), 0.28), fill: p.skin ?? p.body, ...ol });
|
|
664
|
+
out.push({ k: "line", pts: [s * 0.98, -0.12, s * 1.16, -0.3], stroke: p.accent, lw: ow * 0.8, alpha: 0.5 });
|
|
665
|
+
break;
|
|
573
666
|
case "human":
|
|
574
667
|
out.push({ k: "ellipse", x: s * 0.98, y: 0.06, rx: 0.17, ry: 0.23, fill: p.skin ?? p.body, ...ol });
|
|
575
668
|
break;
|
|
@@ -602,15 +695,15 @@ function eyes(def, ow) {
|
|
|
602
695
|
const shine = (x, y, r) => ({ k: "circle", x, y, r, fill: WHITE });
|
|
603
696
|
switch (f2.eyes) {
|
|
604
697
|
case "dot":
|
|
605
|
-
for (const
|
|
606
|
-
out.push({ k: "ellipse", x:
|
|
607
|
-
out.push(shine(
|
|
698
|
+
for (const e of eyeSpots(def, ex, ey)) {
|
|
699
|
+
out.push({ k: "ellipse", x: e.x, y: e.y, rx: 0.12 * e.k, ry: 0.155 * e.k, fill: ink });
|
|
700
|
+
out.push(shine(e.x + 0.04 * e.k, e.y - 0.06 * e.k, 0.045 * e.k));
|
|
608
701
|
}
|
|
609
702
|
break;
|
|
610
703
|
case "bead":
|
|
611
|
-
for (const
|
|
612
|
-
out.push({ k: "ellipse", x:
|
|
613
|
-
out.push(shine(
|
|
704
|
+
for (const e of eyeSpots(def, ex, ey)) {
|
|
705
|
+
out.push({ k: "ellipse", x: e.x, y: e.y, rx: 0.16 * e.k, ry: 0.22 * e.k, fill: ink });
|
|
706
|
+
out.push(shine(e.x + 0.05 * e.k, e.y - 0.08 * e.k, 0.065 * e.k));
|
|
614
707
|
}
|
|
615
708
|
break;
|
|
616
709
|
case "big":
|
|
@@ -719,6 +812,21 @@ function mouth(def, ow) {
|
|
|
719
812
|
out.push({ k: "ellipse", x: 0, y: 0.48, rx: 0.16, ry: 0.11, fill: MOUTH });
|
|
720
813
|
out.push({ k: "path", d: "M-0.3 0.12 Q0 -0.02 0.3 0.12 Q0.36 0.52 0.04 0.76 Q0.08 0.52 -0.08 0.46 Q-0.28 0.38 -0.3 0.12 Z", fill: p.accent, stroke: p.outline, lw: ow * 0.8 });
|
|
721
814
|
break;
|
|
815
|
+
case "toothy": {
|
|
816
|
+
out.push({ k: "path", d: "M-1.4 0.1 Q-0.5 0.36 0.5 0.2 Q0.64 0.16 0.68 0.02 Q0.78 0.28 0.52 0.38 Q-0.4 0.78 -1.3 0.26 Z", fill: dark, stroke: dark, lw: ow * 0.6 });
|
|
817
|
+
out.push({ k: "ellipse", x: -0.38, y: 0.5, rx: 0.24, ry: 0.08, fill: TONGUE });
|
|
818
|
+
for (const t of [0.12, 0.27, 0.42, 0.57, 0.72, 0.86]) {
|
|
819
|
+
const x = (1 - t) * (1 - t) * -1.4 + 2 * (1 - t) * t * -0.5 + t * t * 0.5;
|
|
820
|
+
const y = (1 - t) * (1 - t) * 0.1 + 2 * (1 - t) * t * 0.36 + t * t * 0.2 - 0.01;
|
|
821
|
+
out.push({ k: "path", d: roundedPolygon(pts(x - 0.08, y, x + 0.08, y, x, y + 0.14), 0.3), fill: WHITE });
|
|
822
|
+
}
|
|
823
|
+
for (const t of [0.3, 0.7]) {
|
|
824
|
+
const x = (1 - t) * (1 - t) * 0.52 + 2 * (1 - t) * t * -0.4 + t * t * -1.3;
|
|
825
|
+
const y = (1 - t) * (1 - t) * 0.38 + 2 * (1 - t) * t * 0.78 + t * t * 0.26 + 0.01;
|
|
826
|
+
out.push({ k: "path", d: roundedPolygon(pts(x - 0.07, y, x + 0.07, y, x, y - 0.12), 0.3), fill: WHITE });
|
|
827
|
+
}
|
|
828
|
+
break;
|
|
829
|
+
}
|
|
722
830
|
case "jaws": {
|
|
723
831
|
out.push({ k: "path", d: "M-0.5 0.3 Q0 0.5 0.5 0.3 Q0.38 0.72 0 0.72 Q-0.38 0.72 -0.5 0.3 Z", fill: dark, stroke: dark, lw: ow * 0.5 });
|
|
724
832
|
for (const x of [-0.34, -0.17, 0, 0.17, 0.34]) {
|
|
@@ -741,7 +849,10 @@ var BAND_BODY = {
|
|
|
741
849
|
"9-12": { head: 0.5, neck: 0.92, arm: 0.17, leg: 0.21, hand: 0.125, foot: 0.9, shoulderMin: 0.36, shoulderPad: 0.07, hipMin: 0.32, hipPad: 0.08, bottom: 0.1, bulge: 1, armsOverHead: false }
|
|
742
850
|
};
|
|
743
851
|
var DEFAULT_BAND = "6-8";
|
|
744
|
-
var bodyBand = (def) =>
|
|
852
|
+
var bodyBand = (def, shown) => {
|
|
853
|
+
if (shown !== void 0 && def.band !== "all" && def.alsoIn?.includes(shown) && shown in BAND_BODY) return shown;
|
|
854
|
+
return def.band in BAND_BODY ? def.band : DEFAULT_BAND;
|
|
855
|
+
};
|
|
745
856
|
var HEAD = BAND_BODY[DEFAULT_BAND].head;
|
|
746
857
|
var MAX_TILT = 0.3;
|
|
747
858
|
var TILT_DEAD_ZONE = 0.12;
|
|
@@ -835,7 +946,7 @@ function headTilt(body, at, angle, U) {
|
|
|
835
946
|
return 0;
|
|
836
947
|
}
|
|
837
948
|
function avatarShapes(body, def, box, opts = {}) {
|
|
838
|
-
const band = bodyBand(def);
|
|
949
|
+
const band = bodyBand(def, opts.band);
|
|
839
950
|
const bb = BAND_BODY[band];
|
|
840
951
|
const F = frameOf(body, box, { ...opts, band });
|
|
841
952
|
if (!F) return [];
|
|
@@ -867,8 +978,8 @@ function avatarShapes(body, def, box, opts = {}) {
|
|
|
867
978
|
out.push({ k: "line", pts: flat, stroke: p.outline, lw: w + ow * 2 });
|
|
868
979
|
out.push({ k: "line", pts: flat, stroke: color, lw: w });
|
|
869
980
|
};
|
|
870
|
-
const legW = bb.leg * U;
|
|
871
|
-
const armW = bb.arm * U;
|
|
981
|
+
const legW = bb.leg * U * (has("strong-legs") ? 1.3 : 1);
|
|
982
|
+
const armW = bb.arm * U * (has("tiny-arms") ? 0.6 : 1);
|
|
872
983
|
const pad = Math.max(bb.arm / 0.22, 0.9);
|
|
873
984
|
if (F.hipsOk) {
|
|
874
985
|
for (const [hip, kneeI, ankleI, side] of [
|
|
@@ -882,7 +993,7 @@ function avatarShapes(body, def, box, opts = {}) {
|
|
|
882
993
|
const pts2 = ankle.ok ? [top, knee, ankle] : [top, knee];
|
|
883
994
|
limb(legs, pts2, legW, p.limbs);
|
|
884
995
|
if (has("joints")) legs.push({ k: "circle", x: knee.x, y: knee.y, r: 0.15 * U * pad, fill: p.accent, ...ol });
|
|
885
|
-
if (ankle.ok) legs.push(
|
|
996
|
+
if (ankle.ok) legs.push(...feet(def, ankle, F, side, ow, bb.foot));
|
|
886
997
|
}
|
|
887
998
|
}
|
|
888
999
|
for (const [sh, elbowI, wristI, side] of [
|
|
@@ -915,7 +1026,20 @@ function avatarShapes(body, def, box, opts = {}) {
|
|
|
915
1026
|
out.push({ k: "ellipse", x: q.x, y: q.y, rx: 0.2 * U, ry: 0.09 * U, rot: Math.atan2(fd.y, fd.x), fill: p.hands ?? p.accent, ...ol });
|
|
916
1027
|
}
|
|
917
1028
|
}
|
|
918
|
-
|
|
1029
|
+
if (has("wand") && side === 1 && !opts.portrait) out.push(...wand(def, hand, norm(add(mul(dir, 0.4), sub(mul(r, 0.6), d))), U, ow));
|
|
1030
|
+
const handR = bb.hand * U * (has("tiny-arms") ? 0.72 : 1);
|
|
1031
|
+
if (has("tiny-arms")) {
|
|
1032
|
+
for (const a of [-0.45, 0.45]) {
|
|
1033
|
+
const c = Math.cos(a);
|
|
1034
|
+
const s = Math.sin(a);
|
|
1035
|
+
const cd = { x: dir.x * c - dir.y * s, y: dir.x * s + dir.y * c };
|
|
1036
|
+
const base = add(hand, mul(cd, handR * 0.75));
|
|
1037
|
+
const tip = add(hand, mul(cd, handR * 1.6));
|
|
1038
|
+
const nrm = { x: -cd.y * handR * 0.32, y: cd.x * handR * 0.32 };
|
|
1039
|
+
out.push({ k: "path", d: roundedPolygon([add(base, nrm), tip, sub(base, nrm)], 0.2), fill: p.belly, ...ol });
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
out.push({ k: "circle", x: hand.x, y: hand.y, r: handR, fill: p.hands ?? p.limbs, ...ol });
|
|
919
1043
|
}
|
|
920
1044
|
if (has("wings")) {
|
|
921
1045
|
for (const s of [-1, 1]) {
|
|
@@ -930,6 +1054,7 @@ function avatarShapes(body, def, box, opts = {}) {
|
|
|
930
1054
|
back.push({ k: "path", d: roundedPolygon(w, 0.18), fill: p.accent, ...ol });
|
|
931
1055
|
}
|
|
932
1056
|
}
|
|
1057
|
+
if (has("wings-fairy")) back.push(...fairyWings(def, T, ht, U, ow));
|
|
933
1058
|
if (has("cape")) {
|
|
934
1059
|
const yb = L + 0.8 * U;
|
|
935
1060
|
const wb = hb + 0.36 * U;
|
|
@@ -1147,6 +1272,7 @@ function avatarShapes(body, def, box, opts = {}) {
|
|
|
1147
1272
|
const k = 0.15 * U;
|
|
1148
1273
|
torso.push({ k: "group", x: c.x, y: c.y, rot: F.angle - 0.2, s: k, items: [{ k: "path", d: STAR, fill: def.extraColors?.[0] ?? "#ffd84a", stroke: p.outline, lw: ow * 0.8 / k }] });
|
|
1149
1274
|
}
|
|
1275
|
+
if (has("petals")) torso.push(...petals(def, T, ht, hb, bulge, L, y1, U, ow, bb));
|
|
1150
1276
|
if (has("scarf")) {
|
|
1151
1277
|
const y = Math.max(0.04, bb.head * (1 - bb.neck) - 0.06) * U;
|
|
1152
1278
|
const c = T(0, y);
|
|
@@ -1171,6 +1297,18 @@ function cachedHead(def, ow) {
|
|
|
1171
1297
|
}
|
|
1172
1298
|
return shapes;
|
|
1173
1299
|
}
|
|
1300
|
+
function feet(def, ankle, F, side, ow, k) {
|
|
1301
|
+
const shoe = foot(def, ankle, F, side, ow, k);
|
|
1302
|
+
if (!def.bodyExtras.includes("strong-legs")) return [shoe];
|
|
1303
|
+
const U = F.U * k;
|
|
1304
|
+
const c = add(add(ankle, mul(F.d, 0.07 * U)), mul(F.r, side * 0.06 * U));
|
|
1305
|
+
const out = [shoe];
|
|
1306
|
+
for (const t of [-1, 0, 1]) {
|
|
1307
|
+
const q = add(add(c, mul(F.r, t * 0.12 * U)), mul(F.d, 0.09 * U));
|
|
1308
|
+
out.push({ k: "ellipse", x: q.x, y: q.y, rx: 0.05 * U, ry: 0.045 * U, rot: F.angle, fill: def.palette.belly, stroke: def.palette.outline, lw: ow * 0.8 });
|
|
1309
|
+
}
|
|
1310
|
+
return out;
|
|
1311
|
+
}
|
|
1174
1312
|
function foot(def, ankle, F, side, ow, k) {
|
|
1175
1313
|
const p = def.palette;
|
|
1176
1314
|
const { d, r } = F;
|
|
@@ -1324,6 +1462,30 @@ function tail(def, F, hb, ow) {
|
|
|
1324
1462
|
});
|
|
1325
1463
|
break;
|
|
1326
1464
|
}
|
|
1465
|
+
case "thick": {
|
|
1466
|
+
const pts2 = TP([
|
|
1467
|
+
[hb * 0.3, L * 0.42],
|
|
1468
|
+
[hb + 0.42 * U, L * 0.62],
|
|
1469
|
+
[hb + 0.92 * U, L * 0.96],
|
|
1470
|
+
[hb + 1.32 * U, L * 1.08],
|
|
1471
|
+
[hb + 1.44 * U, L * 0.98],
|
|
1472
|
+
[hb + 0.98 * U, L * 1.2],
|
|
1473
|
+
[hb + 0.4 * U, L * 1.22],
|
|
1474
|
+
[hb * 0.3, L * 1.12]
|
|
1475
|
+
]);
|
|
1476
|
+
out.push({ k: "path", d: smoothClosed(pts2, 0.8), fill: p.body, ...ol });
|
|
1477
|
+
const c = def.extraColors?.[0] ?? p.accent;
|
|
1478
|
+
for (const [x, y, l] of [
|
|
1479
|
+
[0.28, 0.58, 0.26],
|
|
1480
|
+
[0.62, 0.8, 0.22],
|
|
1481
|
+
[0.96, 1, 0.16]
|
|
1482
|
+
]) {
|
|
1483
|
+
const a = T(hb + x * U, L * y + 0.03 * U);
|
|
1484
|
+
const b = T(hb + (x - 0.1) * U, L * y + l * U);
|
|
1485
|
+
out.push({ k: "line", pts: [a.x, a.y, b.x, b.y], stroke: c, lw: 0.09 * U });
|
|
1486
|
+
}
|
|
1487
|
+
break;
|
|
1488
|
+
}
|
|
1327
1489
|
case "spiky":
|
|
1328
1490
|
case "arrow": {
|
|
1329
1491
|
const pts2 = TP([
|
|
@@ -1356,6 +1518,81 @@ function tail(def, F, hb, ow) {
|
|
|
1356
1518
|
}
|
|
1357
1519
|
return out;
|
|
1358
1520
|
}
|
|
1521
|
+
function wand(def, hand, wd, U, ow) {
|
|
1522
|
+
const p = def.palette;
|
|
1523
|
+
const from = add(hand, mul(wd, -0.08 * U));
|
|
1524
|
+
const tip = add(hand, mul(wd, 0.52 * U));
|
|
1525
|
+
const k = 0.2 * U;
|
|
1526
|
+
return [
|
|
1527
|
+
{ k: "line", pts: [from.x, from.y, tip.x, tip.y], stroke: p.outline, lw: 0.06 * U + ow * 2 },
|
|
1528
|
+
{ k: "line", pts: [from.x, from.y, tip.x, tip.y], stroke: "#fff3d6", lw: 0.06 * U },
|
|
1529
|
+
{ k: "circle", x: tip.x, y: tip.y, r: 0.24 * U, fill: p.accent, alpha: 0.28 },
|
|
1530
|
+
{ k: "group", x: tip.x, y: tip.y, rot: Math.atan2(wd.y, wd.x) + Math.PI / 2, s: k, items: [{ k: "path", d: STAR, fill: p.accent, stroke: p.outline, lw: ow * 0.9 / k }] }
|
|
1531
|
+
];
|
|
1532
|
+
}
|
|
1533
|
+
function fairyWings(def, T, ht, U, ow) {
|
|
1534
|
+
const p = def.palette;
|
|
1535
|
+
const fill = def.extraColors?.[0] ?? "#cdeeff";
|
|
1536
|
+
const out = [];
|
|
1537
|
+
for (const s of [-1, 1]) {
|
|
1538
|
+
const lobes = [
|
|
1539
|
+
// Upper: big, up and out.
|
|
1540
|
+
[
|
|
1541
|
+
[s * ht * 0.3, 0.12 * U],
|
|
1542
|
+
[s * (ht + 0.42 * U), -0.66 * U],
|
|
1543
|
+
[s * (ht + 1 * U), -0.74 * U],
|
|
1544
|
+
[s * (ht + 1.02 * U), -0.18 * U],
|
|
1545
|
+
[s * (ht + 0.5 * U), 0.26 * U]
|
|
1546
|
+
],
|
|
1547
|
+
// Lower: smaller, down and out.
|
|
1548
|
+
[
|
|
1549
|
+
[s * ht * 0.4, 0.3 * U],
|
|
1550
|
+
[s * (ht + 0.6 * U), 0.36 * U],
|
|
1551
|
+
[s * (ht + 0.74 * U), 0.78 * U],
|
|
1552
|
+
[s * (ht + 0.3 * U), 0.9 * U]
|
|
1553
|
+
]
|
|
1554
|
+
];
|
|
1555
|
+
for (const lobe of lobes) {
|
|
1556
|
+
const d = smoothClosed(lobe.map(([x, y]) => T(x, y)), 0.9);
|
|
1557
|
+
out.push({ k: "path", d, fill, alpha: 0.6 });
|
|
1558
|
+
out.push({ k: "path", d, stroke: p.outline, lw: ow * 0.9 });
|
|
1559
|
+
const inner = lobe[Math.floor(lobe.length / 2)];
|
|
1560
|
+
const a = T(lobe[0][0], lobe[0][1]);
|
|
1561
|
+
const b = T(lobe[0][0] + (inner[0] - lobe[0][0]) * 0.75, lobe[0][1] + (inner[1] - lobe[0][1]) * 0.75);
|
|
1562
|
+
out.push({ k: "line", pts: [a.x, a.y, b.x, b.y], stroke: "#ffffff", lw: ow * 0.9, alpha: 0.85 });
|
|
1563
|
+
}
|
|
1564
|
+
for (const [x, y, r] of [
|
|
1565
|
+
[ht + 0.72 * U, -0.42 * U, 0.075 * U],
|
|
1566
|
+
[ht + 0.44 * U, -0.16 * U, 0.05 * U],
|
|
1567
|
+
[ht + 0.5 * U, 0.6 * U, 0.055 * U]
|
|
1568
|
+
]) {
|
|
1569
|
+
const q = T(s * x, y);
|
|
1570
|
+
out.push({ k: "group", x: q.x, y: q.y, s: r, items: [{ k: "path", d: STAR, fill: "#ffffff" }] });
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
return out;
|
|
1574
|
+
}
|
|
1575
|
+
function petals(def, T, ht, hb, bulge, L, y1, U, ow, bb) {
|
|
1576
|
+
const p = def.palette;
|
|
1577
|
+
const second = def.extraColors?.[1] ?? p.belly;
|
|
1578
|
+
const out = [];
|
|
1579
|
+
const ang = Math.atan2(T(1, 0).y - T(0, 0).y, T(1, 0).x - T(0, 0).x);
|
|
1580
|
+
const n = 5;
|
|
1581
|
+
const w = Math.max(hb, bulge * 0.92) * 1.08;
|
|
1582
|
+
const y = (L * 0.8 + y1) / 2;
|
|
1583
|
+
const order = [0, 4, 1, 3, 2];
|
|
1584
|
+
for (const i of order) {
|
|
1585
|
+
const t = i / (n - 1) - 0.5;
|
|
1586
|
+
const c = T(t * 2 * w, y + (0.5 - Math.abs(t)) * 0.1 * U);
|
|
1587
|
+
out.push({ k: "ellipse", x: c.x, y: c.y, rx: w * 0.3, ry: 0.34 * U, rot: ang - t * 0.6, fill: i % 2 ? second : p.body, stroke: p.outline, lw: ow });
|
|
1588
|
+
}
|
|
1589
|
+
const cy = Math.max(0.04, bb.head * (1 - bb.neck) - 0.04) * U;
|
|
1590
|
+
for (const t of [-1, 1, 0]) {
|
|
1591
|
+
const c = T(t * ht * 0.32, cy + (t ? 0 : 0.05 * U));
|
|
1592
|
+
out.push({ k: "ellipse", x: c.x, y: c.y, rx: ht * 0.26, ry: 0.12 * U, rot: ang + t * 0.5, fill: t ? second : p.body, stroke: p.outline, lw: ow * 0.9 });
|
|
1593
|
+
}
|
|
1594
|
+
return out;
|
|
1595
|
+
}
|
|
1359
1596
|
|
|
1360
1597
|
// ../avatars/src/defs.ts
|
|
1361
1598
|
var SOFT = "#4a382c";
|
|
@@ -1472,6 +1709,7 @@ var AVATARS = [
|
|
|
1472
1709
|
id: "lion",
|
|
1473
1710
|
name: { en: "Lion", it: "Leoncino" },
|
|
1474
1711
|
band: "6-8",
|
|
1712
|
+
alsoIn: ["3-5"],
|
|
1475
1713
|
palette: { body: "#f6c454", limbs: "#f6c454", belly: "#fde7b0", accent: "#fde7b0", outline: INK },
|
|
1476
1714
|
head: "round",
|
|
1477
1715
|
ears: "round",
|
|
@@ -1838,6 +2076,39 @@ var AVATARS = [
|
|
|
1838
2076
|
headExtras: ["hair", "domino"],
|
|
1839
2077
|
bodyExtras: ["cape", "emblem", "boots"],
|
|
1840
2078
|
extraColors: ["#ffd84a"]
|
|
2079
|
+
},
|
|
2080
|
+
// ── In both 3–5 and 6–8 (home 6–8, alsoIn 3–5: drawn with the proportions of the band shown) ──
|
|
2081
|
+
{
|
|
2082
|
+
// Next to the round 'dinosaur': a T-rex at a glance, a big head turned three-quarters with a long snout and a
|
|
2083
|
+
// toothy grin, tiny arms, strong legs, a thick striped tail.
|
|
2084
|
+
id: "trex",
|
|
2085
|
+
name: { en: "T-Rex", it: "T-Rex" },
|
|
2086
|
+
band: "6-8",
|
|
2087
|
+
alsoIn: ["3-5"],
|
|
2088
|
+
palette: { body: "#6cb84e", limbs: "#6cb84e", belly: "#f4e8a8", accent: "#f4e8a8", outline: INK },
|
|
2089
|
+
head: "snout-long",
|
|
2090
|
+
ears: "none",
|
|
2091
|
+
face: { eyes: "dot", mouth: "toothy", muzzle: "none", cheeks: "#f59b8f" },
|
|
2092
|
+
tail: "thick",
|
|
2093
|
+
headExtras: ["stripes"],
|
|
2094
|
+
bodyExtras: ["tiny-arms", "strong-legs"],
|
|
2095
|
+
extraColors: ["#4a9235", "#58a23f"]
|
|
2096
|
+
},
|
|
2097
|
+
{
|
|
2098
|
+
// An original child fairy: a bun with a flower crown, little pointed ears, a petal tunic, see-through wings and
|
|
2099
|
+
// a star wand in one hand.
|
|
2100
|
+
id: "fairy",
|
|
2101
|
+
name: { en: "Fairy", it: "Fatina" },
|
|
2102
|
+
band: "6-8",
|
|
2103
|
+
alsoIn: ["3-5"],
|
|
2104
|
+
palette: { body: "#b99af0", limbs: "#a8693f", belly: "#b99af0", accent: "#ffd84a", outline: INK, skin: "#a8693f", hair: "#2e1d17", hands: "#a8693f", feet: "#f58cbc" },
|
|
2105
|
+
head: "round",
|
|
2106
|
+
ears: "pixie",
|
|
2107
|
+
face: { eyes: "dot", mouth: "smile", muzzle: "none", cheeks: "#f0857a" },
|
|
2108
|
+
tail: "none",
|
|
2109
|
+
headExtras: ["bun", "flower-crown"],
|
|
2110
|
+
bodyExtras: ["wings-fairy", "petals", "wand"],
|
|
2111
|
+
extraColors: ["#c4ecff", "#f7a8cf", "#fff3a0", "#ffffff"]
|
|
1841
2112
|
}
|
|
1842
2113
|
];
|
|
1843
2114
|
|
|
@@ -1983,7 +2254,7 @@ var isAvatarId = (v) => typeof v === "string" && BY_ID.has(v);
|
|
|
1983
2254
|
var avatarById = (id) => BY_ID.get(id);
|
|
1984
2255
|
var resolve = (def) => typeof def === "string" ? BY_ID.get(def) ?? DEFAULT_AVATAR : def;
|
|
1985
2256
|
function drawAvatar(ctx, body, def, box, opts = {}) {
|
|
1986
|
-
const shapes = avatarShapes(body, resolve(def), box, { mirrored: opts.mirrored });
|
|
2257
|
+
const shapes = avatarShapes(body, resolve(def), box, { mirrored: opts.mirrored, band: opts.band });
|
|
1987
2258
|
if (!shapes.length) return false;
|
|
1988
2259
|
ctx.save();
|
|
1989
2260
|
if (opts.alpha !== void 0) ctx.globalAlpha *= Math.min(Math.max(opts.alpha, 0), 1);
|
|
@@ -1991,17 +2262,19 @@ function drawAvatar(ctx, body, def, box, opts = {}) {
|
|
|
1991
2262
|
ctx.restore();
|
|
1992
2263
|
return true;
|
|
1993
2264
|
}
|
|
1994
|
-
function iconShapes(def, size) {
|
|
2265
|
+
function iconShapes(def, size, band) {
|
|
1995
2266
|
const d = resolve(def);
|
|
1996
2267
|
const body = neutralPose();
|
|
1997
|
-
const
|
|
1998
|
-
const unit = frameOf(body, { x: 0, y: 0, w: 1, h: 1 }, { band });
|
|
1999
|
-
const
|
|
2268
|
+
const shown = bodyBand(d, band);
|
|
2269
|
+
const unit = frameOf(body, { x: 0, y: 0, w: 1, h: 1 }, { band: shown });
|
|
2270
|
+
const hb = bounds(headShapes(d, 0.05));
|
|
2271
|
+
const top = -hb.minY;
|
|
2000
2272
|
const headR = Math.min(0.27, 0.43 / Math.max(top, 1)) * size;
|
|
2001
|
-
const
|
|
2273
|
+
const dx = d.head === "snout-long" ? (hb.minX + hb.maxX) / 2 * headR : 0;
|
|
2274
|
+
const k = headR / BAND_BODY[shown].head / unit.U;
|
|
2002
2275
|
const cy = 0.5 * size + Math.min(0, (top - 1.6) * 0.06 * size);
|
|
2003
|
-
const box = { x: size / 2 - unit.head.x * k, y: cy - unit.head.y * k, w: k, h: k };
|
|
2004
|
-
return avatarShapes(body, d, box, { portrait: true });
|
|
2276
|
+
const box = { x: size / 2 - dx - unit.head.x * k, y: cy - unit.head.y * k, w: k, h: k };
|
|
2277
|
+
return avatarShapes(body, d, box, { portrait: true, band: shown });
|
|
2005
2278
|
}
|
|
2006
2279
|
function smoothBody(prev, next, amount = 0.5) {
|
|
2007
2280
|
if (!prev || prev.length !== next.length) return next.slice();
|
|
@@ -2102,6 +2375,10 @@ function bounds(shapes) {
|
|
|
2102
2375
|
|
|
2103
2376
|
export {
|
|
2104
2377
|
__publicField,
|
|
2378
|
+
MAX_PICTURE_SIDE,
|
|
2379
|
+
PICTURE_JPEG_QUALITY,
|
|
2380
|
+
checkPicture,
|
|
2381
|
+
parsePictureTitle,
|
|
2105
2382
|
PROTOCOL_VERSION,
|
|
2106
2383
|
defaultAvatarFor,
|
|
2107
2384
|
NS,
|
|
@@ -2110,6 +2387,8 @@ export {
|
|
|
2110
2387
|
toScreen,
|
|
2111
2388
|
parseDeviceHint,
|
|
2112
2389
|
parseGameStats,
|
|
2390
|
+
parseCursor,
|
|
2391
|
+
parsePictureResult,
|
|
2113
2392
|
parseTurnPlayers,
|
|
2114
2393
|
parseTurnOrder,
|
|
2115
2394
|
drawShapes,
|