@botharness/pixel-avatar 0.3.0 → 0.4.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 +1 -0
- package/dist/index.d.ts +47 -10
- package/dist/index.js +333 -37
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,5 +19,6 @@ const svg = pixelAvatarSvg(recipe); // 32×32 viewBox, crisp edges, no ids or sc
|
|
|
19
19
|
- `isPixelAvatarRecipe`, `canonicalRecipe`: validate and normalise saved recipes.
|
|
20
20
|
- `AVATAR_HAIR_PARTS`, `AVATAR_RANGES`, `detailedRecipe(recipe)`: optional finer controls. A recipe may carry `bangs`, `sideHair`, `backHair`, `spacing`, `height` and `hairLength`, all six or none; `detailedRecipe` derives them from the plain `hair` so an editor can start from what is on screen. Recipes without them render exactly as before.
|
|
21
21
|
- `AVATAR_SPECIES`, `AVATAR_SPECIES_SWATCHES`, `AVATAR_PIECE_COLORS`, `withSpecies(recipe, species)`: Avatar Species (`human`, `goblin`) on the same rig, as asset version 2. `withSpecies` keeps every choice, splits side hair into `sideHair` (left) and `rightSideHair`, and accepts optional `leftSideHairColor`/`rightSideHairColor`, where left and right are as seen on screen. Consumers built before version 2 reject these recipes with `isPixelAvatarRecipe` and can show a saved snapshot instead.
|
|
22
|
+
- `AVATAR_PARTS_V2`, `AVATAR_EXTRA_PARTS`, `hiddenChoices(recipe)`: version 2 adds elf, dwarf, orc and flower species, armor/robe/tunic/cloak outfits, helmet/hood headwear, an optional `beard`, and a flower's `petals` and `flowerBase`. `hiddenChoices` names saved choices the current species or headwear keeps but does not draw, so an editor can say so instead of discarding them.
|
|
22
23
|
- `faceCells`, `pixelSymbolCells`, `symbolArtCells`: pixels for [`@botharness/pixel-morph`](../morph).
|
|
23
24
|
- `pixelAvatarSvg(recipe, { turns, classPrefix })`: optional pre-rendered head turns, and rig layer classes (`<prefix>-body`, `-head`, `-face`, `-gaze`, `-blink`) for CSS animation.
|
package/dist/index.d.ts
CHANGED
|
@@ -34,37 +34,65 @@ type AvatarColor = (typeof AVATAR_COLORS)[number];
|
|
|
34
34
|
* Avatar Species: a base on the pixel bust rig. Species share motion and anchors; each sets its
|
|
35
35
|
* own ears, face details and suggested body colors. A recipe with a species is asset version 2.
|
|
36
36
|
*/
|
|
37
|
-
declare const AVATAR_SPECIES: readonly ["human", "goblin"];
|
|
37
|
+
declare const AVATAR_SPECIES: readonly ["human", "goblin", "elf", "dwarf", "orc", "flower"];
|
|
38
38
|
type AvatarSpecies = (typeof AVATAR_SPECIES)[number];
|
|
39
39
|
/** Optional per-piece hair colors (asset version 2); an absent piece uses `hairColor`. */
|
|
40
40
|
declare const AVATAR_PIECE_COLORS: readonly ["leftSideHairColor", "rightSideHairColor"];
|
|
41
41
|
type AvatarPieceColor = (typeof AVATAR_PIECE_COLORS)[number];
|
|
42
|
-
|
|
42
|
+
/** Part choices that exist only in asset version 2, added after every version 1 choice. */
|
|
43
|
+
declare const AVATAR_PARTS_V2: {
|
|
44
|
+
readonly outfit: readonly ["tee", "shirttie", "hoodie", "turtleneck", "sailor", "blazer", "overalls", "dress", "kimono", "cardigan", "maid", "jacket", "armor", "robe", "tunic", "cloak"];
|
|
45
|
+
readonly accessory: readonly ["none", "beanie", "cap", "headphones", "flower", "bow", "earring", "crown", "halo", "catears", "hairclip", "horns", "beret", "ribbon", "headband", "bunnyears", "horseears", "flowercrown", "witch", "pins", "helmet", "hood"];
|
|
46
|
+
readonly head: readonly ["round", "oval", "square", "long", "heart", "vchin", "chubby", "diamond"];
|
|
47
|
+
readonly pose: readonly ["front", "left", "right"];
|
|
48
|
+
readonly hair: readonly ["crop", "sweep", "spiky", "buzz", "curly", "mohawk", "bob", "long", "bun", "pigtails", "afro", "twintails", "drills", "ponytail", "sidetail", "hime", "odango", "messy", "wavy", "braids", "wolf", "ahoge", "none"];
|
|
49
|
+
readonly eyes: readonly ["round", "dot", "sparkle", "lashes", "sleepy", "happy", "wink", "sharp"];
|
|
50
|
+
readonly brows: readonly ["soft", "thick", "raised", "angry", "worried", "none"];
|
|
51
|
+
readonly nose: readonly ["button", "dot", "line", "none"];
|
|
52
|
+
readonly mouth: readonly ["smile", "grin", "open", "flat", "smirk", "cat", "tongue", "o"];
|
|
53
|
+
readonly cheeks: readonly ["blush", "freckles", "none"];
|
|
54
|
+
readonly glasses: readonly ["none", "round", "square", "shades", "monocle"];
|
|
55
|
+
readonly backdrop: readonly ["sparkles", "hearts", "stars", "dots", "none"];
|
|
56
|
+
};
|
|
57
|
+
/** Optional asset version 2 parts; an absent part is not drawn (or uses the first choice). */
|
|
58
|
+
declare const AVATAR_EXTRA_PARTS: {
|
|
59
|
+
readonly beard: readonly ["short", "full", "braided"];
|
|
60
|
+
readonly petals: readonly ["trumpet", "daisy", "sunflower", "tulip", "sakura"];
|
|
61
|
+
readonly flowerBase: readonly ["leaves", "pot"];
|
|
62
|
+
};
|
|
63
|
+
type AvatarExtraPart = keyof typeof AVATAR_EXTRA_PARTS;
|
|
64
|
+
type Meta = {
|
|
43
65
|
schemaVersion: 1;
|
|
44
66
|
family: 'illustrated';
|
|
45
67
|
rigVersion: 1;
|
|
46
|
-
} &
|
|
68
|
+
} & Record<AvatarColor, string>;
|
|
47
69
|
/** Asset version 1: optional split hair and geometry, all six or none. */
|
|
48
|
-
type PixelAvatarRecipeV1 =
|
|
70
|
+
type PixelAvatarRecipeV1 = Meta & {
|
|
49
71
|
assetVersion: 1;
|
|
50
|
-
} & { [P in AvatarHairPart]?: (typeof AVATAR_PARTS)['hair'][number] } & { [P in AvatarRange]?: number } & {
|
|
72
|
+
} & { [P in AvatarPart]: (typeof AVATAR_PARTS)[P][number] } & { [P in AvatarHairPart]?: (typeof AVATAR_PARTS)['hair'][number] } & { [P in AvatarRange]?: number } & {
|
|
51
73
|
species?: never;
|
|
52
74
|
rightSideHair?: never;
|
|
53
|
-
} & { [P in AvatarPieceColor]?: never };
|
|
75
|
+
} & { [P in AvatarPieceColor | AvatarExtraPart]?: never };
|
|
54
76
|
/**
|
|
55
77
|
* Asset version 2: a species, the full split hair and geometry, a separate right side hair
|
|
56
78
|
* (`sideHair` is then the left side) and optional per-side hair colors.
|
|
57
79
|
*/
|
|
58
|
-
type PixelAvatarRecipeV2 =
|
|
80
|
+
type PixelAvatarRecipeV2 = Meta & {
|
|
59
81
|
assetVersion: 2;
|
|
60
|
-
} & { [P in AvatarHairPart]: (typeof AVATAR_PARTS)['hair'][number] } & { [P in AvatarRange]: number } & {
|
|
82
|
+
} & { [P in AvatarPart]: (typeof AVATAR_PARTS_V2)[P][number] } & { [P in AvatarHairPart]: (typeof AVATAR_PARTS)['hair'][number] } & { [P in AvatarRange]: number } & {
|
|
61
83
|
species: AvatarSpecies;
|
|
62
84
|
rightSideHair: (typeof AVATAR_HAIR_PARTS)['sideHair'][number];
|
|
63
|
-
} & { [P in AvatarPieceColor]?: string };
|
|
85
|
+
} & { [P in AvatarPieceColor]?: string } & { [P in AvatarExtraPart]?: (typeof AVATAR_EXTRA_PARTS)[P][number] };
|
|
64
86
|
type PixelAvatarRecipe = PixelAvatarRecipeV1 | PixelAvatarRecipeV2;
|
|
65
87
|
declare const DEFAULT_RECIPE: PixelAvatarRecipeV1;
|
|
66
88
|
declare function isPixelAvatarRecipe(value: unknown): value is PixelAvatarRecipe;
|
|
67
89
|
declare function canonicalRecipe(recipe: PixelAvatarRecipe): PixelAvatarRecipe;
|
|
90
|
+
/**
|
|
91
|
+
* Saved choices the recipe keeps but does not draw: a flower shows petals and a stem instead of
|
|
92
|
+
* hair, outfit, beard and headwear, and a helmet or hood covers the hair. The choices return when
|
|
93
|
+
* the species or headwear changes back.
|
|
94
|
+
*/
|
|
95
|
+
declare function hiddenChoices(recipe: PixelAvatarRecipe): readonly string[];
|
|
68
96
|
/**
|
|
69
97
|
* Returns the recipe as asset version 2 with the given species, keeping every other choice.
|
|
70
98
|
* Side hair splits into left (`sideHair`) and right (`rightSideHair`) pieces that start equal.
|
|
@@ -79,6 +107,15 @@ declare const AVATAR_SWATCHES: Record<AvatarColor, readonly string[]>;
|
|
|
79
107
|
* name; the factory takes the name only, so it is safe to pass straight to `Array.map`.
|
|
80
108
|
*/
|
|
81
109
|
declare function createSeededRecipe(namespace: string): (seed: string) => PixelAvatarRecipe;
|
|
110
|
+
/**
|
|
111
|
+
* A version 2 name-seeded recipe factory over every species. Species are equally likely; a
|
|
112
|
+
* person may get a beard or a version 2 outfit, and a flower gets petals and a base. Version 1
|
|
113
|
+
* factories (`createSeededRecipe`) keep their faces, so a consumer chooses per identity which
|
|
114
|
+
* seed version it was created with.
|
|
115
|
+
*/
|
|
116
|
+
declare function createSeededRecipeV2(namespace: string): (seed: string) => PixelAvatarRecipeV2;
|
|
117
|
+
/** Version 2 counterpart of `seededRecipe` (BotHarness's namespace). */
|
|
118
|
+
declare const seededRecipeV2: (seed: string) => PixelAvatarRecipeV2;
|
|
82
119
|
/** The same name always gives the same face (BotHarness's namespace). */
|
|
83
120
|
declare const seededRecipe: (seed: string) => PixelAvatarRecipe;
|
|
84
121
|
/** Suggested body colors per species; any color remains allowed. */
|
|
@@ -150,5 +187,5 @@ declare function pixelSymbolCells(symbol: PixelSymbol, color: string): PixelCell
|
|
|
150
187
|
*/
|
|
151
188
|
declare function symbolArtCells(rows: readonly string[], color: string): PixelCell$1[];
|
|
152
189
|
//#endregion
|
|
153
|
-
export { AVATAR_COLORS, AVATAR_HAIR_PARTS, AVATAR_PARTS, AVATAR_PIECE_COLORS, AVATAR_PRESETS, AVATAR_RANGES, AVATAR_SPECIES, AVATAR_SPECIES_SWATCHES, AVATAR_SWATCHES, AVATAR_TURNS, type AvatarColor, type AvatarHairPart, type AvatarPart, type AvatarPieceColor, type AvatarRange, type AvatarSpecies, DEFAULT_RECIPE, PIXEL_SYMBOLS, type PixelAvatarRecipe, type PixelAvatarRecipeV1, type PixelAvatarRecipeV2, type PixelAvatarSvgOptions, type PixelCell, type PixelFigureOptions, type PixelGrid, type PixelMouthState, type PixelSymbol, canonicalRecipe, createSeededRecipe, detailedRecipe, faceCells, isPixelAvatarRecipe, pixelAvatarSvg, pixelFigure, pixelSymbolCells, pixelTileColor, seededRandom, seededRecipe, symbolArtCells, withSpecies };
|
|
190
|
+
export { AVATAR_COLORS, AVATAR_EXTRA_PARTS, AVATAR_HAIR_PARTS, AVATAR_PARTS, AVATAR_PARTS_V2, AVATAR_PIECE_COLORS, AVATAR_PRESETS, AVATAR_RANGES, AVATAR_SPECIES, AVATAR_SPECIES_SWATCHES, AVATAR_SWATCHES, AVATAR_TURNS, type AvatarColor, type AvatarExtraPart, type AvatarHairPart, type AvatarPart, type AvatarPieceColor, type AvatarRange, type AvatarSpecies, DEFAULT_RECIPE, PIXEL_SYMBOLS, type PixelAvatarRecipe, type PixelAvatarRecipeV1, type PixelAvatarRecipeV2, type PixelAvatarSvgOptions, type PixelCell, type PixelFigureOptions, type PixelGrid, type PixelMouthState, type PixelSymbol, canonicalRecipe, createSeededRecipe, createSeededRecipeV2, detailedRecipe, faceCells, hiddenChoices, isPixelAvatarRecipe, pixelAvatarSvg, pixelFigure, pixelSymbolCells, pixelTileColor, seededRandom, seededRecipe, seededRecipeV2, symbolArtCells, withSpecies };
|
|
154
191
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -323,9 +323,48 @@ const AVATAR_COLORS = [
|
|
|
323
323
|
* Avatar Species: a base on the pixel bust rig. Species share motion and anchors; each sets its
|
|
324
324
|
* own ears, face details and suggested body colors. A recipe with a species is asset version 2.
|
|
325
325
|
*/
|
|
326
|
-
const AVATAR_SPECIES = [
|
|
326
|
+
const AVATAR_SPECIES = [
|
|
327
|
+
"human",
|
|
328
|
+
"goblin",
|
|
329
|
+
"elf",
|
|
330
|
+
"dwarf",
|
|
331
|
+
"orc",
|
|
332
|
+
"flower"
|
|
333
|
+
];
|
|
327
334
|
/** Optional per-piece hair colors (asset version 2); an absent piece uses `hairColor`. */
|
|
328
335
|
const AVATAR_PIECE_COLORS = ["leftSideHairColor", "rightSideHairColor"];
|
|
336
|
+
/** Part choices that exist only in asset version 2, added after every version 1 choice. */
|
|
337
|
+
const AVATAR_PARTS_V2 = {
|
|
338
|
+
...AVATAR_PARTS,
|
|
339
|
+
outfit: [
|
|
340
|
+
...AVATAR_PARTS.outfit,
|
|
341
|
+
"armor",
|
|
342
|
+
"robe",
|
|
343
|
+
"tunic",
|
|
344
|
+
"cloak"
|
|
345
|
+
],
|
|
346
|
+
accessory: [
|
|
347
|
+
...AVATAR_PARTS.accessory,
|
|
348
|
+
"helmet",
|
|
349
|
+
"hood"
|
|
350
|
+
]
|
|
351
|
+
};
|
|
352
|
+
/** Optional asset version 2 parts; an absent part is not drawn (or uses the first choice). */
|
|
353
|
+
const AVATAR_EXTRA_PARTS = {
|
|
354
|
+
beard: [
|
|
355
|
+
"short",
|
|
356
|
+
"full",
|
|
357
|
+
"braided"
|
|
358
|
+
],
|
|
359
|
+
petals: [
|
|
360
|
+
"trumpet",
|
|
361
|
+
"daisy",
|
|
362
|
+
"sunflower",
|
|
363
|
+
"tulip",
|
|
364
|
+
"sakura"
|
|
365
|
+
],
|
|
366
|
+
flowerBase: ["leaves", "pot"]
|
|
367
|
+
};
|
|
329
368
|
const DEFAULT_RECIPE = {
|
|
330
369
|
schemaVersion: 1,
|
|
331
370
|
family: "illustrated",
|
|
@@ -356,14 +395,16 @@ function isPixelAvatarRecipe(value) {
|
|
|
356
395
|
if (detailed !== 0 && detailed !== DETAIL_KEYS.length) return false;
|
|
357
396
|
const v2 = r["assetVersion"] === 2;
|
|
358
397
|
const pieces = AVATAR_PIECE_COLORS.filter((key) => Object.hasOwn(r, key));
|
|
359
|
-
const
|
|
398
|
+
const extras = Object.keys(AVATAR_EXTRA_PARTS).filter((key) => Object.hasOwn(r, key));
|
|
399
|
+
const v2Keys = v2 ? 2 + pieces.length + extras.length : 0;
|
|
360
400
|
if (Object.keys(r).length !== Object.keys(DEFAULT_RECIPE).length + detailed + v2Keys) return false;
|
|
361
|
-
if (v2 && (detailed === 0 || !AVATAR_SPECIES.includes(r["species"]) || !AVATAR_HAIR_PARTS.sideHair.includes(r["rightSideHair"]) || !pieces.every((key) => typeof r[key] === "string" && /^#[\da-f]{6}$/iu.test(r[key])))) return false;
|
|
401
|
+
if (v2 && (detailed === 0 || !AVATAR_SPECIES.includes(r["species"]) || !AVATAR_HAIR_PARTS.sideHair.includes(r["rightSideHair"]) || !pieces.every((key) => typeof r[key] === "string" && /^#[\da-f]{6}$/iu.test(r[key])) || !extras.every((key) => AVATAR_EXTRA_PARTS[key].includes(r[key])))) return false;
|
|
402
|
+
const catalog = v2 ? AVATAR_PARTS_V2 : AVATAR_PARTS;
|
|
362
403
|
return (detailed === 0 || Object.keys(AVATAR_HAIR_PARTS).every((key) => typeof r[key] === "string" && AVATAR_HAIR_PARTS[key].includes(r[key])) && Object.keys(AVATAR_RANGES).every((key) => {
|
|
363
404
|
const v = r[key];
|
|
364
405
|
const [min, max] = AVATAR_RANGES[key];
|
|
365
406
|
return typeof v === "number" && Number.isInteger(v) && v >= min && v <= max;
|
|
366
|
-
})) && r["schemaVersion"] === 1 && r["family"] === "illustrated" && (r["assetVersion"] === 1 || v2) && r["rigVersion"] === 1 && PART_KEYS.every((key) => typeof r[key] === "string" &&
|
|
407
|
+
})) && r["schemaVersion"] === 1 && r["family"] === "illustrated" && (r["assetVersion"] === 1 || v2) && r["rigVersion"] === 1 && PART_KEYS.every((key) => typeof r[key] === "string" && catalog[key].includes(r[key])) && AVATAR_COLORS.every((key) => typeof r[key] === "string" && /^#[\da-f]{6}$/iu.test(r[key]));
|
|
367
408
|
}
|
|
368
409
|
function canonicalRecipe(recipe) {
|
|
369
410
|
const canonical = {
|
|
@@ -379,9 +420,38 @@ function canonicalRecipe(recipe) {
|
|
|
379
420
|
canonical["species"] = recipe.species;
|
|
380
421
|
canonical["rightSideHair"] = recipe.rightSideHair;
|
|
381
422
|
for (const key of AVATAR_PIECE_COLORS) if (recipe[key] !== void 0) canonical[key] = recipe[key].toLowerCase();
|
|
423
|
+
for (const key of Object.keys(AVATAR_EXTRA_PARTS)) if (recipe[key] !== void 0) canonical[key] = recipe[key];
|
|
382
424
|
}
|
|
383
425
|
return canonical;
|
|
384
426
|
}
|
|
427
|
+
const HAIR_PIECES = [
|
|
428
|
+
"bangs",
|
|
429
|
+
"sideHair",
|
|
430
|
+
"rightSideHair",
|
|
431
|
+
"backHair"
|
|
432
|
+
];
|
|
433
|
+
/**
|
|
434
|
+
* Saved choices the recipe keeps but does not draw: a flower shows petals and a stem instead of
|
|
435
|
+
* hair, outfit, beard and headwear, and a helmet or hood covers the hair. The choices return when
|
|
436
|
+
* the species or headwear changes back.
|
|
437
|
+
*/
|
|
438
|
+
function hiddenChoices(recipe) {
|
|
439
|
+
if (recipe.species === "flower") return [
|
|
440
|
+
...HAIR_PIECES,
|
|
441
|
+
"outfit",
|
|
442
|
+
"accessory",
|
|
443
|
+
"eyes",
|
|
444
|
+
"brows",
|
|
445
|
+
"nose",
|
|
446
|
+
"cheeks",
|
|
447
|
+
"glasses",
|
|
448
|
+
...recipe.beard ? ["beard"] : []
|
|
449
|
+
];
|
|
450
|
+
const hidden = [];
|
|
451
|
+
if (recipe.accessory === "helmet" || recipe.accessory === "hood") hidden.push(...HAIR_PIECES);
|
|
452
|
+
if (recipe.species === "dwarf") hidden.push("nose");
|
|
453
|
+
return hidden;
|
|
454
|
+
}
|
|
385
455
|
/**
|
|
386
456
|
* Returns the recipe as asset version 2 with the given species, keeping every other choice.
|
|
387
457
|
* Side hair splits into left (`sideHair`) and right (`rightSideHair`) pieces that start equal.
|
|
@@ -551,6 +621,38 @@ function createSeededRecipe(namespace) {
|
|
|
551
621
|
return recipe;
|
|
552
622
|
};
|
|
553
623
|
}
|
|
624
|
+
/**
|
|
625
|
+
* A version 2 name-seeded recipe factory over every species. Species are equally likely; a
|
|
626
|
+
* person may get a beard or a version 2 outfit, and a flower gets petals and a base. Version 1
|
|
627
|
+
* factories (`createSeededRecipe`) keep their faces, so a consumer chooses per identity which
|
|
628
|
+
* seed version it was created with.
|
|
629
|
+
*/
|
|
630
|
+
function createSeededRecipeV2(namespace) {
|
|
631
|
+
const base = createSeededRecipe(`${namespace}:base`);
|
|
632
|
+
return (seed) => {
|
|
633
|
+
const random = seededRandom(`${namespace}:${seed.trim().toLowerCase()}`);
|
|
634
|
+
const pick = (values) => values[Math.floor(random() * values.length)];
|
|
635
|
+
const species = pick(AVATAR_SPECIES);
|
|
636
|
+
const recipe = {
|
|
637
|
+
...withSpecies(base(seed), species),
|
|
638
|
+
skinColor: pick(AVATAR_SPECIES_SWATCHES[species])
|
|
639
|
+
};
|
|
640
|
+
if (species === "flower") return {
|
|
641
|
+
...recipe,
|
|
642
|
+
petals: pick(AVATAR_EXTRA_PARTS.petals),
|
|
643
|
+
flowerBase: pick(AVATAR_EXTRA_PARTS.flowerBase)
|
|
644
|
+
};
|
|
645
|
+
const beard = random() < .3 ? pick(AVATAR_EXTRA_PARTS.beard) : void 0;
|
|
646
|
+
const outfit = random() < .25 ? pick(AVATAR_PARTS_V2.outfit.slice(-4)) : recipe.outfit;
|
|
647
|
+
return {
|
|
648
|
+
...recipe,
|
|
649
|
+
outfit,
|
|
650
|
+
...beard ? { beard } : {}
|
|
651
|
+
};
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
/** Version 2 counterpart of `seededRecipe` (BotHarness's namespace). */
|
|
655
|
+
const seededRecipeV2 = createSeededRecipeV2("botharness-avatar-v2");
|
|
554
656
|
/** The same name always gives the same face (BotHarness's namespace). */
|
|
555
657
|
const seededRecipe = createSeededRecipe("botharness-avatar");
|
|
556
658
|
/** Suggested body colors per species; any color remains allowed. */
|
|
@@ -563,6 +665,38 @@ const AVATAR_SPECIES_SWATCHES = {
|
|
|
563
665
|
"#b3cf7a",
|
|
564
666
|
"#5c8a4a",
|
|
565
667
|
"#8fa86a"
|
|
668
|
+
],
|
|
669
|
+
elf: [
|
|
670
|
+
"#fbe7d6",
|
|
671
|
+
"#f2d3c0",
|
|
672
|
+
"#e2c9b0",
|
|
673
|
+
"#d9c7e8",
|
|
674
|
+
"#b9c6dd",
|
|
675
|
+
"#8f7fa8"
|
|
676
|
+
],
|
|
677
|
+
dwarf: [
|
|
678
|
+
"#f2c4a0",
|
|
679
|
+
"#e9b08a",
|
|
680
|
+
"#e0a87e",
|
|
681
|
+
"#c68863",
|
|
682
|
+
"#f0b8a0",
|
|
683
|
+
"#9a6142"
|
|
684
|
+
],
|
|
685
|
+
orc: [
|
|
686
|
+
"#8a9a5b",
|
|
687
|
+
"#6f8a4e",
|
|
688
|
+
"#7d8c6a",
|
|
689
|
+
"#5f6f4a",
|
|
690
|
+
"#9aa070",
|
|
691
|
+
"#6a7a7a"
|
|
692
|
+
],
|
|
693
|
+
flower: [
|
|
694
|
+
"#f6d36b",
|
|
695
|
+
"#f2b84b",
|
|
696
|
+
"#ffe3cf",
|
|
697
|
+
"#e8a85a",
|
|
698
|
+
"#c98b4a",
|
|
699
|
+
"#f4f1ec"
|
|
566
700
|
]
|
|
567
701
|
};
|
|
568
702
|
const preset = (parts) => ({
|
|
@@ -1121,6 +1255,34 @@ function outfit(recipe, g, s) {
|
|
|
1121
1255
|
paint(g, (x, y) => y === 24 && Math.abs(dx(x)) <= 4.5, WHITE);
|
|
1122
1256
|
sprite(g, ["RR.RR", ".RRR."], Math.round(C + s * .5) - 2, 24, { R: shirt });
|
|
1123
1257
|
break;
|
|
1258
|
+
case "armor": {
|
|
1259
|
+
const metal = "#9aa3ad";
|
|
1260
|
+
paint(g, torso, metal);
|
|
1261
|
+
paint(g, (x, y) => torso(x, y) && y >= 26 && Math.abs(dx(x)) <= 2.5, shirt);
|
|
1262
|
+
paint(g, (x, y) => torso(x, y) && y <= 26 && Math.abs(dx(x)) >= half(y) - 3.5, mix$1(metal, WHITE, .35));
|
|
1263
|
+
paint(g, (x, y) => torso(x, y) && y === 27 && Math.abs(dx(x)) >= half(y) - 3.5, shade(metal, .7));
|
|
1264
|
+
paint(g, (x, y) => y === 24 && Math.abs(dx(x)) <= 3.5, shade(metal, .8));
|
|
1265
|
+
sprite(g, ["G"], Math.round(C + s * .5), 28, { G: GOLD });
|
|
1266
|
+
break;
|
|
1267
|
+
}
|
|
1268
|
+
case "robe":
|
|
1269
|
+
paint(g, v(5, 4), mix$1(shirt, WHITE, .7));
|
|
1270
|
+
paint(g, (x, y) => torso(x, y) && y <= 29 && Math.abs(Math.abs(dx(x)) - (4 - (y - 24) * .8)) <= .5, GOLD);
|
|
1271
|
+
paint(g, (x, y) => torso(x, y) && y >= 28 && Math.abs(dx(x)) >= half(y) - 1.5, dark);
|
|
1272
|
+
break;
|
|
1273
|
+
case "tunic":
|
|
1274
|
+
paint(g, v(2, 2), shade(skin, .85));
|
|
1275
|
+
sprite(g, ["L.L", ".L."], Math.round(C + s * .5) - 1, 25, { L: "#6b4a2b" });
|
|
1276
|
+
paint(g, (x, y) => torso(x, y) && y === 29, "#6b4a2b");
|
|
1277
|
+
sprite(g, ["G"], Math.round(C + s * .5), 29, { G: GOLD });
|
|
1278
|
+
break;
|
|
1279
|
+
case "cloak": {
|
|
1280
|
+
const cloak = mix$1(shirt, "#2f3a4a", .55);
|
|
1281
|
+
paint(g, (x, y) => torso(x, y) && Math.abs(dx(x)) >= 2.5, cloak);
|
|
1282
|
+
paint(g, (x, y) => torso(x, y) && y === 24, shade(cloak, .8));
|
|
1283
|
+
sprite(g, ["G...G"], Math.round(C + s * .5) - 2, 25, { G: GOLD });
|
|
1284
|
+
break;
|
|
1285
|
+
}
|
|
1124
1286
|
case "jacket":
|
|
1125
1287
|
paint(g, v(6, 4.5), WHITE);
|
|
1126
1288
|
paint(g, (x, y) => torso(x, y) && Math.abs(Math.abs(dx(x)) - (4.5 - (y - 24) * .6)) <= .6, dark);
|
|
@@ -1239,6 +1401,22 @@ function accessory(recipe, g, s, face) {
|
|
|
1239
1401
|
}, true);
|
|
1240
1402
|
return;
|
|
1241
1403
|
}
|
|
1404
|
+
case "helmet": {
|
|
1405
|
+
const metal = "#9aa3ad";
|
|
1406
|
+
paint(g, (x, y) => cap(x, y) && y <= 11, metal);
|
|
1407
|
+
paint(g, (x, y) => cap(x, y) && y === 11, shade(metal, .75));
|
|
1408
|
+
paint(g, (x, y) => cap(x, y) && y >= 12 && y <= 19 && Math.abs(x + .5 - C - s) >= 7.5, metal);
|
|
1409
|
+
paint(g, (x, y) => Math.abs(x + .5 - C - s) <= .5 && y >= 11 && y <= 16, shade(metal, .7));
|
|
1410
|
+
paint(g, (x, y) => cap(x, y) && y >= 2 && y <= 5 && x + .5 < C + s - 2, mix$1(metal, WHITE, .4));
|
|
1411
|
+
for (let x = Math.round(C + s) - 9; x <= Math.round(C + s) + 9; x += 3) sprite(g, ["G"], x, 10, { G: GOLD });
|
|
1412
|
+
return;
|
|
1413
|
+
}
|
|
1414
|
+
case "hood": {
|
|
1415
|
+
const outer = ellipse(C + s, 15, 12.6, 12.4);
|
|
1416
|
+
paint(g, (x, y) => outer(x, y) && y <= 25 && !(face(x, y) && y >= 10), shirt);
|
|
1417
|
+
paint(g, (x, y) => outer(x, y) && !face(x, y) && y >= 9 && (face(x + 1, y) || face(x - 1, y) || face(x, y + 1)), shade(shirt, .7));
|
|
1418
|
+
return;
|
|
1419
|
+
}
|
|
1242
1420
|
case "beret":
|
|
1243
1421
|
paint(g, (x, y) => ellipse(C + s + 1.5, 4.6, 10.5, 3.6)(x, y), shirt);
|
|
1244
1422
|
paint(g, (x, y) => ellipse(C + s + 1.5, 4.6, 10.5, 3.6)(x, y) && y >= 6, shade(shirt, .78));
|
|
@@ -1334,6 +1512,75 @@ function accessory(recipe, g, s, face) {
|
|
|
1334
1512
|
return;
|
|
1335
1513
|
}
|
|
1336
1514
|
}
|
|
1515
|
+
const POINTED_EARS = {
|
|
1516
|
+
goblin: {
|
|
1517
|
+
rows: [
|
|
1518
|
+
"E...",
|
|
1519
|
+
"EE..",
|
|
1520
|
+
".EiE",
|
|
1521
|
+
"..Ee"
|
|
1522
|
+
],
|
|
1523
|
+
rise: 2
|
|
1524
|
+
},
|
|
1525
|
+
elf: {
|
|
1526
|
+
rows: [
|
|
1527
|
+
"E....",
|
|
1528
|
+
"EE...",
|
|
1529
|
+
".EE..",
|
|
1530
|
+
"..EiE",
|
|
1531
|
+
"...Ee"
|
|
1532
|
+
],
|
|
1533
|
+
rise: 3
|
|
1534
|
+
},
|
|
1535
|
+
orc: {
|
|
1536
|
+
rows: [
|
|
1537
|
+
"E..",
|
|
1538
|
+
"EE.",
|
|
1539
|
+
".Ee"
|
|
1540
|
+
],
|
|
1541
|
+
rise: 1
|
|
1542
|
+
}
|
|
1543
|
+
};
|
|
1544
|
+
const leafShape = (cx, cy, long, wide, angle) => (x, y) => {
|
|
1545
|
+
const px = x + .5 - cx;
|
|
1546
|
+
const py = y + .5 - cy;
|
|
1547
|
+
const u = px * Math.cos(angle) + py * Math.sin(angle);
|
|
1548
|
+
const v = -px * Math.sin(angle) + py * Math.cos(angle);
|
|
1549
|
+
return (u / long) ** 2 + (v / wide) ** 2 <= 1;
|
|
1550
|
+
};
|
|
1551
|
+
function petals(style, s) {
|
|
1552
|
+
const cx = C + s;
|
|
1553
|
+
const cy = 16.6;
|
|
1554
|
+
const ring = (n, radius, long, wide, phase = 0) => (x, y) => Array.from({ length: n }, (_, k) => phase + 2 * Math.PI * k / n).some((a) => leafShape(cx + radius * Math.cos(a), cy + radius * Math.sin(a), long, wide, a)(x, y));
|
|
1555
|
+
switch (style) {
|
|
1556
|
+
case "daisy": return ring(14, 10.4, 3.4, 1.7, Math.PI / 14);
|
|
1557
|
+
case "sunflower": return (x, y) => ring(16, 11.2, 2.6, 1.5)(x, y) || ellipse(cx, cy, 10.6, 10.2)(x, y);
|
|
1558
|
+
case "tulip": return (x, y) => y <= cy + 6 && (ellipse(cx - 6.5, cy - 3, 4.5, 7.5)(x, y) || ellipse(cx + 6.5, cy - 3, 4.5, 7.5)(x, y) || ellipse(cx, cy - 6, 5, 8)(x, y));
|
|
1559
|
+
case "trumpet": return (x, y) => ellipse(cx, cy - .5, 11.6, 10.4)(x, y) || y <= cy - 6 && ellipse(cx, cy - 3, 13, 8.6)(x, y);
|
|
1560
|
+
case "sakura": return (x, y) => ring(5, 8.6, 4.6, 3.8, -Math.PI / 2)(x, y) && !ring(5, 13.4, 1.4, 1.4, -Math.PI / 2)(x, y);
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
function flowerBase(recipe, g, s) {
|
|
1564
|
+
const leaf = "#5f9e4a";
|
|
1565
|
+
const stem = C + s * .5;
|
|
1566
|
+
paint(g, (x, y) => y >= 22 && Math.abs(x + .5 - stem) <= 1, shade(leaf, .82));
|
|
1567
|
+
if (recipe.flowerBase === "pot") {
|
|
1568
|
+
const pot = recipe.shirtColor;
|
|
1569
|
+
const body = (x, y) => y >= 27 && Math.abs(x + .5 - stem) <= 6.5 - (y - 27) * .45;
|
|
1570
|
+
paint(g, leafShape(stem - 3, 25, 2.8, 1.2, -.5), leaf);
|
|
1571
|
+
paint(g, leafShape(stem + 3, 24.5, 2.8, 1.2, .5), shade(leaf, .9));
|
|
1572
|
+
paint(g, body, pot);
|
|
1573
|
+
paint(g, (x, y) => body(x, y) && y === 27, mix$1(pot, WHITE, .3));
|
|
1574
|
+
paint(g, (x, y) => body(x, y) && y >= 28 && x + .5 > stem + 3, shade(pot, .8));
|
|
1575
|
+
return;
|
|
1576
|
+
}
|
|
1577
|
+
const left = leafShape(stem - 4.5, 27, 4.4, 1.9, -.55);
|
|
1578
|
+
const right = leafShape(stem + 4.5, 25.5, 4.4, 1.9, .55);
|
|
1579
|
+
paint(g, left, leaf);
|
|
1580
|
+
paint(g, right, shade(leaf, .92));
|
|
1581
|
+
paint(g, leafShape(stem - 4.5, 27, 3.6, .5, -.55), shade(leaf, .75));
|
|
1582
|
+
paint(g, leafShape(stem + 4.5, 25.5, 3.6, .5, .55), shade(leaf, .7));
|
|
1583
|
+
}
|
|
1337
1584
|
function pixelTileColor(hair) {
|
|
1338
1585
|
const [r, g, b] = channels$1(hair).map((v) => v / 255);
|
|
1339
1586
|
const max = Math.max(r, g, b);
|
|
@@ -1371,34 +1618,56 @@ function pixelFigure(recipe, yawDeg, options = {}) {
|
|
|
1371
1618
|
const tuck = y >= 17 && d * dx > 4 ? Math.min(2, (y - 16) * .35) : 0;
|
|
1372
1619
|
return front(x - Math.round(d * (1 + Math.max(0, y - 20) * .4) - d * tuck), y);
|
|
1373
1620
|
} : front;
|
|
1374
|
-
const
|
|
1621
|
+
const flower = recipe.species === "flower";
|
|
1622
|
+
const covered = recipe.accessory === "helmet" || recipe.accessory === "hood";
|
|
1623
|
+
const bare = () => false;
|
|
1624
|
+
const petalRing = flower ? petals(recipe.petals ?? "trumpet", s) : bare;
|
|
1625
|
+
const masks = flower ? {
|
|
1626
|
+
back: petalRing,
|
|
1627
|
+
front: bare,
|
|
1628
|
+
sides: [(x, y) => petalRing(x, y) && x + .5 < C + s, (x, y) => petalRing(x, y) && x + .5 >= C + s]
|
|
1629
|
+
} : covered ? {
|
|
1630
|
+
back: bare,
|
|
1631
|
+
front: bare
|
|
1632
|
+
} : recipe.bangs === void 0 ? hair(recipe, s, b) : detailedHair(recipe, s, b);
|
|
1375
1633
|
const body = blank();
|
|
1376
|
-
|
|
1634
|
+
if (flower) flowerBase(recipe, body, s);
|
|
1635
|
+
else outfit(recipe, body, s);
|
|
1377
1636
|
const back = blank();
|
|
1378
|
-
|
|
1637
|
+
if (flower) {
|
|
1638
|
+
paint(back, petalRing, hairColor);
|
|
1639
|
+
paint(back, (x, y) => petalRing(x, y) && (x + .5 - C - s) ** 2 + (y + .5 - 16.6) ** 2 < 90, shade(hairColor, .84));
|
|
1640
|
+
paint(back, (x, y) => petalRing(x, y) && x + .5 < C + s - 3 && y < 10, mix$1(hairColor, WHITE, .3));
|
|
1641
|
+
} else paint(back, masks.back, shade(hairColor, .82));
|
|
1379
1642
|
if (masks.bands) paint(back, (x, y) => masks.back(x, y) && (y + Math.floor(x / 2)) % 4 === 0, shade(hairColor, .68));
|
|
1380
1643
|
const head = blank();
|
|
1381
1644
|
paint(head, face, skin);
|
|
1645
|
+
const h = recipe.height ?? 0;
|
|
1646
|
+
if (recipe.beard && !flower) {
|
|
1647
|
+
const centre = C + fs * .6;
|
|
1648
|
+
const mouthX = Math.round(C - .5 + fs * .6);
|
|
1649
|
+
const chin = (x, y) => face(x, y) || face(x, y - 2);
|
|
1650
|
+
const open = (x, y) => y >= 20 + h && y <= 22 + h && Math.abs(x - mouthX) <= 3;
|
|
1651
|
+
const full = (x, y) => chin(x, y) && (y >= 22 + h || Math.abs(x + .5 - centre) >= 4 && y >= 17 + h);
|
|
1652
|
+
const shape = recipe.beard === "short" ? (x, y) => chin(x, y) && y >= 22 + h : recipe.beard === "full" ? full : (x, y) => full(x, y) || Math.abs(x + .5 - centre) <= 1 && y >= 24 && y <= 30;
|
|
1653
|
+
paint(head, (x, y) => shape(x, y) && !open(x, y), shade(hairColor, .9));
|
|
1654
|
+
paint(head, (x, y) => shape(x, y) && !open(x, y) && (x + y * 2) % 5 === 0, shade(hairColor, .72));
|
|
1655
|
+
}
|
|
1382
1656
|
const earY = 15;
|
|
1383
1657
|
const edge = (side) => {
|
|
1384
1658
|
const xs = [...Array(N).keys()].filter((x) => face(x, earY));
|
|
1385
1659
|
return side < 0 ? xs[0] - 1 : xs.at(-1) + 1;
|
|
1386
1660
|
};
|
|
1387
|
-
const
|
|
1388
|
-
if (
|
|
1389
|
-
const ear = [
|
|
1390
|
-
"E...",
|
|
1391
|
-
"EE..",
|
|
1392
|
-
".EiE",
|
|
1393
|
-
"..Ee"
|
|
1394
|
-
];
|
|
1661
|
+
const pointed = recipe.species ? POINTED_EARS[recipe.species] : void 0;
|
|
1662
|
+
if (flower) {} else if (pointed) {
|
|
1395
1663
|
const pal = {
|
|
1396
1664
|
E: skin,
|
|
1397
1665
|
e: shade(skin, .86),
|
|
1398
1666
|
i: shade(skin, .74)
|
|
1399
1667
|
};
|
|
1668
|
+
const width = pointed.rows[0].length;
|
|
1400
1669
|
paint(head, masks.front, hairColor);
|
|
1401
|
-
for (const ex of turned ? [-d] : [-1, 1]) sprite(head,
|
|
1670
|
+
for (const ex of turned ? [-d] : [-1, 1]) sprite(head, pointed.rows, ex < 0 ? edge(ex) - (width - 1) : edge(ex), earY - pointed.rise, pal, ex > 0);
|
|
1402
1671
|
} else if (!turned) for (const ex of [-1, 1]) sprite(head, [
|
|
1403
1672
|
"E",
|
|
1404
1673
|
"E",
|
|
@@ -1416,9 +1685,9 @@ function pixelFigure(recipe, yawDeg, options = {}) {
|
|
|
1416
1685
|
E: skin,
|
|
1417
1686
|
e: shade(skin, .8)
|
|
1418
1687
|
});
|
|
1419
|
-
if (!
|
|
1688
|
+
if (!pointed && !flower) paint(head, masks.front, hairColor);
|
|
1420
1689
|
for (const [x, y] of masks.ties ?? []) sprite(back, ["T"], Math.round(x), y, { T: shade(recipe.shirtColor, .8) });
|
|
1421
|
-
accessory(recipe, head, s, face);
|
|
1690
|
+
if (!flower) accessory(recipe, head, s, face);
|
|
1422
1691
|
const shadeHair = (g) => {
|
|
1423
1692
|
const out = g.map((r) => [...r]);
|
|
1424
1693
|
const cells = g.flatMap((row, y) => row.flatMap((c, x) => c === hairColor ? [[x, y]] : []));
|
|
@@ -1470,7 +1739,6 @@ function pixelFigure(recipe, yawDeg, options = {}) {
|
|
|
1470
1739
|
const closed = blank();
|
|
1471
1740
|
const glasses = blank();
|
|
1472
1741
|
const sp = recipe.spacing ?? 0;
|
|
1473
|
-
const h = recipe.height ?? 0;
|
|
1474
1742
|
const eyeTop = 15 + h;
|
|
1475
1743
|
const eyeW = (side) => turned && Math.sign(fs) === side ? 3 : 4;
|
|
1476
1744
|
const eyeX = (side) => {
|
|
@@ -1493,21 +1761,47 @@ function pixelFigure(recipe, yawDeg, options = {}) {
|
|
|
1493
1761
|
I: recipe.eyeColor,
|
|
1494
1762
|
L: mix$1(recipe.eyeColor, WHITE, .45)
|
|
1495
1763
|
};
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1764
|
+
if (flower) {
|
|
1765
|
+
const centre = Math.round(C + fs * .6);
|
|
1766
|
+
const bead = {
|
|
1767
|
+
K: "#1d1b22",
|
|
1768
|
+
W: WHITE
|
|
1769
|
+
};
|
|
1770
|
+
for (const ex of [centre - 3, centre + 1]) {
|
|
1771
|
+
sprite(eyes, [
|
|
1772
|
+
"KK",
|
|
1773
|
+
"KW",
|
|
1774
|
+
"KK",
|
|
1775
|
+
"KK"
|
|
1776
|
+
], ex, eyeTop - 1, bead);
|
|
1777
|
+
sprite(closed, [
|
|
1778
|
+
"..",
|
|
1779
|
+
"..",
|
|
1780
|
+
"KK",
|
|
1781
|
+
".."
|
|
1782
|
+
], ex, eyeTop - 1, bead);
|
|
1783
|
+
}
|
|
1784
|
+
} else {
|
|
1785
|
+
const style = EYES[recipe.eyes];
|
|
1786
|
+
const extra = style[0].length - 4;
|
|
1787
|
+
sprite(eyes, fit(style, left.w + extra), left.x - extra, eyeTop, pal);
|
|
1788
|
+
sprite(eyes, recipe.eyes === "wink" ? fit(CLOSED, right.w) : fit(style, right.w + extra), right.x, eyeTop, pal, true);
|
|
1789
|
+
sprite(closed, fit(CLOSED, left.w), left.x, eyeTop, pal);
|
|
1790
|
+
sprite(closed, fit(CLOSED, right.w), right.x, eyeTop, pal, true);
|
|
1791
|
+
}
|
|
1792
|
+
const browRows = flower ? [] : BROWS[recipe.brows];
|
|
1503
1793
|
if (browRows.length) {
|
|
1504
1794
|
sprite(features, fit(browRows, left.w), left.x, eyeTop - 2, { B: shade(hairColor, .55) });
|
|
1505
1795
|
sprite(features, fit(browRows, right.w), right.x, eyeTop - 2, { B: shade(hairColor, .55) }, true);
|
|
1506
1796
|
}
|
|
1507
1797
|
const cx = Math.round(C + fs * (turned ? .85 : .6));
|
|
1508
|
-
if (recipe.
|
|
1509
|
-
|
|
1510
|
-
|
|
1798
|
+
if (flower) {} else if (recipe.species === "dwarf") sprite(features, [".N.", "NnN"], cx - 1, 17 + h, {
|
|
1799
|
+
N: shade(skin, .84),
|
|
1800
|
+
n: shade(skin, .72)
|
|
1801
|
+
});
|
|
1802
|
+
else if (recipe.nose === "dot") sprite(features, ["N"], cx, 18 + h, { N: shade(skin, .78) });
|
|
1803
|
+
else if (recipe.nose === "button") sprite(features, ["N"], cx, 18 + h, { N: shade(skin, .88) });
|
|
1804
|
+
else if (recipe.nose === "line") sprite(features, ["N", "N"], cx, 17 + h, { N: shade(skin, .82) });
|
|
1511
1805
|
const mx = C - .5 + fs * .6;
|
|
1512
1806
|
const withMouth = (mouth) => {
|
|
1513
1807
|
const layer = features.map((row) => [...row]);
|
|
@@ -1518,11 +1812,12 @@ function pixelFigure(recipe, yawDeg, options = {}) {
|
|
|
1518
1812
|
W: WHITE
|
|
1519
1813
|
});
|
|
1520
1814
|
if (recipe.species === "goblin") for (const tx of [Math.round(mx - 2.5), Math.round(mx + 2.5)]) sprite(layer, ["T"], tx, 21 + h, { T: TUSK });
|
|
1521
|
-
if (recipe.
|
|
1815
|
+
if (recipe.species === "orc") for (const tx of [Math.round(mx - 2.5), Math.round(mx + 2.5)]) sprite(layer, ["T", "T"], tx, 20 + h, { T: TUSK });
|
|
1816
|
+
if (recipe.cheeks === "blush" && !flower) {
|
|
1522
1817
|
sprite(layer, ["PP"], left.x - 1, 19 + h, { P: mix$1(BLUSH, skin, .25) });
|
|
1523
1818
|
sprite(layer, ["PP"], right.x + right.w - 1, 19 + h, { P: mix$1(BLUSH, skin, .25) });
|
|
1524
1819
|
}
|
|
1525
|
-
if (recipe.cheeks === "freckles") {
|
|
1820
|
+
if (recipe.cheeks === "freckles" && !flower) {
|
|
1526
1821
|
sprite(layer, ["F.F"], left.x, 19 + h, { F: shade(skin, .7) });
|
|
1527
1822
|
sprite(layer, ["F.F"], right.x + 1, 19 + h, { F: shade(skin, .7) });
|
|
1528
1823
|
}
|
|
@@ -1545,17 +1840,18 @@ function pixelFigure(recipe, yawDeg, options = {}) {
|
|
|
1545
1840
|
const corner = (x === fx - 1 || x === fx + w) && (y === eyeTop - 1 || y === eyeTop + 4);
|
|
1546
1841
|
return box && !inside && !(round && corner);
|
|
1547
1842
|
}, INK$1);
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
frame(
|
|
1843
|
+
const lenses = flower ? "none" : recipe.glasses;
|
|
1844
|
+
if (lenses === "round" || lenses === "square") {
|
|
1845
|
+
frame(left.x, left.w, lenses === "round");
|
|
1846
|
+
frame(right.x, right.w, lenses === "round");
|
|
1551
1847
|
paint(glasses, (x, y) => y === eyeTop + 1 && x > left.x + left.w && x < right.x - 1, INK$1);
|
|
1552
1848
|
}
|
|
1553
|
-
if (
|
|
1849
|
+
if (lenses === "shades") {
|
|
1554
1850
|
for (const e of [left, right]) paint(glasses, (x, y) => x >= e.x - 1 && x <= e.x + e.w && y >= eyeTop && y <= eyeTop + 2, INK$1);
|
|
1555
1851
|
paint(glasses, (x, y) => y === eyeTop && x > left.x + left.w && x < right.x - 1, INK$1);
|
|
1556
1852
|
for (const e of [left, right]) sprite(glasses, ["W"], e.x, eyeTop, { W: "#7d7b88" });
|
|
1557
1853
|
}
|
|
1558
|
-
if (
|
|
1854
|
+
if (lenses === "monocle") {
|
|
1559
1855
|
frame(right.x, right.w, true);
|
|
1560
1856
|
sprite(glasses, [
|
|
1561
1857
|
"G",
|
|
@@ -2131,5 +2427,5 @@ function symbolArtCells(rows, color) {
|
|
|
2131
2427
|
}
|
|
2132
2428
|
|
|
2133
2429
|
//#endregion
|
|
2134
|
-
export { AVATAR_COLORS, AVATAR_HAIR_PARTS, AVATAR_PARTS, AVATAR_PIECE_COLORS, AVATAR_PRESETS, AVATAR_RANGES, AVATAR_SPECIES, AVATAR_SPECIES_SWATCHES, AVATAR_SWATCHES, AVATAR_TURNS, DEFAULT_RECIPE, PIXEL_SYMBOLS, canonicalRecipe, createSeededRecipe, detailedRecipe, faceCells, isPixelAvatarRecipe, pixelAvatarSvg, pixelFigure, pixelSymbolCells, pixelTileColor, seededRandom, seededRecipe, symbolArtCells, withSpecies };
|
|
2430
|
+
export { AVATAR_COLORS, AVATAR_EXTRA_PARTS, AVATAR_HAIR_PARTS, AVATAR_PARTS, AVATAR_PARTS_V2, AVATAR_PIECE_COLORS, AVATAR_PRESETS, AVATAR_RANGES, AVATAR_SPECIES, AVATAR_SPECIES_SWATCHES, AVATAR_SWATCHES, AVATAR_TURNS, DEFAULT_RECIPE, PIXEL_SYMBOLS, canonicalRecipe, createSeededRecipe, createSeededRecipeV2, detailedRecipe, faceCells, hiddenChoices, isPixelAvatarRecipe, pixelAvatarSvg, pixelFigure, pixelSymbolCells, pixelTileColor, seededRandom, seededRecipe, seededRecipeV2, symbolArtCells, withSpecies };
|
|
2135
2431
|
//# sourceMappingURL=index.js.map
|