@blkc-code/render-canvas 0.1.0 → 0.3.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 +13 -3
- package/dist/draw.d.ts.map +1 -1
- package/dist/draw.js +529 -196
- package/dist/draw.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/office.d.ts +23 -2
- package/dist/office.d.ts.map +1 -1
- package/dist/office.js +132 -20
- package/dist/office.js.map +1 -1
- package/dist/runtime.js +1 -1
- package/dist/runtime.js.map +1 -1
- package/dist/runtime.txt +1 -1
- package/dist/types.d.ts +9 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/world.d.ts +15 -3
- package/dist/world.d.ts.map +1 -1
- package/dist/world.js +138 -32
- package/dist/world.js.map +1 -1
- package/package.json +1 -1
- package/src/draw.ts +489 -193
- package/src/index.ts +1 -1
- package/src/office.ts +139 -21
- package/src/runtime.ts +4 -1
- package/src/types.ts +20 -4
- package/src/world.ts +139 -36
package/dist/draw.js
CHANGED
|
@@ -1,90 +1,173 @@
|
|
|
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
6
|
import { FURNITURE, isSeated } from "./world.js";
|
|
6
7
|
export const TILE = 16;
|
|
8
|
+
const LABEL_FONT = "bold 7px ui-monospace, Menlo, Consolas, monospace";
|
|
9
|
+
const BODY_FONT = "7px ui-monospace, Menlo, Consolas, monospace";
|
|
10
|
+
const OUTLINE = "#14141f";
|
|
7
11
|
const rect = (c, color, x, y, w, h) => {
|
|
8
12
|
c.fillStyle = color;
|
|
9
13
|
c.fillRect(Math.round(x), Math.round(y), w, h);
|
|
10
14
|
};
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
const BODY_SIDE = ["...tttttt...", "...tttttt...", "...ttsttt...", "...TTsTTT..."];
|
|
17
|
-
const LEGS_STAND = ["..llllllll..", "..lll..lll..", "..lll..lll..", "..bbb..bbb.."];
|
|
18
|
-
const LEGS_A = ["..llllllll..", "..lll..lll..", "..bbb..lll..", ".......bbb.."];
|
|
19
|
-
const LEGS_B = ["..llllllll..", "..lll..lll..", "..lll..bbb..", "..bbb......."];
|
|
20
|
-
const LEGS_SIDE_STAND = ["...llllll...", "...lll.lll..", "...lll.lll..", "...bbb.bbb.."];
|
|
21
|
-
const LEGS_SIDE_A = ["...llllll...", "..lll..lll..", ".lll....lll.", ".bb......bb."];
|
|
22
|
-
const LEGS_SIDE_B = ["...llllll...", "...llllll...", "....llll....", "....bbbb...."];
|
|
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
|
-
});
|
|
32
|
-
}
|
|
33
|
-
function shade(hex, amount) {
|
|
34
|
-
const n = parseInt(hex.slice(1).padEnd(6, hex.slice(-1)), 16);
|
|
15
|
+
function shade(color, amount) {
|
|
16
|
+
if (!color.startsWith("#"))
|
|
17
|
+
return color;
|
|
18
|
+
const hex = color.length === 4 ? `#${color[1]}${color[1]}${color[2]}${color[2]}${color[3]}${color[3]}` : color;
|
|
19
|
+
const n = parseInt(hex.slice(1, 7), 16);
|
|
35
20
|
const channel = (shift) => Math.max(0, Math.min(255, ((n >> shift) & 255) + amount));
|
|
36
21
|
return `rgb(${channel(16)},${channel(8)},${channel(0)})`;
|
|
37
22
|
}
|
|
23
|
+
export function daylightAt(hour) {
|
|
24
|
+
if (hour >= 7 && hour < 17)
|
|
25
|
+
return { sky: "#8ed0ff", sun: "rgba(255,244,214,0.10)", night: 0 };
|
|
26
|
+
if (hour >= 17 && hour < 20)
|
|
27
|
+
return { sky: "#ffae70", sun: "rgba(255,170,110,0.13)", night: 0.14 };
|
|
28
|
+
if (hour >= 5 && hour < 7)
|
|
29
|
+
return { sky: "#ffc9a3", sun: "rgba(255,200,170,0.08)", night: 0.16 };
|
|
30
|
+
return { sky: "#16204a", sun: "rgba(0,0,0,0)", night: 0.24 };
|
|
31
|
+
}
|
|
32
|
+
/** Parts draw with one outline around the whole figure, so characters read clearly on any floor. */
|
|
33
|
+
function drawParts(c, parts) {
|
|
34
|
+
c.fillStyle = OUTLINE;
|
|
35
|
+
for (const [, x, y, w, h] of parts)
|
|
36
|
+
c.fillRect(Math.round(x) - 1, Math.round(y) - 1, w + 2, h + 2);
|
|
37
|
+
for (const [color, x, y, w, h] of parts)
|
|
38
|
+
rect(c, color, x, y, w, h);
|
|
39
|
+
}
|
|
38
40
|
function drawAgent(c, agent, time, selected) {
|
|
39
|
-
const
|
|
41
|
+
const left = agent.x * TILE + 2; // sprite is 12 wide inside a 16 tile
|
|
42
|
+
const feet = agent.y * TILE + 15;
|
|
40
43
|
const seated = isSeated(agent);
|
|
41
|
-
const
|
|
42
|
-
const
|
|
43
|
-
const
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
const
|
|
44
|
+
const walking = agent.walking;
|
|
45
|
+
const side = agent.facing === "left" || agent.facing === "right";
|
|
46
|
+
const back = agent.facing === "up";
|
|
47
|
+
const flip = agent.facing === "left";
|
|
48
|
+
const phase = Math.floor(time * 9 + (agent.seed % 5));
|
|
49
|
+
const step = walking ? phase % 4 : 0; // 0 pass, 1 left forward, 2 pass, 3 right forward
|
|
50
|
+
const bob = walking && step % 2 === 1 ? -1 : 0;
|
|
51
|
+
const breathe = !walking && !seated && Math.floor(time * 1.4 + (agent.seed % 7)) % 2 === 0 ? 1 : 0;
|
|
52
|
+
const party = agent.party > 0 ? -Math.abs(Math.round(Math.sin(time * 12) * 3)) : 0;
|
|
53
|
+
const skin = agent.skin;
|
|
54
|
+
const hair = agent.hair;
|
|
55
|
+
const shirt = agent.shirt;
|
|
56
|
+
const dark = shade(shirt, -40);
|
|
57
|
+
const light = shade(shirt, 24);
|
|
58
|
+
const legs = "#39415a";
|
|
59
|
+
const shoe = "#1f2433";
|
|
60
|
+
const style = agent.seed % 5; // short, long, bun, spiky, cap
|
|
61
|
+
const outfit = (agent.seed >>> 5) % 3; // tee, hoodie, shirt and tie
|
|
62
|
+
// Shadow, and a highlight under whoever is selected.
|
|
63
|
+
c.fillStyle = "rgba(0,0,0,0.28)";
|
|
64
|
+
c.fillRect(Math.round(left + 1), Math.round(feet - 1), 10, 2);
|
|
65
|
+
if (selected) {
|
|
66
|
+
c.fillStyle = "rgba(255,255,255,0.45)";
|
|
67
|
+
c.fillRect(Math.round(left - 2), Math.round(feet - 2), 16, 4);
|
|
68
|
+
}
|
|
69
|
+
if (agent.state === "blocked") {
|
|
70
|
+
c.fillStyle = Math.floor(time * 3) % 2 === 0 ? "rgba(239,68,68,0.55)" : "rgba(239,68,68,0.22)";
|
|
71
|
+
c.fillRect(Math.round(left - 2), Math.round(feet - 2), 16, 4);
|
|
72
|
+
}
|
|
73
|
+
const lift = bob + party;
|
|
74
|
+
const parts = [];
|
|
75
|
+
const bodyTop = feet - (seated ? 13 : 13) + lift + breathe * 0;
|
|
76
|
+
const headTop = bodyTop - 8 + breathe;
|
|
77
|
+
// Chair behind someone sitting at a desk.
|
|
47
78
|
if (seated) {
|
|
48
|
-
rect(c, "#1f2937",
|
|
49
|
-
rect(c, "#374151",
|
|
50
|
-
rect(c, "#111827",
|
|
79
|
+
rect(c, "#1f2937", left + 1, agent.y * TILE + 3, 10, 9);
|
|
80
|
+
rect(c, "#374151", left + 2, agent.y * TILE + 11, 8, 3);
|
|
81
|
+
rect(c, "#111827", left + 5, agent.y * TILE + 14, 2, 2);
|
|
51
82
|
}
|
|
52
|
-
//
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
83
|
+
// Legs
|
|
84
|
+
if (!seated) {
|
|
85
|
+
const legTop = bodyTop + 7;
|
|
86
|
+
if (side) {
|
|
87
|
+
const swing = step === 1 ? 2 : step === 3 ? -2 : 0;
|
|
88
|
+
parts.push([legs, left + 4 + swing, legTop, 3, 5], [shoe, left + 4 + swing + (flip ? -1 : 0), legTop + 5, 4, 1]);
|
|
89
|
+
parts.push([shade(legs, -12), left + 5 - swing, legTop, 3, 5], [shoe, left + 5 - swing + (flip ? -1 : 0), legTop + 5, 4, 1]);
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
const leftUp = step === 1 ? 1 : 0;
|
|
93
|
+
const rightUp = step === 3 ? 1 : 0;
|
|
94
|
+
parts.push([legs, left + 2, legTop, 3, 5 - leftUp], [shoe, left + 2, legTop + 5 - leftUp, 3, 1]);
|
|
95
|
+
parts.push([legs, left + 7, legTop, 3, 5 - rightUp], [shoe, left + 7, legTop + 5 - rightUp, 3, 1]);
|
|
96
|
+
}
|
|
58
97
|
}
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
98
|
+
// Body and arms
|
|
99
|
+
const swingArm = walking ? (step === 1 ? 1 : step === 3 ? -1 : 0) : 0;
|
|
100
|
+
if (side) {
|
|
101
|
+
parts.push([shirt, left + 3, bodyTop, 6, 7], [dark, left + 3, bodyTop + 6, 6, 1]);
|
|
102
|
+
parts.push([dark, left + 5 + swingArm, bodyTop + 1, 2, 5], [skin, left + 5 + swingArm, bodyTop + 6, 2, 1]);
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
parts.push([shirt, left + 1, bodyTop, 10, 7], [dark, left + 1, bodyTop + 6, 10, 1]);
|
|
106
|
+
const typing = seated && agent.state === "working";
|
|
107
|
+
const type = Math.floor(time * 10 + agent.seed) % 2;
|
|
108
|
+
parts.push([dark, left - 1, bodyTop + 1 + swingArm + (typing ? -type : 0), 2, 5]);
|
|
109
|
+
parts.push([dark, left + 11, bodyTop + 1 - swingArm + (typing ? type - 1 : 0), 2, 5]);
|
|
110
|
+
parts.push([skin, left - 1, bodyTop + 6 + swingArm + (typing ? -type : 0), 2, 1]);
|
|
111
|
+
parts.push([skin, left + 11, bodyTop + 6 - swingArm + (typing ? type - 1 : 0), 2, 1]);
|
|
112
|
+
if (!back && outfit === 2)
|
|
113
|
+
parts.push(["#f3f4f6", left + 4, bodyTop, 4, 3], [agent.seed % 2 ? "#ef4444" : "#1d4ed8", left + 5, bodyTop, 2, 5]);
|
|
114
|
+
if (!back && outfit === 1)
|
|
115
|
+
parts.push([light, left + 3, bodyTop, 6, 2], [dark, left + 5, bodyTop + 2, 2, 4]);
|
|
116
|
+
if (back && outfit === 1)
|
|
117
|
+
parts.push([light, left + 2, bodyTop, 8, 3]);
|
|
118
|
+
}
|
|
119
|
+
// Head
|
|
120
|
+
parts.push([skin, left + 1, headTop, 10, 8]);
|
|
121
|
+
if (back) {
|
|
122
|
+
parts.push([hair, left + 1, headTop, 10, 7]);
|
|
123
|
+
}
|
|
124
|
+
else if (side) {
|
|
125
|
+
const backX = flip ? left + 7 : left + 1;
|
|
126
|
+
parts.push([hair, left + 1, headTop, 10, 3], [hair, backX, headTop, 4, 6]);
|
|
127
|
+
const blink = Math.floor(time * 0.7 + agent.seed % 11) % 7 === 0;
|
|
128
|
+
parts.push([blink ? shade(skin, -40) : "#1a1a24", flip ? left + 3 : left + 8, headTop + 4, 1, blink ? 1 : 2]);
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
parts.push([hair, left + 1, headTop, 10, 3], [hair, left + 1, headTop + 3, 1, 2], [hair, left + 10, headTop + 3, 1, 2]);
|
|
132
|
+
const blink = Math.floor(time * 0.7 + agent.seed % 11) % 7 === 0;
|
|
133
|
+
parts.push([blink ? shade(skin, -40) : "#1a1a24", left + 3, headTop + 4, 2, blink ? 1 : 2]);
|
|
134
|
+
parts.push([blink ? shade(skin, -40) : "#1a1a24", left + 7, headTop + 4, 2, blink ? 1 : 2]);
|
|
135
|
+
parts.push([shade(skin, -28), left + 5, headTop + 6, 2, 1]);
|
|
136
|
+
}
|
|
137
|
+
if (style === 1)
|
|
138
|
+
parts.push([hair, left, headTop + 2, 2, 8], [hair, left + 10, headTop + 2, 2, 8]); // long
|
|
139
|
+
if (style === 2)
|
|
140
|
+
parts.push([hair, left + 4, headTop - 3, 4, 3]); // bun
|
|
141
|
+
if (style === 3)
|
|
142
|
+
parts.push([hair, left + 2, headTop - 2, 2, 2], [hair, left + 5, headTop - 3, 2, 3], [hair, left + 8, headTop - 2, 2, 2]); // spiky
|
|
143
|
+
if (style === 4) { // cap
|
|
144
|
+
parts.push([light, left, headTop - 1, 12, 3]);
|
|
145
|
+
if (!back && !side)
|
|
146
|
+
parts.push([dark, left + 1, headTop + 2, 10, 1]);
|
|
147
|
+
if (side)
|
|
148
|
+
parts.push([dark, flip ? left - 2 : left + 9, headTop + 1, 5, 1]);
|
|
149
|
+
}
|
|
150
|
+
drawParts(c, parts);
|
|
151
|
+
if (agent.party > 0) {
|
|
152
|
+
const colors = ["#f59e0b", "#ec4899", "#22c55e", "#3b82f6", "#a855f7"];
|
|
153
|
+
for (let i = 0; i < 10; i++) {
|
|
154
|
+
const t = (time * 1.1 + i * 0.137) % 1;
|
|
155
|
+
const angle = i * 2.4 + agent.seed;
|
|
156
|
+
rect(c, colors[i % colors.length], left + 6 + Math.cos(angle) * (4 + t * 14), feet - 22 - t * 6 + t * t * 26, 2, 2);
|
|
157
|
+
}
|
|
75
158
|
}
|
|
76
159
|
}
|
|
77
160
|
function bubble(c, x, y, draw) {
|
|
78
|
-
rect(c,
|
|
161
|
+
rect(c, OUTLINE, x - 1, y - 1, 13, 11);
|
|
79
162
|
rect(c, "#ffffff", x, y, 11, 9);
|
|
80
163
|
rect(c, "#ffffff", x + 4, y + 9, 3, 1);
|
|
81
|
-
rect(c,
|
|
164
|
+
rect(c, OUTLINE, x + 4, y + 10, 3, 1);
|
|
82
165
|
draw(x, y);
|
|
83
166
|
}
|
|
84
167
|
function drawStatus(c, agent, time) {
|
|
85
168
|
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;
|
|
169
|
+
const y = Math.round(agent.y * TILE - 22 + (isSeated(agent) ? 2 : -2));
|
|
170
|
+
const pulse = Math.floor(time * 2 + (agent.seed % 3)) % 2 === 0;
|
|
88
171
|
if (agent.state === "blocked") {
|
|
89
172
|
bubble(c, x, y, (bx, by) => {
|
|
90
173
|
rect(c, pulse ? "#dc2626" : "#991b1b", bx + 5, by + 1, 2, 5);
|
|
@@ -115,116 +198,233 @@ function drawStatus(c, agent, time) {
|
|
|
115
198
|
rect(c, color, bx + 4, by + 2, 3, 5);
|
|
116
199
|
});
|
|
117
200
|
}
|
|
201
|
+
else if (agent.state === "idle" && !agent.walking && agent.seed % 3 === 0) {
|
|
202
|
+
bubble(c, x, y, (bx, by) => {
|
|
203
|
+
rect(c, "#92400e", bx + 3, by + 3, 4, 4);
|
|
204
|
+
rect(c, "#d97706", bx + 3, by + 3, 4, 1);
|
|
205
|
+
rect(c, "#92400e", bx + 7, by + 4, 1, 2);
|
|
206
|
+
if (pulse)
|
|
207
|
+
rect(c, "#9ca3af", bx + 4, by + 1, 1, 1);
|
|
208
|
+
});
|
|
209
|
+
}
|
|
118
210
|
}
|
|
119
|
-
// --- The
|
|
120
|
-
function drawFloor(c, room
|
|
211
|
+
// --- The building ----------------------------------------------------------------------------------------
|
|
212
|
+
function drawFloor(c, room) {
|
|
121
213
|
for (let ty = room.y; ty < room.y + room.h; ty++) {
|
|
122
214
|
for (let tx = room.x; tx < room.x + room.w; tx++) {
|
|
123
215
|
const x = tx * TILE;
|
|
124
216
|
const y = ty * TILE;
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
rect(c, "#
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
217
|
+
const noise = (tx * 73 + ty * 151) % 17;
|
|
218
|
+
switch (room.floor) {
|
|
219
|
+
case "carpet":
|
|
220
|
+
rect(c, "#4c4f7a", x, y, TILE, TILE);
|
|
221
|
+
for (let i = 0; i < 8; i++)
|
|
222
|
+
rect(c, i % 2 ? "#55588a" : "#454874", x + ((tx * 7 + i * 5) % 15), y + ((ty * 3 + i * 7) % 15), 1, 1);
|
|
223
|
+
break;
|
|
224
|
+
case "tile":
|
|
225
|
+
rect(c, (tx + ty) % 2 === 0 ? "#d6dee8" : "#c2ccd8", x, y, TILE, TILE);
|
|
226
|
+
rect(c, "#aab5c2", x, y + 15, TILE, 1);
|
|
227
|
+
rect(c, "#aab5c2", x + 15, y, 1, TILE);
|
|
228
|
+
rect(c, "#e8eef5", x + 1, y + 1, 5, 1);
|
|
229
|
+
break;
|
|
230
|
+
case "stone":
|
|
231
|
+
rect(c, noise % 3 === 0 ? "#8d93a3" : "#9aa0b0", x, y, TILE, TILE);
|
|
232
|
+
rect(c, "#7a8090", x, y + 15, TILE, 1);
|
|
233
|
+
rect(c, "#7a8090", x + (ty % 2 ? 7 : 15), y, 1, 15);
|
|
234
|
+
rect(c, "#adb3c2", x + 2, y + 2, 3, 1);
|
|
235
|
+
break;
|
|
236
|
+
case "concrete":
|
|
237
|
+
rect(c, "#6b7280", x, y, TILE, TILE);
|
|
238
|
+
for (let i = 0; i < 5; i++)
|
|
239
|
+
rect(c, i % 2 ? "#747b89" : "#636a78", x + ((tx * 11 + i * 3) % 15), y + ((ty * 5 + i * 5) % 15), 1, 1);
|
|
240
|
+
if (noise === 3)
|
|
241
|
+
rect(c, "#596070", x + 3, y + 8, 8, 1);
|
|
242
|
+
break;
|
|
243
|
+
case "grass":
|
|
244
|
+
rect(c, "#3f8f4f", x, y, TILE, TILE);
|
|
245
|
+
for (let i = 0; i < 7; i++)
|
|
246
|
+
rect(c, i % 3 ? "#4aa35c" : "#347a43", x + ((tx * 5 + i * 9) % 15), y + ((ty * 7 + i * 3) % 14), 1, 2);
|
|
247
|
+
break;
|
|
248
|
+
default: {
|
|
249
|
+
const plank = ty % 2 === 0 ? "#a57448" : "#9c6c42";
|
|
250
|
+
rect(c, plank, x, y, TILE, TILE);
|
|
251
|
+
rect(c, "#8a5d36", x, y + 7, TILE, 1);
|
|
252
|
+
rect(c, "#8a5d36", x, y + 15, TILE, 1);
|
|
253
|
+
rect(c, "#8a5d36", x + ((tx * 8 + (ty % 2 ? 8 : 0)) % 16), y, 1, 7);
|
|
254
|
+
rect(c, "#8a5d36", x + ((tx * 8 + (ty % 2 ? 0 : 8)) % 16), y + 8, 1, 7);
|
|
255
|
+
rect(c, "#b4835a", x + (noise % 9) + 2, y + 3, 4, 1);
|
|
256
|
+
rect(c, "#b4835a", x + (noise % 7) + 4, y + 11, 3, 1);
|
|
257
|
+
}
|
|
142
258
|
}
|
|
143
259
|
}
|
|
144
260
|
}
|
|
145
|
-
void palette;
|
|
146
261
|
}
|
|
147
|
-
const WALL_TOP = "#
|
|
148
|
-
const WALL_FACE = "#
|
|
149
|
-
const
|
|
150
|
-
|
|
262
|
+
const WALL_TOP = "#2a2d48";
|
|
263
|
+
const WALL_FACE = "#5c628f";
|
|
264
|
+
const WALL_DARK = "#4a4f78";
|
|
265
|
+
const WALL_LIGHT = "#6d74a4";
|
|
266
|
+
function drawWalls(c, world, palette, daylight) {
|
|
151
267
|
const open = new Set();
|
|
152
268
|
for (const room of world.rooms) {
|
|
153
269
|
for (let y = room.y; y < room.y + room.h; y++)
|
|
154
270
|
for (let x = room.x; x < room.x + room.w; x++)
|
|
155
271
|
open.add(`${x},${y}`);
|
|
156
272
|
}
|
|
157
|
-
|
|
158
|
-
open.add(`${door.x},${door.y}`);
|
|
273
|
+
const doors = new Set(world.doors.map((door) => `${door.x},${door.y}`));
|
|
159
274
|
for (let ty = 0; ty < world.rows; ty++) {
|
|
160
275
|
for (let tx = 0; tx < world.cols; tx++) {
|
|
161
|
-
|
|
276
|
+
const key = `${tx},${ty}`;
|
|
277
|
+
if (open.has(key) || doors.has(key))
|
|
162
278
|
continue;
|
|
163
279
|
const x = tx * TILE;
|
|
164
280
|
const y = ty * TILE;
|
|
165
|
-
|
|
166
|
-
if (faceBelow) {
|
|
167
|
-
// The wall's front, with a band in the Kit's colour and a window every few tiles.
|
|
281
|
+
if (open.has(`${tx},${ty + 1}`)) {
|
|
168
282
|
rect(c, WALL_FACE, x, y, TILE, TILE);
|
|
169
|
-
rect(c,
|
|
170
|
-
rect(c,
|
|
171
|
-
|
|
283
|
+
rect(c, WALL_LIGHT, x, y, TILE, 1);
|
|
284
|
+
rect(c, WALL_DARK, x, y + 12, TILE, 4);
|
|
285
|
+
rect(c, palette.primary, x, y + 11, TILE, 1);
|
|
286
|
+
if (tx % 4 === 3 && !world.furniture.some((item) => FURNITURE[item.type].wall && item.x === tx && item.y === ty)) {
|
|
287
|
+
// A window, in the colour of the sky right now.
|
|
172
288
|
rect(c, "#1f2937", x + 1, y + 1, 14, 9);
|
|
173
|
-
rect(c,
|
|
174
|
-
rect(c,
|
|
289
|
+
rect(c, daylight.sky, x + 2, y + 2, 12, 7);
|
|
290
|
+
rect(c, shade(daylight.sky, 40), x + 3, y + 3, 4, 2);
|
|
175
291
|
rect(c, "#1f2937", x + 8, y + 2, 1, 7);
|
|
292
|
+
if (daylight.night > 0.3) {
|
|
293
|
+
rect(c, "#fef3c7", x + 4, y + 3, 1, 1);
|
|
294
|
+
rect(c, "#fef3c7", x + 11, y + 5, 1, 1);
|
|
295
|
+
}
|
|
176
296
|
}
|
|
177
297
|
}
|
|
178
298
|
else {
|
|
179
299
|
rect(c, WALL_TOP, x, y, TILE, TILE);
|
|
300
|
+
if (open.has(`${tx},${ty - 1}`))
|
|
301
|
+
rect(c, "#20233a", x, y, TILE, 3);
|
|
302
|
+
if (open.has(`${tx - 1},${ty}`))
|
|
303
|
+
rect(c, "#20233a", x, y, 2, TILE);
|
|
304
|
+
if (open.has(`${tx + 1},${ty}`))
|
|
305
|
+
rect(c, "#20233a", x + 14, y, 2, TILE);
|
|
180
306
|
}
|
|
181
307
|
}
|
|
182
308
|
}
|
|
183
309
|
for (const door of world.doors) {
|
|
184
|
-
|
|
185
|
-
|
|
310
|
+
const x = door.x * TILE;
|
|
311
|
+
const y = door.y * TILE;
|
|
312
|
+
const horizontal = open.has(`${door.x},${door.y - 1}`) || open.has(`${door.x},${door.y + 1}`);
|
|
313
|
+
rect(c, "#7c5637", x, y, TILE, TILE);
|
|
314
|
+
for (let i = 2; i < 16; i += 4)
|
|
315
|
+
rect(c, "#6b4a2f", horizontal ? x : x + i, horizontal ? y + i : y, horizontal ? TILE : 1, horizontal ? 1 : TILE);
|
|
316
|
+
if (horizontal) {
|
|
317
|
+
rect(c, WALL_DARK, x, y, 2, TILE);
|
|
318
|
+
rect(c, WALL_DARK, x + 14, y, 2, TILE);
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
rect(c, WALL_DARK, x, y, TILE, 2);
|
|
322
|
+
rect(c, WALL_DARK, x, y + 14, TILE, 2);
|
|
323
|
+
}
|
|
186
324
|
}
|
|
187
325
|
}
|
|
188
|
-
function
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
326
|
+
function drawLabels(c, world) {
|
|
327
|
+
c.font = LABEL_FONT;
|
|
328
|
+
c.textBaseline = "top";
|
|
329
|
+
for (const room of world.rooms) {
|
|
330
|
+
if (!room.name)
|
|
331
|
+
continue;
|
|
332
|
+
const label = room.name.toUpperCase();
|
|
333
|
+
const width = Math.ceil(c.measureText(label).width) + 6;
|
|
334
|
+
const x = room.x * TILE + 3;
|
|
335
|
+
const y = room.y * TILE - 13;
|
|
336
|
+
rect(c, "#1c1f33", x, y, width, 10);
|
|
337
|
+
rect(c, "#2f3350", x + 1, y + 1, width - 2, 8);
|
|
338
|
+
c.fillStyle = "#f8fafc";
|
|
339
|
+
c.fillText(label, x + 3, y + 1.5);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
/** Everything that doesn't move. Redrawn only when the building or the hour changes. */
|
|
343
|
+
export function drawBuilding(c, world, palette, daylight) {
|
|
344
|
+
rect(c, WALL_TOP, 0, 0, world.cols * TILE, world.rows * TILE);
|
|
345
|
+
for (const room of world.rooms)
|
|
346
|
+
drawFloor(c, room);
|
|
347
|
+
drawWalls(c, world, palette, daylight);
|
|
348
|
+
for (const item of world.furniture)
|
|
349
|
+
if (item.type === "rug")
|
|
350
|
+
drawFurniture(c, item, world, 0, palette);
|
|
351
|
+
for (const item of world.furniture)
|
|
352
|
+
if (FURNITURE[item.type].wall)
|
|
353
|
+
drawFurniture(c, item, world, 0, palette);
|
|
354
|
+
// Light from the windows falling across the floor.
|
|
355
|
+
if (daylight.sun !== "rgba(0,0,0,0)") {
|
|
356
|
+
c.fillStyle = daylight.sun;
|
|
357
|
+
for (const room of world.rooms) {
|
|
358
|
+
for (let tx = room.x; tx < room.x + room.w; tx++) {
|
|
359
|
+
if (tx % 4 !== 3)
|
|
360
|
+
continue;
|
|
361
|
+
c.fillRect(tx * TILE + 2, room.y * TILE, 12, Math.min(room.h, 3) * TILE);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
function drawDesk(c, x, y, on, time, seed, palette, blocked) {
|
|
367
|
+
rect(c, "#55351d", x + 1, y + 22, 46, 8);
|
|
368
|
+
rect(c, "#6b4427", x + 1, y + 13, 46, 10);
|
|
369
|
+
rect(c, "#8a5a33", x + 1, y + 10, 46, 12);
|
|
370
|
+
rect(c, "#a87545", x + 1, y + 10, 46, 2);
|
|
371
|
+
rect(c, "#3b2413", x + 3, y + 22, 3, 9);
|
|
372
|
+
rect(c, "#3b2413", x + 42, y + 22, 3, 9);
|
|
373
|
+
rect(c, "#55351d", x + 30, y + 23, 12, 7);
|
|
374
|
+
rect(c, "#7c5637", x + 31, y + 24, 10, 2);
|
|
375
|
+
rect(c, "#d4a24e", x + 35, y + 24, 2, 1);
|
|
376
|
+
rect(c, "#7c5637", x + 31, y + 27, 10, 2);
|
|
377
|
+
rect(c, "#d4a24e", x + 35, y + 27, 2, 1);
|
|
194
378
|
// Monitor
|
|
195
|
-
rect(c,
|
|
379
|
+
rect(c, OUTLINE, x + 14, y - 3, 20, 15);
|
|
196
380
|
rect(c, "#374151", x + 22, y + 12, 4, 2);
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
rect(c,
|
|
381
|
+
rect(c, "#4b5563", x + 19, y + 14, 10, 1);
|
|
382
|
+
if (blocked) {
|
|
383
|
+
rect(c, "#3f0f14", x + 15, y - 2, 18, 13);
|
|
384
|
+
if (Math.floor(time * 2) % 2 === 0) {
|
|
385
|
+
rect(c, "#ef4444", x + 23, y, 2, 6);
|
|
386
|
+
rect(c, "#ef4444", x + 23, y + 7, 2, 2);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
else if (on) {
|
|
390
|
+
rect(c, "#0f2a2e", x + 15, y - 2, 18, 13);
|
|
391
|
+
const scroll = Math.floor(time * 3 + (seed % 13));
|
|
392
|
+
for (let line = 0; line < 5; line++) {
|
|
393
|
+
const width = 3 + (((seed >> line) & 7) + (scroll + line) * 7) % 12;
|
|
394
|
+
rect(c, line % 3 === 0 ? palette.primary : line % 3 === 1 ? "#7dd3fc" : "#fcd34d", x + 16 + (line % 2) * 2, y - 1 + line * 2.2, width, 1);
|
|
203
395
|
}
|
|
204
|
-
c.fillStyle = "rgba(125,211,252,0.
|
|
205
|
-
c.fillRect(x +
|
|
396
|
+
c.fillStyle = "rgba(125,211,252,0.12)";
|
|
397
|
+
c.fillRect(x + 10, y + 11, 28, 8);
|
|
206
398
|
}
|
|
207
399
|
else {
|
|
208
|
-
rect(c, "#1f2937", x +
|
|
209
|
-
rect(c, "#374151", x +
|
|
400
|
+
rect(c, "#1f2937", x + 15, y - 2, 18, 13);
|
|
401
|
+
rect(c, "#374151", x + 17, y, 3, 1);
|
|
210
402
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
rect(c,
|
|
403
|
+
rect(c, "#d1d5db", x + 16, y + 15, 16, 5);
|
|
404
|
+
for (let row = 0; row < 2; row++)
|
|
405
|
+
for (let key = 0; key < 7; key++)
|
|
406
|
+
rect(c, "#9ca3af", x + 17 + key * 2, y + 16 + row * 2, 1, 1);
|
|
407
|
+
rect(c, "#e5e7eb", x + 34, y + 16, 3, 4);
|
|
408
|
+
rect(c, "#f3f4f6", x + 40, y + 13, 5, 5);
|
|
409
|
+
rect(c, palette.primary, x + 40, y + 15, 5, 1);
|
|
410
|
+
rect(c, "#fde68a", x + 4, y + 13, 7, 6);
|
|
411
|
+
rect(c, "#fbbf24", x + 5, y + 15, 5, 1);
|
|
216
412
|
}
|
|
217
413
|
function drawBoard(c, x, y, world) {
|
|
218
|
-
rect(c, "#
|
|
219
|
-
rect(c, "#d9c29c", x + 2, y -
|
|
414
|
+
rect(c, "#3a2c1e", x, y - 12, 64, 27);
|
|
415
|
+
rect(c, "#d9c29c", x + 2, y - 10, 60, 23);
|
|
416
|
+
for (let i = 0; i < 40; i++)
|
|
417
|
+
rect(c, "#c9b08a", x + 3 + (i * 37) % 58, y - 9 + (i * 13) % 21, 1, 1);
|
|
220
418
|
const colors = { todo: "#fde68a", doing: "#93c5fd", done: "#86efac" };
|
|
221
419
|
const per = 8;
|
|
222
420
|
world.tasks.slice(0, per * 2).forEach((task, index) => {
|
|
223
421
|
const col = index % per;
|
|
224
422
|
const row = Math.floor(index / per);
|
|
225
|
-
rect(c, "#00000033", x + 5 + col * 7, y -
|
|
226
|
-
rect(c, colors[task.state], x + 4 + col * 7, y -
|
|
227
|
-
rect(c, "#
|
|
423
|
+
rect(c, "#00000033", x + 5 + col * 7, y - 7 + row * 10, 6, 8);
|
|
424
|
+
rect(c, colors[task.state], x + 4 + col * 7, y - 8 + row * 10, 6, 8);
|
|
425
|
+
rect(c, "#ef4444", x + 6 + col * 7, y - 8 + row * 10, 2, 1);
|
|
426
|
+
rect(c, "#00000040", x + 5 + col * 7, y - 5 + row * 10, 4, 1);
|
|
427
|
+
rect(c, "#00000040", x + 5 + col * 7, y - 3 + row * 10, 3, 1);
|
|
228
428
|
});
|
|
229
429
|
}
|
|
230
430
|
function drawFurniture(c, item, world, time, palette) {
|
|
@@ -234,112 +434,218 @@ function drawFurniture(c, item, world, time, palette) {
|
|
|
234
434
|
case "desk": {
|
|
235
435
|
const seatX = item.x + 1;
|
|
236
436
|
const seatY = item.y + 2;
|
|
237
|
-
const user = world.agents.find((a) => a.seat?.x === seatX && a.seat?.y === seatY && isSeated(a)
|
|
238
|
-
drawDesk(c, x, y, Boolean(user), time, user?.seed ?? 0, palette);
|
|
437
|
+
const user = world.agents.find((a) => a.seat?.x === seatX && a.seat?.y === seatY && isSeated(a));
|
|
438
|
+
drawDesk(c, x, y, Boolean(user), time, user?.seed ?? 0, palette, user?.state === "blocked");
|
|
239
439
|
break;
|
|
240
440
|
}
|
|
241
441
|
case "coffee":
|
|
242
442
|
rect(c, "#374151", x, y + 2, 32, 14);
|
|
243
443
|
rect(c, "#6b7280", x, y + 2, 32, 2);
|
|
244
|
-
rect(c,
|
|
245
|
-
rect(c, "#
|
|
444
|
+
rect(c, OUTLINE, x + 4, y - 9, 15, 15);
|
|
445
|
+
rect(c, "#27272a", x + 5, y - 8, 13, 13);
|
|
446
|
+
rect(c, Math.floor(time * 2) % 2 ? "#ef4444" : "#7f1d1d", x + 15, y - 6, 2, 2);
|
|
447
|
+
rect(c, "#52525b", x + 7, y - 6, 6, 4);
|
|
246
448
|
rect(c, "#f3f4f6", x + 8, y + 3, 5, 4);
|
|
247
|
-
rect(c, "#f3f4f6", x +
|
|
449
|
+
rect(c, "#f3f4f6", x + 23, y - 1, 5, 5);
|
|
450
|
+
rect(c, "#92400e", x + 24, y, 3, 1);
|
|
248
451
|
for (let i = 0; i < 3; i++) {
|
|
249
|
-
c.fillStyle = "rgba(255,255,255,0.
|
|
452
|
+
c.fillStyle = "rgba(255,255,255,0.5)";
|
|
250
453
|
c.fillRect(x + 9 + Math.round(Math.sin(time * 2 + i) * 1.5), y - 1 - ((time * 8 + i * 4) % 9), 1, 2);
|
|
251
454
|
}
|
|
252
455
|
break;
|
|
253
456
|
case "water":
|
|
254
|
-
rect(c, "#
|
|
255
|
-
rect(c, "#
|
|
256
|
-
rect(c, "#
|
|
457
|
+
rect(c, "#d1d5db", x + 3, y + 3, 10, 13);
|
|
458
|
+
rect(c, "#9ca3af", x + 3, y + 14, 10, 2);
|
|
459
|
+
rect(c, "#60a5fa", x + 4, y - 8, 8, 11);
|
|
460
|
+
rect(c, "#bae6fd", x + 5, y - 7, 2, 9);
|
|
257
461
|
rect(c, "#3b82f6", x + 5, y + 6, 2, 2);
|
|
462
|
+
rect(c, "#ef4444", x + 9, y + 6, 2, 2);
|
|
463
|
+
if (Math.floor(time * 0.4 + item.x) % 5 === 0)
|
|
464
|
+
rect(c, "#e0f2fe", x + 8, y - 2 - ((time * 6) % 7), 1, 1);
|
|
258
465
|
break;
|
|
259
466
|
case "sofa":
|
|
260
|
-
rect(c, palette.primary, x + 1, y -
|
|
261
|
-
rect(c,
|
|
467
|
+
rect(c, shade(palette.primary, -60), x + 1, y - 7, 46, 11);
|
|
468
|
+
rect(c, shade(palette.primary, -30), x + 2, y - 6, 44, 3);
|
|
262
469
|
rect(c, "#4b5563", x, y + 3, 48, 11);
|
|
263
470
|
rect(c, "#6b7280", x + 3, y + 3, 20, 7);
|
|
264
471
|
rect(c, "#6b7280", x + 25, y + 3, 20, 7);
|
|
265
472
|
rect(c, "#374151", x, y + 12, 48, 3);
|
|
473
|
+
rect(c, "#374151", x - 1, y - 2, 4, 16);
|
|
474
|
+
rect(c, "#374151", x + 45, y - 2, 4, 16);
|
|
266
475
|
break;
|
|
267
476
|
case "plant":
|
|
268
477
|
rect(c, "#92400e", x + 4, y + 9, 8, 7);
|
|
269
478
|
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]])
|
|
479
|
+
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
480
|
rect(c, "#15803d", x + lx, y + ly, lw, lh);
|
|
272
|
-
}
|
|
273
481
|
rect(c, "#22c55e", x + 7, y - 3, 2, 8);
|
|
274
482
|
rect(c, "#22c55e", x + 3, y + 1, 2, 3);
|
|
483
|
+
rect(c, "#4ade80", x + 10, y - 4, 1, 3);
|
|
484
|
+
break;
|
|
485
|
+
case "fern":
|
|
486
|
+
rect(c, "#57534e", x + 3, y + 10, 10, 6);
|
|
487
|
+
rect(c, "#78716c", x + 2, y + 9, 12, 2);
|
|
488
|
+
for (let i = 0; i < 7; i++) {
|
|
489
|
+
const lean = i - 3;
|
|
490
|
+
for (let j = 0; j < 5; j++)
|
|
491
|
+
rect(c, j % 2 ? "#16a34a" : "#15803d", x + 7 + lean * (j / 2 + 1), y + 8 - j * 2 + Math.abs(lean), 2, 2);
|
|
492
|
+
}
|
|
275
493
|
break;
|
|
276
494
|
case "board":
|
|
277
495
|
drawBoard(c, x, y, world);
|
|
278
496
|
break;
|
|
497
|
+
case "whiteboard":
|
|
498
|
+
rect(c, "#9ca3af", x, y - 10, 48, 22);
|
|
499
|
+
rect(c, "#f9fafb", x + 2, y - 8, 44, 18);
|
|
500
|
+
rect(c, "#3b82f6", x + 5, y - 5, 14, 1);
|
|
501
|
+
rect(c, "#3b82f6", x + 5, y - 2, 9, 1);
|
|
502
|
+
rect(c, "#ef4444", x + 24, y - 6, 8, 6);
|
|
503
|
+
rect(c, "#f9fafb", x + 25, y - 5, 6, 4);
|
|
504
|
+
rect(c, "#10b981", x + 8, y + 3, 20, 1);
|
|
505
|
+
rect(c, "#10b981", x + 27, y + 2, 1, 3);
|
|
506
|
+
rect(c, "#6b7280", x + 2, y + 10, 44, 2);
|
|
507
|
+
break;
|
|
279
508
|
case "shelf":
|
|
280
509
|
rect(c, "#5b3a21", x, y - 12, 32, 28);
|
|
281
510
|
for (let shelf = 0; shelf < 2; shelf++) {
|
|
282
511
|
rect(c, "#3b2413", x + 2, y - 10 + shelf * 13, 28, 11);
|
|
283
512
|
for (let book = 0; book < 8; book++) {
|
|
284
|
-
const color = ["#ef4444", "#3b82f6", "#10b981", "#f59e0b", "#8b5cf6"][(book * 3 + shelf) % 5];
|
|
513
|
+
const color = ["#ef4444", "#3b82f6", "#10b981", "#f59e0b", "#8b5cf6"][(book * 3 + shelf + item.x) % 5];
|
|
285
514
|
rect(c, color, x + 3 + book * 3, y - 8 + shelf * 13 + (book % 3), 2, 9 - (book % 3));
|
|
515
|
+
rect(c, shade(color, 40), x + 3 + book * 3, y - 6 + shelf * 13, 2, 1);
|
|
286
516
|
}
|
|
287
517
|
}
|
|
288
518
|
break;
|
|
289
519
|
case "server":
|
|
290
|
-
rect(c,
|
|
520
|
+
rect(c, OUTLINE, x, y - 13, 16, 29);
|
|
521
|
+
rect(c, "#1f2937", x + 1, y - 12, 14, 27);
|
|
291
522
|
for (let i = 0; i < 5; i++) {
|
|
292
|
-
rect(c, "#
|
|
523
|
+
rect(c, "#111827", x + 2, y - 11 + i * 5, 12, 4);
|
|
293
524
|
const on = Math.floor(time * 3 + i * 2 + item.x) % 3 !== 0;
|
|
294
525
|
rect(c, on ? "#22c55e" : "#14532d", x + 3, y - 10 + i * 5, 1, 1);
|
|
295
526
|
rect(c, i % 2 === 0 ? "#f59e0b" : "#3b82f6", x + 5, y - 10 + i * 5, 1, 1);
|
|
527
|
+
rect(c, "#374151", x + 8, y - 10 + i * 5, 5, 1);
|
|
296
528
|
}
|
|
297
529
|
break;
|
|
298
530
|
case "table":
|
|
299
|
-
rect(c, "#
|
|
531
|
+
rect(c, "#44403c", x + 2, y + 16, 44, 12);
|
|
300
532
|
rect(c, "#78716c", x + 2, y + 3, 44, 14);
|
|
301
533
|
rect(c, "#a8a29e", x + 2, y + 3, 44, 2);
|
|
302
534
|
rect(c, "#f9fafb", x + 10, y + 6, 8, 6);
|
|
303
535
|
rect(c, "#f9fafb", x + 28, y + 8, 8, 6);
|
|
536
|
+
rect(c, "#1f2937", x + 12, y + 8, 4, 1);
|
|
537
|
+
rect(c, "#3b82f6", x + 30, y + 10, 4, 1);
|
|
304
538
|
break;
|
|
305
539
|
case "rug":
|
|
306
540
|
rect(c, palette.primary, x, y, 64, 48);
|
|
307
|
-
c.fillStyle = "rgba(0,0,0,0.
|
|
541
|
+
c.fillStyle = "rgba(0,0,0,0.42)";
|
|
308
542
|
c.fillRect(x + 3, y + 3, 58, 42);
|
|
309
543
|
rect(c, palette.primary, x + 6, y + 6, 52, 1);
|
|
310
544
|
rect(c, palette.primary, x + 6, y + 41, 52, 1);
|
|
545
|
+
rect(c, palette.primary, x + 6, y + 6, 1, 36);
|
|
546
|
+
rect(c, palette.primary, x + 57, y + 6, 1, 36);
|
|
547
|
+
for (let i = 0; i < 6; i++)
|
|
548
|
+
rect(c, palette.primary, x + 12 + i * 8, y + 22, 4, 4);
|
|
311
549
|
break;
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
550
|
+
case "arcade":
|
|
551
|
+
rect(c, OUTLINE, x, y - 14, 16, 30);
|
|
552
|
+
rect(c, "#4c1d95", x + 1, y - 13, 14, 28);
|
|
553
|
+
rect(c, "#111827", x + 3, y - 10, 10, 9);
|
|
554
|
+
rect(c, ["#22c55e", "#ec4899", "#38bdf8"][Math.floor(time * 2) % 3], x + 4 + (Math.floor(time * 4) % 7), y - 8 + (Math.floor(time * 3) % 5), 2, 2);
|
|
555
|
+
rect(c, "#f59e0b", x + 3, y - 13, 10, 2);
|
|
556
|
+
rect(c, "#27272a", x + 2, y + 1, 12, 4);
|
|
557
|
+
rect(c, "#ef4444", x + 4, y + 2, 2, 2);
|
|
558
|
+
rect(c, "#3b82f6", x + 9, y + 2, 2, 2);
|
|
559
|
+
break;
|
|
560
|
+
case "fridge":
|
|
561
|
+
rect(c, OUTLINE, x + 1, y - 12, 14, 28);
|
|
562
|
+
rect(c, "#e5e7eb", x + 2, y - 11, 12, 26);
|
|
563
|
+
rect(c, "#9ca3af", x + 2, y - 3, 12, 1);
|
|
564
|
+
rect(c, "#6b7280", x + 11, y - 8, 1, 3);
|
|
565
|
+
rect(c, "#6b7280", x + 11, y, 1, 5);
|
|
566
|
+
rect(c, "#fde68a", x + 4, y - 9, 3, 3);
|
|
567
|
+
rect(c, "#fca5a5", x + 5, y + 3, 3, 3);
|
|
568
|
+
break;
|
|
569
|
+
case "printer":
|
|
570
|
+
rect(c, "#4b5563", x + 2, y + 8, 12, 8);
|
|
571
|
+
rect(c, OUTLINE, x + 1, y - 2, 14, 11);
|
|
572
|
+
rect(c, "#d1d5db", x + 2, y - 1, 12, 9);
|
|
573
|
+
rect(c, "#f9fafb", x + 4, y - 5 - (Math.floor(time) % 4 === 0 ? 1 : 0), 8, 5);
|
|
574
|
+
rect(c, "#22c55e", x + 11, y + 1, 2, 1);
|
|
575
|
+
rect(c, "#9ca3af", x + 3, y + 5, 10, 1);
|
|
576
|
+
break;
|
|
577
|
+
case "lamp":
|
|
578
|
+
rect(c, "#374151", x + 5, y + 13, 6, 3);
|
|
579
|
+
rect(c, "#4b5563", x + 7, y - 4, 2, 17);
|
|
580
|
+
rect(c, OUTLINE, x + 2, y - 10, 12, 8);
|
|
581
|
+
rect(c, "#fde68a", x + 3, y - 9, 10, 6);
|
|
582
|
+
rect(c, "#fef3c7", x + 4, y - 8, 4, 2);
|
|
583
|
+
break;
|
|
584
|
+
case "beanbag":
|
|
585
|
+
rect(c, OUTLINE, x, y + 3, 16, 13);
|
|
586
|
+
rect(c, shade(palette.primary, -50), x + 1, y + 4, 14, 11);
|
|
587
|
+
rect(c, shade(palette.primary, -20), x + 3, y + 5, 8, 4);
|
|
588
|
+
rect(c, "#00000040", x + 1, y + 13, 14, 2);
|
|
589
|
+
break;
|
|
590
|
+
case "tv":
|
|
591
|
+
rect(c, "#3b2413", x + 2, y + 8, 44, 8);
|
|
592
|
+
rect(c, "#5b3a21", x + 2, y + 8, 44, 2);
|
|
593
|
+
rect(c, OUTLINE, x + 4, y - 14, 40, 22);
|
|
594
|
+
rect(c, "#0b1120", x + 5, y - 13, 38, 20);
|
|
595
|
+
for (let i = 0; i < 9; i++) {
|
|
596
|
+
const h = 2 + ((i * 5 + Math.floor(time * 3)) % 12);
|
|
597
|
+
rect(c, i % 3 === 0 ? palette.primary : i % 3 === 1 ? "#38bdf8" : "#f472b6", x + 8 + i * 4, y + 5 - h, 3, h);
|
|
598
|
+
}
|
|
599
|
+
break;
|
|
600
|
+
case "vending":
|
|
601
|
+
rect(c, OUTLINE, x, y - 14, 16, 30);
|
|
602
|
+
rect(c, "#b91c1c", x + 1, y - 13, 14, 28);
|
|
603
|
+
rect(c, "#0f172a", x + 3, y - 11, 8, 16);
|
|
604
|
+
for (let row = 0; row < 3; row++)
|
|
605
|
+
for (let col = 0; col < 2; col++)
|
|
606
|
+
rect(c, ["#fbbf24", "#34d399", "#60a5fa", "#f472b6"][(row + col * 2) % 4], x + 4 + col * 3, y - 10 + row * 5, 2, 4);
|
|
607
|
+
rect(c, "#d1d5db", x + 12, y - 9, 2, 6);
|
|
608
|
+
rect(c, "#111827", x + 3, y + 8, 10, 4);
|
|
609
|
+
break;
|
|
610
|
+
case "trophy":
|
|
611
|
+
rect(c, "#6b4427", x + 2, y + 8, 12, 8);
|
|
612
|
+
rect(c, "#8a5a33", x + 2, y + 8, 12, 2);
|
|
613
|
+
rect(c, "#78350f", x + 5, y + 5, 6, 3);
|
|
614
|
+
rect(c, "#f59e0b", x + 7, y + 1, 2, 4);
|
|
615
|
+
rect(c, OUTLINE, x + 3, y - 8, 10, 10);
|
|
616
|
+
rect(c, "#fbbf24", x + 4, y - 7, 8, 8);
|
|
617
|
+
rect(c, "#fde68a", x + 5, y - 6, 2, 5);
|
|
618
|
+
rect(c, "#fbbf24", x + 1, y - 6, 3, 1);
|
|
619
|
+
rect(c, "#fbbf24", x + 12, y - 6, 3, 1);
|
|
620
|
+
break;
|
|
621
|
+
case "clock": {
|
|
622
|
+
rect(c, OUTLINE, x + 3, y + 1, 10, 10);
|
|
623
|
+
rect(c, "#f9fafb", x + 4, y + 2, 8, 8);
|
|
624
|
+
rect(c, "#1f2937", x + 8, y + 3, 1, 3);
|
|
625
|
+
rect(c, "#1f2937", x + 8, y + 6, 3, 1);
|
|
626
|
+
break;
|
|
627
|
+
}
|
|
628
|
+
case "poster": {
|
|
629
|
+
const colors = ["#7c3aed", "#0ea5e9", "#db2777", "#059669"];
|
|
630
|
+
const color = colors[(item.x + item.y) % colors.length];
|
|
631
|
+
rect(c, OUTLINE, x + 2, y, 12, 11);
|
|
632
|
+
rect(c, color, x + 3, y + 1, 10, 9);
|
|
633
|
+
rect(c, shade(color, 60), x + 5, y + 3, 6, 3);
|
|
634
|
+
rect(c, "#f9fafb", x + 5, y + 7, 6, 1);
|
|
635
|
+
break;
|
|
636
|
+
}
|
|
329
637
|
}
|
|
330
638
|
}
|
|
331
639
|
function nameTag(c, agent, selected, width) {
|
|
332
|
-
|
|
333
|
-
c.font = "bold 7px ui-monospace, Menlo, Consolas, monospace";
|
|
640
|
+
c.font = LABEL_FONT;
|
|
334
641
|
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);
|
|
642
|
+
const w = Math.ceil(c.measureText(agent.name).width) + 5;
|
|
643
|
+
const x = Math.round(Math.max(1, Math.min(width - w - 1, agent.x * TILE + 8 - w / 2)));
|
|
644
|
+
const y = Math.round(agent.y * TILE + 16);
|
|
339
645
|
c.fillStyle = selected ? "#ffffff" : "rgba(17,24,39,0.82)";
|
|
340
646
|
c.fillRect(x, y, w, 10);
|
|
341
647
|
c.fillStyle = selected ? "#111827" : "#ffffff";
|
|
342
|
-
c.fillText(name, x + 2.5, y + 1.5);
|
|
648
|
+
c.fillText(agent.name, x + 2.5, y + 1.5);
|
|
343
649
|
}
|
|
344
650
|
function wrap(c, text, max) {
|
|
345
651
|
const lines = [];
|
|
@@ -359,60 +665,87 @@ function wrap(c, text, max) {
|
|
|
359
665
|
return lines.slice(0, 3);
|
|
360
666
|
}
|
|
361
667
|
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 =
|
|
668
|
+
function drawCard(c, agent, world) {
|
|
669
|
+
c.font = BODY_FONT;
|
|
364
670
|
c.textBaseline = "top";
|
|
365
|
-
const
|
|
366
|
-
const
|
|
367
|
-
const
|
|
368
|
-
const
|
|
369
|
-
const
|
|
370
|
-
|
|
671
|
+
const room = world.rooms.find((candidate) => candidate.id === agent.area);
|
|
672
|
+
const body = wrap(c, agent.task || STATE_LABEL[agent.state] || "", 110);
|
|
673
|
+
const w = 120;
|
|
674
|
+
const h = 18 + body.length * 9 + (room ? 9 : 0) + 3;
|
|
675
|
+
const x = Math.round(Math.max(2, Math.min(world.cols * TILE - w - 2, agent.x * TILE + 8 - w / 2)));
|
|
676
|
+
const y = Math.round(Math.max(2, agent.y * TILE - h - 24));
|
|
677
|
+
rect(c, OUTLINE, x - 1, y - 1, w + 2, h + 2);
|
|
371
678
|
rect(c, "#ffffff", x, y, w, h);
|
|
372
679
|
c.fillStyle = "#111827";
|
|
373
|
-
c.font =
|
|
374
|
-
c.fillText(`${agent.name}: ${STATE_LABEL[agent.state] ?? ""}`.slice(0,
|
|
680
|
+
c.font = LABEL_FONT;
|
|
681
|
+
c.fillText(`${agent.name}: ${STATE_LABEL[agent.state] ?? ""}`.slice(0, 28), x + 4, y + 4);
|
|
375
682
|
c.fillStyle = "#374151";
|
|
376
|
-
c.font =
|
|
683
|
+
c.font = BODY_FONT;
|
|
377
684
|
body.forEach((line, index) => c.fillText(line, x + 4, y + 15 + index * 9));
|
|
685
|
+
if (room) {
|
|
686
|
+
c.fillStyle = "#6b7280";
|
|
687
|
+
c.fillText(`In ${room.name}`.slice(0, 28), x + 4, y + 15 + body.length * 9);
|
|
688
|
+
}
|
|
378
689
|
}
|
|
379
690
|
function drawProgress(c, world, palette) {
|
|
380
691
|
if (world.tasks.length === 0)
|
|
381
692
|
return;
|
|
382
693
|
const done = world.tasks.filter((task) => task.state === "done").length;
|
|
383
|
-
|
|
384
|
-
c.font = "bold 7px ui-monospace, Menlo, Consolas, monospace";
|
|
694
|
+
c.font = LABEL_FONT;
|
|
385
695
|
c.textBaseline = "top";
|
|
386
696
|
const w = 96;
|
|
387
697
|
rect(c, "rgba(17,24,39,0.85)", 4, 4, w, 20);
|
|
388
698
|
c.fillStyle = "#ffffff";
|
|
389
|
-
c.fillText(
|
|
699
|
+
c.fillText(`QUESTS ${done}/${world.tasks.length}`, 8, 6.5);
|
|
390
700
|
rect(c, "#374151", 8, 16, w - 8, 4);
|
|
391
701
|
rect(c, palette.primary, 8, 16, Math.round(((w - 8) * done) / world.tasks.length), 4);
|
|
392
702
|
}
|
|
393
|
-
|
|
703
|
+
/** Everything that moves. `building` is the layer from drawBuilding. */
|
|
704
|
+
export function drawWorld(c, world, time, palette, daylight, building, selected) {
|
|
394
705
|
const width = world.cols * TILE;
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
for (const item of world.furniture)
|
|
401
|
-
if (item.type === "rug")
|
|
402
|
-
drawFurniture(c, item, world, time, palette);
|
|
706
|
+
const height = world.rows * TILE;
|
|
707
|
+
if (building)
|
|
708
|
+
c.drawImage(building, 0, 0, width, height);
|
|
709
|
+
else
|
|
710
|
+
drawBuilding(c, world, palette, daylight);
|
|
403
711
|
const scene = [];
|
|
404
712
|
for (const item of world.furniture) {
|
|
405
|
-
|
|
713
|
+
const spec = FURNITURE[item.type];
|
|
714
|
+
if (spec.solid === false)
|
|
406
715
|
continue;
|
|
407
|
-
scene.push({ depth: (item.y +
|
|
716
|
+
scene.push({ depth: (item.y + spec.h) * TILE - 2, draw: () => drawFurniture(c, item, world, time, palette) });
|
|
408
717
|
}
|
|
409
718
|
const shown = world.agents.filter((agent) => agent.state !== "away");
|
|
410
|
-
for (const agent of shown)
|
|
719
|
+
for (const agent of shown)
|
|
411
720
|
scene.push({ depth: agent.y * TILE + 14, draw: () => drawAgent(c, agent, time, agent.id === selected) });
|
|
412
|
-
}
|
|
413
721
|
scene.sort((a, b) => a.depth - b.depth);
|
|
414
722
|
for (const item of scene)
|
|
415
723
|
item.draw();
|
|
724
|
+
// Night: the room dims, and screens and lamps light it.
|
|
725
|
+
if (daylight.night > 0) {
|
|
726
|
+
c.fillStyle = `rgba(12,14,48,${daylight.night})`;
|
|
727
|
+
c.fillRect(0, 0, width, height);
|
|
728
|
+
c.save();
|
|
729
|
+
c.globalCompositeOperation = "lighter";
|
|
730
|
+
const glow = (x, y, radius, color) => {
|
|
731
|
+
const gradient = c.createRadialGradient(x, y, 1, x, y, radius);
|
|
732
|
+
gradient.addColorStop(0, color);
|
|
733
|
+
gradient.addColorStop(1, "rgba(0,0,0,0)");
|
|
734
|
+
c.fillStyle = gradient;
|
|
735
|
+
c.fillRect(x - radius, y - radius, radius * 2, radius * 2);
|
|
736
|
+
};
|
|
737
|
+
for (const item of world.furniture) {
|
|
738
|
+
if (item.type === "lamp")
|
|
739
|
+
glow(item.x * TILE + 8, item.y * TILE, 36, "rgba(253,224,139,0.30)");
|
|
740
|
+
if (item.type === "arcade" || item.type === "vending" || item.type === "tv")
|
|
741
|
+
glow(item.x * TILE + 8, item.y * TILE, 26, "rgba(125,211,252,0.16)");
|
|
742
|
+
if (item.type === "desk" && world.agents.some((a) => a.seat?.x === item.x + 1 && a.seat?.y === item.y + 2 && isSeated(a))) {
|
|
743
|
+
glow(item.x * TILE + 24, item.y * TILE + 8, 30, "rgba(125,211,252,0.26)");
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
c.restore();
|
|
747
|
+
}
|
|
748
|
+
// On top, so tall furniture against a wall can't hide a room's name.
|
|
416
749
|
drawLabels(c, world);
|
|
417
750
|
for (const agent of shown)
|
|
418
751
|
nameTag(c, agent, agent.id === selected, width);
|
|
@@ -422,6 +755,6 @@ export function drawWorld(c, world, time, palette, selected) {
|
|
|
422
755
|
drawProgress(c, world, palette);
|
|
423
756
|
const picked = shown.find((agent) => agent.id === selected);
|
|
424
757
|
if (picked)
|
|
425
|
-
drawCard(c, picked,
|
|
758
|
+
drawCard(c, picked, world);
|
|
426
759
|
}
|
|
427
760
|
//# sourceMappingURL=draw.js.map
|