@songsid/agend 2.3.2-beta.1 → 2.3.2-beta.10
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 +21 -2
- package/dist/agent-cli.js.map +1 -1
- package/dist/agent-endpoint.js +5 -0
- package/dist/agent-endpoint.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-identity.d.ts +64 -0
- package/dist/backend/kiro-identity.js +101 -0
- package/dist/backend/kiro-identity.js.map +1 -1
- 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.d.ts +71 -2
- package/dist/backend/kiro.js +352 -11
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/types.d.ts +59 -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/mcp-tools.js +29 -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 +21 -2
- package/dist/cli.js.map +1 -1
- package/dist/config-validator.d.ts +7 -1
- package/dist/config-validator.js +53 -10
- package/dist/config-validator.js.map +1 -1
- package/dist/config.d.ts +19 -0
- package/dist/config.js +31 -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 +23 -0
- package/dist/daemon.js +106 -7
- package/dist/daemon.js.map +1 -1
- package/dist/dashboard-permissions.d.ts +20 -6
- package/dist/dashboard-permissions.js +58 -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-manager.d.ts +72 -0
- package/dist/fleet-manager.js +507 -89
- 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 +14 -1
- package/dist/general-knowledge/skills/web-appearance/SKILL.canvas-worker-off.md +24 -1
- package/dist/general-knowledge/skills/web-appearance/SKILL.md +24 -1
- package/dist/general-knowledge/skills/web-dashboard/SKILL.canvas-worker-off.md +37 -9
- package/dist/general-knowledge/skills/web-dashboard/SKILL.md +44 -11
- package/dist/instance-config-impact.js +10 -5
- package/dist/instance-config-impact.js.map +1 -1
- package/dist/instance-lifecycle.d.ts +19 -0
- package/dist/instance-lifecycle.js +55 -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 +49 -0
- package/dist/kiro-migrate.js +105 -0
- package/dist/kiro-migrate.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 +7 -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 +88 -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 +8 -2
- package/dist/needs-you.js +6 -0
- 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 +258 -0
- package/dist/office-share.js +549 -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 +26 -2
- package/dist/outbound-schemas.js +27 -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/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 +17 -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 +12 -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 +26 -2
- package/dist/transcript-sources.js +103 -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 +28 -8
- package/dist/ui/backend-signin.js +63 -10
- package/dist/ui/chat-strings.js +2 -2
- package/dist/ui/fleet-office-editor.js +14 -9
- package/dist/ui/fleet-office-layout.js +70 -32
- 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 +7 -2
- package/dist/ui/fleet-office-pack.js +119 -15
- package/dist/ui/fleet-office-skin.js +98 -23
- package/dist/ui/fleet-office-store.js +39 -5
- package/dist/ui/fleet-office-view.js +5 -4
- package/dist/ui/fleet-office-zones.js +172 -12
- package/dist/ui/fleet-office.js +547 -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 +8 -1
- package/dist/ui/panel-log.js +2 -2
- package/dist/ui/panel-needs.js +4 -4
- package/dist/ui/panel-settings.js +37 -11
- package/dist/ui/settings-dialogs.js +8 -1
- package/dist/ui/settings-guests.js +17 -7
- package/dist/ui/settings-model.js +14 -6
- package/dist/ui/settings-registry.js +4 -0
- package/dist/ui/settings-strings.js +16 -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 +67 -2
- 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 +18 -0
- package/dist/web-api.js +201 -0
- 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-skins.d.ts +3 -2
- package/dist/web-office-skins.js +3 -2
- package/dist/web-office-skins.js.map +1 -1
- package/dist/web-office-store.js +40 -7
- 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
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
// #2554: the Office view's "All offices" — every stored office as a tile on one canvas, side by side, each drawn exactly
|
|
2
|
+
// as its own view would be (its style, its members, seats, poses, bubbles, walking), scaled into its tile. This module
|
|
3
|
+
// is the pure part (fleet-office.js draws it): who is in no office (the lobby tile), where the tiles go, what a point
|
|
4
|
+
// on the overview hits, which tiles are on screen, and the traffic that crosses between tiles.
|
|
5
|
+
//
|
|
6
|
+
// - Shown when at least two offices are stored (overviewOk); an id no stored office can have (ALL_ID: '*' is not an
|
|
7
|
+
// office id's character), kept per device as any office pick is.
|
|
8
|
+
// - The tiles: each as large as its office's drawn content — a zones scene its 1280×720, a desks office its rooms'
|
|
9
|
+
// bounds (cropToRooms: an office of three is small) — scaled down to at most CELL_W × CELL_H, never up; a LABEL band
|
|
10
|
+
// above each (the page's label sits there), GAP apart, M from the edge. Shelf packing in the stored order (the lobby
|
|
11
|
+
// last): a row takes tiles while they fit a row width, each row as tall as its tallest tile and centred across. The
|
|
12
|
+
// row width: whichever (of the widths a run of tiles makes) lets the whole overview show largest in the panel (Fit's
|
|
13
|
+
// contain); a tie keeps the narrower. The same input always gives the same picture.
|
|
14
|
+
// - Traffic between people in different tiles is a straight flight between their seats in overview coordinates
|
|
15
|
+
// (createCrossFlights); between two people in the same tile it is that tile's own (createFlights, along its corridors).
|
|
16
|
+
import { FLIGHT, along, desksOf, membersOf, pathLength } from "./fleet-office-model.js";
|
|
17
|
+
import { hue } from "./fleet-traffic.js";
|
|
18
|
+
|
|
19
|
+
/** The switcher's "All offices" value: '*' is never in a stored office's id (fleet-office-model.js officeOk). */
|
|
20
|
+
export const ALL_ID = "*all";
|
|
21
|
+
/** The lobby tile's key: never an office's id. */
|
|
22
|
+
export const LOBBY_TILE = "\u0000lobby";
|
|
23
|
+
/** The overview's geometry, in logical units. */
|
|
24
|
+
export const OV = { CELL_W: 1280, CELL_H: 720, LABEL: 56, GAP: 24, M: 24 };
|
|
25
|
+
|
|
26
|
+
/** Whether "All offices" is offered: at least two stored offices. */
|
|
27
|
+
export const overviewOk = (stored) => Array.isArray(stored) && stored.length >= 2;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The people in no stored office, in the org chart's order (General, the teams, the rest): the lobby tile's. An office
|
|
31
|
+
* with no members holds everyone (fleet-office-model.js membersOf), so then nobody is left for the lobby.
|
|
32
|
+
*/
|
|
33
|
+
export function lobbyNodes(stored, view) {
|
|
34
|
+
const placed = new Set((stored || []).flatMap((o) => membersOf(o, view).map((n) => n.name)));
|
|
35
|
+
const out = new Map();
|
|
36
|
+
for (const n of [...view.top, ...view.teams.flatMap((tm) => tm.nodes), ...view.rest.nodes]) if (!placed.has(n.name) && !out.has(n.name)) out.set(n.name, n);
|
|
37
|
+
return [...out.values()];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** A scene's scale in its tile: its own size, scaled down to fit a CELL_W × CELL_H cell (never up). */
|
|
41
|
+
export const tileScale = (w, h) => Math.min(1, OV.CELL_W / Math.max(1, w), OV.CELL_H / Math.max(1, h));
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* A desks layout cropped to its rooms: everything moved so the rooms' bounds start at (0, 0), its size theirs (the
|
|
45
|
+
* corridors outside the rooms and the margins are not part of a tile). Unchanged when it has no rooms.
|
|
46
|
+
*/
|
|
47
|
+
export function cropToRooms(L) {
|
|
48
|
+
if (!L.rooms.length) return L;
|
|
49
|
+
const x0 = Math.min(...L.rooms.map((r) => r.x)), y0 = Math.min(...L.rooms.map((r) => r.y));
|
|
50
|
+
const x1 = Math.max(...L.rooms.map((r) => r.x + r.w)), y1 = Math.max(...L.rooms.map((r) => r.y + r.h));
|
|
51
|
+
const mv = (p) => ({ ...p, x: p.x - x0, y: p.y - y0 });
|
|
52
|
+
return { ...L, rooms: L.rooms.map((r) => ({ ...mv(r), desks: r.desks.map((d) => ({ ...mv(d), seat: mv(d.seat) })) })), waypoints: (L.waypoints || []).map(mv),
|
|
53
|
+
width: x1 - x0, height: y1 - y0 };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* The tiles of `scenes` ([{ key, w, h }], in order) on shelves at most `rowW` wide (a tile wider than that has a row of
|
|
58
|
+
* its own): each { key, x, y, w, h, s (its scale), label: { x, y, w, h } (the band above it) }; and the overview's size.
|
|
59
|
+
* Each row is centred across the widest; its tiles stand at its top, under their labels.
|
|
60
|
+
*/
|
|
61
|
+
export function arrangeTiles(scenes, rowW) {
|
|
62
|
+
const { LABEL, GAP, M } = OV;
|
|
63
|
+
const rows = [];
|
|
64
|
+
let row = null;
|
|
65
|
+
for (const sc of scenes) {
|
|
66
|
+
const s = tileScale(sc.w, sc.h), it = { key: sc.key, s, w: Math.max(1, sc.w) * s, h: Math.max(1, sc.h) * s };
|
|
67
|
+
if (!row || row.w + GAP + it.w > rowW + 1e-6) { row = { items: [], w: -GAP, h: 0 }; rows.push(row); }
|
|
68
|
+
row.items.push(it); row.w += GAP + it.w; row.h = Math.max(row.h, it.h);
|
|
69
|
+
}
|
|
70
|
+
const inner = rows.length ? Math.max(...rows.map((r) => r.w)) : 0, tiles = [];
|
|
71
|
+
let y = M;
|
|
72
|
+
for (const r of rows) {
|
|
73
|
+
let x = M + (inner - r.w) / 2;
|
|
74
|
+
for (const it of r.items) {
|
|
75
|
+
tiles.push({ key: it.key, x, y: y + LABEL, w: it.w, h: it.h, s: it.s, label: { x, y, w: it.w, h: LABEL } });
|
|
76
|
+
x += it.w + GAP;
|
|
77
|
+
}
|
|
78
|
+
y += LABEL + r.h + GAP;
|
|
79
|
+
}
|
|
80
|
+
return { rows: rows.length, rowW, tiles, width: 2 * M + inner, height: rows.length ? y - GAP + M : 2 * M };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* The tiles packed for a `size` panel: of the row widths a run of consecutive tiles makes, the one whose overview shows
|
|
85
|
+
* largest there (min of the two fits); a tie keeps the narrower.
|
|
86
|
+
*/
|
|
87
|
+
export function tileLayout(scenes, size) {
|
|
88
|
+
const ws = scenes.map((sc) => Math.max(1, sc.w) * tileScale(sc.w, sc.h)), cands = new Set();
|
|
89
|
+
for (let i = 0; i < ws.length; i++) { let run = -OV.GAP; for (let j = i; j < ws.length; j++) { run += OV.GAP + ws[j]; cands.add(run); } }
|
|
90
|
+
let best = arrangeTiles(scenes, 0), bestK = -1;
|
|
91
|
+
for (const rowW of [...cands].sort((a, b) => a - b)) {
|
|
92
|
+
const a = arrangeTiles(scenes, rowW), k = Math.min(size.w / a.width, size.h / a.height);
|
|
93
|
+
if (k > bestK + 1e-9) { best = a; bestK = k; }
|
|
94
|
+
}
|
|
95
|
+
return best;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** The tile whose scene holds the overview point (x, y), or null. */
|
|
99
|
+
export const tileAt = (lay, x, y) => lay.tiles.find((t) => x >= t.x && x < t.x + t.w && y >= t.y && y < t.y + t.h) || null;
|
|
100
|
+
/** The tile whose label band holds the overview point, or null. */
|
|
101
|
+
export const labelAt = (lay, x, y) => lay.tiles.find((t) => x >= t.label.x && x < t.label.x + t.label.w && y >= t.label.y && y < t.label.y + t.label.h) || null;
|
|
102
|
+
/** Overview → the tile's own scene units, and back; a box in the scene → the overview. */
|
|
103
|
+
export const inTile = (t, x, y) => ({ x: (x - t.x) / t.s, y: (y - t.y) / t.s });
|
|
104
|
+
export const fromTile = (t, p) => ({ x: t.x + p.x * t.s, y: t.y + p.y * t.s });
|
|
105
|
+
export const tileBox = (t, b) => ({ x: t.x + b.x * t.s, y: t.y + b.y * t.s, w: b.w * t.s, h: b.h * t.s });
|
|
106
|
+
/** The tiles a view ({ k, x, y }) shows any of in a `size` viewport: the rest are not drawn. */
|
|
107
|
+
export function visibleTiles(lay, v, size) {
|
|
108
|
+
return lay.tiles.filter((t) => {
|
|
109
|
+
const x = v.x + t.x * v.k, y = v.y + t.y * v.k;
|
|
110
|
+
return x < size.w && x + t.w * v.k > 0 && y < size.h && y + t.h * v.k > 0;
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* A straight flight between two people in different tiles: [the sender's seat, the receiver's] in overview units (the
|
|
116
|
+
* first tile each is in), or null — when either is drawn nowhere, or some tile holds both (that tile carries it along
|
|
117
|
+
* its corridors). `parts`: [{ tile, L }] in the tiles' order.
|
|
118
|
+
*/
|
|
119
|
+
export function crossPath(parts, from, to) {
|
|
120
|
+
let a = null, z = null;
|
|
121
|
+
for (const p of parts) {
|
|
122
|
+
const ds = desksOf(p.L), da = ds.find((d) => d.node.name === from), dz = ds.find((d) => d.node.name === to);
|
|
123
|
+
if (da && dz) return null;
|
|
124
|
+
if (da && !a) a = fromTile(p.tile, da.seat);
|
|
125
|
+
if (dz && !z) z = fromTile(p.tile, dz.seat);
|
|
126
|
+
}
|
|
127
|
+
return a && z ? [a, z] : null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const MAX_SEEN = 2000;
|
|
131
|
+
/**
|
|
132
|
+
* The items in the air between tiles — fleet-office-model.js createFlights's lifecycle (each new item once; none for a
|
|
133
|
+
* stale delegation or with reduced motion), on crossPath's straight line: `frame(now, parts)` → { id, hue, kind, from, x, y,
|
|
134
|
+
* t } in overview units; a trip with no crossing (both in one tile, or either gone) is dropped.
|
|
135
|
+
*/
|
|
136
|
+
export function createCrossFlights({ max = FLIGHT.MAX } = {}) {
|
|
137
|
+
const seen = new Set();
|
|
138
|
+
let trips = [];
|
|
139
|
+
return {
|
|
140
|
+
sync(items, now, still = false) {
|
|
141
|
+
let k = 0, started = 0;
|
|
142
|
+
for (const it of items || []) {
|
|
143
|
+
if (seen.has(it.id)) continue;
|
|
144
|
+
seen.add(it.id);
|
|
145
|
+
if (seen.size > MAX_SEEN) seen.delete(seen.values().next().value);
|
|
146
|
+
if (still || it.phase === "stale") continue;
|
|
147
|
+
trips.push({ id: it.id, from: it.from, to: it.to, kind: it.kind, hue: hue(it.kind), start: now + k++ * FLIGHT.STAGGER_MS, parts: null, pts: null, ms: 0 });
|
|
148
|
+
started++;
|
|
149
|
+
}
|
|
150
|
+
if (trips.length > max) trips = trips.slice(trips.length - max);
|
|
151
|
+
return started;
|
|
152
|
+
},
|
|
153
|
+
frame(now, parts) {
|
|
154
|
+
const out = [];
|
|
155
|
+
trips = trips.filter((f) => {
|
|
156
|
+
if (f.parts !== parts) { f.parts = parts; f.pts = crossPath(parts, f.from, f.to); f.ms = f.pts ? Math.min(FLIGHT.MAX_MS, Math.max(FLIGHT.MIN_MS, pathLength(f.pts) / FLIGHT.SPEED)) : 0; }
|
|
157
|
+
if (!f.pts) return false;
|
|
158
|
+
const t = (now - f.start) / f.ms;
|
|
159
|
+
if (t >= 1) return false;
|
|
160
|
+
if (t >= 0) { const p = along(f.pts, t); out.push({ id: f.id, hue: f.hue, kind: f.kind, from: f.from, x: p.x, y: p.y, t }); }
|
|
161
|
+
return true;
|
|
162
|
+
});
|
|
163
|
+
return out;
|
|
164
|
+
},
|
|
165
|
+
size: () => trips.length,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
export interface PackStrip { src: string; frames: number; w: number; h: number; fps: number; ax: number; ay: number; dw: number; dh: number }
|
|
5
5
|
export interface PackPoint { x: number; y: number }
|
|
6
6
|
export interface PackArea { name: string; rect: { x: number; y: number; w: number; h: number }; seats: PackPoint[] }
|
|
7
|
-
|
|
7
|
+
/** `label` (#2574): its name tag, or null. */
|
|
8
|
+
export interface PackFurniture { id: string; sprite: PackStrip; x: number; y: number; depth: number | "y"; activeWhen: string[] | null; activeSprite: PackStrip | null; label: string | null }
|
|
8
9
|
export interface CheckedPack {
|
|
9
10
|
format: 1; id: string; scale: number; background?: string; foreground?: string; matte?: string;
|
|
10
11
|
areas: Record<string, PackArea>; stateZones: Record<string, string> | null; door: PackPoint;
|
|
@@ -17,6 +18,8 @@ export interface LayoutOverrides {
|
|
|
17
18
|
pack: string;
|
|
18
19
|
seats?: Record<string, Array<[number, number]>>;
|
|
19
20
|
furniture?: Record<string, [number, number]>;
|
|
21
|
+
/** #2665: an item's night place (an item with a `night` only). */
|
|
22
|
+
furnitureNight?: Record<string, [number, number]>;
|
|
20
23
|
door?: [number, number];
|
|
21
24
|
waypoints?: Array<{ id: string; x: number; y: number }>;
|
|
22
25
|
edges?: Array<[string, string]>;
|
|
@@ -28,12 +31,14 @@ export function own<T = unknown>(o: unknown, k: unknown): T | undefined;
|
|
|
28
31
|
export function inherited(k: unknown): boolean;
|
|
29
32
|
export function matteOk(v: unknown): v is string;
|
|
30
33
|
export function checkStrip(v: unknown, at?: string, scale?: number): PackStrip;
|
|
31
|
-
export function checkSkinPack(x: unknown): { ok: true; pack: CheckedPack } | { ok: false; error: string };
|
|
34
|
+
export function checkSkinPack(x: unknown, opts?: { kept?: boolean }): { ok: true; pack: CheckedPack } | { ok: false; error: string };
|
|
32
35
|
export function skinPackOk(x: unknown): CheckedPack | null;
|
|
33
36
|
export function packImages(pack: CheckedPack): string[];
|
|
34
37
|
export function packStrips(pack: CheckedPack): Array<{ at: string; strip: PackStrip }>;
|
|
35
38
|
export function checkLayoutOverrides(x: unknown): { ok: true; overrides: LayoutOverrides } | { ok: false; error: string };
|
|
36
39
|
export function applyOverrides(pack: CheckedPack, ov: LayoutOverrides | null | undefined): { ok: true; pack: CheckedPack } | { ok: false; error: string };
|
|
40
|
+
export function fitNight<T extends LayoutOverrides | null | undefined>(pack: CheckedPack, ov: T): T;
|
|
37
41
|
export function overridesBetween(pack: CheckedPack, edited: CheckedPack): LayoutOverrides | null;
|
|
38
42
|
export function poseStrip(set: Record<string, PackStrip> | null | undefined, pose: string, heading?: { dx: number; dy: number } | null): { strip: PackStrip; mirror: boolean } | null;
|
|
43
|
+
export function walkStrip(f: { walk?: Record<string, PackStrip> | null; walking?: boolean; heading?: { dx: number; dy: number } | null } | null | undefined): { strip: PackStrip; mirror: boolean } | null;
|
|
39
44
|
export function stripBox(st: PackStrip, x: number, y: number, mirror?: boolean): { x: number; y: number; w: number; h: number };
|
|
@@ -30,15 +30,32 @@
|
|
|
30
30
|
// "door": [640, 700], optional: where someone new walks in from (default: bottom middle)
|
|
31
31
|
// "waypoints": [{ "id": "hall", "x": 640, "y": 600 }, …], "edges": [["hall", "east"], …],
|
|
32
32
|
// optional: the walkable graph (else people walk straight)
|
|
33
|
+
// "straight": true, optional (#2634): every walk goes across, then up or down — each
|
|
34
|
+
// diagonal leg an L, so the walkSide / walkDown / walkUp strips
|
|
35
|
+
// always fit (graph edges laid out across and up/down put the
|
|
36
|
+
// corners on the aisles); default false
|
|
33
37
|
// "furniture": [ { "id": "rack", "sprite": STRIP, "x": 1020, "y": 140, "depth": 2,
|
|
34
|
-
// "activeWhen": "crashed", "activeSprite": STRIP
|
|
38
|
+
// "activeWhen": "crashed", "activeSprite": STRIP, "activeFor": "atlas", "loop": true, "label": "Rack",
|
|
39
|
+
// "night": { "sprite": STRIP, "x": 300, "y": 420 },
|
|
40
|
+
// "walk": { "walk": STRIP, "walkDown": STRIP, "walkUp": STRIP, "walkSide": STRIP } } ],
|
|
41
|
+
// "label" (#2574): a name tag under it, like a person's
|
|
42
|
+
// (an NPC drawn as furniture). "night" (#2568), optional: at bedtime
|
|
43
|
+
// (when the viewer has it on: 00:00–06:00 their time) drawn as this
|
|
44
|
+
// strip, playing, at (x, y) — its own place for either left out.
|
|
45
|
+
// "walk" (#2596), optional: it strolls near its (x, y) and back
|
|
46
|
+
// like an idle person, drawn walking by these strips as a
|
|
47
|
+
// character set's walking poses ("walk" required); not at bedtime
|
|
48
|
+
// with a "night".
|
|
35
49
|
// drawn by depth: below 1000 under the people, 1000 and up over
|
|
36
50
|
// them; "depth": "y" (the string) sorts it among the people by base
|
|
37
51
|
// y — its anchor's y against each person's feet: someone whose feet
|
|
38
52
|
// are lower down (a greater y; a tie too) is drawn after it, in
|
|
39
53
|
// front. (x, y) is where the strip's anchor goes. `activeWhen` (a
|
|
40
54
|
// state, or a list): while anyone shown is in it, the item plays its
|
|
41
|
-
// strip (or `activeSprite`); otherwise it holds the first frame
|
|
55
|
+
// strip (or `activeSprite`); otherwise it holds the first frame —
|
|
56
|
+
// or, with "loop": true (#2570), plays its strip all along.
|
|
57
|
+
// "activeFor" (an instance's name, or a list): only their state
|
|
58
|
+
// counts, not anyone's (a room several people take turns in)
|
|
42
59
|
// "characters": { "default": { "working": STRIP, "idle": STRIP, "paused": STRIP, "needs": STRIP, "walk": STRIP,
|
|
43
60
|
// "walkDown": STRIP, "walkUp": STRIP, "walkSide": STRIP },
|
|
44
61
|
// "fox": { … } }, "default" with at least "idle" is required; a missing pose is idle.
|
|
@@ -47,6 +64,9 @@
|
|
|
47
64
|
// walkSide, drawn as it is when moving right and mirrored when moving
|
|
48
65
|
// left (so draw it facing right). A missing one is "walk", then idle
|
|
49
66
|
// ("walk" itself is never mirrored)
|
|
67
|
+
// A set may also have "prop": STRIP (#2562) — what its people
|
|
68
|
+
// send: their messages, work and reports fly as it, in place of
|
|
69
|
+
// the pack's "props" (a set without one: the pack's props)
|
|
50
70
|
// "byInstance": { "atlas": "fox" }, optional: an instance name → its character set
|
|
51
71
|
// "props": { "task": STRIP, "send": STRIP, "report": STRIP, "broadcast": STRIP }, optional traffic items
|
|
52
72
|
// "bubbles": { "en": { "working": ["On it."] }, "zh-TW": { … } } optional: short lines per state (≤ 12 a state,
|
|
@@ -82,6 +102,7 @@
|
|
|
82
102
|
// { "pack": "cozy", the pack they were made on (on another pack they are ignored)
|
|
83
103
|
// "seats": { "desks": [[120, 220], …] }, an area's seats: as many as the pack's, each inside the area's rect
|
|
84
104
|
// "furniture": { "rack": [1000, 150] }, an item's (x, y)
|
|
105
|
+
// "furnitureNight": { "elder": [300, 420] }, an item's night place (#2665: an item with a "night" only)
|
|
85
106
|
// "door": [640, 700],
|
|
86
107
|
// "waypoints": [{ "id": "hall", "x": 640, "y": 600 }, …], "edges": [["hall", "east"], …] }
|
|
87
108
|
// the walkable graph, replaced whole (edges need waypoints)
|
|
@@ -89,14 +110,26 @@
|
|
|
89
110
|
// Every coordinate is on the same 1280×720 logical canvas, in the same ranges as the pack's. checkLayoutOverrides is
|
|
90
111
|
// the shape (the host checks it on every PUT /ui/offices); applyOverrides puts them on a checked pack, refusing a
|
|
91
112
|
// 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).
|
|
113
|
+
// that too, for a pack it keeps). A night place goes on the item's `night` (its x, y), so at bedtime (nightFurniture)
|
|
114
|
+
// it is the office's, else the pack's night x/y, else the item's (moved) day place. The host keeps `furnitureNight`
|
|
115
|
+
// beside the offices on disk, never inside them: an older host's stricter check would drop the office (#2665,
|
|
116
|
+
// web-office-store.ts).
|
|
93
117
|
|
|
94
118
|
export const LOGICAL = { w: 1280, h: 720 };
|
|
95
119
|
/** The people's depth: furniture below it is drawn under them, the rest over them. */
|
|
96
120
|
export const CHAR_DEPTH = 1000;
|
|
97
121
|
export const PACK_STATES = ["needs", "crashed", "stuck", "working", "idle", "paused", "stopped", "missing"];
|
|
98
122
|
const POSES = ["working", "idle", "paused", "needs", "walk", "walkDown", "walkUp", "walkSide"], PROPS = ["task", "send", "report", "broadcast"];
|
|
123
|
+
/** A character set's strips: its poses, and what its people send (#2562). */
|
|
124
|
+
const SET_STRIPS = [...POSES, "prop"];
|
|
125
|
+
/** A furniture item's walking strips (#2596): a character set's walking poses. */
|
|
126
|
+
const WALKS = ["walk", "walkDown", "walkUp", "walkSide"];
|
|
99
127
|
const BUILT_IN = new Set(["pixel", "studio"]);
|
|
128
|
+
/**
|
|
129
|
+
* Ids built in after packs could be uploaded under them (#2631: "cozy"): a new upload is refused, but a pack the host
|
|
130
|
+
* kept under one before (`kept`) is still read back, listed and deletable, and wins the id for the offices that name it.
|
|
131
|
+
*/
|
|
132
|
+
const RESERVED = new Set(["cozy"]);
|
|
100
133
|
const ID = /^[A-Za-z0-9_-]{1,40}$/;
|
|
101
134
|
const SRC = /^[A-Za-z0-9._/-]{1,200}$/;
|
|
102
135
|
const HEX = /^#[0-9a-f]{6}$/i;
|
|
@@ -138,6 +171,24 @@ export function checkStrip(v, at = "strip", scale = 2) {
|
|
|
138
171
|
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
172
|
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
173
|
}
|
|
174
|
+
/** 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. */
|
|
175
|
+
function nightOf(v, at, scale) {
|
|
176
|
+
if (v === undefined) return null;
|
|
177
|
+
obj(v, at); keys(v, ["sprite", "x", "y"], at);
|
|
178
|
+
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`) };
|
|
179
|
+
}
|
|
180
|
+
/** A furniture item's walk (#2596): its walking strips, picked by heading as a character set's (poseStrip); "walk" required. */
|
|
181
|
+
function walkOf(v, at, scale) {
|
|
182
|
+
if (v === undefined) return null;
|
|
183
|
+
const w = strips(v, WALKS, at, scale);
|
|
184
|
+
if (!w.walk) throw new Refused(`${at}.walk`, "required");
|
|
185
|
+
return w;
|
|
186
|
+
}
|
|
187
|
+
/** Whose state an item's activeWhen watches (#2570): an instance's name, or a list of up to 8. */
|
|
188
|
+
function names(v, at) {
|
|
189
|
+
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; };
|
|
190
|
+
return Array.isArray(v) ? list(v, 8, at).map((s, i) => one(s, `${at}[${i}]`)) : [one(v, at)];
|
|
191
|
+
}
|
|
141
192
|
function strips(v, names, at, scale) {
|
|
142
193
|
obj(v, at);
|
|
143
194
|
keys(v, names, at);
|
|
@@ -148,16 +199,20 @@ function states(v, at) {
|
|
|
148
199
|
return Array.isArray(v) ? list(v, 8, at).map((s, i) => one(s, `${at}[${i}]`)) : [one(v, at)];
|
|
149
200
|
}
|
|
150
201
|
|
|
151
|
-
/**
|
|
152
|
-
|
|
202
|
+
/**
|
|
203
|
+
* A pack's manifest, strictly: { ok: true, pack } or { ok: false, error: "<where>: <why>" }. `kept`: a pack the host
|
|
204
|
+
* keeps already (read back, not uploaded), which may hold a RESERVED id.
|
|
205
|
+
*/
|
|
206
|
+
export function checkSkinPack(x, { kept = false } = {}) {
|
|
153
207
|
try {
|
|
154
208
|
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");
|
|
209
|
+
keys(x, ["format", "id", "scale", "background", "foreground", "matte", "areas", "stateZones", "door", "waypoints", "edges", "straight", "furniture", "characters", "byInstance", "props", "bubbles", "room", "desk"], "pack");
|
|
156
210
|
if (x.format !== 1) throw new Refused("format", "1");
|
|
157
211
|
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");
|
|
212
|
+
if (BUILT_IN.has(x.id) || (!kept && RESERVED.has(x.id))) throw new Refused("id", "a built-in skin's id");
|
|
159
213
|
const scale = x.scale === undefined ? 2 : int(x.scale, 1, 8, "scale");
|
|
160
214
|
if (x.matte !== undefined && !matteOk(x.matte)) throw new Refused("matte", "a colour as #rrggbb");
|
|
215
|
+
if (x.straight !== undefined && typeof x.straight !== "boolean") throw new Refused("straight", "true or false");
|
|
161
216
|
const areas = {};
|
|
162
217
|
const rawAreas = x.areas === undefined ? {} : obj(x.areas, "areas");
|
|
163
218
|
if (Object.keys(rawAreas).length > LIMIT.areas) throw new Refused("areas", `at most ${LIMIT.areas}`);
|
|
@@ -204,20 +259,25 @@ export function checkSkinPack(x) {
|
|
|
204
259
|
const fids = new Set();
|
|
205
260
|
const furniture = list(x.furniture ?? [], LIMIT.furniture, "furniture").map((f, i) => {
|
|
206
261
|
const at = `furniture[${i}]`;
|
|
207
|
-
obj(f, at); keys(f, ["id", "sprite", "x", "y", "depth", "activeWhen", "activeSprite"], at);
|
|
262
|
+
obj(f, at); keys(f, ["id", "sprite", "x", "y", "depth", "activeWhen", "activeSprite", "activeFor", "loop", "label", "night", "walk"], at);
|
|
208
263
|
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
264
|
fids.add(f.id);
|
|
210
265
|
if (f.activeSprite !== undefined && f.activeWhen === undefined) throw new Refused(`${at}.activeSprite`, "only with activeWhen");
|
|
266
|
+
if (f.activeFor !== undefined && f.activeWhen === undefined) throw new Refused(`${at}.activeFor`, "only with activeWhen");
|
|
267
|
+
if (f.loop !== undefined && typeof f.loop !== "boolean") throw new Refused(`${at}.loop`, "true or false");
|
|
268
|
+
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
269
|
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
270
|
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)
|
|
271
|
+
activeSprite: f.activeSprite === undefined ? null : checkStrip(f.activeSprite, `${at}.activeSprite`, scale),
|
|
272
|
+
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),
|
|
273
|
+
walk: walkOf(f.walk, `${at}.walk`, scale) };
|
|
214
274
|
}).sort((a, b) => order(a) - order(b));
|
|
215
275
|
const sets = obj(x.characters, "characters");
|
|
216
276
|
if (Object.keys(sets).length > LIMIT.sets) throw new Refused("characters", `at most ${LIMIT.sets} sets`);
|
|
217
277
|
const characters = {};
|
|
218
278
|
for (const [k, v] of Object.entries(sets)) {
|
|
219
279
|
if (!nameOk(k)) throw new Refused(`characters.${k}`, "a name of letters, digits, _ or - (not an inherited one)");
|
|
220
|
-
characters[k] = strips(v,
|
|
280
|
+
characters[k] = strips(v, SET_STRIPS, `characters.${k}`, scale);
|
|
221
281
|
if (!characters[k].idle) throw new Refused(`characters.${k}.idle`, "required");
|
|
222
282
|
}
|
|
223
283
|
if (!own(characters, "default")) throw new Refused("characters.default", "required");
|
|
@@ -248,7 +308,7 @@ export function checkSkinPack(x) {
|
|
|
248
308
|
const opt = (v, at) => (v === undefined ? undefined : src(v, at));
|
|
249
309
|
return { ok: true, pack: {
|
|
250
310
|
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,
|
|
311
|
+
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
312
|
props: x.props === undefined ? {} : strips(x.props, PROPS, "props", scale), bubbles,
|
|
253
313
|
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
314
|
} };
|
|
@@ -261,13 +321,18 @@ export function checkSkinPack(x) {
|
|
|
261
321
|
export const skinPackOk = (x) => { const r = checkSkinPack(x); return r.ok ? r.pack : null; };
|
|
262
322
|
/** Every image a pack names (what a loader fetches before spriteSkin draws). */
|
|
263
323
|
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]),
|
|
324
|
+
...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
325
|
...Object.values(pack.characters).flatMap((s) => Object.values(s).map((st) => st.src)), ...Object.values(pack.props).map((st) => st.src)].filter(Boolean))];
|
|
266
326
|
|
|
267
327
|
/** Every frame strip a checked pack names, with where (the host checks each image is frames·w × h). */
|
|
268
328
|
export function packStrips(pack) {
|
|
269
329
|
const out = [];
|
|
270
|
-
for (const f of pack.furniture) {
|
|
330
|
+
for (const f of pack.furniture) {
|
|
331
|
+
out.push({ at: `furniture.${f.id}.sprite`, strip: f.sprite });
|
|
332
|
+
if (f.activeSprite) out.push({ at: `furniture.${f.id}.activeSprite`, strip: f.activeSprite });
|
|
333
|
+
if (f.night) out.push({ at: `furniture.${f.id}.night.sprite`, strip: f.night.sprite });
|
|
334
|
+
for (const [k, st] of Object.entries(f.walk || {})) out.push({ at: `furniture.${f.id}.walk.${k}`, strip: st });
|
|
335
|
+
}
|
|
271
336
|
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
337
|
for (const [k, st] of Object.entries(pack.props)) out.push({ at: `props.${k}`, strip: st });
|
|
273
338
|
return out;
|
|
@@ -280,7 +345,7 @@ const OV_LIMIT = { areas: LIMIT.areas, seats: LIMIT.seats, furniture: LIMIT.furn
|
|
|
280
345
|
export function checkLayoutOverrides(x) {
|
|
281
346
|
try {
|
|
282
347
|
obj(x, "overrides");
|
|
283
|
-
keys(x, ["pack", "seats", "furniture", "door", "waypoints", "edges"], "overrides");
|
|
348
|
+
keys(x, ["pack", "seats", "furniture", "furnitureNight", "door", "waypoints", "edges"], "overrides");
|
|
284
349
|
if (typeof x.pack !== "string" || !ID.test(x.pack)) throw new Refused("overrides.pack", "1–40 letters, digits, _ or -");
|
|
285
350
|
const out = { pack: x.pack };
|
|
286
351
|
if (x.seats !== undefined) {
|
|
@@ -305,6 +370,16 @@ export function checkLayoutOverrides(x) {
|
|
|
305
370
|
out.furniture[id] = [q.x, q.y];
|
|
306
371
|
}
|
|
307
372
|
}
|
|
373
|
+
if (x.furnitureNight !== undefined) {
|
|
374
|
+
obj(x.furnitureNight, "overrides.furnitureNight");
|
|
375
|
+
if (Object.keys(x.furnitureNight).length > OV_LIMIT.furniture) throw new Refused("overrides.furnitureNight", `at most ${OV_LIMIT.furniture}`);
|
|
376
|
+
out.furnitureNight = {};
|
|
377
|
+
for (const [id, p] of Object.entries(x.furnitureNight)) {
|
|
378
|
+
if (!nameOk(id)) throw new Refused(`overrides.furnitureNight.${id}`, "an item's id");
|
|
379
|
+
const q = point(p, `overrides.furnitureNight.${id}`);
|
|
380
|
+
out.furnitureNight[id] = [q.x, q.y];
|
|
381
|
+
}
|
|
382
|
+
}
|
|
308
383
|
if (x.door !== undefined) { const q = point(x.door, "overrides.door"); out.door = [q.x, q.y]; }
|
|
309
384
|
if (x.edges !== undefined && x.waypoints === undefined) throw new Refused("overrides.edges", "only with waypoints");
|
|
310
385
|
if (x.waypoints !== undefined) {
|
|
@@ -352,7 +427,13 @@ export function applyOverrides(pack, ov) {
|
|
|
352
427
|
}
|
|
353
428
|
const fids = new Set(pack.furniture.map((f) => f.id));
|
|
354
429
|
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
|
|
430
|
+
const nids = new Set(pack.furniture.filter((f) => f.night).map((f) => f.id));
|
|
431
|
+
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");
|
|
432
|
+
const furniture = pack.furniture.map((f) => {
|
|
433
|
+
const q = own(ov.furniture, f.id), n = own(ov.furnitureNight, f.id);
|
|
434
|
+
if (!q && !n) return f;
|
|
435
|
+
return { ...f, ...(q ? { x: q[0], y: q[1] } : {}), ...(n ? { night: { ...f.night, x: n[0], y: n[1] } } : {}) };
|
|
436
|
+
});
|
|
356
437
|
const door = ov.door ? { x: ov.door[0], y: ov.door[1] } : pack.door;
|
|
357
438
|
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
439
|
return { ok: true, pack: { ...pack, areas, furniture, door, ...graph } };
|
|
@@ -362,6 +443,20 @@ export function applyOverrides(pack, ov) {
|
|
|
362
443
|
}
|
|
363
444
|
}
|
|
364
445
|
|
|
446
|
+
/**
|
|
447
|
+
* #2665: overrides with only the night places `pack` can take (an item it has, with a `night`): a pack changed since
|
|
448
|
+
* (an item dropped, or its night) drops that entry, never the rest of the layout. applyOverrides itself stays strict
|
|
449
|
+
* (a write naming an item the pack lacks is refused, as for `furniture`).
|
|
450
|
+
*/
|
|
451
|
+
export function fitNight(pack, ov) {
|
|
452
|
+
if (!ov || !ov.furnitureNight || ov.pack !== pack.id) return ov;
|
|
453
|
+
const ok = new Set(pack.furniture.filter((f) => f.night).map((f) => f.id));
|
|
454
|
+
const kept = Object.entries(ov.furnitureNight).filter(([id]) => ok.has(id));
|
|
455
|
+
if (kept.length === Object.keys(ov.furnitureNight).length) return ov;
|
|
456
|
+
const { furnitureNight: _gone, ...rest } = ov;
|
|
457
|
+
return kept.length ? { ...rest, furnitureNight: Object.fromEntries(kept) } : rest;
|
|
458
|
+
}
|
|
459
|
+
|
|
365
460
|
/**
|
|
366
461
|
* What an edited layout keeps as overrides: only the parts that differ from the pack's own (`edited` is a pack with
|
|
367
462
|
* the same areas and furniture, positions moved); null when nothing does (the office goes back to the pack's).
|
|
@@ -375,6 +470,12 @@ export function overridesBetween(pack, edited) {
|
|
|
375
470
|
}
|
|
376
471
|
const at = new Map(pack.furniture.map((f) => [f.id, f]));
|
|
377
472
|
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]; }
|
|
473
|
+
// #2665: a night place that differs from the pack's (its own x/y, or in place: null); in place again is no entry.
|
|
474
|
+
for (const f of edited.furniture) {
|
|
475
|
+
const o = at.get(f.id);
|
|
476
|
+
if (!o || !o.night || !f.night || f.night.x === null || f.night.y === null) continue;
|
|
477
|
+
if (o.night.x !== f.night.x || o.night.y !== f.night.y) (ov.furnitureNight ||= {})[f.id] = [f.night.x, f.night.y];
|
|
478
|
+
}
|
|
378
479
|
if (pack.door.x !== edited.door.x || pack.door.y !== edited.door.y) ov.door = [edited.door.x, edited.door.y];
|
|
379
480
|
if (!same(pack.waypoints, edited.waypoints) || !same(pack.edges, edited.edges)) {
|
|
380
481
|
ov.waypoints = edited.waypoints.map((w) => ({ id: w.id, x: w.x, y: w.y }));
|
|
@@ -403,6 +504,9 @@ export function poseStrip(set, pose, heading = null) {
|
|
|
403
504
|
const st = own(set, pose) || own(set, "idle");
|
|
404
505
|
return st ? { strip: st, mirror: false } : null;
|
|
405
506
|
}
|
|
507
|
+
/** The strip a furniture item shows while it walks (#2596: an NPC with `walk`, `walking` and `heading` set where it is
|
|
508
|
+
* drawn): its walk strip by heading, as a character's (poseStrip); null when it stands (its own sprite, as before). */
|
|
509
|
+
export const walkStrip = (f) => (f && f.walking && f.walk ? poseStrip(f.walk, "walk", f.heading) : null);
|
|
406
510
|
/** The box (logical units) a strip covers with its anchor at (x, y): its displayed size; mirrored about the anchor. */
|
|
407
511
|
export function stripBox(st, x, y, mirror = false) {
|
|
408
512
|
const kx = st.dw / st.w, ky = st.dh / st.h;
|