@songsid/agend 2.3.2-beta.1 → 2.3.2-beta.11
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/THIRD_PARTY_NOTICES.md +40 -0
- package/dist/agent-cli.js +69 -2
- package/dist/agent-cli.js.map +1 -1
- package/dist/agent-endpoint.js +11 -0
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/agent-whats-new.js +3 -0
- package/dist/agent-whats-new.js.map +1 -1
- package/dist/antigravity-usage-scan.d.ts +58 -0
- package/dist/antigravity-usage-scan.js +257 -0
- package/dist/antigravity-usage-scan.js.map +1 -0
- package/dist/antigravity-usage-worker.d.ts +1 -0
- package/dist/antigravity-usage-worker.js +10 -0
- package/dist/antigravity-usage-worker.js.map +1 -0
- package/dist/auth-api.js +4 -0
- package/dist/auth-api.js.map +1 -1
- package/dist/backend/kiro-classic-archive.d.ts +52 -0
- package/dist/backend/kiro-classic-archive.js +444 -0
- package/dist/backend/kiro-classic-archive.js.map +1 -0
- package/dist/backend/kiro-identity.d.ts +83 -0
- package/dist/backend/kiro-identity.js +144 -0
- package/dist/backend/kiro-identity.js.map +1 -1
- package/dist/backend/kiro-migration-verify.d.ts +10 -0
- package/dist/backend/kiro-migration-verify.js +52 -0
- package/dist/backend/kiro-migration-verify.js.map +1 -0
- package/dist/backend/kiro-retired-model.d.ts +62 -0
- package/dist/backend/kiro-retired-model.js +160 -0
- package/dist/backend/kiro-retired-model.js.map +1 -0
- package/dist/backend/kiro-v2-store-worker.js +21 -1
- package/dist/backend/kiro-v2-store-worker.js.map +1 -1
- package/dist/backend/kiro-v2-store.d.ts +8 -0
- package/dist/backend/kiro-v2-store.js.map +1 -1
- package/dist/backend/kiro.d.ts +169 -2
- package/dist/backend/kiro.js +703 -28
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/types.d.ts +108 -0
- package/dist/backend/types.js +9 -0
- package/dist/backend/types.js.map +1 -1
- package/dist/backend-signin-guide.d.ts +1 -0
- package/dist/backend-signin-guide.js +9 -0
- package/dist/backend-signin-guide.js.map +1 -0
- package/dist/backend-signin-pending.d.ts +10 -0
- package/dist/backend-signin-pending.js +44 -0
- package/dist/backend-signin-pending.js.map +1 -0
- package/dist/browser-backend-signins.d.ts +56 -0
- package/dist/browser-backend-signins.js +189 -0
- package/dist/browser-backend-signins.js.map +1 -0
- package/dist/cache-ledger.d.ts +38 -2
- package/dist/cache-ledger.js +133 -8
- package/dist/cache-ledger.js.map +1 -1
- package/dist/cache-prices.d.ts +2 -1
- package/dist/cache-prices.js +5 -2
- package/dist/cache-prices.js.map +1 -1
- package/dist/cache-service.d.ts +68 -0
- package/dist/cache-service.js +355 -13
- package/dist/cache-service.js.map +1 -1
- package/dist/channel/adapters/discord.d.ts +11 -0
- package/dist/channel/adapters/discord.js +71 -14
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/ipc-timeouts.js +3 -0
- package/dist/channel/ipc-timeouts.js.map +1 -1
- package/dist/channel/mcp-tools.js +41 -2
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/channel/tool-router.js +2 -1
- package/dist/channel/tool-router.js.map +1 -1
- package/dist/classic-channel-manager.d.ts +11 -0
- package/dist/classic-channel-manager.js +39 -1
- package/dist/classic-channel-manager.js.map +1 -1
- package/dist/cli.js +54 -2
- package/dist/cli.js.map +1 -1
- package/dist/config-validator.d.ts +7 -1
- package/dist/config-validator.js +55 -10
- package/dist/config-validator.js.map +1 -1
- package/dist/config.d.ts +19 -0
- package/dist/config.js +33 -1
- package/dist/config.js.map +1 -1
- package/dist/cpu-quota.d.ts +23 -0
- package/dist/cpu-quota.js +86 -0
- package/dist/cpu-quota.js.map +1 -0
- package/dist/daemon.d.ts +68 -0
- package/dist/daemon.js +448 -26
- package/dist/daemon.js.map +1 -1
- package/dist/dashboard-permissions.d.ts +31 -6
- package/dist/dashboard-permissions.js +84 -9
- package/dist/dashboard-permissions.js.map +1 -1
- package/dist/doctor.js +2 -1
- package/dist/doctor.js.map +1 -1
- package/dist/fleet-control-audit.d.ts +1 -1
- package/dist/fleet-control-audit.js +3 -0
- package/dist/fleet-control-audit.js.map +1 -1
- package/dist/fleet-manager.d.ts +133 -2
- package/dist/fleet-manager.js +905 -94
- package/dist/fleet-manager.js.map +1 -1
- package/dist/forged-envelope-scan.d.ts +9 -0
- package/dist/forged-envelope-scan.js +30 -5
- package/dist/forged-envelope-scan.js.map +1 -1
- package/dist/general-knowledge/skills/credential-profiles/SKILL.md +5 -2
- package/dist/general-knowledge/skills/kiro-engine-migration/SKILL.md +24 -1
- package/dist/general-knowledge/skills/web-appearance/SKILL.canvas-worker-off.md +49 -1
- package/dist/general-knowledge/skills/web-appearance/SKILL.md +49 -1
- package/dist/general-knowledge/skills/web-dashboard/SKILL.canvas-worker-off.md +47 -9
- package/dist/general-knowledge/skills/web-dashboard/SKILL.md +54 -11
- package/dist/instance-config-impact.js +10 -5
- package/dist/instance-config-impact.js.map +1 -1
- package/dist/instance-lifecycle.d.ts +23 -0
- package/dist/instance-lifecycle.js +62 -12
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/kiro-db-reader.d.ts +5 -0
- package/dist/kiro-db-reader.js +18 -0
- package/dist/kiro-db-reader.js.map +1 -1
- package/dist/kiro-engine-status.d.ts +2 -0
- package/dist/kiro-engine-status.js +9 -0
- package/dist/kiro-engine-status.js.map +1 -1
- package/dist/kiro-migrate.d.ts +60 -0
- package/dist/kiro-migrate.js +160 -0
- package/dist/kiro-migrate.js.map +1 -0
- package/dist/kiro-session-proof.d.ts +41 -0
- package/dist/kiro-session-proof.js +89 -0
- package/dist/kiro-session-proof.js.map +1 -0
- package/dist/kiro-transcript-lane.d.ts +2 -0
- package/dist/kiro-transcript-lane.js.map +1 -1
- package/dist/kiro-transcript-worker.js +1 -0
- package/dist/kiro-transcript-worker.js.map +1 -1
- package/dist/kiro-tui-forensic.d.ts +12 -0
- package/dist/kiro-tui-forensic.js +110 -0
- package/dist/kiro-tui-forensic.js.map +1 -0
- package/dist/kiro-usage-run.d.ts +7 -0
- package/dist/kiro-usage-run.js +39 -0
- package/dist/kiro-usage-run.js.map +1 -0
- package/dist/kiro-usage-scan.d.ts +49 -0
- package/dist/kiro-usage-scan.js +223 -0
- package/dist/kiro-usage-scan.js.map +1 -0
- package/dist/kiro-usage-worker.d.ts +1 -0
- package/dist/kiro-usage-worker.js +10 -0
- package/dist/kiro-usage-worker.js.map +1 -0
- package/dist/kiro-usage.d.ts +58 -0
- package/dist/kiro-usage.js +310 -0
- package/dist/kiro-usage.js.map +1 -0
- package/dist/locale.js +230 -8
- package/dist/locale.js.map +1 -1
- package/dist/log-service.js +1 -1
- package/dist/log-service.js.map +1 -1
- package/dist/login-url.d.ts +4 -0
- package/dist/login-url.js +22 -0
- package/dist/login-url.js.map +1 -0
- package/dist/needs-you-hub.d.ts +2 -1
- package/dist/needs-you-hub.js +1 -1
- package/dist/needs-you-hub.js.map +1 -1
- package/dist/needs-you.d.ts +10 -2
- package/dist/needs-you.js +10 -2
- package/dist/needs-you.js.map +1 -1
- package/dist/notice-delivery.d.ts +16 -0
- package/dist/notice-delivery.js +35 -0
- package/dist/notice-delivery.js.map +1 -0
- package/dist/office-share-page.d.ts +16 -0
- package/dist/office-share-page.js +85 -0
- package/dist/office-share-page.js.map +1 -0
- package/dist/office-share-service.d.ts +91 -0
- package/dist/office-share-service.js +176 -0
- package/dist/office-share-service.js.map +1 -0
- package/dist/office-share.d.ts +268 -0
- package/dist/office-share.js +574 -0
- package/dist/office-share.js.map +1 -0
- package/dist/opencode-usage-scan.d.ts +28 -0
- package/dist/opencode-usage-scan.js +92 -0
- package/dist/opencode-usage-scan.js.map +1 -0
- package/dist/opencode-usage-worker.d.ts +1 -0
- package/dist/opencode-usage-worker.js +10 -0
- package/dist/opencode-usage-worker.js.map +1 -0
- package/dist/outbound-handlers.js +22 -2
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +65 -2
- package/dist/outbound-schemas.js +56 -2
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/public-web-gateway.js +7 -1
- package/dist/public-web-gateway.js.map +1 -1
- package/dist/quickstart-api.js +13 -2
- package/dist/quickstart-api.js.map +1 -1
- package/dist/quickstart.js +2 -1
- package/dist/quickstart.js.map +1 -1
- package/dist/remote-card-answer.d.ts +83 -0
- package/dist/remote-card-answer.js +208 -0
- package/dist/remote-card-answer.js.map +1 -0
- package/dist/remote-card-prompt.d.ts +38 -0
- package/dist/remote-card-prompt.js +86 -0
- package/dist/remote-card-prompt.js.map +1 -0
- package/dist/reply-triggers.d.ts +2 -0
- package/dist/reply-triggers.js +3 -0
- package/dist/reply-triggers.js.map +1 -0
- package/dist/settings-api.d.ts +2 -0
- package/dist/settings-api.js +41 -16
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-change.js +20 -14
- package/dist/settings-change.js.map +1 -1
- package/dist/settings-effect.js +7 -1
- package/dist/settings-effect.js.map +1 -1
- package/dist/settings-instance-patch.js +4 -1
- package/dist/settings-instance-patch.js.map +1 -1
- package/dist/setup-form.d.ts +1 -1
- package/dist/setup-form.js +55 -6
- package/dist/setup-form.js.map +1 -1
- package/dist/setup-host.d.ts +9 -0
- package/dist/setup-host.js +127 -6
- package/dist/setup-host.js.map +1 -1
- package/dist/setup-signin-assets.d.ts +2 -0
- package/dist/setup-signin-assets.js +6 -0
- package/dist/setup-signin-assets.js.map +1 -0
- package/dist/setup-wizard.js +2 -0
- package/dist/setup-wizard.js.map +1 -1
- package/dist/structured-signin-transport.d.ts +8 -0
- package/dist/structured-signin-transport.js +79 -0
- package/dist/structured-signin-transport.js.map +1 -0
- package/dist/structured-signin.d.ts +131 -0
- package/dist/structured-signin.js +388 -0
- package/dist/structured-signin.js.map +1 -0
- package/dist/tool-permissions.js +29 -0
- package/dist/tool-permissions.js.map +1 -1
- package/dist/topic-commands.js +6 -2
- package/dist/topic-commands.js.map +1 -1
- package/dist/transcript-sources.d.ts +27 -2
- package/dist/transcript-sources.js +104 -40
- package/dist/transcript-sources.js.map +1 -1
- package/dist/turn-reply-guard.d.ts +1 -1
- package/dist/turn-reply-guard.js +1 -1
- package/dist/turn-reply-guard.js.map +1 -1
- package/dist/types.d.ts +33 -8
- package/dist/ui/backend-signin.js +63 -10
- package/dist/ui/chat-store.js +3 -2
- package/dist/ui/chat-strings.js +4 -4
- package/dist/ui/fleet-office-editor.js +68 -11
- package/dist/ui/fleet-office-layout.js +110 -35
- package/dist/ui/fleet-office-model.js +6 -3
- package/dist/ui/fleet-office-overview.js +167 -0
- package/dist/ui/fleet-office-pack.d.ts +13 -3
- package/dist/ui/fleet-office-pack.js +214 -24
- package/dist/ui/fleet-office-skin.js +98 -23
- package/dist/ui/fleet-office-store.js +82 -5
- package/dist/ui/fleet-office-view.js +5 -4
- package/dist/ui/fleet-office-zones.js +232 -21
- package/dist/ui/fleet-office.js +549 -186
- package/dist/ui/fleet-usage.js +19 -15
- package/dist/ui/office-share-dialog.js +145 -0
- package/dist/ui/office-share-page.js +164 -0
- package/dist/ui/panel-chat.js +22 -3
- package/dist/ui/panel-log.js +4 -4
- package/dist/ui/panel-needs.js +5 -5
- package/dist/ui/panel-settings.js +38 -12
- package/dist/ui/settings-dialogs.js +8 -1
- package/dist/ui/settings-guests.js +17 -7
- package/dist/ui/settings-model.js +22 -6
- package/dist/ui/settings-registry.js +4 -0
- package/dist/ui/settings-strings.js +18 -2
- package/dist/ui/shared/app-i18n.js +4 -2
- package/dist/ui/shared/app-shell.js +14 -6
- package/dist/ui/shared/app-stream.js +2 -1
- package/dist/ui/shared/app.css +85 -4
- package/dist/ui/shared/office/cozy.png +0 -0
- package/dist/ui/shared/side-panel.js +95 -0
- package/dist/ui/structured-signin.js +115 -0
- package/dist/usage-report.d.ts +20 -0
- package/dist/usage-report.js +54 -17
- package/dist/usage-report.js.map +1 -1
- package/dist/usage-rollup.d.ts +9 -1
- package/dist/usage-rollup.js +20 -4
- package/dist/usage-rollup.js.map +1 -1
- package/dist/web-api.d.ts +23 -1
- package/dist/web-api.js +292 -3
- package/dist/web-api.js.map +1 -1
- package/dist/web-auth.js +1 -1
- package/dist/web-auth.js.map +1 -1
- package/dist/web-backend-login.d.ts +1 -7
- package/dist/web-backend-login.js +2 -24
- package/dist/web-backend-login.js.map +1 -1
- package/dist/web-office-acts.d.ts +173 -0
- package/dist/web-office-acts.js +285 -0
- package/dist/web-office-acts.js.map +1 -0
- package/dist/web-office-pack-read.d.ts +35 -0
- package/dist/web-office-pack-read.js +252 -0
- package/dist/web-office-pack-read.js.map +1 -0
- package/dist/web-office-pack-tools.d.ts +232 -0
- package/dist/web-office-pack-tools.js +754 -0
- package/dist/web-office-pack-tools.js.map +1 -0
- package/dist/web-office-skins.d.ts +71 -6
- package/dist/web-office-skins.js +321 -35
- package/dist/web-office-skins.js.map +1 -1
- package/dist/web-office-store.d.ts +5 -2
- package/dist/web-office-store.js +69 -10
- package/dist/web-office-store.js.map +1 -1
- package/dist/web-settings-patch.d.ts +2 -1
- package/dist/web-settings-patch.js +4 -1
- package/dist/web-settings-patch.js.map +1 -1
- package/dist/working-recovery.d.ts +2 -2
- package/dist/working-recovery.js +43 -8
- package/dist/working-recovery.js.map +1 -1
- package/dist/working-state-journal.d.ts +2 -0
- package/dist/working-state-journal.js +16 -1
- package/dist/working-state-journal.js.map +1 -1
- package/package.json +3 -2
- package/templates/windows-shortcuts.ps1 +155 -140
|
@@ -24,21 +24,45 @@
|
|
|
24
24
|
// "areas": { named zones: a rect [x, y, w, h] and its seats, in the order
|
|
25
25
|
// "desks": { "rect": [40, 120, 620, 440], "seats": [[120, 220], [260, 220], …] }, they fill up
|
|
26
26
|
// "lounge": { "rect": [700, 120, 540, 260], "seats": [[760, 260], …] } },
|
|
27
|
+
// a seat is [x, y], or (#2691) { "x": 760, "y": 260, "for":
|
|
28
|
+
// ["atlas", …], "lend": false }: reserved for those instances
|
|
29
|
+
// (names checked like byInstance's, ≤ 16, none twice) — whoever
|
|
30
|
+
// of them comes to the area takes it first, and nobody else does
|
|
31
|
+
// unless "lend" (default false) lends it while they are all
|
|
32
|
+
// elsewhere (a borrower moves off when one comes). A checked
|
|
33
|
+
// seat is { x, y } — plus, reserved, `for` and `lend`
|
|
27
34
|
// "stateZones": { "working": "desks", "idle": "lounge", "needs": "alert", "default": "lounge" },
|
|
28
35
|
// the org chart's states (fleet-org.js ORG_STATES) → an area; a
|
|
29
36
|
// state it does not name goes to "default", else the first area
|
|
30
37
|
// "door": [640, 700], optional: where someone new walks in from (default: bottom middle)
|
|
31
38
|
// "waypoints": [{ "id": "hall", "x": 640, "y": 600 }, …], "edges": [["hall", "east"], …],
|
|
32
39
|
// optional: the walkable graph (else people walk straight)
|
|
40
|
+
// "straight": true, optional (#2634): every walk goes across, then up or down — each
|
|
41
|
+
// diagonal leg an L, so the walkSide / walkDown / walkUp strips
|
|
42
|
+
// always fit (graph edges laid out across and up/down put the
|
|
43
|
+
// corners on the aisles); default false
|
|
33
44
|
// "furniture": [ { "id": "rack", "sprite": STRIP, "x": 1020, "y": 140, "depth": 2,
|
|
34
|
-
// "activeWhen": "crashed", "activeSprite": STRIP
|
|
45
|
+
// "activeWhen": "crashed", "activeSprite": STRIP, "activeFor": "atlas", "loop": true, "label": "Rack",
|
|
46
|
+
// "night": { "sprite": STRIP, "x": 300, "y": 420 },
|
|
47
|
+
// "walk": { "walk": STRIP, "walkDown": STRIP, "walkUp": STRIP, "walkSide": STRIP } } ],
|
|
48
|
+
// "label" (#2574): a name tag under it, like a person's
|
|
49
|
+
// (an NPC drawn as furniture). "night" (#2568), optional: at bedtime
|
|
50
|
+
// (when the viewer has it on: 00:00–06:00 their time) drawn as this
|
|
51
|
+
// strip, playing, at (x, y) — its own place for either left out.
|
|
52
|
+
// "walk" (#2596), optional: it strolls near its (x, y) and back
|
|
53
|
+
// like an idle person, drawn walking by these strips as a
|
|
54
|
+
// character set's walking poses ("walk" required); not at bedtime
|
|
55
|
+
// with a "night".
|
|
35
56
|
// drawn by depth: below 1000 under the people, 1000 and up over
|
|
36
57
|
// them; "depth": "y" (the string) sorts it among the people by base
|
|
37
58
|
// y — its anchor's y against each person's feet: someone whose feet
|
|
38
59
|
// are lower down (a greater y; a tie too) is drawn after it, in
|
|
39
60
|
// front. (x, y) is where the strip's anchor goes. `activeWhen` (a
|
|
40
61
|
// state, or a list): while anyone shown is in it, the item plays its
|
|
41
|
-
// strip (or `activeSprite`); otherwise it holds the first frame
|
|
62
|
+
// strip (or `activeSprite`); otherwise it holds the first frame —
|
|
63
|
+
// or, with "loop": true (#2570), plays its strip all along.
|
|
64
|
+
// "activeFor" (an instance's name, or a list): only their state
|
|
65
|
+
// counts, not anyone's (a room several people take turns in)
|
|
42
66
|
// "characters": { "default": { "working": STRIP, "idle": STRIP, "paused": STRIP, "needs": STRIP, "walk": STRIP,
|
|
43
67
|
// "walkDown": STRIP, "walkUp": STRIP, "walkSide": STRIP },
|
|
44
68
|
// "fox": { … } }, "default" with at least "idle" is required; a missing pose is idle.
|
|
@@ -47,6 +71,9 @@
|
|
|
47
71
|
// walkSide, drawn as it is when moving right and mirrored when moving
|
|
48
72
|
// left (so draw it facing right). A missing one is "walk", then idle
|
|
49
73
|
// ("walk" itself is never mirrored)
|
|
74
|
+
// A set may also have "prop": STRIP (#2562) — what its people
|
|
75
|
+
// send: their messages, work and reports fly as it, in place of
|
|
76
|
+
// the pack's "props" (a set without one: the pack's props)
|
|
50
77
|
// "byInstance": { "atlas": "fox" }, optional: an instance name → its character set
|
|
51
78
|
// "props": { "task": STRIP, "send": STRIP, "report": STRIP, "broadcast": STRIP }, optional traffic items
|
|
52
79
|
// "bubbles": { "en": { "working": ["On it."] }, "zh-TW": { … } } optional: short lines per state (≤ 12 a state,
|
|
@@ -82,6 +109,10 @@
|
|
|
82
109
|
// { "pack": "cozy", the pack they were made on (on another pack they are ignored)
|
|
83
110
|
// "seats": { "desks": [[120, 220], …] }, an area's seats: as many as the pack's, each inside the area's rect
|
|
84
111
|
// "furniture": { "rack": [1000, 150] }, an item's (x, y)
|
|
112
|
+
// "furnitureNight": { "elder": [300, 420] }, an item's night place (#2665: an item with a "night" only)
|
|
113
|
+
// "seatFor": { "desks": { "0": { "for": ["atlas"], "lend": false } } },
|
|
114
|
+
// a seat's reservation by area and slot (#2691), in place of the
|
|
115
|
+
// pack's own for that seat; "for": [] is none (lends nothing)
|
|
85
116
|
// "door": [640, 700],
|
|
86
117
|
// "waypoints": [{ "id": "hall", "x": 640, "y": 600 }, …], "edges": [["hall", "east"], …] }
|
|
87
118
|
// the walkable graph, replaced whole (edges need waypoints)
|
|
@@ -89,18 +120,30 @@
|
|
|
89
120
|
// Every coordinate is on the same 1280×720 logical canvas, in the same ranges as the pack's. checkLayoutOverrides is
|
|
90
121
|
// the shape (the host checks it on every PUT /ui/offices); applyOverrides puts them on a checked pack, refusing a
|
|
91
122
|
// seat outside its area, an area or furniture the pack does not have, or a seat count that differs (the host checks
|
|
92
|
-
// that too, for a pack it keeps).
|
|
123
|
+
// that too, for a pack it keeps). A night place goes on the item's `night` (its x, y), so at bedtime (nightFurniture)
|
|
124
|
+
// it is the office's, else the pack's night x/y, else the item's (moved) day place. The host keeps `furnitureNight`
|
|
125
|
+
// and `seatFor` (#2691) beside the offices on disk, never inside them: an older host's stricter check would drop the
|
|
126
|
+
// office (#2665, web-office-store.ts).
|
|
93
127
|
|
|
94
128
|
export const LOGICAL = { w: 1280, h: 720 };
|
|
95
129
|
/** The people's depth: furniture below it is drawn under them, the rest over them. */
|
|
96
130
|
export const CHAR_DEPTH = 1000;
|
|
97
131
|
export const PACK_STATES = ["needs", "crashed", "stuck", "working", "idle", "paused", "stopped", "missing"];
|
|
98
132
|
const POSES = ["working", "idle", "paused", "needs", "walk", "walkDown", "walkUp", "walkSide"], PROPS = ["task", "send", "report", "broadcast"];
|
|
133
|
+
/** A character set's strips: its poses, and what its people send (#2562). */
|
|
134
|
+
const SET_STRIPS = [...POSES, "prop"];
|
|
135
|
+
/** A furniture item's walking strips (#2596): a character set's walking poses. */
|
|
136
|
+
const WALKS = ["walk", "walkDown", "walkUp", "walkSide"];
|
|
99
137
|
const BUILT_IN = new Set(["pixel", "studio"]);
|
|
138
|
+
/**
|
|
139
|
+
* Ids built in after packs could be uploaded under them (#2631: "cozy"): a new upload is refused, but a pack the host
|
|
140
|
+
* kept under one before (`kept`) is still read back, listed and deletable, and wins the id for the offices that name it.
|
|
141
|
+
*/
|
|
142
|
+
const RESERVED = new Set(["cozy"]);
|
|
100
143
|
const ID = /^[A-Za-z0-9_-]{1,40}$/;
|
|
101
144
|
const SRC = /^[A-Za-z0-9._/-]{1,200}$/;
|
|
102
145
|
const HEX = /^#[0-9a-f]{6}$/i;
|
|
103
|
-
const LIMIT = { areas: 32, seats: 64, furniture: 128, waypoints: 512, edges: 2048, sets: 64, byInstance: 500, lines: 12, line: 40 };
|
|
146
|
+
const LIMIT = { areas: 32, seats: 64, furniture: 128, waypoints: 512, edges: 2048, sets: 64, byInstance: 500, lines: 12, line: 40, seatFor: 16 };
|
|
104
147
|
|
|
105
148
|
class Refused extends Error { constructor(at, why) { super(`${at}: ${why}`); } }
|
|
106
149
|
/** `o[k]` when it is o's own property, else undefined: a name from a pack or a page never reaches Object.prototype. */
|
|
@@ -138,6 +181,47 @@ export function checkStrip(v, at = "strip", scale = 2) {
|
|
|
138
181
|
return { src: src(v.src, `${at}.src`), frames: int(v.frames, 1, 32, `${at}.frames`), w, h, fps: v.fps === undefined ? 4 : num(v.fps, 0, 30, `${at}.fps`),
|
|
139
182
|
ax: v.ax === undefined ? w / 2 : num(v.ax, 0, w, `${at}.ax`), ay: v.ay === undefined ? h : num(v.ay, 0, h, `${at}.ay`), dw, dh };
|
|
140
183
|
}
|
|
184
|
+
/** A furniture item's night (#2568): { sprite, x, y } — at bedtime it is drawn as `sprite` (playing) at (x, y), its own place when left out. */
|
|
185
|
+
function nightOf(v, at, scale) {
|
|
186
|
+
if (v === undefined) return null;
|
|
187
|
+
obj(v, at); keys(v, ["sprite", "x", "y"], at);
|
|
188
|
+
return { sprite: checkStrip(v.sprite, `${at}.sprite`, scale), x: v.x === undefined ? null : num(v.x, 0, LOGICAL.w, `${at}.x`), y: v.y === undefined ? null : num(v.y, 0, LOGICAL.h, `${at}.y`) };
|
|
189
|
+
}
|
|
190
|
+
/** A furniture item's walk (#2596): its walking strips, picked by heading as a character set's (poseStrip); "walk" required. */
|
|
191
|
+
function walkOf(v, at, scale) {
|
|
192
|
+
if (v === undefined) return null;
|
|
193
|
+
const w = strips(v, WALKS, at, scale);
|
|
194
|
+
if (!w.walk) throw new Refused(`${at}.walk`, "required");
|
|
195
|
+
return w;
|
|
196
|
+
}
|
|
197
|
+
/** Whose state an item's activeWhen watches (#2570): an instance's name, or a list of up to `max` (8). */
|
|
198
|
+
function names(v, at, max = 8) {
|
|
199
|
+
const one = (s, here) => { if (typeof s !== "string" || !s || s.length > 200 || inherited(s) || /[\u0000-\u001f\u007f]/.test(s)) throw new Refused(here, "an instance's name"); return s; };
|
|
200
|
+
return Array.isArray(v) ? list(v, max, at).map((s, i) => one(s, `${at}[${i}]`)) : [one(v, at)];
|
|
201
|
+
}
|
|
202
|
+
/** Whom a seat is reserved for (#2691): instances' names (as names()), up to LIMIT.seatFor, none twice; `empty` may be []. */
|
|
203
|
+
function owners(v, at, empty = false) {
|
|
204
|
+
const out = names(v, at, LIMIT.seatFor);
|
|
205
|
+
if (!out.length && !empty) throw new Refused(at, "at least one instance");
|
|
206
|
+
if (new Set(out).size !== out.length) throw new Refused(at, "a name twice");
|
|
207
|
+
return out;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* A seat (#2691): [x, y], or { x, y, for, lend } — reserved for `for` (lent to anyone while they are all elsewhere when
|
|
211
|
+
* `lend`). Checked: { x, y }, with `for` and `lend` only when reserved, so a plain seat is what it always was.
|
|
212
|
+
*/
|
|
213
|
+
function seatOf(v, at, rect) {
|
|
214
|
+
let p = null, extra = {};
|
|
215
|
+
if (isObj(v)) {
|
|
216
|
+
keys(v, ["x", "y", "for", "lend"], at);
|
|
217
|
+
p = { x: num(v.x, 0, LOGICAL.w, `${at}.x`), y: num(v.y, 0, LOGICAL.h, `${at}.y`) };
|
|
218
|
+
if (v.lend !== undefined && typeof v.lend !== "boolean") throw new Refused(`${at}.lend`, "true or false");
|
|
219
|
+
if (v.lend !== undefined && v.for === undefined) throw new Refused(`${at}.lend`, "only with for");
|
|
220
|
+
if (v.for !== undefined) extra = { for: owners(v.for, `${at}.for`), lend: v.lend === true };
|
|
221
|
+
} else p = point(v, at);
|
|
222
|
+
if (p.x < rect.x || p.x > rect.x + rect.w || p.y < rect.y || p.y > rect.y + rect.h) throw new Refused(at, "outside its area's rect");
|
|
223
|
+
return { ...p, ...extra };
|
|
224
|
+
}
|
|
141
225
|
function strips(v, names, at, scale) {
|
|
142
226
|
obj(v, at);
|
|
143
227
|
keys(v, names, at);
|
|
@@ -148,16 +232,20 @@ function states(v, at) {
|
|
|
148
232
|
return Array.isArray(v) ? list(v, 8, at).map((s, i) => one(s, `${at}[${i}]`)) : [one(v, at)];
|
|
149
233
|
}
|
|
150
234
|
|
|
151
|
-
/**
|
|
152
|
-
|
|
235
|
+
/**
|
|
236
|
+
* A pack's manifest, strictly: { ok: true, pack } or { ok: false, error: "<where>: <why>" }. `kept`: a pack the host
|
|
237
|
+
* keeps already (read back, not uploaded), which may hold a RESERVED id.
|
|
238
|
+
*/
|
|
239
|
+
export function checkSkinPack(x, { kept = false } = {}) {
|
|
153
240
|
try {
|
|
154
241
|
obj(x, "pack");
|
|
155
|
-
keys(x, ["format", "id", "scale", "background", "foreground", "matte", "areas", "stateZones", "door", "waypoints", "edges", "furniture", "characters", "byInstance", "props", "bubbles", "room", "desk"], "pack");
|
|
242
|
+
keys(x, ["format", "id", "scale", "background", "foreground", "matte", "areas", "stateZones", "door", "waypoints", "edges", "straight", "furniture", "characters", "byInstance", "props", "bubbles", "room", "desk"], "pack");
|
|
156
243
|
if (x.format !== 1) throw new Refused("format", "1");
|
|
157
244
|
if (typeof x.id !== "string" || !ID.test(x.id)) throw new Refused("id", "1–40 letters, digits, _ or -");
|
|
158
|
-
if (BUILT_IN.has(x.id)) throw new Refused("id", "a built-in skin's id");
|
|
245
|
+
if (BUILT_IN.has(x.id) || (!kept && RESERVED.has(x.id))) throw new Refused("id", "a built-in skin's id");
|
|
159
246
|
const scale = x.scale === undefined ? 2 : int(x.scale, 1, 8, "scale");
|
|
160
247
|
if (x.matte !== undefined && !matteOk(x.matte)) throw new Refused("matte", "a colour as #rrggbb");
|
|
248
|
+
if (x.straight !== undefined && typeof x.straight !== "boolean") throw new Refused("straight", "true or false");
|
|
161
249
|
const areas = {};
|
|
162
250
|
const rawAreas = x.areas === undefined ? {} : obj(x.areas, "areas");
|
|
163
251
|
if (Object.keys(rawAreas).length > LIMIT.areas) throw new Refused("areas", `at most ${LIMIT.areas}`);
|
|
@@ -168,11 +256,7 @@ export function checkSkinPack(x) {
|
|
|
168
256
|
if (!Array.isArray(a.rect) || a.rect.length !== 4) throw new Refused(`${at}.rect`, "[x, y, w, h]");
|
|
169
257
|
const [rx, ry] = [num(a.rect[0], 0, LOGICAL.w, `${at}.rect[0]`), num(a.rect[1], 0, LOGICAL.h, `${at}.rect[1]`)];
|
|
170
258
|
const rect = { x: rx, y: ry, w: num(a.rect[2], 1, LOGICAL.w - rx, `${at}.rect[2]`), h: num(a.rect[3], 1, LOGICAL.h - ry, `${at}.rect[3]`) };
|
|
171
|
-
const seats = list(a.seats, LIMIT.seats, `${at}.seats`).map((s, i) => {
|
|
172
|
-
const p = point(s, `${at}.seats[${i}]`);
|
|
173
|
-
if (p.x < rect.x || p.x > rect.x + rect.w || p.y < rect.y || p.y > rect.y + rect.h) throw new Refused(`${at}.seats[${i}]`, "outside its area's rect");
|
|
174
|
-
return p;
|
|
175
|
-
});
|
|
259
|
+
const seats = list(a.seats, LIMIT.seats, `${at}.seats`).map((s, i) => seatOf(s, `${at}.seats[${i}]`, rect));
|
|
176
260
|
if (!seats.length) throw new Refused(`${at}.seats`, "at least one seat");
|
|
177
261
|
areas[name] = { name, rect, seats };
|
|
178
262
|
}
|
|
@@ -204,20 +288,25 @@ export function checkSkinPack(x) {
|
|
|
204
288
|
const fids = new Set();
|
|
205
289
|
const furniture = list(x.furniture ?? [], LIMIT.furniture, "furniture").map((f, i) => {
|
|
206
290
|
const at = `furniture[${i}]`;
|
|
207
|
-
obj(f, at); keys(f, ["id", "sprite", "x", "y", "depth", "activeWhen", "activeSprite"], at);
|
|
291
|
+
obj(f, at); keys(f, ["id", "sprite", "x", "y", "depth", "activeWhen", "activeSprite", "activeFor", "loop", "label", "night", "walk"], at);
|
|
208
292
|
if (!nameOk(f.id) || fids.has(f.id)) throw new Refused(`${at}.id`, "a new name of letters, digits, _ or - (not an inherited one)");
|
|
209
293
|
fids.add(f.id);
|
|
210
294
|
if (f.activeSprite !== undefined && f.activeWhen === undefined) throw new Refused(`${at}.activeSprite`, "only with activeWhen");
|
|
295
|
+
if (f.activeFor !== undefined && f.activeWhen === undefined) throw new Refused(`${at}.activeFor`, "only with activeWhen");
|
|
296
|
+
if (f.loop !== undefined && typeof f.loop !== "boolean") throw new Refused(`${at}.loop`, "true or false");
|
|
297
|
+
if (f.label !== undefined && (typeof f.label !== "string" || !f.label.trim() || [...f.label].length > LIMIT.line || /[\u0000-\u001f\u007f]/.test(f.label))) throw new Refused(`${at}.label`, `1–${LIMIT.line} characters`);
|
|
211
298
|
return { id: f.id, sprite: checkStrip(f.sprite, `${at}.sprite`, scale), x: num(f.x, 0, LOGICAL.w, `${at}.x`), y: num(f.y, 0, LOGICAL.h, `${at}.y`),
|
|
212
299
|
depth: f.depth === "y" ? "y" : depth(f.depth ?? 0, `${at}.depth`), activeWhen: f.activeWhen === undefined ? null : states(f.activeWhen, `${at}.activeWhen`),
|
|
213
|
-
activeSprite: f.activeSprite === undefined ? null : checkStrip(f.activeSprite, `${at}.activeSprite`, scale)
|
|
300
|
+
activeSprite: f.activeSprite === undefined ? null : checkStrip(f.activeSprite, `${at}.activeSprite`, scale),
|
|
301
|
+
activeFor: f.activeFor === undefined ? null : names(f.activeFor, `${at}.activeFor`), loop: f.loop === true, label: f.label === undefined ? null : f.label.trim(), night: nightOf(f.night, `${at}.night`, scale),
|
|
302
|
+
walk: walkOf(f.walk, `${at}.walk`, scale) };
|
|
214
303
|
}).sort((a, b) => order(a) - order(b));
|
|
215
304
|
const sets = obj(x.characters, "characters");
|
|
216
305
|
if (Object.keys(sets).length > LIMIT.sets) throw new Refused("characters", `at most ${LIMIT.sets} sets`);
|
|
217
306
|
const characters = {};
|
|
218
307
|
for (const [k, v] of Object.entries(sets)) {
|
|
219
308
|
if (!nameOk(k)) throw new Refused(`characters.${k}`, "a name of letters, digits, _ or - (not an inherited one)");
|
|
220
|
-
characters[k] = strips(v,
|
|
309
|
+
characters[k] = strips(v, SET_STRIPS, `characters.${k}`, scale);
|
|
221
310
|
if (!characters[k].idle) throw new Refused(`characters.${k}.idle`, "required");
|
|
222
311
|
}
|
|
223
312
|
if (!own(characters, "default")) throw new Refused("characters.default", "required");
|
|
@@ -248,7 +337,7 @@ export function checkSkinPack(x) {
|
|
|
248
337
|
const opt = (v, at) => (v === undefined ? undefined : src(v, at));
|
|
249
338
|
return { ok: true, pack: {
|
|
250
339
|
format: 1, id: x.id, scale, background: opt(x.background, "background"), foreground: opt(x.foreground, "foreground"), matte: x.matte === undefined ? undefined : x.matte.toLowerCase(), areas, stateZones,
|
|
251
|
-
door: x.door === undefined ? { x: LOGICAL.w / 2, y: LOGICAL.h - 10 } : point(x.door, "door"), waypoints, edges, furniture, characters, byInstance,
|
|
340
|
+
door: x.door === undefined ? { x: LOGICAL.w / 2, y: LOGICAL.h - 10 } : point(x.door, "door"), waypoints, edges, straight: x.straight === true, furniture, characters, byInstance,
|
|
252
341
|
props: x.props === undefined ? {} : strips(x.props, PROPS, "props", scale), bubbles,
|
|
253
342
|
room: { floor: opt(room.floor, "room.floor"), wall: opt(room.wall, "room.wall") }, desk: { back: opt(desk.back, "desk.back"), front: opt(desk.front, "desk.front"), lit: opt(desk.lit, "desk.lit") },
|
|
254
343
|
} };
|
|
@@ -261,13 +350,18 @@ export function checkSkinPack(x) {
|
|
|
261
350
|
export const skinPackOk = (x) => { const r = checkSkinPack(x); return r.ok ? r.pack : null; };
|
|
262
351
|
/** Every image a pack names (what a loader fetches before spriteSkin draws). */
|
|
263
352
|
export const packImages = (pack) => [...new Set([pack.background, pack.foreground, pack.room.floor, pack.room.wall, pack.desk.back, pack.desk.front, pack.desk.lit,
|
|
264
|
-
...pack.furniture.flatMap((f) => [f.sprite.src, f.activeSprite && f.activeSprite.src]),
|
|
353
|
+
...pack.furniture.flatMap((f) => [f.sprite.src, f.activeSprite && f.activeSprite.src, f.night && f.night.sprite.src, ...Object.values(f.walk || {}).map((st) => st.src)]),
|
|
265
354
|
...Object.values(pack.characters).flatMap((s) => Object.values(s).map((st) => st.src)), ...Object.values(pack.props).map((st) => st.src)].filter(Boolean))];
|
|
266
355
|
|
|
267
356
|
/** Every frame strip a checked pack names, with where (the host checks each image is frames·w × h). */
|
|
268
357
|
export function packStrips(pack) {
|
|
269
358
|
const out = [];
|
|
270
|
-
for (const f of pack.furniture) {
|
|
359
|
+
for (const f of pack.furniture) {
|
|
360
|
+
out.push({ at: `furniture.${f.id}.sprite`, strip: f.sprite });
|
|
361
|
+
if (f.activeSprite) out.push({ at: `furniture.${f.id}.activeSprite`, strip: f.activeSprite });
|
|
362
|
+
if (f.night) out.push({ at: `furniture.${f.id}.night.sprite`, strip: f.night.sprite });
|
|
363
|
+
for (const [k, st] of Object.entries(f.walk || {})) out.push({ at: `furniture.${f.id}.walk.${k}`, strip: st });
|
|
364
|
+
}
|
|
271
365
|
for (const [k, set] of Object.entries(pack.characters)) for (const [pose, st] of Object.entries(set)) out.push({ at: `characters.${k}.${pose}`, strip: st });
|
|
272
366
|
for (const [k, st] of Object.entries(pack.props)) out.push({ at: `props.${k}`, strip: st });
|
|
273
367
|
return out;
|
|
@@ -276,11 +370,17 @@ export function packStrips(pack) {
|
|
|
276
370
|
// ── Layout overrides ──
|
|
277
371
|
|
|
278
372
|
const OV_LIMIT = { areas: LIMIT.areas, seats: LIMIT.seats, furniture: LIMIT.furniture, waypoints: LIMIT.waypoints, edges: LIMIT.edges };
|
|
373
|
+
/** A seat's slot in `seatFor` (#2691): its index in the area's order, as a key ("0", "1", …). */
|
|
374
|
+
const SLOT = /^(0|[1-9][0-9]{0,2})$/;
|
|
375
|
+
/** A seat's reservation as an override keeps it: { for, lend }, or null (not reserved). */
|
|
376
|
+
const reservedOf = (s) => (s.for && s.for.length ? { for: [...s.for], lend: s.lend === true } : null);
|
|
377
|
+
/** A seat with reservation `r` ({ for, lend }; for [] or null: none) in place of its own. */
|
|
378
|
+
const reserve = (s, r) => { const { for: _f, lend: _l, ...p } = s; return r && r.for.length ? { ...p, for: [...r.for], lend: r.lend === true } : p; };
|
|
279
379
|
/** An office's layout overrides, strictly (their shape and ranges): { ok: true, overrides } or { ok: false, error }. */
|
|
280
380
|
export function checkLayoutOverrides(x) {
|
|
281
381
|
try {
|
|
282
382
|
obj(x, "overrides");
|
|
283
|
-
keys(x, ["pack", "seats", "furniture", "door", "waypoints", "edges"], "overrides");
|
|
383
|
+
keys(x, ["pack", "seats", "furniture", "furnitureNight", "seatFor", "door", "waypoints", "edges"], "overrides");
|
|
284
384
|
if (typeof x.pack !== "string" || !ID.test(x.pack)) throw new Refused("overrides.pack", "1–40 letters, digits, _ or -");
|
|
285
385
|
const out = { pack: x.pack };
|
|
286
386
|
if (x.seats !== undefined) {
|
|
@@ -305,6 +405,36 @@ export function checkLayoutOverrides(x) {
|
|
|
305
405
|
out.furniture[id] = [q.x, q.y];
|
|
306
406
|
}
|
|
307
407
|
}
|
|
408
|
+
if (x.furnitureNight !== undefined) {
|
|
409
|
+
obj(x.furnitureNight, "overrides.furnitureNight");
|
|
410
|
+
if (Object.keys(x.furnitureNight).length > OV_LIMIT.furniture) throw new Refused("overrides.furnitureNight", `at most ${OV_LIMIT.furniture}`);
|
|
411
|
+
out.furnitureNight = {};
|
|
412
|
+
for (const [id, p] of Object.entries(x.furnitureNight)) {
|
|
413
|
+
if (!nameOk(id)) throw new Refused(`overrides.furnitureNight.${id}`, "an item's id");
|
|
414
|
+
const q = point(p, `overrides.furnitureNight.${id}`);
|
|
415
|
+
out.furnitureNight[id] = [q.x, q.y];
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
if (x.seatFor !== undefined) {
|
|
419
|
+
obj(x.seatFor, "overrides.seatFor");
|
|
420
|
+
if (Object.keys(x.seatFor).length > OV_LIMIT.areas) throw new Refused("overrides.seatFor", `at most ${OV_LIMIT.areas} areas`);
|
|
421
|
+
out.seatFor = {};
|
|
422
|
+
for (const [area, slots] of Object.entries(x.seatFor)) {
|
|
423
|
+
const at = `overrides.seatFor.${area}`;
|
|
424
|
+
if (!nameOk(area)) throw new Refused(at, "an area's name");
|
|
425
|
+
obj(slots, at);
|
|
426
|
+
if (Object.keys(slots).length > OV_LIMIT.seats) throw new Refused(at, `at most ${OV_LIMIT.seats} seats`);
|
|
427
|
+
out.seatFor[area] = {};
|
|
428
|
+
for (const [slot, r] of Object.entries(slots)) {
|
|
429
|
+
const here = `${at}.${slot}`;
|
|
430
|
+
if (!SLOT.test(slot) || Number(slot) >= OV_LIMIT.seats) throw new Refused(here, `a seat's number, 0 to ${OV_LIMIT.seats - 1}`);
|
|
431
|
+
obj(r, here); keys(r, ["for", "lend"], here);
|
|
432
|
+
if (!Array.isArray(r.for)) throw new Refused(`${here}.for`, "a list");
|
|
433
|
+
if (r.lend !== undefined && typeof r.lend !== "boolean") throw new Refused(`${here}.lend`, "true or false");
|
|
434
|
+
out.seatFor[area][slot] = { for: owners(r.for, `${here}.for`, true), lend: r.lend === true };
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
308
438
|
if (x.door !== undefined) { const q = point(x.door, "overrides.door"); out.door = [q.x, q.y]; }
|
|
309
439
|
if (x.edges !== undefined && x.waypoints === undefined) throw new Refused("overrides.edges", "only with waypoints");
|
|
310
440
|
if (x.waypoints !== undefined) {
|
|
@@ -347,12 +477,24 @@ export function applyOverrides(pack, ov) {
|
|
|
347
477
|
const r = a.rect;
|
|
348
478
|
areas[name] = { ...a, seats: pts.map(([x, y], i) => {
|
|
349
479
|
if (x < r.x || x > r.x + r.w || y < r.y || y > r.y + r.h) throw new Refused(`${at}[${i}]`, "outside its area's rect");
|
|
350
|
-
return { x, y };
|
|
480
|
+
return { ...a.seats[i], x, y }; // its reservation kept (#2691)
|
|
351
481
|
}) };
|
|
352
482
|
}
|
|
483
|
+
for (const [name, slots] of Object.entries(ov.seatFor || {})) {
|
|
484
|
+
const a = own(areas, name), at = `overrides.seatFor.${name}`;
|
|
485
|
+
if (!a) throw new Refused(at, "an area the pack defines");
|
|
486
|
+
for (const slot of Object.keys(slots)) if (!a.seats[Number(slot)]) throw new Refused(`${at}.${slot}`, `a seat the area has (it has ${a.seats.length})`);
|
|
487
|
+
areas[name] = { ...a, seats: a.seats.map((s, i) => { const v = own(slots, String(i)); return v ? reserve(s, v) : s; }) };
|
|
488
|
+
}
|
|
353
489
|
const fids = new Set(pack.furniture.map((f) => f.id));
|
|
354
490
|
for (const id of Object.keys(ov.furniture || {})) if (!fids.has(id)) throw new Refused(`overrides.furniture.${id}`, "an item the pack has");
|
|
355
|
-
const
|
|
491
|
+
const nids = new Set(pack.furniture.filter((f) => f.night).map((f) => f.id));
|
|
492
|
+
for (const id of Object.keys(ov.furnitureNight || {})) if (!nids.has(id)) throw new Refused(`overrides.furnitureNight.${id}`, "an item with a night the pack has");
|
|
493
|
+
const furniture = pack.furniture.map((f) => {
|
|
494
|
+
const q = own(ov.furniture, f.id), n = own(ov.furnitureNight, f.id);
|
|
495
|
+
if (!q && !n) return f;
|
|
496
|
+
return { ...f, ...(q ? { x: q[0], y: q[1] } : {}), ...(n ? { night: { ...f.night, x: n[0], y: n[1] } } : {}) };
|
|
497
|
+
});
|
|
356
498
|
const door = ov.door ? { x: ov.door[0], y: ov.door[1] } : pack.door;
|
|
357
499
|
const graph = ov.waypoints ? { waypoints: ov.waypoints.map((w) => ({ ...w })), edges: (ov.edges || []).map((e) => [e[0], e[1]]) } : { waypoints: pack.waypoints, edges: pack.edges };
|
|
358
500
|
return { ok: true, pack: { ...pack, areas, furniture, door, ...graph } };
|
|
@@ -362,6 +504,40 @@ export function applyOverrides(pack, ov) {
|
|
|
362
504
|
}
|
|
363
505
|
}
|
|
364
506
|
|
|
507
|
+
/**
|
|
508
|
+
* #2665: overrides with only the night places `pack` can take (an item it has, with a `night`): a pack changed since
|
|
509
|
+
* (an item dropped, or its night) drops that entry, never the rest of the layout. applyOverrides itself stays strict
|
|
510
|
+
* (a write naming an item the pack lacks is refused, as for `furniture`).
|
|
511
|
+
*/
|
|
512
|
+
export function fitNight(pack, ov) {
|
|
513
|
+
if (!ov || !ov.furnitureNight || ov.pack !== pack.id) return ov;
|
|
514
|
+
const ok = new Set(pack.furniture.filter((f) => f.night).map((f) => f.id));
|
|
515
|
+
const kept = Object.entries(ov.furnitureNight).filter(([id]) => ok.has(id));
|
|
516
|
+
if (kept.length === Object.keys(ov.furnitureNight).length) return ov;
|
|
517
|
+
const { furnitureNight: _gone, ...rest } = ov;
|
|
518
|
+
return kept.length ? { ...rest, furnitureNight: Object.fromEntries(kept) } : rest;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* #2691: overrides with only the reservations `pack` can take (an area it has, a seat that area has): a pack changed
|
|
523
|
+
* since drops that entry, never the rest of the layout (as fitNight; applyOverrides stays strict).
|
|
524
|
+
*/
|
|
525
|
+
export function fitSeatFor(pack, ov) {
|
|
526
|
+
if (!ov || !ov.seatFor || ov.pack !== pack.id) return ov;
|
|
527
|
+
let dropped = false;
|
|
528
|
+
const kept = {};
|
|
529
|
+
for (const [area, slots] of Object.entries(ov.seatFor)) {
|
|
530
|
+
const a = own(pack.areas, area);
|
|
531
|
+
const ok = Object.entries(slots).filter(([slot]) => a && !!a.seats[Number(slot)]);
|
|
532
|
+
// An area the pack no longer has is dropped even with no slots under it (`{}`: 0 kept of 0, yet applyOverrides refuses it).
|
|
533
|
+
if (!a || ok.length !== Object.keys(slots).length) dropped = true;
|
|
534
|
+
if (ok.length) kept[area] = Object.fromEntries(ok);
|
|
535
|
+
}
|
|
536
|
+
if (!dropped) return ov;
|
|
537
|
+
const { seatFor: _gone, ...rest } = ov;
|
|
538
|
+
return Object.keys(kept).length ? { ...rest, seatFor: kept } : rest;
|
|
539
|
+
}
|
|
540
|
+
|
|
365
541
|
/**
|
|
366
542
|
* What an edited layout keeps as overrides: only the parts that differ from the pack's own (`edited` is a pack with
|
|
367
543
|
* the same areas and furniture, positions moved); null when nothing does (the office goes back to the pack's).
|
|
@@ -370,11 +546,22 @@ export function overridesBetween(pack, edited) {
|
|
|
370
546
|
const same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
371
547
|
const ov = { pack: pack.id };
|
|
372
548
|
for (const [name, a] of Object.entries(pack.areas)) {
|
|
373
|
-
const e = own(edited.areas, name);
|
|
374
|
-
if (e && !same(a.seats, e.seats)) (ov.seats ||= {})[name] = e.seats.map(
|
|
549
|
+
const e = own(edited.areas, name), xy = (s) => [s.x, s.y];
|
|
550
|
+
if (e && !same(a.seats.map(xy), e.seats.map(xy))) (ov.seats ||= {})[name] = e.seats.map(xy);
|
|
551
|
+
// #2691: a seat whose reservation differs from the pack's (none where the pack has one: for []).
|
|
552
|
+
if (e) e.seats.forEach((s, i) => {
|
|
553
|
+
const was = a.seats[i] ? reservedOf(a.seats[i]) : null, now = reservedOf(s);
|
|
554
|
+
if (a.seats[i] && !same(was, now)) ((ov.seatFor ||= {})[name] ||= {})[String(i)] = now || { for: [], lend: false };
|
|
555
|
+
});
|
|
375
556
|
}
|
|
376
557
|
const at = new Map(pack.furniture.map((f) => [f.id, f]));
|
|
377
558
|
for (const f of edited.furniture) { const o = at.get(f.id); if (o && (o.x !== f.x || o.y !== f.y)) (ov.furniture ||= {})[f.id] = [f.x, f.y]; }
|
|
559
|
+
// #2665: a night place that differs from the pack's (its own x/y, or in place: null); in place again is no entry.
|
|
560
|
+
for (const f of edited.furniture) {
|
|
561
|
+
const o = at.get(f.id);
|
|
562
|
+
if (!o || !o.night || !f.night || f.night.x === null || f.night.y === null) continue;
|
|
563
|
+
if (o.night.x !== f.night.x || o.night.y !== f.night.y) (ov.furnitureNight ||= {})[f.id] = [f.night.x, f.night.y];
|
|
564
|
+
}
|
|
378
565
|
if (pack.door.x !== edited.door.x || pack.door.y !== edited.door.y) ov.door = [edited.door.x, edited.door.y];
|
|
379
566
|
if (!same(pack.waypoints, edited.waypoints) || !same(pack.edges, edited.edges)) {
|
|
380
567
|
ov.waypoints = edited.waypoints.map((w) => ({ id: w.id, x: w.x, y: w.y }));
|
|
@@ -403,6 +590,9 @@ export function poseStrip(set, pose, heading = null) {
|
|
|
403
590
|
const st = own(set, pose) || own(set, "idle");
|
|
404
591
|
return st ? { strip: st, mirror: false } : null;
|
|
405
592
|
}
|
|
593
|
+
/** The strip a furniture item shows while it walks (#2596: an NPC with `walk`, `walking` and `heading` set where it is
|
|
594
|
+
* drawn): its walk strip by heading, as a character's (poseStrip); null when it stands (its own sprite, as before). */
|
|
595
|
+
export const walkStrip = (f) => (f && f.walking && f.walk ? poseStrip(f.walk, "walk", f.heading) : null);
|
|
406
596
|
/** The box (logical units) a strip covers with its anchor at (x, y): its displayed size; mirrored about the anchor. */
|
|
407
597
|
export function stripBox(st, x, y, mirror = false) {
|
|
408
598
|
const kx = st.dw / st.w, ky = st.dh / st.h;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
// The Office view's art (fleet-office.js draws with it on a <canvas>). A skin draws a layout that fleet-office-model.js
|
|
2
|
-
// (desks) or fleet-office-zones.js (zones) made.
|
|
3
|
-
// pixel people, desks, lamps
|
|
4
|
-
//
|
|
5
|
-
//
|
|
2
|
+
// (desks) or fleet-office-zones.js (zones) made. Three skins are built in. Two draw everything procedurally — little
|
|
3
|
+
// pixel people, desks, lamps: "pixel" (the desks layout: a room per team, a desk per instance) and "studio" (the zones
|
|
4
|
+
// layout: work desks, a lounge, an alert corner and a nap corner; people walk to the area for their state). The third,
|
|
5
|
+
// "cozy" (#2631), is the zones layout on one picture shipped with the page (a warm pixel-art office), the same built-in
|
|
6
|
+
// people in it. Every drawing call goes through the OfficeSkin interface below, so a skin pack (pictures from an artist,
|
|
6
7
|
// format v1 in fleet-office-pack.js) can replace any part; a part a pack does not supply falls back to the built-in
|
|
7
8
|
// drawing. An admin uploads a pack (#2513, the host keeps it: src/web-office-skins.ts); loadPackSkin() reads it back —
|
|
8
9
|
// its manifest checked here again (checkSkinPack), its pictures by their content ids — and spriteSkin() draws it.
|
|
@@ -40,8 +41,9 @@
|
|
|
40
41
|
// pictures and draws the characters, props and bubbles each frame. In zones the background is kept; furniture, people (walking
|
|
41
42
|
// along the pack's waypoints), tags, props and bubbles are drawn each frame, furniture by depth around the people (depth "y":
|
|
42
43
|
// among them by base y), then the foreground, then the crowd counts, tags and bubbles.
|
|
44
|
+
import { t } from "/assets/app-i18n.js";
|
|
43
45
|
import { frameAt, nameHash } from "./fleet-office-model.js";
|
|
44
|
-
import { CHAR_DEPTH, LOGICAL, checkSkinPack, own, packImages, poseStrip, stripBox } from "./fleet-office-pack.js";
|
|
46
|
+
import { CHAR_DEPTH, LOGICAL, checkSkinPack, own, packImages, poseStrip, stripBox, walkStrip } from "./fleet-office-pack.js";
|
|
45
47
|
|
|
46
48
|
export { nameHash };
|
|
47
49
|
|
|
@@ -222,14 +224,16 @@ function drawDeskFront(ctx, d, look, env) {
|
|
|
222
224
|
ctx.fillStyle = pal.text2; ctx.font = "600 8px system-ui, sans-serif"; ctx.textAlign = "center"; ctx.textBaseline = "middle";
|
|
223
225
|
ctx.fillText(fitText(ctx, env.labels.closed, 32), d.x + 48, d.y + 63);
|
|
224
226
|
}
|
|
225
|
-
// The name tag.
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
227
|
+
// The name tag (#2554: none when `notag` — a tile too small to read it; the page draws it again on hover).
|
|
228
|
+
if (!look.notag) {
|
|
229
|
+
ctx.fillStyle = pal.surface;
|
|
230
|
+
ctx.fillRect(d.x + 6, d.y + 78, d.w - 12, 16);
|
|
231
|
+
ctx.strokeStyle = look.match ? pal.focus : pal.border;
|
|
232
|
+
ctx.lineWidth = look.match ? 2 : 1;
|
|
233
|
+
ctx.strokeRect(d.x + 6.5, d.y + 78.5, d.w - 13, 15);
|
|
234
|
+
ctx.fillStyle = pal.text; ctx.font = "600 11px system-ui, sans-serif"; ctx.textAlign = "center"; ctx.textBaseline = "middle";
|
|
235
|
+
ctx.fillText(fitText(ctx, look.label, d.w - 18), d.x + d.w / 2, d.y + 86.5);
|
|
236
|
+
}
|
|
233
237
|
ctx.restore();
|
|
234
238
|
if (look.match) { ctx.save(); ctx.strokeStyle = pal.focus; ctx.lineWidth = 2; ctx.strokeRect(d.x + 2, d.y + 2, d.w - 4, d.h - 6); ctx.restore(); }
|
|
235
239
|
}
|
|
@@ -374,7 +378,7 @@ const STUDIO_FURNITURE = [
|
|
|
374
378
|
].sort((a, b) => a.depth - b.depth);
|
|
375
379
|
/** The studio's pack: the same shape as a checked one (fleet-office-pack.js), furniture by `shape` instead of sprites. */
|
|
376
380
|
export const STUDIO_PACK = {
|
|
377
|
-
format: 1, id: "studio", scale: 1, areas: STUDIO_AREAS, door: { x: HALL_X, y: 704 }, furniture: STUDIO_FURNITURE, ...studioGraph(STUDIO_AREAS),
|
|
381
|
+
format: 1, id: "studio", scale: 1, areas: STUDIO_AREAS, door: { x: HALL_X, y: 704 }, furniture: STUDIO_FURNITURE, ...studioGraph(STUDIO_AREAS), straight: false,
|
|
378
382
|
stateZones: { working: "desks", idle: "lounge", needs: "alert", crashed: "alert", stuck: "alert", paused: "nap", stopped: "nap", default: "lounge" },
|
|
379
383
|
characters: {}, byInstance: {}, props: {}, bubbles: {},
|
|
380
384
|
};
|
|
@@ -511,8 +515,12 @@ export function spriteSkin(pack, images, base = pixelSkin) {
|
|
|
511
515
|
},
|
|
512
516
|
drawFurniture(ctx, f, active, ms, env) {
|
|
513
517
|
if (f.shape) { drawFurniture(ctx, f, active, ms, env); return; }
|
|
514
|
-
//
|
|
518
|
+
// An NPC walking (#2596): its walk strip for its heading (walkSide mirrored going left), when its picture is loaded.
|
|
519
|
+
const ws = walkStrip(f);
|
|
520
|
+
if (ws && strip(ctx, ws.strip, ms, f.x, f.y, null, ws.mirror)) return;
|
|
521
|
+
// Active: its activeSprite (or its strip) plays; otherwise its strip loops ("loop", #2570) or its first frame holds.
|
|
515
522
|
if (active) strip(ctx, f.activeSprite || f.sprite, ms, f.x, f.y);
|
|
523
|
+
else if (f.loop) strip(ctx, f.sprite, ms, f.x, f.y);
|
|
516
524
|
else strip(ctx, f.sprite, 0, f.x, f.y, 0);
|
|
517
525
|
},
|
|
518
526
|
drawCharacter(ctx, d, look, ms, env) {
|
|
@@ -528,18 +536,84 @@ export function spriteSkin(pack, images, base = pixelSkin) {
|
|
|
528
536
|
const ps = shown({ ...look, heading: at.heading }, !!at.walking);
|
|
529
537
|
return ps ? stripBox(ps.strip, at.x, at.y, ps.mirror) : base.charBox(look, at);
|
|
530
538
|
},
|
|
531
|
-
|
|
539
|
+
// An item in the air: its sender's own (their set's "prop", #2562), else the pack's for its kind, else the built-in one.
|
|
540
|
+
drawProp(ctx, it, ms, env) {
|
|
541
|
+
const mine = it.from ? own(setOf({ name: it.from }) || {}, "prop") : null;
|
|
542
|
+
if (!strip(ctx, mine || own(pack.props, it.hue), ms, it.x, it.y)) base.drawProp(ctx, it, ms, env);
|
|
543
|
+
},
|
|
532
544
|
};
|
|
533
545
|
}
|
|
534
546
|
|
|
547
|
+
// ── The built-in cozy (#2631): the zones layout on a warm pixel-art office drawn as one picture, the built-in people ──
|
|
548
|
+
|
|
549
|
+
/** Its picture, shipped with the page (src/ui/shared/office/cozy.png, auth-api.ts ASSETS: public static, as the font). */
|
|
550
|
+
export const COZY_BACKGROUND = "/assets/office-cozy.png";
|
|
551
|
+
const pts = (...xy) => xy.map(([x, y]) => ({ x, y }));
|
|
552
|
+
// The seats are feet points on the picture: chairs in front of the desks, the sofa, the coffee table and machine, the
|
|
553
|
+
// beanbags (a sleeper's bottom), in front of the whiteboard. The rects put a crowd's first extra spots (fleet-office-zones.js
|
|
554
|
+
// overflowSpots: rows from the rect's top) on the aisles: under the first row of desks, in front of the sofa.
|
|
555
|
+
const COZY_AREAS = {
|
|
556
|
+
desks: { name: "desks", rect: { x: 130, y: 196, w: 430, h: 220 }, seats: pts([193, 238], [336, 238], [481, 238], [193, 354], [336, 354], [481, 354]) },
|
|
557
|
+
lounge: { name: "lounge", rect: { x: 700, y: 196, w: 476, h: 184 }, seats: pts([940, 240], [1025, 310], [1120, 305]) },
|
|
558
|
+
alert: { name: "alert", rect: { x: 900, y: 500, w: 220, h: 110 }, seats: pts([965, 570], [1045, 570]) },
|
|
559
|
+
nap: { name: "nap", rect: { x: 110, y: 470, w: 400, h: 150 }, seats: pts([180, 562], [300, 562], [422, 565]) },
|
|
560
|
+
};
|
|
561
|
+
/** Its walkable graph, on the picture's aisles, every edge across or up/down; each seat's nearest waypoint is in front of it. */
|
|
562
|
+
const COZY_WAYPOINTS = [
|
|
563
|
+
["door", 642, 640], ["hall", 642, 395], ["top", 642, 175],
|
|
564
|
+
// the desks: the aisle under the first row (y 250), the one under the second (y 395), the column beside them (x 560)
|
|
565
|
+
["d-east", 560, 395], ["d-up", 560, 250], ["d1-3", 481, 250], ["d1-2", 336, 250], ["d1-1", 193, 250],
|
|
566
|
+
["d2-3", 481, 395], ["d2-2", 336, 395], ["d2-x", 300, 395], ["d2-1", 193, 395],
|
|
567
|
+
// the beanbags: the aisle above them (y 460)
|
|
568
|
+
["n-x", 300, 460], ["n-1", 180, 460], ["n-3", 422, 460],
|
|
569
|
+
// the lounge: below the coffee table (y 340), up the sofa's west end; the coffee machine's column (x 1140)
|
|
570
|
+
["l-hall", 946, 395], ["l-mid", 946, 340], ["l-west", 860, 340], ["l-sofa", 860, 250], ["l-table", 1025, 340],
|
|
571
|
+
["e-hall", 1140, 395], ["e-coffee", 1140, 305],
|
|
572
|
+
// the whiteboard: in front of it (y 570), from the coffee machine's column
|
|
573
|
+
["a-east", 1140, 570], ["a-mid", 1005, 570],
|
|
574
|
+
].map(([id, x, y]) => ({ id, x, y }));
|
|
575
|
+
const COZY_EDGES = [
|
|
576
|
+
["door", "hall"], ["hall", "top"], ["hall", "d-east"], ["d-east", "d2-3"], ["d2-3", "d2-2"], ["d2-2", "d2-x"], ["d2-x", "d2-1"],
|
|
577
|
+
["d-east", "d-up"], ["d-up", "d1-3"], ["d1-3", "d1-2"], ["d1-2", "d1-1"], ["d2-x", "n-x"], ["n-x", "n-1"], ["n-x", "n-3"],
|
|
578
|
+
["hall", "l-hall"], ["l-hall", "l-mid"], ["l-mid", "l-west"], ["l-west", "l-sofa"], ["l-mid", "l-table"],
|
|
579
|
+
["l-hall", "e-hall"], ["e-hall", "e-coffee"], ["e-hall", "a-east"], ["a-east", "a-mid"],
|
|
580
|
+
];
|
|
581
|
+
/** The cozy's pack: the picture is the room; the one item is the notice board, its tag saying a pack can restyle it. */
|
|
582
|
+
export const COZY_PACK = {
|
|
583
|
+
format: 1, id: "cozy", scale: 1, background: COZY_BACKGROUND, areas: COZY_AREAS, door: { x: 642, y: 662 },
|
|
584
|
+
waypoints: COZY_WAYPOINTS, edges: COZY_EDGES, straight: true,
|
|
585
|
+
furniture: [{ id: "notice", x: 1211, y: 628, depth: 5, get label() { return t("orgo.cozyNotice"); } }],
|
|
586
|
+
stateZones: { working: "desks", idle: "lounge", needs: "alert", crashed: "alert", stuck: "alert", paused: "nap", stopped: "nap", default: "lounge" },
|
|
587
|
+
characters: {}, byInstance: {}, props: {}, bubbles: {},
|
|
588
|
+
};
|
|
589
|
+
/** Until its picture is in (loadCozySkin): the studio's floors under the cozy's areas. */
|
|
590
|
+
const cozyBare = spriteSkin(COZY_PACK, new Map(), studioSkin);
|
|
591
|
+
let cozyJob = null;
|
|
592
|
+
|
|
535
593
|
/** The skins built into this page; an uploaded pack is loaded by loadPackSkin. */
|
|
536
|
-
export const SKINS = new Map([["pixel", pixelSkin], ["studio", studioSkin]]);
|
|
537
|
-
export const BUILT_IN_SKINS = ["pixel", "studio"];
|
|
594
|
+
export const SKINS = new Map([["pixel", pixelSkin], ["studio", studioSkin], ["cozy", cozyBare]]);
|
|
595
|
+
export const BUILT_IN_SKINS = ["pixel", "studio", "cozy"];
|
|
596
|
+
/**
|
|
597
|
+
* #2631: the cozy's picture, loaded once; the skin drawn from it then takes the cozy's place in SKINS (skinFor). The
|
|
598
|
+
* skin, or null while it cannot load (the bare one stays; tried again next time).
|
|
599
|
+
*/
|
|
600
|
+
export function loadCozySkin({ image = defaultImage } = {}) {
|
|
601
|
+
if (SKINS.get("cozy") !== cozyBare) return Promise.resolve(SKINS.get("cozy"));
|
|
602
|
+
cozyJob ??= image(COZY_BACKGROUND).then((im) => {
|
|
603
|
+
cozyJob = null;
|
|
604
|
+
if (!im) return null;
|
|
605
|
+
const sk = spriteSkin(COZY_PACK, new Map([[COZY_BACKGROUND, im]]), studioSkin);
|
|
606
|
+
SKINS.set("cozy", sk);
|
|
607
|
+
return sk;
|
|
608
|
+
}, () => { cozyJob = null; return null; });
|
|
609
|
+
return cozyJob;
|
|
610
|
+
}
|
|
538
611
|
/** A skin by id: a built-in one, else a pack loaded here (`packs`: id → skin), else the default (pixel). */
|
|
539
612
|
export const skinFor = (id, packs = null) => SKINS.get(id) || (packs && packs.get(id)) || pixelSkin;
|
|
540
613
|
|
|
541
614
|
const loaded = new Map(); // `${id}@${sha}` → Promise<OfficeSkin | null>
|
|
542
|
-
|
|
615
|
+
// Room for every pack the host keeps (web-office-skins.ts SKIN_LIMITS.packs) and a few replaced ones (each a new sha).
|
|
616
|
+
const MAX_LOADED = 24;
|
|
543
617
|
const defaultImage = (url) => new Promise((resolve) => {
|
|
544
618
|
if (typeof Image !== "function") { resolve(null); return; }
|
|
545
619
|
const im = new Image();
|
|
@@ -550,9 +624,10 @@ const defaultImage = (url) => new Promise((resolve) => {
|
|
|
550
624
|
/**
|
|
551
625
|
* An uploaded pack (GET /ui/office-skins/<id>?v=<sha>) as an OfficeSkin: its manifest checked (null when it is not a
|
|
552
626
|
* pack: the page then draws a built-in style and says so), its pictures loaded from their content ids (one that does
|
|
553
|
-
* not load is drawn by the built-in skin). Kept per id and sha, so a pack is fetched once.
|
|
627
|
+
* not load is drawn by the built-in skin). Kept per id and sha, so a pack is fetched once. `url`: where a picture's path
|
|
628
|
+
* is read (a share link's page reads its own address, #2636).
|
|
554
629
|
*/
|
|
555
|
-
export function loadPackSkin(id, sha, { fetch: get = globalThis.fetch, image = defaultImage } = {}) {
|
|
630
|
+
export function loadPackSkin(id, sha, { fetch: get = globalThis.fetch, image = defaultImage, url = (p) => p } = {}) {
|
|
556
631
|
const key = `${id}@${sha}`;
|
|
557
632
|
if (loaded.has(key)) return loaded.get(key);
|
|
558
633
|
const base = `/ui/office-skins/${encodeURIComponent(id)}`;
|
|
@@ -561,14 +636,14 @@ export function loadPackSkin(id, sha, { fetch: get = globalThis.fetch, image = d
|
|
|
561
636
|
const r = await get(`${base}?v=${encodeURIComponent(sha)}`, { method: "GET", headers: { "Content-Type": "application/json" } });
|
|
562
637
|
if (!r || !r.ok) return null;
|
|
563
638
|
const d = await r.json();
|
|
564
|
-
const c = checkSkinPack(d && d.manifest);
|
|
639
|
+
const c = checkSkinPack(d && d.manifest, { kept: true });
|
|
565
640
|
if (!c.ok || c.pack.id !== id) return null;
|
|
566
641
|
const files = d.files && typeof d.files === "object" ? d.files : {};
|
|
567
642
|
const images = new Map();
|
|
568
643
|
await Promise.all(packImages(c.pack).map(async (src) => {
|
|
569
644
|
const fid = own(files, src);
|
|
570
645
|
if (typeof fid !== "string" || !/^[0-9a-f]{32}$/.test(fid)) return;
|
|
571
|
-
const im = await image(`${base}/img/${fid}.png`);
|
|
646
|
+
const im = await image(url(`${base}/img/${fid}.png`));
|
|
572
647
|
if (im) images.set(src, im);
|
|
573
648
|
}));
|
|
574
649
|
return spriteSkin(c.pack, images);
|