@blkc-code/render-canvas 0.1.0 → 0.6.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/dist/draw.d.ts +22 -12
- package/dist/draw.d.ts.map +1 -1
- package/dist/draw.js +374 -273
- package/dist/draw.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/links.d.ts +16 -0
- package/dist/links.d.ts.map +1 -0
- package/dist/links.js +75 -0
- package/dist/links.js.map +1 -0
- package/dist/office.d.ts +25 -2
- package/dist/office.d.ts.map +1 -1
- package/dist/office.js +134 -20
- package/dist/office.js.map +1 -1
- package/dist/pack.d.ts +31 -0
- package/dist/pack.d.ts.map +1 -0
- package/dist/pack.js +10 -0
- package/dist/pack.js.map +1 -0
- package/dist/paint.d.ts +15 -0
- package/dist/paint.d.ts.map +1 -0
- package/dist/paint.js +18 -0
- package/dist/paint.js.map +1 -0
- package/dist/runtime.js +1 -1
- package/dist/runtime.js.map +1 -1
- package/dist/runtime.txt +1 -1
- package/dist/types.d.ts +20 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/world.d.ts +29 -13
- package/dist/world.d.ts.map +1 -1
- package/dist/world.js +150 -72
- package/dist/world.js.map +1 -1
- package/dist/worlds/office-data.d.ts +14 -0
- package/dist/worlds/office-data.d.ts.map +1 -0
- package/dist/worlds/office-data.js +52 -0
- package/dist/worlds/office-data.js.map +1 -0
- package/dist/worlds/office-paint.d.ts +13 -0
- package/dist/worlds/office-paint.d.ts.map +1 -0
- package/dist/worlds/office-paint.js +292 -0
- package/dist/worlds/office-paint.js.map +1 -0
- package/package.json +7 -2
- package/src/draw.ts +352 -272
- package/src/index.ts +4 -1
- package/src/links.ts +76 -0
- package/src/office.ts +142 -21
- package/src/pack.ts +36 -0
- package/src/paint.ts +24 -0
- package/src/runtime.ts +4 -1
- package/src/types.ts +31 -5
- package/src/world.ts +161 -77
- package/src/worlds/office-data.ts +57 -0
- package/src/worlds/office-paint.ts +276 -0
package/dist/draw.js
CHANGED
|
@@ -1,90 +1,166 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Draws the office. Every piece of art here is original to this project, made
|
|
3
|
-
*
|
|
2
|
+
* Draws the office. Every piece of art here is original to this project, made in code at
|
|
3
|
+
* 16 pixels a tile. The building is drawn once to a layer (drawBuilding); people, screens
|
|
4
|
+
* and anything else that moves are drawn on top each frame (drawWorld).
|
|
4
5
|
*/
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
function drawMap(c, map, x, y, colors, flip = false) {
|
|
24
|
-
map.forEach((row, ry) => {
|
|
25
|
-
for (let rx = 0; rx < row.length; rx++) {
|
|
26
|
-
const key = row[flip ? row.length - 1 - rx : rx];
|
|
27
|
-
const color = colors[key];
|
|
28
|
-
if (color)
|
|
29
|
-
rect(c, color, x + rx, y + ry, 1, 1);
|
|
30
|
-
}
|
|
31
|
-
});
|
|
6
|
+
import { drawLinks, linkRoutes } from "./links.js";
|
|
7
|
+
import { BODY_FONT, LABEL_FONT, OUTLINE, TILE, rect, shade } from "./paint.js";
|
|
8
|
+
import { isSeated, specOf } from "./world.js";
|
|
9
|
+
import { OFFICE_PAINTER } from "./worlds/office-paint.js";
|
|
10
|
+
export { TILE } from "./paint.js";
|
|
11
|
+
const painters = new Map([["office", OFFICE_PAINTER]]);
|
|
12
|
+
export function registerPainter(id, painter) {
|
|
13
|
+
painters.set(id, painter);
|
|
14
|
+
}
|
|
15
|
+
const painterFor = (world) => painters.get(world.pack.id) ?? OFFICE_PAINTER;
|
|
16
|
+
export function daylightAt(hour) {
|
|
17
|
+
if (hour >= 7 && hour < 17)
|
|
18
|
+
return { sky: "#8ed0ff", sun: "rgba(255,244,214,0.10)", night: 0 };
|
|
19
|
+
if (hour >= 17 && hour < 20)
|
|
20
|
+
return { sky: "#ffae70", sun: "rgba(255,170,110,0.13)", night: 0.14 };
|
|
21
|
+
if (hour >= 5 && hour < 7)
|
|
22
|
+
return { sky: "#ffc9a3", sun: "rgba(255,200,170,0.08)", night: 0.16 };
|
|
23
|
+
return { sky: "#16204a", sun: "rgba(0,0,0,0)", night: 0.24 };
|
|
32
24
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
25
|
+
/** Parts draw with one outline around the whole figure, so characters read clearly on any floor. */
|
|
26
|
+
function drawParts(c, parts) {
|
|
27
|
+
c.fillStyle = OUTLINE;
|
|
28
|
+
for (const [, x, y, w, h] of parts)
|
|
29
|
+
c.fillRect(Math.round(x) - 1, Math.round(y) - 1, w + 2, h + 2);
|
|
30
|
+
for (const [color, x, y, w, h] of parts)
|
|
31
|
+
rect(c, color, x, y, w, h);
|
|
37
32
|
}
|
|
38
33
|
function drawAgent(c, agent, time, selected) {
|
|
39
|
-
const
|
|
34
|
+
const left = agent.x * TILE + 2; // sprite is 12 wide inside a 16 tile
|
|
35
|
+
const feet = agent.y * TILE + 15;
|
|
40
36
|
const seated = isSeated(agent);
|
|
41
|
-
const
|
|
42
|
-
const
|
|
43
|
-
const
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
const
|
|
37
|
+
const walking = agent.walking;
|
|
38
|
+
const side = agent.facing === "left" || agent.facing === "right";
|
|
39
|
+
const back = agent.facing === "up";
|
|
40
|
+
const flip = agent.facing === "left";
|
|
41
|
+
const phase = Math.floor(time * 9 + (agent.seed % 5));
|
|
42
|
+
const step = walking ? phase % 4 : 0; // 0 pass, 1 left forward, 2 pass, 3 right forward
|
|
43
|
+
const bob = walking && step % 2 === 1 ? -1 : 0;
|
|
44
|
+
const breathe = !walking && !seated && Math.floor(time * 1.4 + (agent.seed % 7)) % 2 === 0 ? 1 : 0;
|
|
45
|
+
const party = agent.party > 0 ? -Math.abs(Math.round(Math.sin(time * 12) * 3)) : 0;
|
|
46
|
+
const skin = agent.skin;
|
|
47
|
+
const hair = agent.hair;
|
|
48
|
+
const shirt = agent.shirt;
|
|
49
|
+
const dark = shade(shirt, -40);
|
|
50
|
+
const light = shade(shirt, 24);
|
|
51
|
+
const legs = "#39415a";
|
|
52
|
+
const shoe = "#1f2433";
|
|
53
|
+
const style = agent.seed % 5; // short, long, bun, spiky, cap
|
|
54
|
+
const outfit = (agent.seed >>> 5) % 3; // tee, hoodie, shirt and tie
|
|
55
|
+
// Shadow, and a highlight under whoever is selected.
|
|
56
|
+
c.fillStyle = "rgba(0,0,0,0.28)";
|
|
57
|
+
c.fillRect(Math.round(left + 1), Math.round(feet - 1), 10, 2);
|
|
58
|
+
if (selected) {
|
|
59
|
+
c.fillStyle = "rgba(255,255,255,0.45)";
|
|
60
|
+
c.fillRect(Math.round(left - 2), Math.round(feet - 2), 16, 4);
|
|
61
|
+
}
|
|
62
|
+
if (agent.state === "blocked") {
|
|
63
|
+
c.fillStyle = Math.floor(time * 3) % 2 === 0 ? "rgba(239,68,68,0.55)" : "rgba(239,68,68,0.22)";
|
|
64
|
+
c.fillRect(Math.round(left - 2), Math.round(feet - 2), 16, 4);
|
|
65
|
+
}
|
|
66
|
+
const lift = bob + party;
|
|
67
|
+
const parts = [];
|
|
68
|
+
const bodyTop = feet - (seated ? 13 : 13) + lift + breathe * 0;
|
|
69
|
+
const headTop = bodyTop - 8 + breathe;
|
|
70
|
+
// Chair behind someone sitting at a desk.
|
|
47
71
|
if (seated) {
|
|
48
|
-
rect(c, "#1f2937",
|
|
49
|
-
rect(c, "#374151",
|
|
50
|
-
rect(c, "#111827",
|
|
72
|
+
rect(c, "#1f2937", left + 1, agent.y * TILE + 3, 10, 9);
|
|
73
|
+
rect(c, "#374151", left + 2, agent.y * TILE + 11, 8, 3);
|
|
74
|
+
rect(c, "#111827", left + 5, agent.y * TILE + 14, 2, 2);
|
|
51
75
|
}
|
|
52
|
-
//
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
76
|
+
// Legs
|
|
77
|
+
if (!seated) {
|
|
78
|
+
const legTop = bodyTop + 7;
|
|
79
|
+
if (side) {
|
|
80
|
+
const swing = step === 1 ? 2 : step === 3 ? -2 : 0;
|
|
81
|
+
parts.push([legs, left + 4 + swing, legTop, 3, 5], [shoe, left + 4 + swing + (flip ? -1 : 0), legTop + 5, 4, 1]);
|
|
82
|
+
parts.push([shade(legs, -12), left + 5 - swing, legTop, 3, 5], [shoe, left + 5 - swing + (flip ? -1 : 0), legTop + 5, 4, 1]);
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
const leftUp = step === 1 ? 1 : 0;
|
|
86
|
+
const rightUp = step === 3 ? 1 : 0;
|
|
87
|
+
parts.push([legs, left + 2, legTop, 3, 5 - leftUp], [shoe, left + 2, legTop + 5 - leftUp, 3, 1]);
|
|
88
|
+
parts.push([legs, left + 7, legTop, 3, 5 - rightUp], [shoe, left + 7, legTop + 5 - rightUp, 3, 1]);
|
|
89
|
+
}
|
|
58
90
|
}
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
91
|
+
// Body and arms
|
|
92
|
+
const swingArm = walking ? (step === 1 ? 1 : step === 3 ? -1 : 0) : 0;
|
|
93
|
+
if (side) {
|
|
94
|
+
parts.push([shirt, left + 3, bodyTop, 6, 7], [dark, left + 3, bodyTop + 6, 6, 1]);
|
|
95
|
+
parts.push([dark, left + 5 + swingArm, bodyTop + 1, 2, 5], [skin, left + 5 + swingArm, bodyTop + 6, 2, 1]);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
parts.push([shirt, left + 1, bodyTop, 10, 7], [dark, left + 1, bodyTop + 6, 10, 1]);
|
|
99
|
+
const typing = seated && agent.state === "working";
|
|
100
|
+
const type = Math.floor(time * 10 + agent.seed) % 2;
|
|
101
|
+
parts.push([dark, left - 1, bodyTop + 1 + swingArm + (typing ? -type : 0), 2, 5]);
|
|
102
|
+
parts.push([dark, left + 11, bodyTop + 1 - swingArm + (typing ? type - 1 : 0), 2, 5]);
|
|
103
|
+
parts.push([skin, left - 1, bodyTop + 6 + swingArm + (typing ? -type : 0), 2, 1]);
|
|
104
|
+
parts.push([skin, left + 11, bodyTop + 6 - swingArm + (typing ? type - 1 : 0), 2, 1]);
|
|
105
|
+
if (!back && outfit === 2)
|
|
106
|
+
parts.push(["#f3f4f6", left + 4, bodyTop, 4, 3], [agent.seed % 2 ? "#ef4444" : "#1d4ed8", left + 5, bodyTop, 2, 5]);
|
|
107
|
+
if (!back && outfit === 1)
|
|
108
|
+
parts.push([light, left + 3, bodyTop, 6, 2], [dark, left + 5, bodyTop + 2, 2, 4]);
|
|
109
|
+
if (back && outfit === 1)
|
|
110
|
+
parts.push([light, left + 2, bodyTop, 8, 3]);
|
|
111
|
+
}
|
|
112
|
+
// Head
|
|
113
|
+
parts.push([skin, left + 1, headTop, 10, 8]);
|
|
114
|
+
if (back) {
|
|
115
|
+
parts.push([hair, left + 1, headTop, 10, 7]);
|
|
116
|
+
}
|
|
117
|
+
else if (side) {
|
|
118
|
+
const backX = flip ? left + 7 : left + 1;
|
|
119
|
+
parts.push([hair, left + 1, headTop, 10, 3], [hair, backX, headTop, 4, 6]);
|
|
120
|
+
const blink = Math.floor(time * 0.7 + agent.seed % 11) % 7 === 0;
|
|
121
|
+
parts.push([blink ? shade(skin, -40) : "#1a1a24", flip ? left + 3 : left + 8, headTop + 4, 1, blink ? 1 : 2]);
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
parts.push([hair, left + 1, headTop, 10, 3], [hair, left + 1, headTop + 3, 1, 2], [hair, left + 10, headTop + 3, 1, 2]);
|
|
125
|
+
const blink = Math.floor(time * 0.7 + agent.seed % 11) % 7 === 0;
|
|
126
|
+
parts.push([blink ? shade(skin, -40) : "#1a1a24", left + 3, headTop + 4, 2, blink ? 1 : 2]);
|
|
127
|
+
parts.push([blink ? shade(skin, -40) : "#1a1a24", left + 7, headTop + 4, 2, blink ? 1 : 2]);
|
|
128
|
+
parts.push([shade(skin, -28), left + 5, headTop + 6, 2, 1]);
|
|
129
|
+
}
|
|
130
|
+
if (style === 1)
|
|
131
|
+
parts.push([hair, left, headTop + 2, 2, 8], [hair, left + 10, headTop + 2, 2, 8]); // long
|
|
132
|
+
if (style === 2)
|
|
133
|
+
parts.push([hair, left + 4, headTop - 3, 4, 3]); // bun
|
|
134
|
+
if (style === 3)
|
|
135
|
+
parts.push([hair, left + 2, headTop - 2, 2, 2], [hair, left + 5, headTop - 3, 2, 3], [hair, left + 8, headTop - 2, 2, 2]); // spiky
|
|
136
|
+
if (style === 4) { // cap
|
|
137
|
+
parts.push([light, left, headTop - 1, 12, 3]);
|
|
138
|
+
if (!back && !side)
|
|
139
|
+
parts.push([dark, left + 1, headTop + 2, 10, 1]);
|
|
140
|
+
if (side)
|
|
141
|
+
parts.push([dark, flip ? left - 2 : left + 9, headTop + 1, 5, 1]);
|
|
142
|
+
}
|
|
143
|
+
drawParts(c, parts);
|
|
144
|
+
if (agent.party > 0) {
|
|
145
|
+
const colors = ["#f59e0b", "#ec4899", "#22c55e", "#3b82f6", "#a855f7"];
|
|
146
|
+
for (let i = 0; i < 10; i++) {
|
|
147
|
+
const t = (time * 1.1 + i * 0.137) % 1;
|
|
148
|
+
const angle = i * 2.4 + agent.seed;
|
|
149
|
+
rect(c, colors[i % colors.length], left + 6 + Math.cos(angle) * (4 + t * 14), feet - 22 - t * 6 + t * t * 26, 2, 2);
|
|
150
|
+
}
|
|
75
151
|
}
|
|
76
152
|
}
|
|
77
153
|
function bubble(c, x, y, draw) {
|
|
78
|
-
rect(c,
|
|
154
|
+
rect(c, OUTLINE, x - 1, y - 1, 13, 11);
|
|
79
155
|
rect(c, "#ffffff", x, y, 11, 9);
|
|
80
156
|
rect(c, "#ffffff", x + 4, y + 9, 3, 1);
|
|
81
|
-
rect(c,
|
|
157
|
+
rect(c, OUTLINE, x + 4, y + 10, 3, 1);
|
|
82
158
|
draw(x, y);
|
|
83
159
|
}
|
|
84
160
|
function drawStatus(c, agent, time) {
|
|
85
161
|
const x = Math.round(agent.x * TILE + 2);
|
|
86
|
-
const y = Math.round(agent.y * TILE -
|
|
87
|
-
const pulse = Math.floor(time * 2 + agent.seed % 3) % 2 === 0;
|
|
162
|
+
const y = Math.round(agent.y * TILE - 22 + (isSeated(agent) ? 2 : -2));
|
|
163
|
+
const pulse = Math.floor(time * 2 + (agent.seed % 3)) % 2 === 0;
|
|
88
164
|
if (agent.state === "blocked") {
|
|
89
165
|
bubble(c, x, y, (bx, by) => {
|
|
90
166
|
rect(c, pulse ? "#dc2626" : "#991b1b", bx + 5, by + 1, 2, 5);
|
|
@@ -115,231 +191,230 @@ function drawStatus(c, agent, time) {
|
|
|
115
191
|
rect(c, color, bx + 4, by + 2, 3, 5);
|
|
116
192
|
});
|
|
117
193
|
}
|
|
194
|
+
else if (agent.state === "idle" && !agent.walking && agent.seed % 3 === 0) {
|
|
195
|
+
bubble(c, x, y, (bx, by) => {
|
|
196
|
+
rect(c, "#92400e", bx + 3, by + 3, 4, 4);
|
|
197
|
+
rect(c, "#d97706", bx + 3, by + 3, 4, 1);
|
|
198
|
+
rect(c, "#92400e", bx + 7, by + 4, 1, 2);
|
|
199
|
+
if (pulse)
|
|
200
|
+
rect(c, "#9ca3af", bx + 4, by + 1, 1, 1);
|
|
201
|
+
});
|
|
202
|
+
}
|
|
118
203
|
}
|
|
119
|
-
// --- The
|
|
120
|
-
function drawFloor(c, room
|
|
204
|
+
// --- The building ----------------------------------------------------------------------------------------
|
|
205
|
+
function drawFloor(c, room) {
|
|
121
206
|
for (let ty = room.y; ty < room.y + room.h; ty++) {
|
|
122
207
|
for (let tx = room.x; tx < room.x + room.w; tx++) {
|
|
123
208
|
const x = tx * TILE;
|
|
124
209
|
const y = ty * TILE;
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
rect(c, "#
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
210
|
+
const noise = (tx * 73 + ty * 151) % 17;
|
|
211
|
+
switch (room.floor) {
|
|
212
|
+
case "carpet":
|
|
213
|
+
rect(c, "#4c4f7a", x, y, TILE, TILE);
|
|
214
|
+
for (let i = 0; i < 8; i++)
|
|
215
|
+
rect(c, i % 2 ? "#55588a" : "#454874", x + ((tx * 7 + i * 5) % 15), y + ((ty * 3 + i * 7) % 15), 1, 1);
|
|
216
|
+
break;
|
|
217
|
+
case "tile":
|
|
218
|
+
rect(c, (tx + ty) % 2 === 0 ? "#d6dee8" : "#c2ccd8", x, y, TILE, TILE);
|
|
219
|
+
rect(c, "#aab5c2", x, y + 15, TILE, 1);
|
|
220
|
+
rect(c, "#aab5c2", x + 15, y, 1, TILE);
|
|
221
|
+
rect(c, "#e8eef5", x + 1, y + 1, 5, 1);
|
|
222
|
+
break;
|
|
223
|
+
case "stone":
|
|
224
|
+
rect(c, noise % 3 === 0 ? "#8d93a3" : "#9aa0b0", x, y, TILE, TILE);
|
|
225
|
+
rect(c, "#7a8090", x, y + 15, TILE, 1);
|
|
226
|
+
rect(c, "#7a8090", x + (ty % 2 ? 7 : 15), y, 1, 15);
|
|
227
|
+
rect(c, "#adb3c2", x + 2, y + 2, 3, 1);
|
|
228
|
+
break;
|
|
229
|
+
case "concrete":
|
|
230
|
+
rect(c, "#6b7280", x, y, TILE, TILE);
|
|
231
|
+
for (let i = 0; i < 5; i++)
|
|
232
|
+
rect(c, i % 2 ? "#747b89" : "#636a78", x + ((tx * 11 + i * 3) % 15), y + ((ty * 5 + i * 5) % 15), 1, 1);
|
|
233
|
+
if (noise === 3)
|
|
234
|
+
rect(c, "#596070", x + 3, y + 8, 8, 1);
|
|
235
|
+
break;
|
|
236
|
+
case "hall": {
|
|
237
|
+
// Polished floor, with a runner down the middle of a corridor's length.
|
|
238
|
+
rect(c, "#cfc8b8", x, y, TILE, TILE);
|
|
239
|
+
rect(c, "#bdb6a6", x, y + 15, TILE, 1);
|
|
240
|
+
rect(c, "#bdb6a6", x + 15, y, 1, TILE);
|
|
241
|
+
const along = room.w >= room.h;
|
|
242
|
+
const offset = along ? ty - room.y : tx - room.x;
|
|
243
|
+
const size = along ? room.h : room.w;
|
|
244
|
+
if (along && offset === 0)
|
|
245
|
+
rect(c, "#7f6a55", x, y + 12, TILE, 4);
|
|
246
|
+
if (along && offset === size - 1)
|
|
247
|
+
rect(c, "#7f6a55", x, y, TILE, 4);
|
|
248
|
+
if (!along && offset === 0)
|
|
249
|
+
rect(c, "#7f6a55", x + 12, y, 4, TILE);
|
|
250
|
+
if (!along && offset === size - 1)
|
|
251
|
+
rect(c, "#7f6a55", x, y, 4, TILE);
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
254
|
+
case "grass":
|
|
255
|
+
rect(c, "#3f8f4f", x, y, TILE, TILE);
|
|
256
|
+
for (let i = 0; i < 7; i++)
|
|
257
|
+
rect(c, i % 3 ? "#4aa35c" : "#347a43", x + ((tx * 5 + i * 9) % 15), y + ((ty * 7 + i * 3) % 14), 1, 2);
|
|
258
|
+
break;
|
|
259
|
+
default: {
|
|
260
|
+
const plank = ty % 2 === 0 ? "#a57448" : "#9c6c42";
|
|
261
|
+
rect(c, plank, x, y, TILE, TILE);
|
|
262
|
+
rect(c, "#8a5d36", x, y + 7, TILE, 1);
|
|
263
|
+
rect(c, "#8a5d36", x, y + 15, TILE, 1);
|
|
264
|
+
rect(c, "#8a5d36", x + ((tx * 8 + (ty % 2 ? 8 : 0)) % 16), y, 1, 7);
|
|
265
|
+
rect(c, "#8a5d36", x + ((tx * 8 + (ty % 2 ? 0 : 8)) % 16), y + 8, 1, 7);
|
|
266
|
+
rect(c, "#b4835a", x + (noise % 9) + 2, y + 3, 4, 1);
|
|
267
|
+
rect(c, "#b4835a", x + (noise % 7) + 4, y + 11, 3, 1);
|
|
268
|
+
}
|
|
142
269
|
}
|
|
143
270
|
}
|
|
144
271
|
}
|
|
145
|
-
void palette;
|
|
146
272
|
}
|
|
147
|
-
const WALL_TOP = "#
|
|
148
|
-
const WALL_FACE = "#
|
|
149
|
-
const
|
|
150
|
-
|
|
273
|
+
const WALL_TOP = "#2a2d48";
|
|
274
|
+
const WALL_FACE = "#5c628f";
|
|
275
|
+
const WALL_DARK = "#4a4f78";
|
|
276
|
+
const WALL_LIGHT = "#6d74a4";
|
|
277
|
+
/** Outside the building: lawn, with the odd shrub and tree. */
|
|
278
|
+
function drawGrounds(c, tx, ty) {
|
|
279
|
+
const x = tx * TILE;
|
|
280
|
+
const y = ty * TILE;
|
|
281
|
+
rect(c, (tx + ty) % 2 === 0 ? "#4b8a4f" : "#478549", x, y, TILE, TILE);
|
|
282
|
+
const noise = (tx * 73 + ty * 151) % 29;
|
|
283
|
+
for (let i = 0; i < 4; i++)
|
|
284
|
+
rect(c, "#5a9b5c", x + ((tx * 5 + i * 7) % 15), y + ((ty * 3 + i * 5) % 15), 1, 2);
|
|
285
|
+
if (noise === 4) {
|
|
286
|
+
rect(c, "#2f6a38", x + 3, y + 7, 10, 7);
|
|
287
|
+
rect(c, "#3c7f45", x + 5, y + 8, 5, 3);
|
|
288
|
+
}
|
|
289
|
+
else if (noise === 11) {
|
|
290
|
+
rect(c, "#5b3a22", x + 7, y + 10, 2, 5);
|
|
291
|
+
rect(c, "#24592e", x + 2, y + 1, 12, 10);
|
|
292
|
+
rect(c, "#2f6f3a", x + 4, y + 2, 7, 5);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
function drawWalls(c, world, palette, daylight) {
|
|
151
296
|
const open = new Set();
|
|
152
297
|
for (const room of world.rooms) {
|
|
153
298
|
for (let y = room.y; y < room.y + room.h; y++)
|
|
154
299
|
for (let x = room.x; x < room.x + room.w; x++)
|
|
155
300
|
open.add(`${x},${y}`);
|
|
156
301
|
}
|
|
157
|
-
|
|
158
|
-
open.add(`${door.x},${door.y}`);
|
|
302
|
+
const doors = new Set(world.doors.map((door) => `${door.x},${door.y}`));
|
|
159
303
|
for (let ty = 0; ty < world.rows; ty++) {
|
|
160
304
|
for (let tx = 0; tx < world.cols; tx++) {
|
|
161
|
-
|
|
305
|
+
const key = `${tx},${ty}`;
|
|
306
|
+
if (open.has(key) || doors.has(key))
|
|
162
307
|
continue;
|
|
163
308
|
const x = tx * TILE;
|
|
164
309
|
const y = ty * TILE;
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
310
|
+
const nearBuilding = (cx, cy) => {
|
|
311
|
+
for (let ay = cy - 1; ay <= cy + 1; ay++) {
|
|
312
|
+
for (let ax = cx - 1; ax <= cx + 1; ax++)
|
|
313
|
+
if (open.has(`${ax},${ay}`) || doors.has(`${ax},${ay}`))
|
|
314
|
+
return true;
|
|
315
|
+
}
|
|
316
|
+
return false;
|
|
317
|
+
};
|
|
318
|
+
if (!nearBuilding(tx, ty)) {
|
|
319
|
+
drawGrounds(c, tx, ty);
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
if (open.has(`${tx},${ty + 1}`)) {
|
|
168
323
|
rect(c, WALL_FACE, x, y, TILE, TILE);
|
|
169
|
-
rect(c,
|
|
170
|
-
rect(c,
|
|
171
|
-
|
|
324
|
+
rect(c, WALL_LIGHT, x, y, TILE, 1);
|
|
325
|
+
rect(c, WALL_DARK, x, y + 12, TILE, 4);
|
|
326
|
+
rect(c, palette.primary, x, y + 11, TILE, 1);
|
|
327
|
+
// Windows look outside, so only an outer wall has them.
|
|
328
|
+
if (tx % 4 === 3 && !nearBuilding(tx, ty - 1) && !world.furniture.some((item) => specOf(world, item).wall && item.x === tx && item.y === ty)) {
|
|
329
|
+
// A window, in the colour of the sky right now.
|
|
172
330
|
rect(c, "#1f2937", x + 1, y + 1, 14, 9);
|
|
173
|
-
rect(c,
|
|
174
|
-
rect(c,
|
|
331
|
+
rect(c, daylight.sky, x + 2, y + 2, 12, 7);
|
|
332
|
+
rect(c, shade(daylight.sky, 40), x + 3, y + 3, 4, 2);
|
|
175
333
|
rect(c, "#1f2937", x + 8, y + 2, 1, 7);
|
|
334
|
+
if (daylight.night > 0.3) {
|
|
335
|
+
rect(c, "#fef3c7", x + 4, y + 3, 1, 1);
|
|
336
|
+
rect(c, "#fef3c7", x + 11, y + 5, 1, 1);
|
|
337
|
+
}
|
|
176
338
|
}
|
|
177
339
|
}
|
|
178
340
|
else {
|
|
179
341
|
rect(c, WALL_TOP, x, y, TILE, TILE);
|
|
342
|
+
if (open.has(`${tx},${ty - 1}`))
|
|
343
|
+
rect(c, "#20233a", x, y, TILE, 3);
|
|
344
|
+
if (open.has(`${tx - 1},${ty}`))
|
|
345
|
+
rect(c, "#20233a", x, y, 2, TILE);
|
|
346
|
+
if (open.has(`${tx + 1},${ty}`))
|
|
347
|
+
rect(c, "#20233a", x + 14, y, 2, TILE);
|
|
180
348
|
}
|
|
181
349
|
}
|
|
182
350
|
}
|
|
183
351
|
for (const door of world.doors) {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
rect(c, "#4a2e1a", x + 42, y + 22, 3, 9);
|
|
194
|
-
// Monitor
|
|
195
|
-
rect(c, "#111827", x + 15, y - 1, 18, 13);
|
|
196
|
-
rect(c, "#374151", x + 22, y + 12, 4, 2);
|
|
197
|
-
if (on) {
|
|
198
|
-
rect(c, "#0f2a2e", x + 16, y, 16, 11);
|
|
199
|
-
const scroll = Math.floor(time * 3 + seed % 13);
|
|
200
|
-
for (let line = 0; line < 4; line++) {
|
|
201
|
-
const width = 3 + ((seed >> line) + (scroll + line) * 7) % 11;
|
|
202
|
-
rect(c, line % 2 === 0 ? palette.primary : "#7dd3fc", x + 17, y + 1 + line * 2.5, width, 1);
|
|
352
|
+
const x = door.x * TILE;
|
|
353
|
+
const y = door.y * TILE;
|
|
354
|
+
const horizontal = open.has(`${door.x},${door.y - 1}`) || open.has(`${door.x},${door.y + 1}`);
|
|
355
|
+
rect(c, "#7c5637", x, y, TILE, TILE);
|
|
356
|
+
for (let i = 2; i < 16; i += 4)
|
|
357
|
+
rect(c, "#6b4a2f", horizontal ? x : x + i, horizontal ? y + i : y, horizontal ? TILE : 1, horizontal ? 1 : TILE);
|
|
358
|
+
if (horizontal) {
|
|
359
|
+
rect(c, WALL_DARK, x, y, 2, TILE);
|
|
360
|
+
rect(c, WALL_DARK, x + 14, y, 2, TILE);
|
|
203
361
|
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
else {
|
|
208
|
-
rect(c, "#1f2937", x + 16, y, 16, 11);
|
|
209
|
-
rect(c, "#374151", x + 18, y + 2, 3, 1);
|
|
210
|
-
}
|
|
211
|
-
// Keyboard and mug
|
|
212
|
-
rect(c, "#d1d5db", x + 17, y + 15, 14, 4);
|
|
213
|
-
rect(c, "#9ca3af", x + 18, y + 16, 12, 1);
|
|
214
|
-
rect(c, "#f3f4f6", x + 38, y + 14, 4, 4);
|
|
215
|
-
rect(c, palette.primary, x + 38, y + 16, 4, 1);
|
|
216
|
-
}
|
|
217
|
-
function drawBoard(c, x, y, world) {
|
|
218
|
-
rect(c, "#2b2118", x, y - 11, 64, 26);
|
|
219
|
-
rect(c, "#d9c29c", x + 2, y - 9, 60, 22);
|
|
220
|
-
const colors = { todo: "#fde68a", doing: "#93c5fd", done: "#86efac" };
|
|
221
|
-
const per = 8;
|
|
222
|
-
world.tasks.slice(0, per * 2).forEach((task, index) => {
|
|
223
|
-
const col = index % per;
|
|
224
|
-
const row = Math.floor(index / per);
|
|
225
|
-
rect(c, "#00000033", x + 5 + col * 7, y - 6 + row * 9, 6, 7);
|
|
226
|
-
rect(c, colors[task.state], x + 4 + col * 7, y - 7 + row * 9, 6, 7);
|
|
227
|
-
rect(c, "#00000040", x + 5 + col * 7, y - 5 + row * 9, 4, 1);
|
|
228
|
-
});
|
|
229
|
-
}
|
|
230
|
-
function drawFurniture(c, item, world, time, palette) {
|
|
231
|
-
const x = item.x * TILE;
|
|
232
|
-
const y = item.y * TILE;
|
|
233
|
-
switch (item.type) {
|
|
234
|
-
case "desk": {
|
|
235
|
-
const seatX = item.x + 1;
|
|
236
|
-
const seatY = item.y + 2;
|
|
237
|
-
const user = world.agents.find((a) => a.seat?.x === seatX && a.seat?.y === seatY && isSeated(a) && a.state !== "blocked");
|
|
238
|
-
drawDesk(c, x, y, Boolean(user), time, user?.seed ?? 0, palette);
|
|
239
|
-
break;
|
|
362
|
+
else {
|
|
363
|
+
rect(c, WALL_DARK, x, y, TILE, 2);
|
|
364
|
+
rect(c, WALL_DARK, x, y + 14, TILE, 2);
|
|
240
365
|
}
|
|
241
|
-
case "coffee":
|
|
242
|
-
rect(c, "#374151", x, y + 2, 32, 14);
|
|
243
|
-
rect(c, "#6b7280", x, y + 2, 32, 2);
|
|
244
|
-
rect(c, "#111827", x + 4, y - 8, 14, 14);
|
|
245
|
-
rect(c, "#ef4444", x + 15, y - 5, 2, 2);
|
|
246
|
-
rect(c, "#f3f4f6", x + 8, y + 3, 5, 4);
|
|
247
|
-
rect(c, "#f3f4f6", x + 22, y - 1, 5, 5);
|
|
248
|
-
for (let i = 0; i < 3; i++) {
|
|
249
|
-
c.fillStyle = "rgba(255,255,255,0.45)";
|
|
250
|
-
c.fillRect(x + 9 + Math.round(Math.sin(time * 2 + i) * 1.5), y - 1 - ((time * 8 + i * 4) % 9), 1, 2);
|
|
251
|
-
}
|
|
252
|
-
break;
|
|
253
|
-
case "water":
|
|
254
|
-
rect(c, "#e5e7eb", x + 3, y + 3, 10, 13);
|
|
255
|
-
rect(c, "#7dd3fc", x + 4, y - 7, 8, 10);
|
|
256
|
-
rect(c, "#bae6fd", x + 5, y - 6, 2, 8);
|
|
257
|
-
rect(c, "#3b82f6", x + 5, y + 6, 2, 2);
|
|
258
|
-
break;
|
|
259
|
-
case "sofa":
|
|
260
|
-
rect(c, palette.primary, x + 1, y - 6, 46, 9);
|
|
261
|
-
rect(c, "#00000030", x + 1, y + 1, 46, 2);
|
|
262
|
-
rect(c, "#4b5563", x, y + 3, 48, 11);
|
|
263
|
-
rect(c, "#6b7280", x + 3, y + 3, 20, 7);
|
|
264
|
-
rect(c, "#6b7280", x + 25, y + 3, 20, 7);
|
|
265
|
-
rect(c, "#374151", x, y + 12, 48, 3);
|
|
266
|
-
break;
|
|
267
|
-
case "plant":
|
|
268
|
-
rect(c, "#92400e", x + 4, y + 9, 8, 7);
|
|
269
|
-
rect(c, "#b45309", x + 3, y + 8, 10, 2);
|
|
270
|
-
for (const [lx, ly, lw, lh] of [[6, -3, 4, 11], [2, 0, 5, 6], [9, 1, 5, 6], [4, -6, 3, 6], [9, -5, 3, 5]]) {
|
|
271
|
-
rect(c, "#15803d", x + lx, y + ly, lw, lh);
|
|
272
|
-
}
|
|
273
|
-
rect(c, "#22c55e", x + 7, y - 3, 2, 8);
|
|
274
|
-
rect(c, "#22c55e", x + 3, y + 1, 2, 3);
|
|
275
|
-
break;
|
|
276
|
-
case "board":
|
|
277
|
-
drawBoard(c, x, y, world);
|
|
278
|
-
break;
|
|
279
|
-
case "shelf":
|
|
280
|
-
rect(c, "#5b3a21", x, y - 12, 32, 28);
|
|
281
|
-
for (let shelf = 0; shelf < 2; shelf++) {
|
|
282
|
-
rect(c, "#3b2413", x + 2, y - 10 + shelf * 13, 28, 11);
|
|
283
|
-
for (let book = 0; book < 8; book++) {
|
|
284
|
-
const color = ["#ef4444", "#3b82f6", "#10b981", "#f59e0b", "#8b5cf6"][(book * 3 + shelf) % 5];
|
|
285
|
-
rect(c, color, x + 3 + book * 3, y - 8 + shelf * 13 + (book % 3), 2, 9 - (book % 3));
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
break;
|
|
289
|
-
case "server":
|
|
290
|
-
rect(c, "#111827", x + 1, y - 12, 14, 28);
|
|
291
|
-
for (let i = 0; i < 5; i++) {
|
|
292
|
-
rect(c, "#1f2937", x + 2, y - 11 + i * 5, 12, 4);
|
|
293
|
-
const on = Math.floor(time * 3 + i * 2 + item.x) % 3 !== 0;
|
|
294
|
-
rect(c, on ? "#22c55e" : "#14532d", x + 3, y - 10 + i * 5, 1, 1);
|
|
295
|
-
rect(c, i % 2 === 0 ? "#f59e0b" : "#3b82f6", x + 5, y - 10 + i * 5, 1, 1);
|
|
296
|
-
}
|
|
297
|
-
break;
|
|
298
|
-
case "table":
|
|
299
|
-
rect(c, "#57534e", x + 2, y + 14, 44, 14);
|
|
300
|
-
rect(c, "#78716c", x + 2, y + 3, 44, 14);
|
|
301
|
-
rect(c, "#a8a29e", x + 2, y + 3, 44, 2);
|
|
302
|
-
rect(c, "#f9fafb", x + 10, y + 6, 8, 6);
|
|
303
|
-
rect(c, "#f9fafb", x + 28, y + 8, 8, 6);
|
|
304
|
-
break;
|
|
305
|
-
case "rug":
|
|
306
|
-
rect(c, palette.primary, x, y, 64, 48);
|
|
307
|
-
c.fillStyle = "rgba(0,0,0,0.35)";
|
|
308
|
-
c.fillRect(x + 3, y + 3, 58, 42);
|
|
309
|
-
rect(c, palette.primary, x + 6, y + 6, 52, 1);
|
|
310
|
-
rect(c, palette.primary, x + 6, y + 41, 52, 1);
|
|
311
|
-
break;
|
|
312
366
|
}
|
|
313
|
-
void FURNITURE;
|
|
314
367
|
}
|
|
315
368
|
function drawLabels(c, world) {
|
|
316
|
-
c.font =
|
|
369
|
+
c.font = LABEL_FONT;
|
|
317
370
|
c.textBaseline = "top";
|
|
318
371
|
for (const room of world.rooms) {
|
|
319
372
|
if (!room.name)
|
|
320
373
|
continue;
|
|
321
374
|
const label = room.name.toUpperCase();
|
|
322
|
-
|
|
375
|
+
const width = Math.ceil(c.measureText(label).width) + 6;
|
|
323
376
|
const x = room.x * TILE + 3;
|
|
324
|
-
const y = room.y * TILE -
|
|
325
|
-
c
|
|
326
|
-
c
|
|
327
|
-
c.fillStyle = "
|
|
328
|
-
c.fillText(label, x, y);
|
|
377
|
+
const y = room.y * TILE - 13;
|
|
378
|
+
rect(c, "#1c1f33", x, y, width, 10);
|
|
379
|
+
rect(c, "#2f3350", x + 1, y + 1, width - 2, 8);
|
|
380
|
+
c.fillStyle = "#f8fafc";
|
|
381
|
+
c.fillText(label, x + 3, y + 1.5);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
/** Everything that doesn't move. Redrawn only when the building or the hour changes. */
|
|
385
|
+
export function drawBuilding(c, world, palette, daylight) {
|
|
386
|
+
rect(c, WALL_TOP, 0, 0, world.cols * TILE, world.rows * TILE);
|
|
387
|
+
for (const room of world.rooms)
|
|
388
|
+
drawFloor(c, room);
|
|
389
|
+
drawWalls(c, world, palette, daylight);
|
|
390
|
+
for (const item of world.furniture)
|
|
391
|
+
if (item.type === "rug")
|
|
392
|
+
painterFor(world).furniture(c, item, world, 0, palette);
|
|
393
|
+
for (const item of world.furniture)
|
|
394
|
+
if (specOf(world, item).wall)
|
|
395
|
+
painterFor(world).furniture(c, item, world, 0, palette);
|
|
396
|
+
// Light from the windows falling across the floor.
|
|
397
|
+
if (daylight.sun !== "rgba(0,0,0,0)") {
|
|
398
|
+
c.fillStyle = daylight.sun;
|
|
399
|
+
for (const room of world.rooms) {
|
|
400
|
+
for (let tx = room.x; tx < room.x + room.w; tx++) {
|
|
401
|
+
if (tx % 4 !== 3)
|
|
402
|
+
continue;
|
|
403
|
+
c.fillRect(tx * TILE + 2, room.y * TILE, 12, Math.min(room.h, 3) * TILE);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
329
406
|
}
|
|
330
407
|
}
|
|
331
408
|
function nameTag(c, agent, selected, width) {
|
|
332
|
-
|
|
333
|
-
c.font = "bold 7px ui-monospace, Menlo, Consolas, monospace";
|
|
409
|
+
c.font = LABEL_FONT;
|
|
334
410
|
c.textBaseline = "top";
|
|
335
|
-
const w = Math.ceil(c.measureText(name).width) + 5;
|
|
336
|
-
const
|
|
337
|
-
const
|
|
338
|
-
const y = Math.round(agent.y * TILE + 15);
|
|
411
|
+
const w = Math.ceil(c.measureText(agent.name).width) + 5;
|
|
412
|
+
const x = Math.round(Math.max(1, Math.min(width - w - 1, agent.x * TILE + 8 - w / 2)));
|
|
413
|
+
const y = Math.round(agent.y * TILE + 16);
|
|
339
414
|
c.fillStyle = selected ? "#ffffff" : "rgba(17,24,39,0.82)";
|
|
340
415
|
c.fillRect(x, y, w, 10);
|
|
341
416
|
c.fillStyle = selected ? "#111827" : "#ffffff";
|
|
342
|
-
c.fillText(name, x + 2.5, y + 1.5);
|
|
417
|
+
c.fillText(agent.name, x + 2.5, y + 1.5);
|
|
343
418
|
}
|
|
344
419
|
function wrap(c, text, max) {
|
|
345
420
|
const lines = [];
|
|
@@ -359,60 +434,86 @@ function wrap(c, text, max) {
|
|
|
359
434
|
return lines.slice(0, 3);
|
|
360
435
|
}
|
|
361
436
|
const STATE_LABEL = { working: "Working", thinking: "Thinking", blocked: "Blocked", reviewing: "Reviewing", idle: "On a break", done: "Finished", away: "Away" };
|
|
362
|
-
function drawCard(c, agent,
|
|
363
|
-
c.font =
|
|
437
|
+
function drawCard(c, agent, world) {
|
|
438
|
+
c.font = BODY_FONT;
|
|
364
439
|
c.textBaseline = "top";
|
|
365
|
-
const
|
|
366
|
-
const
|
|
367
|
-
const
|
|
368
|
-
const
|
|
369
|
-
const
|
|
370
|
-
|
|
440
|
+
const room = world.rooms.find((candidate) => candidate.id === agent.area);
|
|
441
|
+
const body = wrap(c, agent.task || STATE_LABEL[agent.state] || "", 110);
|
|
442
|
+
const w = 120;
|
|
443
|
+
const h = 18 + body.length * 9 + (room ? 9 : 0) + 3;
|
|
444
|
+
const x = Math.round(Math.max(2, Math.min(world.cols * TILE - w - 2, agent.x * TILE + 8 - w / 2)));
|
|
445
|
+
const y = Math.round(Math.max(2, agent.y * TILE - h - 24));
|
|
446
|
+
rect(c, OUTLINE, x - 1, y - 1, w + 2, h + 2);
|
|
371
447
|
rect(c, "#ffffff", x, y, w, h);
|
|
372
448
|
c.fillStyle = "#111827";
|
|
373
|
-
c.font =
|
|
374
|
-
c.fillText(`${agent.name}: ${STATE_LABEL[agent.state] ?? ""}`.slice(0,
|
|
449
|
+
c.font = LABEL_FONT;
|
|
450
|
+
c.fillText(`${agent.name}: ${STATE_LABEL[agent.state] ?? ""}`.slice(0, 28), x + 4, y + 4);
|
|
375
451
|
c.fillStyle = "#374151";
|
|
376
|
-
c.font =
|
|
452
|
+
c.font = BODY_FONT;
|
|
377
453
|
body.forEach((line, index) => c.fillText(line, x + 4, y + 15 + index * 9));
|
|
454
|
+
if (room) {
|
|
455
|
+
c.fillStyle = "#6b7280";
|
|
456
|
+
c.fillText(`In ${room.name}`.slice(0, 28), x + 4, y + 15 + body.length * 9);
|
|
457
|
+
}
|
|
378
458
|
}
|
|
379
459
|
function drawProgress(c, world, palette) {
|
|
380
460
|
if (world.tasks.length === 0)
|
|
381
461
|
return;
|
|
382
462
|
const done = world.tasks.filter((task) => task.state === "done").length;
|
|
383
|
-
|
|
384
|
-
c.font = "bold 7px ui-monospace, Menlo, Consolas, monospace";
|
|
463
|
+
c.font = LABEL_FONT;
|
|
385
464
|
c.textBaseline = "top";
|
|
386
465
|
const w = 96;
|
|
387
466
|
rect(c, "rgba(17,24,39,0.85)", 4, 4, w, 20);
|
|
388
467
|
c.fillStyle = "#ffffff";
|
|
389
|
-
c.fillText(
|
|
468
|
+
c.fillText(`${world.pack.words.quests} ${done}/${world.tasks.length}`, 8, 6.5);
|
|
390
469
|
rect(c, "#374151", 8, 16, w - 8, 4);
|
|
391
470
|
rect(c, palette.primary, 8, 16, Math.round(((w - 8) * done) / world.tasks.length), 4);
|
|
392
471
|
}
|
|
393
|
-
|
|
472
|
+
/** Everything that moves. `building` is the layer from drawBuilding. */
|
|
473
|
+
export function drawWorld(c, world, time, palette, daylight, building, selected) {
|
|
394
474
|
const width = world.cols * TILE;
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
475
|
+
const height = world.rows * TILE;
|
|
476
|
+
if (building)
|
|
477
|
+
c.drawImage(building, 0, 0, width, height);
|
|
478
|
+
else
|
|
479
|
+
drawBuilding(c, world, palette, daylight);
|
|
480
|
+
// On the floor, so people and furniture stay in front of the lines.
|
|
481
|
+
if (world.showLinks && world.links.length > 0) {
|
|
482
|
+
world.routes ??= linkRoutes(world);
|
|
483
|
+
drawLinks(c, world.routes, time);
|
|
484
|
+
}
|
|
485
|
+
const painter = painterFor(world);
|
|
403
486
|
const scene = [];
|
|
404
487
|
for (const item of world.furniture) {
|
|
405
|
-
|
|
488
|
+
const spec = specOf(world, item);
|
|
489
|
+
if (spec.solid === false)
|
|
406
490
|
continue;
|
|
407
|
-
scene.push({ depth: (item.y +
|
|
491
|
+
scene.push({ depth: (item.y + spec.h) * TILE - 2, draw: () => painterFor(world).furniture(c, item, world, time, palette) });
|
|
408
492
|
}
|
|
409
493
|
const shown = world.agents.filter((agent) => agent.state !== "away");
|
|
410
|
-
for (const agent of shown)
|
|
494
|
+
for (const agent of shown)
|
|
411
495
|
scene.push({ depth: agent.y * TILE + 14, draw: () => drawAgent(c, agent, time, agent.id === selected) });
|
|
412
|
-
}
|
|
413
496
|
scene.sort((a, b) => a.depth - b.depth);
|
|
414
497
|
for (const item of scene)
|
|
415
498
|
item.draw();
|
|
499
|
+
// Night: the room dims, and screens and lamps light it.
|
|
500
|
+
if (daylight.night > 0) {
|
|
501
|
+
c.fillStyle = `rgba(12,14,48,${daylight.night})`;
|
|
502
|
+
c.fillRect(0, 0, width, height);
|
|
503
|
+
c.save();
|
|
504
|
+
c.globalCompositeOperation = "lighter";
|
|
505
|
+
const glow = (x, y, radius, color) => {
|
|
506
|
+
const gradient = c.createRadialGradient(x, y, 1, x, y, radius);
|
|
507
|
+
gradient.addColorStop(0, color);
|
|
508
|
+
gradient.addColorStop(1, "rgba(0,0,0,0)");
|
|
509
|
+
c.fillStyle = gradient;
|
|
510
|
+
c.fillRect(x - radius, y - radius, radius * 2, radius * 2);
|
|
511
|
+
};
|
|
512
|
+
for (const item of world.furniture)
|
|
513
|
+
painter.glow(item, world, glow);
|
|
514
|
+
c.restore();
|
|
515
|
+
}
|
|
516
|
+
// On top, so tall furniture against a wall can't hide a room's name.
|
|
416
517
|
drawLabels(c, world);
|
|
417
518
|
for (const agent of shown)
|
|
418
519
|
nameTag(c, agent, agent.id === selected, width);
|
|
@@ -422,6 +523,6 @@ export function drawWorld(c, world, time, palette, selected) {
|
|
|
422
523
|
drawProgress(c, world, palette);
|
|
423
524
|
const picked = shown.find((agent) => agent.id === selected);
|
|
424
525
|
if (picked)
|
|
425
|
-
drawCard(c, picked,
|
|
526
|
+
drawCard(c, picked, world);
|
|
426
527
|
}
|
|
427
528
|
//# sourceMappingURL=draw.js.map
|