@blkc-code/render-canvas 0.3.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 +9 -9
- package/dist/draw.d.ts.map +1 -1
- package/dist/draw.js +73 -305
- package/dist/draw.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- 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 +2 -0
- package/dist/office.d.ts.map +1 -1
- package/dist/office.js +2 -0
- 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.txt +1 -1
- package/dist/types.d.ts +13 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/world.d.ts +15 -11
- package/dist/world.d.ts.map +1 -1
- package/dist/world.js +31 -59
- 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 +73 -289
- package/src/index.ts +3 -0
- package/src/links.ts +76 -0
- package/src/office.ts +3 -0
- package/src/pack.ts +36 -0
- package/src/paint.ts +24 -0
- package/src/types.ts +12 -2
- package/src/world.ts +41 -60
- package/src/worlds/office-data.ts +57 -0
- package/src/worlds/office-paint.ts +276 -0
package/src/draw.ts
CHANGED
|
@@ -4,31 +4,30 @@
|
|
|
4
4
|
* and anything else that moves are drawn on top each frame (drawWorld).
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { drawLinks, linkRoutes } from "./links.js";
|
|
8
|
+
import { BODY_FONT, LABEL_FONT, OUTLINE, TILE, rect, shade, type Ctx, type Palette } from "./paint.js";
|
|
9
|
+
import { isSeated, specOf, type Agent, type Item, type Room, type World } from "./world.js";
|
|
10
|
+
import { OFFICE_PAINTER } from "./worlds/office-paint.js";
|
|
8
11
|
|
|
9
|
-
export
|
|
12
|
+
export { TILE, type Palette } from "./paint.js";
|
|
10
13
|
|
|
11
|
-
|
|
14
|
+
// --- Worlds ---------------------------------------------------------------------------------------
|
|
12
15
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
/** How one world looks. The engine draws the building, characters and labels around it. */
|
|
17
|
+
export interface Painter {
|
|
18
|
+
furniture(c: Ctx, item: Item, world: World, time: number, palette: Palette): void;
|
|
19
|
+
/** What lights up at night. Call `glow` for anything that does. */
|
|
20
|
+
glow(item: Item, world: World, glow: (x: number, y: number, radius: number, color: string) => void): void;
|
|
21
|
+
}
|
|
18
22
|
|
|
19
|
-
const
|
|
20
|
-
c.fillStyle = color;
|
|
21
|
-
c.fillRect(Math.round(x), Math.round(y), w, h);
|
|
22
|
-
};
|
|
23
|
+
const painters = new Map<string, Painter>([["office", OFFICE_PAINTER]]);
|
|
23
24
|
|
|
24
|
-
function
|
|
25
|
-
|
|
26
|
-
const hex = color.length === 4 ? `#${color[1]}${color[1]}${color[2]}${color[2]}${color[3]}${color[3]}` : color;
|
|
27
|
-
const n = parseInt(hex.slice(1, 7), 16);
|
|
28
|
-
const channel = (shift: number): number => Math.max(0, Math.min(255, ((n >> shift) & 255) + amount));
|
|
29
|
-
return `rgb(${channel(16)},${channel(8)},${channel(0)})`;
|
|
25
|
+
export function registerPainter(id: string, painter: Painter): void {
|
|
26
|
+
painters.set(id, painter);
|
|
30
27
|
}
|
|
31
28
|
|
|
29
|
+
const painterFor = (world: World): Painter => painters.get(world.pack.id) ?? OFFICE_PAINTER;
|
|
30
|
+
|
|
32
31
|
// --- Time of day, from the viewer's own clock ------------------------------------------------------------
|
|
33
32
|
|
|
34
33
|
export interface Daylight { sky: string; sun: string; night: number }
|
|
@@ -238,6 +237,20 @@ function drawFloor(c: Ctx, room: Room): void {
|
|
|
238
237
|
for (let i = 0; i < 5; i++) rect(c, i % 2 ? "#747b89" : "#636a78", x + ((tx * 11 + i * 3) % 15), y + ((ty * 5 + i * 5) % 15), 1, 1);
|
|
239
238
|
if (noise === 3) rect(c, "#596070", x + 3, y + 8, 8, 1);
|
|
240
239
|
break;
|
|
240
|
+
case "hall": {
|
|
241
|
+
// Polished floor, with a runner down the middle of a corridor's length.
|
|
242
|
+
rect(c, "#cfc8b8", x, y, TILE, TILE);
|
|
243
|
+
rect(c, "#bdb6a6", x, y + 15, TILE, 1);
|
|
244
|
+
rect(c, "#bdb6a6", x + 15, y, 1, TILE);
|
|
245
|
+
const along = room.w >= room.h;
|
|
246
|
+
const offset = along ? ty - room.y : tx - room.x;
|
|
247
|
+
const size = along ? room.h : room.w;
|
|
248
|
+
if (along && offset === 0) rect(c, "#7f6a55", x, y + 12, TILE, 4);
|
|
249
|
+
if (along && offset === size - 1) rect(c, "#7f6a55", x, y, TILE, 4);
|
|
250
|
+
if (!along && offset === 0) rect(c, "#7f6a55", x + 12, y, 4, TILE);
|
|
251
|
+
if (!along && offset === size - 1) rect(c, "#7f6a55", x, y, 4, TILE);
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
241
254
|
case "grass":
|
|
242
255
|
rect(c, "#3f8f4f", x, y, TILE, TILE);
|
|
243
256
|
for (let i = 0; i < 7; i++) rect(c, i % 3 ? "#4aa35c" : "#347a43", x + ((tx * 5 + i * 9) % 15), y + ((ty * 7 + i * 3) % 14), 1, 2);
|
|
@@ -262,6 +275,23 @@ const WALL_FACE = "#5c628f";
|
|
|
262
275
|
const WALL_DARK = "#4a4f78";
|
|
263
276
|
const WALL_LIGHT = "#6d74a4";
|
|
264
277
|
|
|
278
|
+
/** Outside the building: lawn, with the odd shrub and tree. */
|
|
279
|
+
function drawGrounds(c: Ctx, tx: number, ty: number): void {
|
|
280
|
+
const x = tx * TILE;
|
|
281
|
+
const y = ty * TILE;
|
|
282
|
+
rect(c, (tx + ty) % 2 === 0 ? "#4b8a4f" : "#478549", x, y, TILE, TILE);
|
|
283
|
+
const noise = (tx * 73 + ty * 151) % 29;
|
|
284
|
+
for (let i = 0; i < 4; i++) 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
|
+
} else if (noise === 11) {
|
|
289
|
+
rect(c, "#5b3a22", x + 7, y + 10, 2, 5);
|
|
290
|
+
rect(c, "#24592e", x + 2, y + 1, 12, 10);
|
|
291
|
+
rect(c, "#2f6f3a", x + 4, y + 2, 7, 5);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
265
295
|
function drawWalls(c: Ctx, world: World, palette: Palette, daylight: Daylight): void {
|
|
266
296
|
const open = new Set<string>();
|
|
267
297
|
for (const room of world.rooms) {
|
|
@@ -275,12 +305,23 @@ function drawWalls(c: Ctx, world: World, palette: Palette, daylight: Daylight):
|
|
|
275
305
|
if (open.has(key) || doors.has(key)) continue;
|
|
276
306
|
const x = tx * TILE;
|
|
277
307
|
const y = ty * TILE;
|
|
308
|
+
const nearBuilding = (cx: number, cy: number): boolean => {
|
|
309
|
+
for (let ay = cy - 1; ay <= cy + 1; ay++) {
|
|
310
|
+
for (let ax = cx - 1; ax <= cx + 1; ax++) if (open.has(`${ax},${ay}`) || doors.has(`${ax},${ay}`)) return true;
|
|
311
|
+
}
|
|
312
|
+
return false;
|
|
313
|
+
};
|
|
314
|
+
if (!nearBuilding(tx, ty)) {
|
|
315
|
+
drawGrounds(c, tx, ty);
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
278
318
|
if (open.has(`${tx},${ty + 1}`)) {
|
|
279
319
|
rect(c, WALL_FACE, x, y, TILE, TILE);
|
|
280
320
|
rect(c, WALL_LIGHT, x, y, TILE, 1);
|
|
281
321
|
rect(c, WALL_DARK, x, y + 12, TILE, 4);
|
|
282
322
|
rect(c, palette.primary, x, y + 11, TILE, 1);
|
|
283
|
-
|
|
323
|
+
// Windows look outside, so only an outer wall has them.
|
|
324
|
+
if (tx % 4 === 3 && !nearBuilding(tx, ty - 1) && !world.furniture.some((item) => specOf(world, item).wall && item.x === tx && item.y === ty)) {
|
|
284
325
|
// A window, in the colour of the sky right now.
|
|
285
326
|
rect(c, "#1f2937", x + 1, y + 1, 14, 9);
|
|
286
327
|
rect(c, daylight.sky, x + 2, y + 2, 12, 7);
|
|
@@ -328,8 +369,8 @@ export function drawBuilding(c: Ctx, world: World, palette: Palette, daylight: D
|
|
|
328
369
|
rect(c, WALL_TOP, 0, 0, world.cols * TILE, world.rows * TILE);
|
|
329
370
|
for (const room of world.rooms) drawFloor(c, room);
|
|
330
371
|
drawWalls(c, world, palette, daylight);
|
|
331
|
-
for (const item of world.furniture) if (item.type === "rug")
|
|
332
|
-
for (const item of world.furniture) if (
|
|
372
|
+
for (const item of world.furniture) if (item.type === "rug") painterFor(world).furniture(c, item, world, 0, palette);
|
|
373
|
+
for (const item of world.furniture) if (specOf(world, item).wall) painterFor(world).furniture(c, item, world, 0, palette);
|
|
333
374
|
// Light from the windows falling across the floor.
|
|
334
375
|
if (daylight.sun !== "rgba(0,0,0,0)") {
|
|
335
376
|
c.fillStyle = daylight.sun;
|
|
@@ -342,264 +383,6 @@ export function drawBuilding(c: Ctx, world: World, palette: Palette, daylight: D
|
|
|
342
383
|
}
|
|
343
384
|
}
|
|
344
385
|
|
|
345
|
-
function drawDesk(c: Ctx, x: number, y: number, on: boolean, time: number, seed: number, palette: Palette, blocked: boolean): void {
|
|
346
|
-
rect(c, "#55351d", x + 1, y + 22, 46, 8);
|
|
347
|
-
rect(c, "#6b4427", x + 1, y + 13, 46, 10);
|
|
348
|
-
rect(c, "#8a5a33", x + 1, y + 10, 46, 12);
|
|
349
|
-
rect(c, "#a87545", x + 1, y + 10, 46, 2);
|
|
350
|
-
rect(c, "#3b2413", x + 3, y + 22, 3, 9);
|
|
351
|
-
rect(c, "#3b2413", x + 42, y + 22, 3, 9);
|
|
352
|
-
rect(c, "#55351d", x + 30, y + 23, 12, 7);
|
|
353
|
-
rect(c, "#7c5637", x + 31, y + 24, 10, 2);
|
|
354
|
-
rect(c, "#d4a24e", x + 35, y + 24, 2, 1);
|
|
355
|
-
rect(c, "#7c5637", x + 31, y + 27, 10, 2);
|
|
356
|
-
rect(c, "#d4a24e", x + 35, y + 27, 2, 1);
|
|
357
|
-
// Monitor
|
|
358
|
-
rect(c, OUTLINE, x + 14, y - 3, 20, 15);
|
|
359
|
-
rect(c, "#374151", x + 22, y + 12, 4, 2);
|
|
360
|
-
rect(c, "#4b5563", x + 19, y + 14, 10, 1);
|
|
361
|
-
if (blocked) {
|
|
362
|
-
rect(c, "#3f0f14", x + 15, y - 2, 18, 13);
|
|
363
|
-
if (Math.floor(time * 2) % 2 === 0) { rect(c, "#ef4444", x + 23, y, 2, 6); rect(c, "#ef4444", x + 23, y + 7, 2, 2); }
|
|
364
|
-
} else if (on) {
|
|
365
|
-
rect(c, "#0f2a2e", x + 15, y - 2, 18, 13);
|
|
366
|
-
const scroll = Math.floor(time * 3 + (seed % 13));
|
|
367
|
-
for (let line = 0; line < 5; line++) {
|
|
368
|
-
const width = 3 + (((seed >> line) & 7) + (scroll + line) * 7) % 12;
|
|
369
|
-
rect(c, line % 3 === 0 ? palette.primary : line % 3 === 1 ? "#7dd3fc" : "#fcd34d", x + 16 + (line % 2) * 2, y - 1 + line * 2.2, width, 1);
|
|
370
|
-
}
|
|
371
|
-
c.fillStyle = "rgba(125,211,252,0.12)";
|
|
372
|
-
c.fillRect(x + 10, y + 11, 28, 8);
|
|
373
|
-
} else {
|
|
374
|
-
rect(c, "#1f2937", x + 15, y - 2, 18, 13);
|
|
375
|
-
rect(c, "#374151", x + 17, y, 3, 1);
|
|
376
|
-
}
|
|
377
|
-
rect(c, "#d1d5db", x + 16, y + 15, 16, 5);
|
|
378
|
-
for (let row = 0; row < 2; row++) for (let key = 0; key < 7; key++) rect(c, "#9ca3af", x + 17 + key * 2, y + 16 + row * 2, 1, 1);
|
|
379
|
-
rect(c, "#e5e7eb", x + 34, y + 16, 3, 4);
|
|
380
|
-
rect(c, "#f3f4f6", x + 40, y + 13, 5, 5);
|
|
381
|
-
rect(c, palette.primary, x + 40, y + 15, 5, 1);
|
|
382
|
-
rect(c, "#fde68a", x + 4, y + 13, 7, 6);
|
|
383
|
-
rect(c, "#fbbf24", x + 5, y + 15, 5, 1);
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
function drawBoard(c: Ctx, x: number, y: number, world: World): void {
|
|
387
|
-
rect(c, "#3a2c1e", x, y - 12, 64, 27);
|
|
388
|
-
rect(c, "#d9c29c", x + 2, y - 10, 60, 23);
|
|
389
|
-
for (let i = 0; i < 40; i++) rect(c, "#c9b08a", x + 3 + (i * 37) % 58, y - 9 + (i * 13) % 21, 1, 1);
|
|
390
|
-
const colors = { todo: "#fde68a", doing: "#93c5fd", done: "#86efac" };
|
|
391
|
-
const per = 8;
|
|
392
|
-
world.tasks.slice(0, per * 2).forEach((task, index) => {
|
|
393
|
-
const col = index % per;
|
|
394
|
-
const row = Math.floor(index / per);
|
|
395
|
-
rect(c, "#00000033", x + 5 + col * 7, y - 7 + row * 10, 6, 8);
|
|
396
|
-
rect(c, colors[task.state], x + 4 + col * 7, y - 8 + row * 10, 6, 8);
|
|
397
|
-
rect(c, "#ef4444", x + 6 + col * 7, y - 8 + row * 10, 2, 1);
|
|
398
|
-
rect(c, "#00000040", x + 5 + col * 7, y - 5 + row * 10, 4, 1);
|
|
399
|
-
rect(c, "#00000040", x + 5 + col * 7, y - 3 + row * 10, 3, 1);
|
|
400
|
-
});
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
function drawFurniture(c: Ctx, item: Item, world: World, time: number, palette: Palette): void {
|
|
404
|
-
const x = item.x * TILE;
|
|
405
|
-
const y = item.y * TILE;
|
|
406
|
-
switch (item.type) {
|
|
407
|
-
case "desk": {
|
|
408
|
-
const seatX = item.x + 1;
|
|
409
|
-
const seatY = item.y + 2;
|
|
410
|
-
const user = world.agents.find((a) => a.seat?.x === seatX && a.seat?.y === seatY && isSeated(a));
|
|
411
|
-
drawDesk(c, x, y, Boolean(user), time, user?.seed ?? 0, palette, user?.state === "blocked");
|
|
412
|
-
break;
|
|
413
|
-
}
|
|
414
|
-
case "coffee":
|
|
415
|
-
rect(c, "#374151", x, y + 2, 32, 14);
|
|
416
|
-
rect(c, "#6b7280", x, y + 2, 32, 2);
|
|
417
|
-
rect(c, OUTLINE, x + 4, y - 9, 15, 15);
|
|
418
|
-
rect(c, "#27272a", x + 5, y - 8, 13, 13);
|
|
419
|
-
rect(c, Math.floor(time * 2) % 2 ? "#ef4444" : "#7f1d1d", x + 15, y - 6, 2, 2);
|
|
420
|
-
rect(c, "#52525b", x + 7, y - 6, 6, 4);
|
|
421
|
-
rect(c, "#f3f4f6", x + 8, y + 3, 5, 4);
|
|
422
|
-
rect(c, "#f3f4f6", x + 23, y - 1, 5, 5);
|
|
423
|
-
rect(c, "#92400e", x + 24, y, 3, 1);
|
|
424
|
-
for (let i = 0; i < 3; i++) {
|
|
425
|
-
c.fillStyle = "rgba(255,255,255,0.5)";
|
|
426
|
-
c.fillRect(x + 9 + Math.round(Math.sin(time * 2 + i) * 1.5), y - 1 - ((time * 8 + i * 4) % 9), 1, 2);
|
|
427
|
-
}
|
|
428
|
-
break;
|
|
429
|
-
case "water":
|
|
430
|
-
rect(c, "#d1d5db", x + 3, y + 3, 10, 13);
|
|
431
|
-
rect(c, "#9ca3af", x + 3, y + 14, 10, 2);
|
|
432
|
-
rect(c, "#60a5fa", x + 4, y - 8, 8, 11);
|
|
433
|
-
rect(c, "#bae6fd", x + 5, y - 7, 2, 9);
|
|
434
|
-
rect(c, "#3b82f6", x + 5, y + 6, 2, 2);
|
|
435
|
-
rect(c, "#ef4444", x + 9, y + 6, 2, 2);
|
|
436
|
-
if (Math.floor(time * 0.4 + item.x) % 5 === 0) rect(c, "#e0f2fe", x + 8, y - 2 - ((time * 6) % 7), 1, 1);
|
|
437
|
-
break;
|
|
438
|
-
case "sofa":
|
|
439
|
-
rect(c, shade(palette.primary, -60), x + 1, y - 7, 46, 11);
|
|
440
|
-
rect(c, shade(palette.primary, -30), x + 2, y - 6, 44, 3);
|
|
441
|
-
rect(c, "#4b5563", x, y + 3, 48, 11);
|
|
442
|
-
rect(c, "#6b7280", x + 3, y + 3, 20, 7);
|
|
443
|
-
rect(c, "#6b7280", x + 25, y + 3, 20, 7);
|
|
444
|
-
rect(c, "#374151", x, y + 12, 48, 3);
|
|
445
|
-
rect(c, "#374151", x - 1, y - 2, 4, 16);
|
|
446
|
-
rect(c, "#374151", x + 45, y - 2, 4, 16);
|
|
447
|
-
break;
|
|
448
|
-
case "plant":
|
|
449
|
-
rect(c, "#92400e", x + 4, y + 9, 8, 7);
|
|
450
|
-
rect(c, "#b45309", x + 3, y + 8, 10, 2);
|
|
451
|
-
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]] as const) rect(c, "#15803d", x + lx, y + ly, lw, lh);
|
|
452
|
-
rect(c, "#22c55e", x + 7, y - 3, 2, 8);
|
|
453
|
-
rect(c, "#22c55e", x + 3, y + 1, 2, 3);
|
|
454
|
-
rect(c, "#4ade80", x + 10, y - 4, 1, 3);
|
|
455
|
-
break;
|
|
456
|
-
case "fern":
|
|
457
|
-
rect(c, "#57534e", x + 3, y + 10, 10, 6);
|
|
458
|
-
rect(c, "#78716c", x + 2, y + 9, 12, 2);
|
|
459
|
-
for (let i = 0; i < 7; i++) {
|
|
460
|
-
const lean = i - 3;
|
|
461
|
-
for (let j = 0; j < 5; j++) rect(c, j % 2 ? "#16a34a" : "#15803d", x + 7 + lean * (j / 2 + 1), y + 8 - j * 2 + Math.abs(lean), 2, 2);
|
|
462
|
-
}
|
|
463
|
-
break;
|
|
464
|
-
case "board":
|
|
465
|
-
drawBoard(c, x, y, world);
|
|
466
|
-
break;
|
|
467
|
-
case "whiteboard":
|
|
468
|
-
rect(c, "#9ca3af", x, y - 10, 48, 22);
|
|
469
|
-
rect(c, "#f9fafb", x + 2, y - 8, 44, 18);
|
|
470
|
-
rect(c, "#3b82f6", x + 5, y - 5, 14, 1); rect(c, "#3b82f6", x + 5, y - 2, 9, 1);
|
|
471
|
-
rect(c, "#ef4444", x + 24, y - 6, 8, 6); rect(c, "#f9fafb", x + 25, y - 5, 6, 4);
|
|
472
|
-
rect(c, "#10b981", x + 8, y + 3, 20, 1); rect(c, "#10b981", x + 27, y + 2, 1, 3);
|
|
473
|
-
rect(c, "#6b7280", x + 2, y + 10, 44, 2);
|
|
474
|
-
break;
|
|
475
|
-
case "shelf":
|
|
476
|
-
rect(c, "#5b3a21", x, y - 12, 32, 28);
|
|
477
|
-
for (let shelf = 0; shelf < 2; shelf++) {
|
|
478
|
-
rect(c, "#3b2413", x + 2, y - 10 + shelf * 13, 28, 11);
|
|
479
|
-
for (let book = 0; book < 8; book++) {
|
|
480
|
-
const color = ["#ef4444", "#3b82f6", "#10b981", "#f59e0b", "#8b5cf6"][(book * 3 + shelf + item.x) % 5] as string;
|
|
481
|
-
rect(c, color, x + 3 + book * 3, y - 8 + shelf * 13 + (book % 3), 2, 9 - (book % 3));
|
|
482
|
-
rect(c, shade(color, 40), x + 3 + book * 3, y - 6 + shelf * 13, 2, 1);
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
break;
|
|
486
|
-
case "server":
|
|
487
|
-
rect(c, OUTLINE, x, y - 13, 16, 29);
|
|
488
|
-
rect(c, "#1f2937", x + 1, y - 12, 14, 27);
|
|
489
|
-
for (let i = 0; i < 5; i++) {
|
|
490
|
-
rect(c, "#111827", x + 2, y - 11 + i * 5, 12, 4);
|
|
491
|
-
const on = Math.floor(time * 3 + i * 2 + item.x) % 3 !== 0;
|
|
492
|
-
rect(c, on ? "#22c55e" : "#14532d", x + 3, y - 10 + i * 5, 1, 1);
|
|
493
|
-
rect(c, i % 2 === 0 ? "#f59e0b" : "#3b82f6", x + 5, y - 10 + i * 5, 1, 1);
|
|
494
|
-
rect(c, "#374151", x + 8, y - 10 + i * 5, 5, 1);
|
|
495
|
-
}
|
|
496
|
-
break;
|
|
497
|
-
case "table":
|
|
498
|
-
rect(c, "#44403c", x + 2, y + 16, 44, 12);
|
|
499
|
-
rect(c, "#78716c", x + 2, y + 3, 44, 14);
|
|
500
|
-
rect(c, "#a8a29e", x + 2, y + 3, 44, 2);
|
|
501
|
-
rect(c, "#f9fafb", x + 10, y + 6, 8, 6);
|
|
502
|
-
rect(c, "#f9fafb", x + 28, y + 8, 8, 6);
|
|
503
|
-
rect(c, "#1f2937", x + 12, y + 8, 4, 1);
|
|
504
|
-
rect(c, "#3b82f6", x + 30, y + 10, 4, 1);
|
|
505
|
-
break;
|
|
506
|
-
case "rug":
|
|
507
|
-
rect(c, palette.primary, x, y, 64, 48);
|
|
508
|
-
c.fillStyle = "rgba(0,0,0,0.42)";
|
|
509
|
-
c.fillRect(x + 3, y + 3, 58, 42);
|
|
510
|
-
rect(c, palette.primary, x + 6, y + 6, 52, 1);
|
|
511
|
-
rect(c, palette.primary, x + 6, y + 41, 52, 1);
|
|
512
|
-
rect(c, palette.primary, x + 6, y + 6, 1, 36);
|
|
513
|
-
rect(c, palette.primary, x + 57, y + 6, 1, 36);
|
|
514
|
-
for (let i = 0; i < 6; i++) rect(c, palette.primary, x + 12 + i * 8, y + 22, 4, 4);
|
|
515
|
-
break;
|
|
516
|
-
case "arcade":
|
|
517
|
-
rect(c, OUTLINE, x, y - 14, 16, 30);
|
|
518
|
-
rect(c, "#4c1d95", x + 1, y - 13, 14, 28);
|
|
519
|
-
rect(c, "#111827", x + 3, y - 10, 10, 9);
|
|
520
|
-
rect(c, ["#22c55e", "#ec4899", "#38bdf8"][Math.floor(time * 2) % 3] as string, x + 4 + (Math.floor(time * 4) % 7), y - 8 + (Math.floor(time * 3) % 5), 2, 2);
|
|
521
|
-
rect(c, "#f59e0b", x + 3, y - 13, 10, 2);
|
|
522
|
-
rect(c, "#27272a", x + 2, y + 1, 12, 4);
|
|
523
|
-
rect(c, "#ef4444", x + 4, y + 2, 2, 2);
|
|
524
|
-
rect(c, "#3b82f6", x + 9, y + 2, 2, 2);
|
|
525
|
-
break;
|
|
526
|
-
case "fridge":
|
|
527
|
-
rect(c, OUTLINE, x + 1, y - 12, 14, 28);
|
|
528
|
-
rect(c, "#e5e7eb", x + 2, y - 11, 12, 26);
|
|
529
|
-
rect(c, "#9ca3af", x + 2, y - 3, 12, 1);
|
|
530
|
-
rect(c, "#6b7280", x + 11, y - 8, 1, 3);
|
|
531
|
-
rect(c, "#6b7280", x + 11, y, 1, 5);
|
|
532
|
-
rect(c, "#fde68a", x + 4, y - 9, 3, 3);
|
|
533
|
-
rect(c, "#fca5a5", x + 5, y + 3, 3, 3);
|
|
534
|
-
break;
|
|
535
|
-
case "printer":
|
|
536
|
-
rect(c, "#4b5563", x + 2, y + 8, 12, 8);
|
|
537
|
-
rect(c, OUTLINE, x + 1, y - 2, 14, 11);
|
|
538
|
-
rect(c, "#d1d5db", x + 2, y - 1, 12, 9);
|
|
539
|
-
rect(c, "#f9fafb", x + 4, y - 5 - (Math.floor(time) % 4 === 0 ? 1 : 0), 8, 5);
|
|
540
|
-
rect(c, "#22c55e", x + 11, y + 1, 2, 1);
|
|
541
|
-
rect(c, "#9ca3af", x + 3, y + 5, 10, 1);
|
|
542
|
-
break;
|
|
543
|
-
case "lamp":
|
|
544
|
-
rect(c, "#374151", x + 5, y + 13, 6, 3);
|
|
545
|
-
rect(c, "#4b5563", x + 7, y - 4, 2, 17);
|
|
546
|
-
rect(c, OUTLINE, x + 2, y - 10, 12, 8);
|
|
547
|
-
rect(c, "#fde68a", x + 3, y - 9, 10, 6);
|
|
548
|
-
rect(c, "#fef3c7", x + 4, y - 8, 4, 2);
|
|
549
|
-
break;
|
|
550
|
-
case "beanbag":
|
|
551
|
-
rect(c, OUTLINE, x, y + 3, 16, 13);
|
|
552
|
-
rect(c, shade(palette.primary, -50), x + 1, y + 4, 14, 11);
|
|
553
|
-
rect(c, shade(palette.primary, -20), x + 3, y + 5, 8, 4);
|
|
554
|
-
rect(c, "#00000040", x + 1, y + 13, 14, 2);
|
|
555
|
-
break;
|
|
556
|
-
case "tv":
|
|
557
|
-
rect(c, "#3b2413", x + 2, y + 8, 44, 8);
|
|
558
|
-
rect(c, "#5b3a21", x + 2, y + 8, 44, 2);
|
|
559
|
-
rect(c, OUTLINE, x + 4, y - 14, 40, 22);
|
|
560
|
-
rect(c, "#0b1120", x + 5, y - 13, 38, 20);
|
|
561
|
-
for (let i = 0; i < 9; i++) {
|
|
562
|
-
const h = 2 + ((i * 5 + Math.floor(time * 3)) % 12);
|
|
563
|
-
rect(c, i % 3 === 0 ? palette.primary : i % 3 === 1 ? "#38bdf8" : "#f472b6", x + 8 + i * 4, y + 5 - h, 3, h);
|
|
564
|
-
}
|
|
565
|
-
break;
|
|
566
|
-
case "vending":
|
|
567
|
-
rect(c, OUTLINE, x, y - 14, 16, 30);
|
|
568
|
-
rect(c, "#b91c1c", x + 1, y - 13, 14, 28);
|
|
569
|
-
rect(c, "#0f172a", x + 3, y - 11, 8, 16);
|
|
570
|
-
for (let row = 0; row < 3; row++) for (let col = 0; col < 2; col++) rect(c, ["#fbbf24", "#34d399", "#60a5fa", "#f472b6"][(row + col * 2) % 4] as string, x + 4 + col * 3, y - 10 + row * 5, 2, 4);
|
|
571
|
-
rect(c, "#d1d5db", x + 12, y - 9, 2, 6);
|
|
572
|
-
rect(c, "#111827", x + 3, y + 8, 10, 4);
|
|
573
|
-
break;
|
|
574
|
-
case "trophy":
|
|
575
|
-
rect(c, "#6b4427", x + 2, y + 8, 12, 8);
|
|
576
|
-
rect(c, "#8a5a33", x + 2, y + 8, 12, 2);
|
|
577
|
-
rect(c, "#78350f", x + 5, y + 5, 6, 3);
|
|
578
|
-
rect(c, "#f59e0b", x + 7, y + 1, 2, 4);
|
|
579
|
-
rect(c, OUTLINE, x + 3, y - 8, 10, 10);
|
|
580
|
-
rect(c, "#fbbf24", x + 4, y - 7, 8, 8);
|
|
581
|
-
rect(c, "#fde68a", x + 5, y - 6, 2, 5);
|
|
582
|
-
rect(c, "#fbbf24", x + 1, y - 6, 3, 1); rect(c, "#fbbf24", x + 12, y - 6, 3, 1);
|
|
583
|
-
break;
|
|
584
|
-
case "clock": {
|
|
585
|
-
rect(c, OUTLINE, x + 3, y + 1, 10, 10);
|
|
586
|
-
rect(c, "#f9fafb", x + 4, y + 2, 8, 8);
|
|
587
|
-
rect(c, "#1f2937", x + 8, y + 3, 1, 3);
|
|
588
|
-
rect(c, "#1f2937", x + 8, y + 6, 3, 1);
|
|
589
|
-
break;
|
|
590
|
-
}
|
|
591
|
-
case "poster": {
|
|
592
|
-
const colors = ["#7c3aed", "#0ea5e9", "#db2777", "#059669"];
|
|
593
|
-
const color = colors[(item.x + item.y) % colors.length] as string;
|
|
594
|
-
rect(c, OUTLINE, x + 2, y, 12, 11);
|
|
595
|
-
rect(c, color, x + 3, y + 1, 10, 9);
|
|
596
|
-
rect(c, shade(color, 60), x + 5, y + 3, 6, 3);
|
|
597
|
-
rect(c, "#f9fafb", x + 5, y + 7, 6, 1);
|
|
598
|
-
break;
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
|
|
603
386
|
function nameTag(c: Ctx, agent: Agent, selected: boolean, width: number): void {
|
|
604
387
|
c.font = LABEL_FONT;
|
|
605
388
|
c.textBaseline = "top";
|
|
@@ -661,7 +444,7 @@ function drawProgress(c: Ctx, world: World, palette: Palette): void {
|
|
|
661
444
|
const w = 96;
|
|
662
445
|
rect(c, "rgba(17,24,39,0.85)", 4, 4, w, 20);
|
|
663
446
|
c.fillStyle = "#ffffff";
|
|
664
|
-
c.fillText(
|
|
447
|
+
c.fillText(`${world.pack.words.quests} ${done}/${world.tasks.length}`, 8, 6.5);
|
|
665
448
|
rect(c, "#374151", 8, 16, w - 8, 4);
|
|
666
449
|
rect(c, palette.primary, 8, 16, Math.round(((w - 8) * done) / world.tasks.length), 4);
|
|
667
450
|
}
|
|
@@ -681,11 +464,18 @@ export function drawWorld(
|
|
|
681
464
|
if (building) c.drawImage(building, 0, 0, width, height);
|
|
682
465
|
else drawBuilding(c, world, palette, daylight);
|
|
683
466
|
|
|
467
|
+
// On the floor, so people and furniture stay in front of the lines.
|
|
468
|
+
if (world.showLinks && world.links.length > 0) {
|
|
469
|
+
world.routes ??= linkRoutes(world);
|
|
470
|
+
drawLinks(c, world.routes, time);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
const painter = painterFor(world);
|
|
684
474
|
const scene: { depth: number; draw: () => void }[] = [];
|
|
685
475
|
for (const item of world.furniture) {
|
|
686
|
-
const spec =
|
|
476
|
+
const spec = specOf(world, item);
|
|
687
477
|
if (spec.solid === false) continue;
|
|
688
|
-
scene.push({ depth: (item.y + spec.h) * TILE - 2, draw: () =>
|
|
478
|
+
scene.push({ depth: (item.y + spec.h) * TILE - 2, draw: () => painterFor(world).furniture(c, item, world, time, palette) });
|
|
689
479
|
}
|
|
690
480
|
const shown = world.agents.filter((agent) => agent.state !== "away");
|
|
691
481
|
for (const agent of shown) scene.push({ depth: agent.y * TILE + 14, draw: () => drawAgent(c, agent, time, agent.id === selected) });
|
|
@@ -705,13 +495,7 @@ export function drawWorld(
|
|
|
705
495
|
c.fillStyle = gradient;
|
|
706
496
|
c.fillRect(x - radius, y - radius, radius * 2, radius * 2);
|
|
707
497
|
};
|
|
708
|
-
for (const item of world.furniture)
|
|
709
|
-
if (item.type === "lamp") glow(item.x * TILE + 8, item.y * TILE, 36, "rgba(253,224,139,0.30)");
|
|
710
|
-
if (item.type === "arcade" || item.type === "vending" || item.type === "tv") glow(item.x * TILE + 8, item.y * TILE, 26, "rgba(125,211,252,0.16)");
|
|
711
|
-
if (item.type === "desk" && world.agents.some((a) => a.seat?.x === item.x + 1 && a.seat?.y === item.y + 2 && isSeated(a))) {
|
|
712
|
-
glow(item.x * TILE + 24, item.y * TILE + 8, 30, "rgba(125,211,252,0.26)");
|
|
713
|
-
}
|
|
714
|
-
}
|
|
498
|
+
for (const item of world.furniture) painter.glow(item, world, glow);
|
|
715
499
|
c.restore();
|
|
716
500
|
}
|
|
717
501
|
|
package/src/index.ts
CHANGED
|
@@ -13,6 +13,9 @@ import { createOffice, type OfficeHandle } from "./office.js";
|
|
|
13
13
|
import type { OfficeScene } from "./types.js";
|
|
14
14
|
|
|
15
15
|
export { createOffice } from "./office.js";
|
|
16
|
+
export { registerPainter, type Painter } from "./draw.js";
|
|
17
|
+
export { drawLinks, linkRoutes, type LinkRoute } from "./links.js";
|
|
18
|
+
export { registerWorld, type ItemSpec, type WorldData } from "./pack.js";
|
|
16
19
|
export type { OfficeHandle, OfficeOptions } from "./office.js";
|
|
17
20
|
export * from "./types.js";
|
|
18
21
|
export { FURNITURE, agentAt, applyLayout, applyUpdate, createWorld, findPath, step } from "./world.js";
|
package/src/links.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/** How the code depends on itself, drawn as lines along the routes agents really walk. */
|
|
2
|
+
|
|
3
|
+
import { TILE, type Ctx } from "./paint.js";
|
|
4
|
+
import { findPath, isWalkable, type World } from "./world.js";
|
|
5
|
+
|
|
6
|
+
export interface LinkRoute { from: string; to: string; points: { x: number; y: number }[] }
|
|
7
|
+
|
|
8
|
+
const COLOURS = ["#38bdf8", "#f472b6", "#fbbf24", "#a78bfa", "#34d399", "#fb7185"];
|
|
9
|
+
|
|
10
|
+
/** A tile someone can stand on, as near the middle of the room as possible. */
|
|
11
|
+
function standingTile(world: World, roomId: string): { x: number; y: number } | undefined {
|
|
12
|
+
const room = world.rooms.find((candidate) => candidate.id === roomId);
|
|
13
|
+
if (!room) return undefined;
|
|
14
|
+
const cx = room.x + (room.w >> 1);
|
|
15
|
+
const cy = room.y + (room.h >> 1);
|
|
16
|
+
let best: { x: number; y: number } | undefined;
|
|
17
|
+
let bestDistance = Infinity;
|
|
18
|
+
for (let y = room.y; y < room.y + room.h; y++) {
|
|
19
|
+
for (let x = room.x; x < room.x + room.w; x++) {
|
|
20
|
+
const distance = Math.abs(x - cx) + Math.abs(y - cy);
|
|
21
|
+
if (distance < bestDistance && isWalkable(world, x, y)) {
|
|
22
|
+
best = { x, y };
|
|
23
|
+
bestDistance = distance;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return best;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Each link's walking route. A link whose rooms aren't on the map, or can't be reached, is left out. */
|
|
31
|
+
export function linkRoutes(world: World): LinkRoute[] {
|
|
32
|
+
const routes: LinkRoute[] = [];
|
|
33
|
+
for (const link of world.links) {
|
|
34
|
+
const start = standingTile(world, link.from);
|
|
35
|
+
const end = standingTile(world, link.to);
|
|
36
|
+
if (!start || !end) continue;
|
|
37
|
+
const path = findPath(world, start, end);
|
|
38
|
+
if (path && path.length > 0) routes.push({ from: link.from, to: link.to, points: [start, ...path] });
|
|
39
|
+
}
|
|
40
|
+
return routes;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Lines that move towards what is depended on. Shared stretches sit side by side. */
|
|
44
|
+
export function drawLinks(c: Ctx, routes: LinkRoute[], time: number): void {
|
|
45
|
+
routes.forEach((route, index) => {
|
|
46
|
+
const colour = COLOURS[index % COLOURS.length] as string;
|
|
47
|
+
const offset = ((index % 5) - 2) * 2;
|
|
48
|
+
const at = (point: { x: number; y: number }): [number, number] => [point.x * TILE + 8 + offset, point.y * TILE + 8 + offset];
|
|
49
|
+
c.save();
|
|
50
|
+
c.lineWidth = 2;
|
|
51
|
+
c.strokeStyle = colour;
|
|
52
|
+
c.globalAlpha = 0.85;
|
|
53
|
+
c.setLineDash([6, 5]);
|
|
54
|
+
c.lineDashOffset = -time * 14;
|
|
55
|
+
c.beginPath();
|
|
56
|
+
route.points.forEach((point, i) => {
|
|
57
|
+
const [x, y] = at(point);
|
|
58
|
+
if (i === 0) c.moveTo(x, y);
|
|
59
|
+
else c.lineTo(x, y);
|
|
60
|
+
});
|
|
61
|
+
c.stroke();
|
|
62
|
+
c.setLineDash([]);
|
|
63
|
+
const first = route.points[0];
|
|
64
|
+
const last = route.points[route.points.length - 1];
|
|
65
|
+
if (first && last) {
|
|
66
|
+
const [sx, sy] = at(first);
|
|
67
|
+
const [ex, ey] = at(last);
|
|
68
|
+
c.fillStyle = colour;
|
|
69
|
+
c.fillRect(sx - 2, sy - 2, 4, 4);
|
|
70
|
+
c.beginPath();
|
|
71
|
+
c.arc(ex, ey, 4, 0, Math.PI * 2);
|
|
72
|
+
c.fill();
|
|
73
|
+
}
|
|
74
|
+
c.restore();
|
|
75
|
+
});
|
|
76
|
+
}
|
package/src/office.ts
CHANGED
|
@@ -10,6 +10,8 @@ export interface OfficeHandle {
|
|
|
10
10
|
update(scene: Pick<OfficeScene, "agents" | "tasks">): void;
|
|
11
11
|
/** Change the building while everyone is in it. */
|
|
12
12
|
setLayout(layout: OfficeLayout): void;
|
|
13
|
+
/** Show or hide the lines between rooms whose code is linked. */
|
|
14
|
+
setLinksVisible(on: boolean): void;
|
|
13
15
|
/** Pick an agent by id, or pass nothing to clear. */
|
|
14
16
|
select(id?: string): void;
|
|
15
17
|
/** The tile under a pointer event, allowing for the camera. */
|
|
@@ -239,6 +241,7 @@ export function createOffice(canvas: HTMLCanvasElement, scene: OfficeScene = {},
|
|
|
239
241
|
select,
|
|
240
242
|
tileAt,
|
|
241
243
|
setOverlay(draw) { overlay = draw; },
|
|
244
|
+
setLinksVisible(on) { world.showLinks = on; if (still) paint(); },
|
|
242
245
|
setInteractive(on) { interactive = on; drag = undefined; if (!on) canvas.style.cursor = "crosshair"; },
|
|
243
246
|
zoom,
|
|
244
247
|
destroy() {
|
package/src/pack.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What makes one world different from another. The engine handles walking, seats, time of day
|
|
3
|
+
* and agents, for any world. A world brings this data, plus a painter (see registerPainter).
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { OfficeLayout } from "./types.js";
|
|
7
|
+
|
|
8
|
+
export interface ItemSpec {
|
|
9
|
+
w: number;
|
|
10
|
+
h: number;
|
|
11
|
+
solid?: boolean;
|
|
12
|
+
wall?: boolean;
|
|
13
|
+
/** Where someone stands to use it, relative to its corner. */
|
|
14
|
+
spot?: { x: number; y: number };
|
|
15
|
+
/** seat: where an agent works. lounge: where it rests. board: where it reviews. */
|
|
16
|
+
use?: "seat" | "lounge" | "board";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface WorldData {
|
|
20
|
+
id: string;
|
|
21
|
+
name: string;
|
|
22
|
+
words: {
|
|
23
|
+
/** The progress label over the map. */
|
|
24
|
+
quests: string;
|
|
25
|
+
};
|
|
26
|
+
items: Record<string, ItemSpec>;
|
|
27
|
+
layout: Required<Pick<OfficeLayout, "cols" | "rows" | "rooms" | "doors" | "furniture">>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const worlds = new Map<string, WorldData>();
|
|
31
|
+
|
|
32
|
+
export function registerWorld(data: WorldData): void {
|
|
33
|
+
worlds.set(data.id, data);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const worldData = (id: string | undefined, fallback: WorldData): WorldData => (id && worlds.get(id)) || fallback;
|
package/src/paint.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** The small tools every painter uses. */
|
|
2
|
+
|
|
3
|
+
export const TILE = 16;
|
|
4
|
+
|
|
5
|
+
export interface Palette { primary: string; background: string; text: string; accent: string }
|
|
6
|
+
|
|
7
|
+
export type Ctx = CanvasRenderingContext2D;
|
|
8
|
+
|
|
9
|
+
export const LABEL_FONT = "bold 7px ui-monospace, Menlo, Consolas, monospace";
|
|
10
|
+
export const BODY_FONT = "7px ui-monospace, Menlo, Consolas, monospace";
|
|
11
|
+
export const OUTLINE = "#14141f";
|
|
12
|
+
|
|
13
|
+
export const rect = (c: Ctx, color: string, x: number, y: number, w: number, h: number): void => {
|
|
14
|
+
c.fillStyle = color;
|
|
15
|
+
c.fillRect(Math.round(x), Math.round(y), w, h);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export function shade(color: string, amount: number): string {
|
|
19
|
+
if (!color.startsWith("#")) return color;
|
|
20
|
+
const hex = color.length === 4 ? `#${color[1]}${color[1]}${color[2]}${color[2]}${color[3]}${color[3]}` : color;
|
|
21
|
+
const n = parseInt(hex.slice(1, 7), 16);
|
|
22
|
+
const channel = (shift: number): number => Math.max(0, Math.min(255, ((n >> shift) & 255) + amount));
|
|
23
|
+
return `rgb(${channel(16)},${channel(8)},${channel(0)})`;
|
|
24
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -2,11 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
export type AgentState = "working" | "thinking" | "blocked" | "reviewing" | "idle" | "done" | "away";
|
|
4
4
|
export type TaskState = "todo" | "doing" | "done";
|
|
5
|
-
export type FloorStyle = "wood" | "carpet" | "tile" | "stone" | "concrete" | "grass";
|
|
6
|
-
export type
|
|
5
|
+
export type FloorStyle = "wood" | "carpet" | "tile" | "stone" | "concrete" | "grass" | "hall";
|
|
6
|
+
export type OfficeFurnitureType =
|
|
7
7
|
| "desk" | "coffee" | "sofa" | "plant" | "fern" | "board" | "whiteboard" | "shelf" | "server" | "table" | "rug"
|
|
8
8
|
| "water" | "arcade" | "fridge" | "printer" | "lamp" | "beanbag" | "tv" | "vending" | "clock" | "poster" | "trophy";
|
|
9
9
|
|
|
10
|
+
/** An office item, or whatever else the world defines. */
|
|
11
|
+
export type FurnitureType = OfficeFurnitureType | (string & {});
|
|
12
|
+
|
|
10
13
|
export interface OfficeRoom {
|
|
11
14
|
id?: string;
|
|
12
15
|
name?: string;
|
|
@@ -35,15 +38,22 @@ export interface OfficeAgent {
|
|
|
35
38
|
}
|
|
36
39
|
export interface OfficeTask { title: string; state?: TaskState; agent?: string }
|
|
37
40
|
|
|
41
|
+
/** `from` depends on `to`. */
|
|
42
|
+
export interface OfficeLink { from: string; to: string }
|
|
43
|
+
|
|
38
44
|
export interface OfficeLayout {
|
|
39
45
|
cols?: number;
|
|
40
46
|
rows?: number;
|
|
41
47
|
rooms?: OfficeRoom[];
|
|
42
48
|
doors?: OfficeDoor[];
|
|
43
49
|
furniture?: OfficeFurniture[];
|
|
50
|
+
/** How the rooms' code depends on each other. Drawn as lines between them. */
|
|
51
|
+
links?: OfficeLink[];
|
|
44
52
|
}
|
|
45
53
|
|
|
46
54
|
export interface OfficeScene extends OfficeLayout {
|
|
55
|
+
/** Which world to draw. The office if left out or unknown. */
|
|
56
|
+
world?: string;
|
|
47
57
|
agents?: OfficeAgent[];
|
|
48
58
|
tasks?: OfficeTask[];
|
|
49
59
|
}
|