@siegesailor/terminal-sigma 1.4.0 → 1.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 +35 -23
- package/build/app.js +74 -6
- package/build/app.js.map +1 -1
- package/build/command-line-interface.js +15 -1
- package/build/command-line-interface.js.map +1 -1
- package/build/components/character.js +1 -2
- package/build/components/character.js.map +1 -1
- package/build/components/today-panel.js +11 -8
- package/build/components/today-panel.js.map +1 -1
- package/build/content.js +58 -20
- package/build/content.js.map +1 -1
- package/build/i18n.js +78 -9
- package/build/i18n.js.map +1 -1
- package/build/pixel-art.js +592 -296
- package/build/pixel-art.js.map +1 -1
- package/build/progress.js +1 -0
- package/build/progress.js.map +1 -1
- package/build/update.js +61 -0
- package/build/update.js.map +1 -0
- package/package.json +1 -1
package/build/pixel-art.js
CHANGED
|
@@ -1,135 +1,255 @@
|
|
|
1
|
-
// The
|
|
2
|
-
// terminal cell draws 2 stacked pixels with "▀", foreground for the top
|
|
3
|
-
// background for the bottom, so the stage takes
|
|
4
|
-
export const stageHeight =
|
|
5
|
-
export const defaultLook = {
|
|
1
|
+
// The character stands on a stage as wide as its panel and 32 pixels tall.
|
|
2
|
+
// Each terminal cell draws 2 stacked pixels with "▀", foreground for the top
|
|
3
|
+
// and background for the bottom, so the stage takes 16 rows.
|
|
4
|
+
export const stageHeight = 32;
|
|
5
|
+
export const defaultLook = {
|
|
6
|
+
height: 23,
|
|
7
|
+
build: "average",
|
|
8
|
+
gender: "other",
|
|
9
|
+
style: "desk",
|
|
10
|
+
hair: "dark",
|
|
11
|
+
};
|
|
12
|
+
// 145 cm draws 20 pixels tall and 205 cm 26; the build follows the BMI.
|
|
6
13
|
export function lookOf(profile) {
|
|
7
14
|
if (!profile) {
|
|
8
15
|
return defaultLook;
|
|
9
16
|
}
|
|
10
17
|
const bmi = profile.weight / (profile.height / 100) ** 2;
|
|
11
18
|
let build = "average";
|
|
12
|
-
if (bmi <
|
|
19
|
+
if (bmi < 19.5) {
|
|
13
20
|
build = "slim";
|
|
14
21
|
}
|
|
15
|
-
else if (bmi >=
|
|
16
|
-
build = "
|
|
22
|
+
else if (bmi >= 30) {
|
|
23
|
+
build = "heavy";
|
|
24
|
+
}
|
|
25
|
+
else if (bmi >= 25) {
|
|
26
|
+
build = "stocky";
|
|
27
|
+
}
|
|
28
|
+
let hair = "dark";
|
|
29
|
+
if (profile.age >= 70) {
|
|
30
|
+
hair = "white";
|
|
31
|
+
}
|
|
32
|
+
else if (profile.age >= 55) {
|
|
33
|
+
hair = "grey";
|
|
17
34
|
}
|
|
18
35
|
return {
|
|
19
|
-
|
|
36
|
+
height: Math.min(Math.max(Math.round(20 + (profile.height - 145) / 10), 19), 27),
|
|
20
37
|
build,
|
|
21
38
|
gender: profile.gender,
|
|
39
|
+
style: profile.workStyle,
|
|
40
|
+
hair,
|
|
22
41
|
goal: profile.goal,
|
|
23
42
|
};
|
|
24
43
|
}
|
|
25
|
-
export const noBadges = { meal: false, workout: false, focus: false };
|
|
26
44
|
// Each equipment slot upgrades once from level 2 and again 4 levels later,
|
|
27
45
|
// staggered so every level from 2 to 9 changes exactly 1 piece.
|
|
28
46
|
export const gearOf = (level) => {
|
|
29
47
|
const tier = (slot) => Math.min(Math.max(Math.floor((level - 2 - slot) / 4) + 1, 0), 2);
|
|
30
|
-
return { gadget: tier(0), wear: tier(1), headwear: tier(2),
|
|
48
|
+
return { gadget: tier(0), wear: tier(1), headwear: tier(2), carry: tier(3) };
|
|
31
49
|
};
|
|
32
|
-
// The
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const height = 9 + look.size + growth * 2 + squash;
|
|
39
|
-
return { width, height, top: height + 1 };
|
|
50
|
+
// The feet stand on row 1, in the middle of their shadow.
|
|
51
|
+
const ground = 1;
|
|
52
|
+
const halfWidths = {
|
|
53
|
+
female: [3, 2, 3],
|
|
54
|
+
male: [4, 3, 3],
|
|
55
|
+
other: [3, 3, 3],
|
|
40
56
|
};
|
|
57
|
+
// A head of 5 rows and a neck take 6 of the height; legs take 56% of the rest.
|
|
58
|
+
function bodyOf(look) {
|
|
59
|
+
const span = look.height - 6;
|
|
60
|
+
const legs = Math.round(span * 0.56);
|
|
61
|
+
const torso = span - legs;
|
|
62
|
+
let [chest, waist, hips] = halfWidths[look.gender];
|
|
63
|
+
switch (look.build) {
|
|
64
|
+
case "slim": {
|
|
65
|
+
chest -= look.gender === "male" ? 1 : 0;
|
|
66
|
+
waist = Math.max(waist - 1, 2);
|
|
67
|
+
hips = Math.max(hips - 1, 2);
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
case "stocky": {
|
|
71
|
+
waist += 1;
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
case "heavy": {
|
|
75
|
+
chest += 1;
|
|
76
|
+
waist += 2;
|
|
77
|
+
hips += 1;
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
case "average": {
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (look.goal === "buildMuscle") {
|
|
85
|
+
chest += 1;
|
|
86
|
+
}
|
|
87
|
+
const hip = ground + legs;
|
|
88
|
+
const shoulder = hip + torso - 1;
|
|
89
|
+
const chin = shoulder + 2;
|
|
90
|
+
return {
|
|
91
|
+
hip,
|
|
92
|
+
shoulder,
|
|
93
|
+
chin,
|
|
94
|
+
top: chin + 4,
|
|
95
|
+
legs,
|
|
96
|
+
chest,
|
|
97
|
+
waist,
|
|
98
|
+
hips,
|
|
99
|
+
arm: look.build === "heavy" || look.goal === "buildMuscle" ? 2 : 1,
|
|
100
|
+
reach: torso + 1,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
41
103
|
const span = (put, from, to, y, key) => {
|
|
42
104
|
for (let x = from; x <= to; x++) {
|
|
43
105
|
put(x, y, key);
|
|
44
106
|
}
|
|
45
107
|
};
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
108
|
+
const pointsOf = ([x0, y0], [x1, y1]) => {
|
|
109
|
+
const points = [];
|
|
110
|
+
const steps = Math.max(Math.abs(x1 - x0), Math.abs(y1 - y0), 1);
|
|
111
|
+
for (let step = 0; step <= steps; step++) {
|
|
112
|
+
points.push([
|
|
113
|
+
Math.round(x0 + ((x1 - x0) * step) / steps),
|
|
114
|
+
Math.round(y0 + ((y1 - y0) * step) / steps),
|
|
115
|
+
]);
|
|
116
|
+
}
|
|
117
|
+
return points;
|
|
118
|
+
};
|
|
119
|
+
const hanging = (b, side, swing = 0) => [
|
|
120
|
+
side * (b.chest + 1),
|
|
121
|
+
b.shoulder - b.reach + swing,
|
|
122
|
+
];
|
|
123
|
+
// A standing desk at the waist, with the laptop's lid facing out.
|
|
124
|
+
const desk = (put, _upper, b) => {
|
|
125
|
+
const top = b.hip + 1;
|
|
126
|
+
span(put, -8, 8, top, "L");
|
|
127
|
+
for (let y = 0; y < top; y++) {
|
|
128
|
+
put(-7, y, "x");
|
|
129
|
+
put(7, y, "x");
|
|
130
|
+
}
|
|
131
|
+
for (let y = top + 1; y <= top + 3; y++) {
|
|
132
|
+
span(put, -2, 2, y, "d");
|
|
133
|
+
}
|
|
134
|
+
put(0, top + 2, "l");
|
|
135
|
+
};
|
|
136
|
+
const cup = ([x, y], steam) => (_put, upper) => {
|
|
137
|
+
span(upper, x, x + 1, y, "Q");
|
|
138
|
+
span(upper, x, x + 1, y + 1, "Q");
|
|
139
|
+
upper(x, y + 1, "o");
|
|
140
|
+
if (steam !== undefined) {
|
|
141
|
+
upper(x + (steam % 2), y + 3, "v");
|
|
142
|
+
upper(x + 1 - (steam % 2), y + 4, "v");
|
|
51
143
|
}
|
|
52
144
|
};
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
145
|
+
const apple = ([x, y], isBitten) => (_put, upper) => {
|
|
146
|
+
span(upper, x, x + 1, y, "F");
|
|
147
|
+
upper(x, y + 1, "F");
|
|
148
|
+
upper(x + 1, y + 1, isBitten ? "." : "F");
|
|
149
|
+
upper(x + 1, y + 2, "G");
|
|
150
|
+
};
|
|
151
|
+
const barbell = (y) => (_put, upper, b) => {
|
|
152
|
+
const end = b.chest + 6;
|
|
153
|
+
span(upper, -end, end, y, "d");
|
|
154
|
+
for (const x of [-end, -end + 1, end - 1, end]) {
|
|
155
|
+
span(upper, x, x, y - 1, "D");
|
|
156
|
+
upper(x, y, "D");
|
|
157
|
+
span(upper, x, x, y + 1, "D");
|
|
56
158
|
}
|
|
57
159
|
};
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
put(width + 7, y, "x");
|
|
160
|
+
const sparkles = (points) => (put) => {
|
|
161
|
+
for (const [x, y] of points) {
|
|
162
|
+
put(x, y, "y");
|
|
62
163
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
164
|
+
};
|
|
165
|
+
const sleepyZ = (at) => (put, _upper, b) => {
|
|
166
|
+
const rows = ["ZZZZ", "..Z.", ".Z..", "ZZZZ"];
|
|
167
|
+
for (const [offset, row] of rows.entries()) {
|
|
168
|
+
for (const [index, key] of [...row].entries()) {
|
|
169
|
+
put(3 + (at % 2) + index, b.top + 4 + at - offset, key);
|
|
170
|
+
}
|
|
67
171
|
}
|
|
68
172
|
};
|
|
69
|
-
|
|
70
|
-
|
|
173
|
+
// Standing in place between walks: breathing, blinking, looking about, and
|
|
174
|
+
// waving when every target is met.
|
|
175
|
+
const resting = (t, mood) => (b) => {
|
|
176
|
+
const isWaving = mood === "happy" && t >= 3 && t <= 5;
|
|
177
|
+
let turn = 0;
|
|
178
|
+
if (t === 2 || t === 3) {
|
|
179
|
+
turn = -1;
|
|
180
|
+
}
|
|
181
|
+
else if (t === 4) {
|
|
182
|
+
turn = 1;
|
|
183
|
+
}
|
|
184
|
+
return {
|
|
185
|
+
mouth: mood === "happy" ? "smile" : undefined,
|
|
186
|
+
bob: Math.floor(t / 2) % 2,
|
|
187
|
+
eyes: t === 6 ? "closed" : "open",
|
|
188
|
+
turn,
|
|
189
|
+
lift: mood === "happy" && t === 1 ? 1 : 0,
|
|
190
|
+
hands: isWaving
|
|
191
|
+
? [undefined, [b.chest + 3 + (t % 2), b.top - 1]]
|
|
192
|
+
: undefined,
|
|
193
|
+
};
|
|
71
194
|
};
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
195
|
+
const walking = (step, mood) => (b) => ({
|
|
196
|
+
mouth: mood === "happy" ? "smile" : undefined,
|
|
197
|
+
turn: 1,
|
|
198
|
+
step: [0, 1, 0, 3][step % 4],
|
|
199
|
+
bob: step % 2 === 0 ? 1 : 0,
|
|
200
|
+
hands: [
|
|
201
|
+
hanging(b, -1, step % 4 === 3 ? 1 : 0),
|
|
202
|
+
hanging(b, 1, step % 4 === 1 ? 1 : 0),
|
|
203
|
+
],
|
|
204
|
+
});
|
|
205
|
+
// Where the character stands and what it does on every tick of `counter`.
|
|
206
|
+
function sceneOf(activity, counter, width, mood) {
|
|
207
|
+
const home = Math.floor(width / 2);
|
|
208
|
+
const minimum = 8;
|
|
209
|
+
const maximum = Math.max(width - 9, minimum + 1);
|
|
77
210
|
const travel = maximum - minimum;
|
|
78
211
|
const tick = counter % 4;
|
|
212
|
+
const mouth = mood === "happy" ? "smile" : undefined;
|
|
79
213
|
if (activity === "idle" && mood === "sleepy") {
|
|
80
|
-
//
|
|
214
|
+
// Dozing on its feet: nodding, yawning, with Zs drifting up.
|
|
215
|
+
const at = counter % 6;
|
|
216
|
+
const isYawning = at === 4;
|
|
81
217
|
return {
|
|
82
|
-
x:
|
|
218
|
+
x: home,
|
|
83
219
|
facing: 1,
|
|
84
|
-
pose:
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
],
|
|
220
|
+
pose: (b) => ({
|
|
221
|
+
eyes: "closed",
|
|
222
|
+
bob: at % 3 === 0 ? 1 : 0,
|
|
223
|
+
mouth: isYawning ? "open" : undefined,
|
|
224
|
+
hands: isYawning ? [undefined, [2, b.chin]] : undefined,
|
|
225
|
+
}),
|
|
226
|
+
props: [sleepyZ(at)],
|
|
91
227
|
};
|
|
92
228
|
}
|
|
93
229
|
if (activity === "idle") {
|
|
94
230
|
// Wander: pause, walk across, pause and look about, walk back.
|
|
95
|
-
const pause =
|
|
96
|
-
const
|
|
97
|
-
const at = counter % cycle;
|
|
98
|
-
const isHappy = mood === "happy";
|
|
99
|
-
const walk = (step) => ({
|
|
100
|
-
step: step % 2 === 0 ? 1 : 2,
|
|
101
|
-
look: 1,
|
|
102
|
-
lift: isHappy && step % 2 === 1 ? 1 : 0,
|
|
103
|
-
eyes: isHappy ? "happy" : "open",
|
|
104
|
-
});
|
|
105
|
-
const rest = (moment) => ({
|
|
106
|
-
squash: Math.floor(moment / 2) % 2,
|
|
107
|
-
eyes: isHappy ? "happy" : moment === 4 ? "closed" : "open",
|
|
108
|
-
arms: isHappy && moment % 2 === 0 ? ["up", "up"] : undefined,
|
|
109
|
-
});
|
|
231
|
+
const pause = 8;
|
|
232
|
+
const at = counter % (2 * (pause + travel));
|
|
110
233
|
if (at < pause) {
|
|
111
|
-
return {
|
|
112
|
-
x: minimum,
|
|
113
|
-
facing: at === 2 || at === 3 ? -1 : 1,
|
|
114
|
-
pose: rest(at),
|
|
115
|
-
};
|
|
234
|
+
return { x: minimum, facing: 1, pose: resting(at, mood) };
|
|
116
235
|
}
|
|
117
236
|
if (at < pause + travel) {
|
|
118
|
-
|
|
237
|
+
const step = at - pause;
|
|
238
|
+
return { x: minimum + step, facing: 1, pose: walking(step, mood) };
|
|
119
239
|
}
|
|
120
240
|
if (at < 2 * pause + travel) {
|
|
121
|
-
const moment = at - pause - travel;
|
|
122
241
|
return {
|
|
123
242
|
x: maximum,
|
|
124
|
-
facing:
|
|
125
|
-
pose:
|
|
243
|
+
facing: -1,
|
|
244
|
+
pose: resting(at - pause - travel, mood),
|
|
126
245
|
};
|
|
127
246
|
}
|
|
128
247
|
const step = at - 2 * pause - travel;
|
|
129
|
-
return { x: maximum - step, facing: -1, pose:
|
|
248
|
+
return { x: maximum - step, facing: -1, pose: walking(step, mood) };
|
|
130
249
|
}
|
|
131
250
|
switch (activity) {
|
|
132
251
|
case "run": {
|
|
252
|
+
// Laps across the stage, 2 pixels a tick, with the arms bent.
|
|
133
253
|
const lap = Math.max(Math.ceil(travel / 2), 1);
|
|
134
254
|
const at = counter % (2 * lap);
|
|
135
255
|
const forward = at < lap;
|
|
@@ -139,125 +259,166 @@ function sceneOf(activity, counter, width, shape, mood) {
|
|
|
139
259
|
? Math.min(minimum + step * 2, maximum)
|
|
140
260
|
: Math.max(maximum - step * 2, minimum),
|
|
141
261
|
facing: forward ? 1 : -1,
|
|
142
|
-
pose: {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
262
|
+
pose: (b) => ({
|
|
263
|
+
mouth,
|
|
264
|
+
turn: 1,
|
|
265
|
+
step: counter % 2 === 0 ? 1 : 3,
|
|
266
|
+
bob: counter % 2,
|
|
267
|
+
hands: [
|
|
268
|
+
[-(b.chest + 2), b.hip + 2 + (counter % 2)],
|
|
269
|
+
[b.chest + 2, b.hip + 3 - (counter % 2)],
|
|
270
|
+
],
|
|
271
|
+
}),
|
|
272
|
+
props: [
|
|
273
|
+
(put, _upper, b) => {
|
|
274
|
+
put(-(b.chest + 4), b.top - 1 - (counter % 2), "v");
|
|
275
|
+
put(-(b.chest + 6), b.hip + (counter % 3), "v");
|
|
276
|
+
},
|
|
277
|
+
],
|
|
149
278
|
};
|
|
150
279
|
}
|
|
151
280
|
case "focus": {
|
|
281
|
+
// Typing at a standing desk, the laptop's glow on the face.
|
|
152
282
|
return {
|
|
153
283
|
x: home,
|
|
154
284
|
facing: 1,
|
|
155
|
-
pose: {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
285
|
+
pose: (b) => ({
|
|
286
|
+
mouth,
|
|
287
|
+
bob: tick === 3 ? 1 : 0,
|
|
288
|
+
hands: [
|
|
289
|
+
[-3, b.hip + 2 + (tick % 2)],
|
|
290
|
+
[3, b.hip + 3 - (tick % 2)],
|
|
291
|
+
],
|
|
292
|
+
}),
|
|
160
293
|
props: [
|
|
161
|
-
|
|
162
|
-
(put,
|
|
163
|
-
put(
|
|
164
|
-
put(body + 7 - (tick % 2), 9 + ((tick + 2) % 4), "y");
|
|
294
|
+
desk,
|
|
295
|
+
(put, _upper, b) => {
|
|
296
|
+
put(-3 + tick, b.top + 2 + (tick % 2), "y");
|
|
165
297
|
},
|
|
166
298
|
],
|
|
167
299
|
};
|
|
168
300
|
}
|
|
169
301
|
case "break": {
|
|
170
|
-
|
|
171
|
-
const
|
|
302
|
+
// A coffee, sipped every few ticks.
|
|
303
|
+
const at = counter % 6;
|
|
304
|
+
const isSipping = at >= 4;
|
|
305
|
+
const hand = (b) => isSipping ? [2, b.chin] : [b.chest + 2, b.hip + 2];
|
|
172
306
|
return {
|
|
173
307
|
x: home,
|
|
174
308
|
facing: 1,
|
|
175
|
-
pose: {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
eyes:
|
|
179
|
-
|
|
180
|
-
},
|
|
309
|
+
pose: (b) => ({
|
|
310
|
+
mouth,
|
|
311
|
+
bob: at % 2,
|
|
312
|
+
eyes: isSipping ? "closed" : "open",
|
|
313
|
+
hands: [undefined, hand(b)],
|
|
314
|
+
}),
|
|
181
315
|
props: [
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
: [
|
|
186
|
-
held(step % 2 === 0 ? [".v", "v."] : ["v.", ".v"], (shape) => Math.ceil(shape.height / 2) + 5),
|
|
187
|
-
]),
|
|
316
|
+
(put, upper, b) => {
|
|
317
|
+
cup(hand(b), isSipping ? undefined : at)(put, upper, b);
|
|
318
|
+
},
|
|
188
319
|
],
|
|
189
320
|
};
|
|
190
321
|
}
|
|
191
322
|
case "eat": {
|
|
192
|
-
const
|
|
323
|
+
const isBiting = tick === 1 || tick === 2;
|
|
324
|
+
const hand = (b) => isBiting ? [2, b.chin] : [b.chest + 2, b.hip + 3];
|
|
193
325
|
return {
|
|
194
326
|
x: home,
|
|
195
327
|
facing: 1,
|
|
196
|
-
pose: {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
},
|
|
328
|
+
pose: (b) => ({
|
|
329
|
+
bob: tick === 2 ? 1 : 0,
|
|
330
|
+
mouth: isBiting ? "open" : mouth,
|
|
331
|
+
hands: [undefined, hand(b)],
|
|
332
|
+
}),
|
|
202
333
|
props: [
|
|
203
|
-
|
|
334
|
+
(put, upper, b) => {
|
|
335
|
+
const [x, y] = hand(b);
|
|
336
|
+
apple([x, y - 1], tick === 3)(put, upper, b);
|
|
337
|
+
},
|
|
204
338
|
],
|
|
205
339
|
};
|
|
206
340
|
}
|
|
207
341
|
case "lift": {
|
|
208
|
-
|
|
342
|
+
// An overhead press: rack, press, lock out, lower.
|
|
343
|
+
const height = (b) => [b.shoulder, b.shoulder + 3, b.top + 1, b.shoulder + 3][tick] ??
|
|
344
|
+
b.shoulder;
|
|
209
345
|
return {
|
|
210
346
|
x: home,
|
|
211
347
|
facing: 1,
|
|
212
|
-
pose
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
348
|
+
pose(b) {
|
|
349
|
+
const y = height(b);
|
|
350
|
+
const x = tick === 2 ? b.chest + 1 : b.chest + 2;
|
|
351
|
+
return {
|
|
352
|
+
mouth,
|
|
353
|
+
bob: tick === 0 ? 1 : 0,
|
|
354
|
+
hands: [
|
|
355
|
+
[-x, y],
|
|
356
|
+
[x, y],
|
|
357
|
+
],
|
|
358
|
+
};
|
|
216
359
|
},
|
|
217
360
|
props: [
|
|
218
|
-
(put,
|
|
219
|
-
|
|
220
|
-
span(put, -3, shape.width + 2, y, "d");
|
|
221
|
-
icon(["DD", "DD", "DD"], -4, y + 1)(put, shape, 0);
|
|
222
|
-
icon(["DD", "DD", "DD"], shape.width + 2, y + 1)(put, shape, 0);
|
|
361
|
+
(put, upper, b) => {
|
|
362
|
+
barbell(height(b))(put, upper, b);
|
|
223
363
|
},
|
|
224
364
|
],
|
|
225
365
|
};
|
|
226
366
|
}
|
|
227
367
|
case "stretch": {
|
|
368
|
+
// Reach up, rise on the toes, open the arms wide, and come down.
|
|
228
369
|
return {
|
|
229
370
|
x: home,
|
|
230
371
|
facing: 1,
|
|
231
|
-
pose
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
372
|
+
pose(b) {
|
|
373
|
+
const reaches = [
|
|
374
|
+
[1, b.top + 2],
|
|
375
|
+
[1, b.top + 3],
|
|
376
|
+
[b.chest + 6, b.shoulder],
|
|
377
|
+
[b.chest + 4, b.shoulder + 4],
|
|
378
|
+
];
|
|
379
|
+
const [x, y] = reaches[tick] ?? [1, b.top + 2];
|
|
380
|
+
return {
|
|
381
|
+
mouth,
|
|
382
|
+
bob: tick === 1 ? 0 : 1,
|
|
383
|
+
lift: tick === 1 ? 1 : 0,
|
|
384
|
+
eyes: tick === 1 ? "closed" : "open",
|
|
385
|
+
hands: [
|
|
386
|
+
[-x, y],
|
|
387
|
+
[x, y],
|
|
388
|
+
],
|
|
389
|
+
};
|
|
235
390
|
},
|
|
236
391
|
};
|
|
237
392
|
}
|
|
238
393
|
case "levelUp": {
|
|
394
|
+
const lift = [0, 2, 3, 1][tick] ?? 0;
|
|
239
395
|
return {
|
|
240
396
|
x: home,
|
|
241
397
|
facing: 1,
|
|
242
|
-
pose: {
|
|
243
|
-
|
|
244
|
-
lift
|
|
245
|
-
|
|
246
|
-
|
|
398
|
+
pose: (b) => ({
|
|
399
|
+
mouth: "smile",
|
|
400
|
+
lift,
|
|
401
|
+
hands: [
|
|
402
|
+
[-(b.chest + 3), b.top],
|
|
403
|
+
[b.chest + 3, b.top],
|
|
404
|
+
],
|
|
405
|
+
}),
|
|
247
406
|
props: [
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
[
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
[
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
407
|
+
(put, upper, b) => {
|
|
408
|
+
sparkles(tick % 2 === 0
|
|
409
|
+
? [
|
|
410
|
+
[-8, b.top],
|
|
411
|
+
[8, b.top + 2],
|
|
412
|
+
[-9, b.shoulder],
|
|
413
|
+
[9, b.shoulder - 2],
|
|
414
|
+
]
|
|
415
|
+
: [
|
|
416
|
+
[-9, b.top + 2],
|
|
417
|
+
[9, b.top],
|
|
418
|
+
[-8, b.shoulder - 2],
|
|
419
|
+
[8, b.shoulder],
|
|
420
|
+
])(put, upper, b);
|
|
421
|
+
},
|
|
261
422
|
],
|
|
262
423
|
};
|
|
263
424
|
}
|
|
@@ -269,226 +430,361 @@ export const intervalOf = (activity) => ({
|
|
|
269
430
|
break: 380,
|
|
270
431
|
eat: 300,
|
|
271
432
|
run: 110,
|
|
272
|
-
lift:
|
|
433
|
+
lift: 420,
|
|
273
434
|
stretch: 480,
|
|
274
435
|
levelUp: 160,
|
|
275
436
|
})[activity];
|
|
276
437
|
// How many ticks one loop of an activity takes on a stage of `width`.
|
|
277
438
|
export function cycleOf(activity, width) {
|
|
278
|
-
const travel = Math.max(width -
|
|
439
|
+
const travel = Math.max(width - 17, 1);
|
|
279
440
|
if (activity === "idle") {
|
|
280
|
-
return 2 * (
|
|
441
|
+
return 2 * (8 + travel);
|
|
281
442
|
}
|
|
282
443
|
if (activity === "run") {
|
|
283
444
|
return 2 * Math.max(Math.ceil(travel / 2), 1);
|
|
284
445
|
}
|
|
285
446
|
return activity === "break" ? 6 : 4;
|
|
286
447
|
}
|
|
287
|
-
//
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
448
|
+
// The tops by work style: a hoodie, a shirt under an apron, a tee, a tank.
|
|
449
|
+
const topOf = {
|
|
450
|
+
desk: ["C", "c"],
|
|
451
|
+
standing: ["Q", "U"],
|
|
452
|
+
active: ["C", "c"],
|
|
453
|
+
athlete: ["S", "z"],
|
|
454
|
+
};
|
|
455
|
+
// How much of the arm the sleeve covers, from the shoulder down.
|
|
456
|
+
const sleeveOf = {
|
|
457
|
+
desk: 1,
|
|
458
|
+
standing: 0.5,
|
|
459
|
+
active: 0.3,
|
|
460
|
+
athlete: 0,
|
|
461
|
+
};
|
|
462
|
+
// Jeans and sneakers, chinos and shoes, cargo pants and boots, or shorts
|
|
463
|
+
// and running shoes.
|
|
464
|
+
const bottomOf = {
|
|
465
|
+
desk: { pants: "P", shade: "p", shoe: "w", shoeRows: 1 },
|
|
466
|
+
standing: { pants: "N", shade: "n", shoe: "b", shoeRows: 1 },
|
|
467
|
+
active: { pants: "O", shade: "V", shoe: "B", shoeRows: 2 },
|
|
468
|
+
athlete: { pants: "x", shade: "S", shoe: "S", shoeRows: 1 },
|
|
469
|
+
};
|
|
470
|
+
const hairKeys = { dark: "h", grey: "H", white: "I" };
|
|
471
|
+
// An oval on the ground with the feet in its middle, shrinking in the air.
|
|
472
|
+
function paintShadow(put, b, lift) {
|
|
473
|
+
const half = b.hips + 4 - Math.min(lift, 2);
|
|
474
|
+
span(put, -half + 2, half - 2, ground - 1, "A");
|
|
475
|
+
span(put, -half, half, ground, "A");
|
|
476
|
+
span(put, -half + 2, half - 2, ground + 1, "A");
|
|
477
|
+
}
|
|
478
|
+
// Long hair falls behind the shoulders, so it goes on before the body.
|
|
479
|
+
function paintHairBehind({ upper, b, look, counter }) {
|
|
480
|
+
const hair = hairKeys[look.hair];
|
|
481
|
+
if (look.gender !== "female") {
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
if (look.style === "athlete") {
|
|
485
|
+
// A ponytail that swings.
|
|
486
|
+
const sway = counter % 4 < 2 ? 0 : 1;
|
|
487
|
+
upper(3, b.chin + 3, hair);
|
|
488
|
+
upper(4, b.chin + 2, hair);
|
|
489
|
+
upper(4 + sway, b.chin + 1, hair);
|
|
490
|
+
upper(4 + sway, b.chin, hair);
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
for (let y = b.shoulder - 2; y <= b.chin + 3; y++) {
|
|
494
|
+
upper(-3, y, hair);
|
|
495
|
+
upper(3, y, hair);
|
|
496
|
+
}
|
|
497
|
+
for (let y = b.shoulder - 1; y < b.chin; y++) {
|
|
498
|
+
span(upper, -2, 2, y, hair);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
function paintLegs({ put, b, look, pose }) {
|
|
502
|
+
const { pants, shade, shoe, shoeRows } = bottomOf[look.style];
|
|
503
|
+
const thigh = b.hips;
|
|
504
|
+
const calf = Math.max(thigh - 1, 2);
|
|
505
|
+
const knee = ground + Math.floor(b.legs / 2);
|
|
506
|
+
const shorts = ground + Math.round(b.legs * 0.55);
|
|
507
|
+
for (const side of [-1, 1]) {
|
|
508
|
+
const isLifted = (side === -1 && pose.step === 1) || (side === 1 && pose.step === 3);
|
|
509
|
+
const sole = ground + (isLifted ? 1 : 0);
|
|
510
|
+
for (let y = sole; y < b.hip; y++) {
|
|
511
|
+
const width = y >= knee ? thigh : calf;
|
|
512
|
+
let key = pants;
|
|
513
|
+
if (y < sole + shoeRows) {
|
|
514
|
+
key = shoe;
|
|
515
|
+
}
|
|
516
|
+
else if (look.style === "athlete" && y < shorts) {
|
|
517
|
+
key = "s";
|
|
518
|
+
}
|
|
519
|
+
for (let x = 1; x <= width; x++) {
|
|
520
|
+
put(side * x, y, key);
|
|
521
|
+
}
|
|
522
|
+
if (key === pants) {
|
|
523
|
+
put(side * width, y, shade);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
// Shoes stick out a little past the ankle.
|
|
527
|
+
put(side * (calf + 1), sole, shoe);
|
|
528
|
+
if (look.style === "active") {
|
|
529
|
+
put(side * thigh, knee + 1, "V");
|
|
530
|
+
put(side * thigh, knee + 2, "V");
|
|
293
531
|
}
|
|
294
532
|
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
533
|
+
// The thighs meet at the crotch, and all the way down on a heavy build.
|
|
534
|
+
let crotch = b.hip - 1;
|
|
535
|
+
if (look.build === "heavy") {
|
|
536
|
+
crotch = look.style === "athlete" ? shorts : knee;
|
|
537
|
+
}
|
|
538
|
+
for (let y = crotch; y < b.hip; y++) {
|
|
539
|
+
put(0, y, pants);
|
|
300
540
|
}
|
|
301
541
|
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
const
|
|
305
|
-
const
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
542
|
+
function paintTorso({ upper, b, look, gear }) {
|
|
543
|
+
const [main, shade] = topOf[look.style];
|
|
544
|
+
const torso = b.shoulder - b.hip + 1;
|
|
545
|
+
const chestRows = Math.ceil(torso * 0.45);
|
|
546
|
+
const halfAt = (y) => {
|
|
547
|
+
if (b.shoulder - y < chestRows) {
|
|
548
|
+
return b.chest;
|
|
549
|
+
}
|
|
550
|
+
return y - b.hip < 2 ? Math.max(b.hips, b.waist) : b.waist;
|
|
551
|
+
};
|
|
552
|
+
for (let y = b.hip; y <= b.shoulder; y++) {
|
|
553
|
+
const half = halfAt(y);
|
|
554
|
+
span(upper, -half, half, y, main);
|
|
555
|
+
upper(-half, y, shade);
|
|
556
|
+
upper(half, y, shade);
|
|
557
|
+
}
|
|
558
|
+
span(upper, -1, 1, b.shoulder + 1, "s");
|
|
559
|
+
switch (look.style) {
|
|
560
|
+
case "desk": {
|
|
561
|
+
// A hoodie: the hood behind the neck, drawstrings, a front pocket.
|
|
562
|
+
upper(-2, b.shoulder + 1, shade);
|
|
563
|
+
upper(2, b.shoulder + 1, shade);
|
|
564
|
+
upper(-1, b.shoulder - 1, "w");
|
|
565
|
+
upper(1, b.shoulder - 1, "w");
|
|
566
|
+
span(upper, -2, 2, b.hip + 1, shade);
|
|
567
|
+
break;
|
|
568
|
+
}
|
|
569
|
+
case "standing": {
|
|
570
|
+
// A collared shirt under an apron that hangs over the thighs.
|
|
571
|
+
upper(-1, b.shoulder, "w");
|
|
572
|
+
upper(1, b.shoulder, "w");
|
|
573
|
+
upper(0, b.shoulder, "s");
|
|
574
|
+
for (const x of [-2, 2]) {
|
|
575
|
+
upper(x, b.shoulder, "S");
|
|
576
|
+
upper(x, b.shoulder - 1, "S");
|
|
577
|
+
}
|
|
578
|
+
for (let y = b.hip - 2; y <= b.shoulder - 2; y++) {
|
|
579
|
+
span(upper, -b.waist, b.waist, y, "S");
|
|
580
|
+
}
|
|
581
|
+
span(upper, -1, 1, b.hip + 1, "z");
|
|
582
|
+
break;
|
|
583
|
+
}
|
|
584
|
+
case "active": {
|
|
585
|
+
upper(0, b.shoulder, shade);
|
|
586
|
+
break;
|
|
587
|
+
}
|
|
588
|
+
case "athlete": {
|
|
589
|
+
upper(0, b.shoulder, "s");
|
|
590
|
+
span(upper, -b.chest, b.chest, b.shoulder - 2, shade);
|
|
591
|
+
break;
|
|
311
592
|
}
|
|
312
|
-
put(x, 1, "o");
|
|
313
593
|
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
594
|
+
if (gear.wear === 2) {
|
|
595
|
+
// An open jacket over whatever is underneath.
|
|
596
|
+
for (let y = b.hip; y <= b.shoulder; y++) {
|
|
597
|
+
const half = halfAt(y);
|
|
598
|
+
span(upper, -half, -half + 1, y, "X");
|
|
599
|
+
span(upper, half - 1, half, y, "X");
|
|
600
|
+
}
|
|
320
601
|
}
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
602
|
+
if (gear.carry >= 1) {
|
|
603
|
+
// Backpack straps over both shoulders.
|
|
604
|
+
for (let y = b.shoulder - 3; y <= b.shoulder; y++) {
|
|
605
|
+
upper(-(b.chest - 1), y, "B");
|
|
606
|
+
upper(b.chest - 1, y, "B");
|
|
607
|
+
}
|
|
324
608
|
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
609
|
+
if (gear.wear >= 1) {
|
|
610
|
+
// A scarf around the neck, its end hanging down the chest.
|
|
611
|
+
span(upper, -2, 2, b.shoulder + 1, "S");
|
|
612
|
+
upper(1, b.shoulder, "z");
|
|
613
|
+
upper(1, b.shoulder - 1, "z");
|
|
328
614
|
}
|
|
329
|
-
if (gear.
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
615
|
+
if (gear.carry === 2) {
|
|
616
|
+
// A gold medal on its ribbon.
|
|
617
|
+
upper(-1, b.shoulder, "z");
|
|
618
|
+
upper(1, b.shoulder, "z");
|
|
619
|
+
upper(0, b.shoulder - 1, "z");
|
|
620
|
+
upper(0, b.shoulder - 2, "g");
|
|
333
621
|
}
|
|
334
622
|
}
|
|
335
|
-
function
|
|
336
|
-
const
|
|
337
|
-
const
|
|
338
|
-
const
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
623
|
+
function paintArms({ upper, b, look, gear, pose }) {
|
|
624
|
+
const isJacket = gear.wear === 2;
|
|
625
|
+
const [main, shade] = isJacket ? ["X", "X"] : topOf[look.style];
|
|
626
|
+
const sleeve = isJacket ? 1 : sleeveOf[look.style];
|
|
627
|
+
for (const [index, side] of [-1, 1].entries()) {
|
|
628
|
+
const joint = [side * (b.chest + 1), b.shoulder];
|
|
629
|
+
const hand = pose.hands?.[index] ?? hanging(b, side);
|
|
630
|
+
const points = pointsOf(joint, hand);
|
|
631
|
+
const covered = Math.round((points.length - 1) * sleeve);
|
|
632
|
+
const isLevel = Math.abs(hand[0] - joint[0]) > Math.abs(hand[1] - joint[1]);
|
|
633
|
+
const [thickX, thickY] = isLevel ? [0, -1] : [side, 0];
|
|
634
|
+
for (const [step, [x, y]] of points.entries()) {
|
|
635
|
+
const isHand = step === points.length - 1;
|
|
636
|
+
const isSleeve = !isHand && (step < covered || (step === 0 && sleeve > 0));
|
|
637
|
+
upper(x, y, isSleeve ? main : "s");
|
|
638
|
+
if (b.arm === 2 && step < points.length - 2) {
|
|
639
|
+
upper(x + thickX, y + thickY, isSleeve ? shade : "q");
|
|
640
|
+
}
|
|
351
641
|
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
642
|
+
const wrist = points.at(-2);
|
|
643
|
+
if (side === 1 && gear.gadget === 2 && wrist && points.length > 2) {
|
|
644
|
+
upper(wrist[0], wrist[1], "t");
|
|
355
645
|
}
|
|
356
646
|
}
|
|
357
647
|
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
const
|
|
361
|
-
const
|
|
362
|
-
const
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
const
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
if (look.gender === "
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
648
|
+
function paintHead({ upper, b, look, gear, pose }) {
|
|
649
|
+
const { chin } = b;
|
|
650
|
+
const hair = hairKeys[look.hair];
|
|
651
|
+
const turn = pose.turn ?? 0;
|
|
652
|
+
const jaw = look.build === "heavy" ? 2 : 1;
|
|
653
|
+
span(upper, -jaw, jaw, chin, "s");
|
|
654
|
+
span(upper, -2, 2, chin + 1, "s");
|
|
655
|
+
span(upper, -2, 2, chin + 2, "s");
|
|
656
|
+
span(upper, -2, 2, chin + 3, hair);
|
|
657
|
+
span(upper, -1, 1, chin + 4, hair);
|
|
658
|
+
const eye = pose.eyes === "closed" ? "q" : "e";
|
|
659
|
+
upper(turn - 1, chin + 1, eye);
|
|
660
|
+
upper(turn + 1, chin + 1, eye);
|
|
661
|
+
let mouth = "q";
|
|
662
|
+
if (pose.mouth === "smile") {
|
|
663
|
+
mouth = "m";
|
|
664
|
+
}
|
|
665
|
+
else if (pose.mouth === "open") {
|
|
666
|
+
mouth = "e";
|
|
667
|
+
}
|
|
668
|
+
upper(turn, chin, mouth);
|
|
669
|
+
// Short hair with sideburns, a side-swept fringe, or a chin-length bob.
|
|
670
|
+
if (look.gender === "male") {
|
|
671
|
+
upper(-2, chin + 2, hair);
|
|
672
|
+
upper(2, chin + 2, hair);
|
|
673
|
+
}
|
|
674
|
+
else if (look.gender === "female") {
|
|
675
|
+
upper(-2, chin + 2, hair);
|
|
676
|
+
upper(-1, chin + 2, hair);
|
|
677
|
+
upper(2, chin + 2, hair);
|
|
387
678
|
}
|
|
388
679
|
else {
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
680
|
+
for (const x of [-3, 3]) {
|
|
681
|
+
for (let y = chin; y <= chin + 3; y++) {
|
|
682
|
+
upper(x, y, hair);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
upper(-2, chin + 2, hair);
|
|
686
|
+
upper(2, chin + 2, hair);
|
|
392
687
|
}
|
|
393
688
|
if (gear.gadget >= 1) {
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
689
|
+
// Headphones: a band over the top and a cup on each ear.
|
|
690
|
+
upper(-2, chin + 4, "k");
|
|
691
|
+
upper(2, chin + 4, "k");
|
|
692
|
+
span(upper, -1, 1, chin + 5, "k");
|
|
693
|
+
for (const x of [-3, 3]) {
|
|
694
|
+
upper(x, chin + 1, "k");
|
|
695
|
+
upper(x, chin + 2, "k");
|
|
696
|
+
}
|
|
401
697
|
}
|
|
402
698
|
if (gear.headwear === 1) {
|
|
403
|
-
span(
|
|
404
|
-
span(
|
|
405
|
-
span(
|
|
699
|
+
span(upper, -1, 1, chin + 4, "S");
|
|
700
|
+
span(upper, -2, 2, chin + 3, "S");
|
|
701
|
+
span(upper, -3 + turn, 3 + turn, chin + 2, "z");
|
|
406
702
|
}
|
|
407
703
|
else if (gear.headwear === 2) {
|
|
408
|
-
span(
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
}
|
|
414
|
-
if (gear.aura >= 1) {
|
|
415
|
-
const bob = counter % 4 < 2 ? 0 : 1;
|
|
416
|
-
put(width + 4, top + 2 + bob, "t");
|
|
417
|
-
put(width + 4, top + 3 + bob, "y");
|
|
704
|
+
span(upper, -2, 2, chin + 5, "g");
|
|
705
|
+
for (const x of [-2, 0, 2]) {
|
|
706
|
+
upper(x, chin + 6, "g");
|
|
707
|
+
}
|
|
708
|
+
upper(0, chin + 5, "t");
|
|
418
709
|
}
|
|
419
710
|
}
|
|
420
711
|
export function paletteOf(palette) {
|
|
421
|
-
const { cloak, cloakShade, glow } = palette.sprite;
|
|
712
|
+
const { cloak, cloakShade, scarf, scarfShade, glow } = palette.sprite;
|
|
422
713
|
return new Map([
|
|
423
|
-
["
|
|
714
|
+
["A", "#121212"],
|
|
715
|
+
["B", "#3E3226"],
|
|
716
|
+
["b", "#6B4A2E"],
|
|
717
|
+
["C", cloak],
|
|
718
|
+
["c", cloakShade],
|
|
424
719
|
["D", "#50555F"],
|
|
425
|
-
["d", "#
|
|
426
|
-
["e", "#
|
|
720
|
+
["d", "#9AA0AA"],
|
|
721
|
+
["e", "#2B2B2B"],
|
|
427
722
|
["F", "#C8473F"],
|
|
428
723
|
["G", "#6FA35A"],
|
|
429
724
|
["g", "#D9B45A"],
|
|
430
|
-
["
|
|
725
|
+
["H", "#9C9C9C"],
|
|
726
|
+
["h", "#4A3222"],
|
|
727
|
+
["I", "#E4E4E4"],
|
|
431
728
|
["k", "#2B2B2B"],
|
|
432
729
|
["L", "#4A505B"],
|
|
433
730
|
["l", glow],
|
|
434
|
-
["
|
|
435
|
-
["
|
|
731
|
+
["m", "#B5524A"],
|
|
732
|
+
["N", "#B59A6A"],
|
|
733
|
+
["n", "#937B52"],
|
|
734
|
+
["O", "#5E6B45"],
|
|
735
|
+
["o", "#5A3A25"],
|
|
736
|
+
["P", "#3E6870"],
|
|
737
|
+
["p", "#33565D"],
|
|
436
738
|
["Q", "#E6E0D4"],
|
|
437
|
-
["
|
|
438
|
-
["S",
|
|
439
|
-
["s",
|
|
440
|
-
["T", "#E5533D"],
|
|
739
|
+
["q", "#C99872"],
|
|
740
|
+
["S", scarf],
|
|
741
|
+
["s", "#E2B48C"],
|
|
441
742
|
["t", glow],
|
|
743
|
+
["U", "#B4AE9F"],
|
|
744
|
+
["V", "#4A5536"],
|
|
442
745
|
["v", "#9AA0A8"],
|
|
443
746
|
["w", "#F2F2F2"],
|
|
747
|
+
["X", "#2F3540"],
|
|
444
748
|
["x", "#3D414B"],
|
|
445
749
|
["y", palette.soft],
|
|
446
|
-
["
|
|
750
|
+
["Z", "#C8C8C8"],
|
|
751
|
+
["z", scarfShade],
|
|
447
752
|
]);
|
|
448
753
|
}
|
|
449
754
|
// The pixel keys of 1 frame, row by row, "." where nothing is drawn.
|
|
450
|
-
export function composeScene({ activity, counter, level, look, mood,
|
|
755
|
+
export function composeScene({ activity, counter, level, look, mood, width, }) {
|
|
451
756
|
const canvas = Array.from({ length: stageHeight }, () => Array.from({ length: width }, () => "."));
|
|
452
|
-
const
|
|
453
|
-
|
|
757
|
+
const scene = sceneOf(activity, counter, width, mood);
|
|
758
|
+
const b = bodyOf(look);
|
|
759
|
+
const pose = scene.pose(b);
|
|
760
|
+
const lift = pose.lift ?? 0;
|
|
761
|
+
// The character's x = 0 is its center column, mirrored when facing left.
|
|
762
|
+
const plant = (rise) => (x, y, key) => {
|
|
763
|
+
const column = scene.x + scene.facing * x;
|
|
764
|
+
const row = canvas[stageHeight - 1 - (y + rise)];
|
|
454
765
|
if (row && column >= 0 && column < width && key !== ".") {
|
|
455
766
|
row[column] = key;
|
|
456
767
|
}
|
|
457
768
|
};
|
|
458
|
-
const
|
|
459
|
-
const
|
|
460
|
-
const shape = shapeOf(look, level, scene.pose.squash ?? 0);
|
|
461
|
-
const lift = scene.pose.lift ?? 0;
|
|
462
|
-
// Local x runs from the body's left edge toward where the pet faces.
|
|
463
|
-
const put = (x, y, key) => {
|
|
464
|
-
const column = scene.facing === 1 ? scene.x + x : scene.x + baseShape.width - 1 - x;
|
|
465
|
-
plain(column, y + lift, key);
|
|
466
|
-
};
|
|
769
|
+
const put = plant(lift);
|
|
770
|
+
const upper = plant(lift - (pose.bob ?? 0));
|
|
467
771
|
const paint = {
|
|
468
772
|
put,
|
|
469
|
-
|
|
773
|
+
upper,
|
|
774
|
+
b,
|
|
470
775
|
look,
|
|
471
776
|
gear: gearOf(level),
|
|
472
|
-
pose
|
|
777
|
+
pose,
|
|
473
778
|
counter,
|
|
474
779
|
};
|
|
475
|
-
|
|
780
|
+
paintShadow(plant(0), b, lift);
|
|
781
|
+
paintHairBehind(paint);
|
|
476
782
|
paintLegs(paint);
|
|
477
|
-
|
|
478
|
-
paintFace(paint);
|
|
783
|
+
paintTorso(paint);
|
|
479
784
|
paintArms(paint);
|
|
480
785
|
paintHead(paint);
|
|
481
786
|
for (const draw of scene.props ?? []) {
|
|
482
|
-
draw(put,
|
|
483
|
-
}
|
|
484
|
-
// Today's badges float in the top right corner, whatever the pet does.
|
|
485
|
-
const icons = [
|
|
486
|
-
badges.focus ? ["GGG", "TTT", "TTT"] : undefined,
|
|
487
|
-
badges.meal ? [".G.", "FFF", ".F."] : undefined,
|
|
488
|
-
badges.workout ? ["D.D", "DdD", "D.D"] : undefined,
|
|
489
|
-
].filter((rows) => rows !== undefined);
|
|
490
|
-
for (const [index, rows] of icons.entries()) {
|
|
491
|
-
icon(rows, width - 4 - index * 4, stageHeight - 2)(plain, shape, 0);
|
|
787
|
+
draw(put, upper, b);
|
|
492
788
|
}
|
|
493
789
|
return canvas.map((row) => row.join(""));
|
|
494
790
|
}
|