sci2-ts 0.0.0-stage → 0.2.1
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/CHANGELOG.md +72 -0
- package/LICENSE +21 -0
- package/README.md +113 -2
- package/apps/viewer/editor.html +43 -0
- package/apps/viewer/explore.html +27 -0
- package/apps/viewer/index.html +26 -0
- package/apps/viewer/package.json +27 -0
- package/apps/viewer/play.html +26 -0
- package/apps/viewer/plugin.d.ts +18 -0
- package/apps/viewer/plugin.js +75 -0
- package/apps/viewer/plugin.ts +84 -0
- package/apps/viewer/src/adlib.d.ts +20 -0
- package/apps/viewer/src/adlib.js +55 -0
- package/apps/viewer/src/adlib.ts +62 -0
- package/apps/viewer/src/app.d.ts +5 -0
- package/apps/viewer/src/app.js +5 -0
- package/apps/viewer/src/app.ts +5 -0
- package/apps/viewer/src/asset-index.d.ts +22 -0
- package/apps/viewer/src/asset-index.js +74 -0
- package/apps/viewer/src/asset-index.ts +90 -0
- package/apps/viewer/src/audio.d.ts +28 -0
- package/apps/viewer/src/audio.js +102 -0
- package/apps/viewer/src/audio.ts +110 -0
- package/apps/viewer/src/editor-handles.d.ts +63 -0
- package/apps/viewer/src/editor-handles.js +332 -0
- package/apps/viewer/src/editor-handles.ts +368 -0
- package/apps/viewer/src/editor-languages.d.ts +23 -0
- package/apps/viewer/src/editor-languages.js +157 -0
- package/apps/viewer/src/editor-languages.ts +135 -0
- package/apps/viewer/src/editor.css +213 -0
- package/apps/viewer/src/editor.d.ts +1 -0
- package/apps/viewer/src/editor.js +393 -0
- package/apps/viewer/src/editor.ts +412 -0
- package/apps/viewer/src/explore/classes.d.ts +7 -0
- package/apps/viewer/src/explore/classes.js +63 -0
- package/apps/viewer/src/explore/classes.ts +61 -0
- package/apps/viewer/src/explore/context.d.ts +33 -0
- package/apps/viewer/src/explore/context.js +96 -0
- package/apps/viewer/src/explore/context.ts +124 -0
- package/apps/viewer/src/explore/explore.css +93 -0
- package/apps/viewer/src/explore/explore.d.ts +1 -0
- package/apps/viewer/src/explore/explore.js +102 -0
- package/apps/viewer/src/explore/explore.ts +104 -0
- package/apps/viewer/src/explore/inspect.d.ts +5 -0
- package/apps/viewer/src/explore/inspect.js +208 -0
- package/apps/viewer/src/explore/inspect.ts +279 -0
- package/apps/viewer/src/explore/live.d.ts +22 -0
- package/apps/viewer/src/explore/live.js +318 -0
- package/apps/viewer/src/explore/live.ts +365 -0
- package/apps/viewer/src/explore/map.d.ts +11 -0
- package/apps/viewer/src/explore/map.js +182 -0
- package/apps/viewer/src/explore/map.ts +193 -0
- package/apps/viewer/src/files.d.ts +21 -0
- package/apps/viewer/src/files.js +69 -0
- package/apps/viewer/src/files.ts +72 -0
- package/apps/viewer/src/game-profile.d.ts +19 -0
- package/apps/viewer/src/game-profile.js +4 -0
- package/apps/viewer/src/game-profile.ts +26 -0
- package/apps/viewer/src/main.d.ts +1 -0
- package/apps/viewer/src/main.js +473 -0
- package/apps/viewer/src/main.ts +527 -0
- package/apps/viewer/src/music.d.ts +14 -0
- package/apps/viewer/src/music.js +32 -0
- package/apps/viewer/src/music.ts +34 -0
- package/apps/viewer/src/opl-worklet.d.ts +1 -0
- package/apps/viewer/src/opl-worklet.js +47 -0
- package/apps/viewer/src/opl-worklet.ts +55 -0
- package/apps/viewer/src/overlay.d.ts +31 -0
- package/apps/viewer/src/overlay.js +103 -0
- package/apps/viewer/src/overlay.ts +123 -0
- package/apps/viewer/src/play.css +14 -0
- package/apps/viewer/src/play.d.ts +1 -0
- package/apps/viewer/src/play.js +179 -0
- package/apps/viewer/src/play.ts +177 -0
- package/apps/viewer/src/saves.d.ts +10 -0
- package/apps/viewer/src/saves.js +50 -0
- package/apps/viewer/src/saves.ts +51 -0
- package/apps/viewer/src/session.d.ts +91 -0
- package/apps/viewer/src/session.js +301 -0
- package/apps/viewer/src/session.ts +321 -0
- package/apps/viewer/src/sound-preview.d.ts +7 -0
- package/apps/viewer/src/sound-preview.js +214 -0
- package/apps/viewer/src/sound-preview.ts +228 -0
- package/apps/viewer/src/style.css +64 -0
- package/apps/viewer/vite.config.ts +25 -0
- package/bin/sci-ts.mjs +9 -0
- package/lib/994.sc +49 -0
- package/lib/995.sc +53 -0
- package/lib/996.sc +299 -0
- package/lib/997.sc +198 -0
- package/lib/998.sc +338 -0
- package/lib/999.sc +189 -0
- package/lib/resources.d.ts +2 -0
- package/lib/resources.js +84 -0
- package/lib/resources.ts +88 -0
- package/lib/system.sh +62 -0
- package/package.json +75 -4
- package/packages/content/room.schema.json +267 -0
- package/packages/content/src/index.d.ts +3 -0
- package/packages/content/src/index.js +3 -0
- package/packages/content/src/index.ts +3 -0
- package/packages/content/src/room.d.ts +111 -0
- package/packages/content/src/room.js +886 -0
- package/packages/content/src/room.ts +1011 -0
- package/packages/content/src/target.d.ts +80 -0
- package/packages/content/src/target.js +1 -0
- package/packages/content/src/target.ts +60 -0
- package/packages/content/src/yarn.d.ts +90 -0
- package/packages/content/src/yarn.js +214 -0
- package/packages/content/src/yarn.ts +261 -0
- package/packages/sci/src/analysis/game.d.ts +90 -0
- package/packages/sci/src/analysis/game.js +221 -0
- package/packages/sci/src/analysis/game.ts +305 -0
- package/packages/sci/src/analysis/runtime.d.ts +58 -0
- package/packages/sci/src/analysis/runtime.js +133 -0
- package/packages/sci/src/analysis/runtime.ts +163 -0
- package/packages/sci/src/audio/adlib.d.ts +73 -0
- package/packages/sci/src/audio/adlib.js +280 -0
- package/packages/sci/src/audio/adlib.ts +329 -0
- package/packages/sci/src/audio/index.d.ts +43 -0
- package/packages/sci/src/audio/index.js +99 -0
- package/packages/sci/src/audio/index.ts +122 -0
- package/packages/sci/src/audio/midi.d.ts +92 -0
- package/packages/sci/src/audio/midi.js +242 -0
- package/packages/sci/src/audio/midi.ts +262 -0
- package/packages/sci/src/audio/opl2.d.ts +74 -0
- package/packages/sci/src/audio/opl2.js +283 -0
- package/packages/sci/src/audio/opl2.ts +283 -0
- package/packages/sci/src/audio/sol.d.ts +32 -0
- package/packages/sci/src/audio/sol.js +81 -0
- package/packages/sci/src/audio/sol.ts +100 -0
- package/packages/sci/src/audio/write.d.ts +35 -0
- package/packages/sci/src/audio/write.js +91 -0
- package/packages/sci/src/audio/write.ts +108 -0
- package/packages/sci/src/gfx/cel.d.ts +36 -0
- package/packages/sci/src/gfx/cel.js +82 -0
- package/packages/sci/src/gfx/cel.ts +110 -0
- package/packages/sci/src/gfx/palette.d.ts +24 -0
- package/packages/sci/src/gfx/palette.js +49 -0
- package/packages/sci/src/gfx/palette.ts +56 -0
- package/packages/sci/src/gfx/pic-file.d.ts +46 -0
- package/packages/sci/src/gfx/pic-file.js +114 -0
- package/packages/sci/src/gfx/pic-file.ts +155 -0
- package/packages/sci/src/gfx/pic.d.ts +27 -0
- package/packages/sci/src/gfx/pic.js +34 -0
- package/packages/sci/src/gfx/pic.ts +53 -0
- package/packages/sci/src/gfx/quantize.d.ts +42 -0
- package/packages/sci/src/gfx/quantize.js +199 -0
- package/packages/sci/src/gfx/quantize.ts +218 -0
- package/packages/sci/src/gfx/view-file.d.ts +62 -0
- package/packages/sci/src/gfx/view-file.js +182 -0
- package/packages/sci/src/gfx/view-file.ts +233 -0
- package/packages/sci/src/gfx/view.d.ts +28 -0
- package/packages/sci/src/gfx/view.js +45 -0
- package/packages/sci/src/gfx/view.ts +62 -0
- package/packages/sci/src/index.d.ts +38 -0
- package/packages/sci/src/index.js +38 -0
- package/packages/sci/src/index.ts +38 -0
- package/packages/sci/src/motion/pathfinding.d.ts +40 -0
- package/packages/sci/src/motion/pathfinding.js +140 -0
- package/packages/sci/src/motion/pathfinding.ts +147 -0
- package/packages/sci/src/resource/archive.d.ts +16 -0
- package/packages/sci/src/resource/archive.js +51 -0
- package/packages/sci/src/resource/archive.ts +56 -0
- package/packages/sci/src/resource/lzs.d.ts +12 -0
- package/packages/sci/src/resource/lzs.js +64 -0
- package/packages/sci/src/resource/lzs.ts +66 -0
- package/packages/sci/src/resource/manager.d.ts +45 -0
- package/packages/sci/src/resource/manager.js +158 -0
- package/packages/sci/src/resource/manager.ts +184 -0
- package/packages/sci/src/resource/map.d.ts +18 -0
- package/packages/sci/src/resource/map.js +34 -0
- package/packages/sci/src/resource/map.ts +44 -0
- package/packages/sci/src/resource/types.d.ts +37 -0
- package/packages/sci/src/resource/types.js +45 -0
- package/packages/sci/src/resource/types.ts +57 -0
- package/packages/sci/src/script/assembly.d.ts +67 -0
- package/packages/sci/src/script/assembly.js +748 -0
- package/packages/sci/src/script/assembly.ts +714 -0
- package/packages/sci/src/script/compiler.d.ts +75 -0
- package/packages/sci/src/script/compiler.js +993 -0
- package/packages/sci/src/script/compiler.ts +1040 -0
- package/packages/sci/src/script/disasm.d.ts +81 -0
- package/packages/sci/src/script/disasm.js +318 -0
- package/packages/sci/src/script/disasm.ts +323 -0
- package/packages/sci/src/script/kernel-names.d.ts +6 -0
- package/packages/sci/src/script/kernel-names.js +165 -0
- package/packages/sci/src/script/kernel-names.ts +165 -0
- package/packages/sci/src/script/opcodes.d.ts +22 -0
- package/packages/sci/src/script/opcodes.js +45 -0
- package/packages/sci/src/script/opcodes.ts +72 -0
- package/packages/sci/src/script/script.d.ts +56 -0
- package/packages/sci/src/script/script.js +63 -0
- package/packages/sci/src/script/script.ts +111 -0
- package/packages/sci/src/script/sexpr.d.ts +45 -0
- package/packages/sci/src/script/sexpr.js +151 -0
- package/packages/sci/src/script/sexpr.ts +139 -0
- package/packages/sci/src/script/vocab.d.ts +15 -0
- package/packages/sci/src/script/vocab.js +44 -0
- package/packages/sci/src/script/vocab.ts +48 -0
- package/packages/sci/src/script/world.d.ts +25 -0
- package/packages/sci/src/script/world.js +65 -0
- package/packages/sci/src/script/world.ts +70 -0
- package/packages/sci/src/text/font.d.ts +37 -0
- package/packages/sci/src/text/font.js +103 -0
- package/packages/sci/src/text/font.ts +125 -0
- package/packages/sci/src/text/message.d.ts +69 -0
- package/packages/sci/src/text/message.js +171 -0
- package/packages/sci/src/text/message.ts +205 -0
- package/packages/sci/src/vm/kernels/arrays.d.ts +19 -0
- package/packages/sci/src/vm/kernels/arrays.js +182 -0
- package/packages/sci/src/vm/kernels/arrays.ts +164 -0
- package/packages/sci/src/vm/kernels/audio.d.ts +70 -0
- package/packages/sci/src/vm/kernels/audio.js +347 -0
- package/packages/sci/src/vm/kernels/audio.ts +349 -0
- package/packages/sci/src/vm/kernels/core.d.ts +5 -0
- package/packages/sci/src/vm/kernels/core.js +87 -0
- package/packages/sci/src/vm/kernels/core.ts +91 -0
- package/packages/sci/src/vm/kernels/graphics.d.ts +141 -0
- package/packages/sci/src/vm/kernels/graphics.js +467 -0
- package/packages/sci/src/vm/kernels/graphics.ts +502 -0
- package/packages/sci/src/vm/kernels/index.d.ts +8 -0
- package/packages/sci/src/vm/kernels/index.js +29 -0
- package/packages/sci/src/vm/kernels/index.ts +32 -0
- package/packages/sci/src/vm/kernels/lists.d.ts +6 -0
- package/packages/sci/src/vm/kernels/lists.js +127 -0
- package/packages/sci/src/vm/kernels/lists.ts +111 -0
- package/packages/sci/src/vm/kernels/motion.d.ts +7 -0
- package/packages/sci/src/vm/kernels/motion.js +204 -0
- package/packages/sci/src/vm/kernels/motion.ts +194 -0
- package/packages/sci/src/vm/kernels/palette.d.ts +82 -0
- package/packages/sci/src/vm/kernels/palette.js +292 -0
- package/packages/sci/src/vm/kernels/palette.ts +303 -0
- package/packages/sci/src/vm/kernels/saves.d.ts +45 -0
- package/packages/sci/src/vm/kernels/saves.js +123 -0
- package/packages/sci/src/vm/kernels/saves.ts +149 -0
- package/packages/sci/src/vm/kernels/system.d.ts +62 -0
- package/packages/sci/src/vm/kernels/system.js +110 -0
- package/packages/sci/src/vm/kernels/system.ts +128 -0
- package/packages/sci/src/vm/kernels/text.d.ts +13 -0
- package/packages/sci/src/vm/kernels/text.js +338 -0
- package/packages/sci/src/vm/kernels/text.ts +328 -0
- package/packages/sci/src/vm/memory.d.ts +138 -0
- package/packages/sci/src/vm/memory.js +168 -0
- package/packages/sci/src/vm/memory.ts +266 -0
- package/packages/sci/src/vm/replace.d.ts +11 -0
- package/packages/sci/src/vm/replace.js +14 -0
- package/packages/sci/src/vm/replace.ts +16 -0
- package/packages/sci/src/vm/savegame.d.ts +89 -0
- package/packages/sci/src/vm/savegame.js +161 -0
- package/packages/sci/src/vm/savegame.ts +188 -0
- package/packages/sci/src/vm/value.d.ts +17 -0
- package/packages/sci/src/vm/value.js +12 -0
- package/packages/sci/src/vm/value.ts +25 -0
- package/packages/sci/src/vm/vm.d.ts +155 -0
- package/packages/sci/src/vm/vm.js +1053 -0
- package/packages/sci/src/vm/vm.ts +914 -0
- package/tools/adlib-render.d.ts +1 -0
- package/tools/adlib-render.js +49 -0
- package/tools/adlib-render.ts +42 -0
- package/tools/analyze.d.ts +1 -0
- package/tools/analyze.js +25 -0
- package/tools/analyze.ts +25 -0
- package/tools/art/build.d.ts +34 -0
- package/tools/art/build.js +175 -0
- package/tools/art/build.ts +153 -0
- package/tools/art.d.ts +1 -0
- package/tools/art.js +31 -0
- package/tools/art.ts +29 -0
- package/tools/asm.d.ts +1 -0
- package/tools/asm.js +61 -0
- package/tools/asm.ts +55 -0
- package/tools/cli.d.ts +1 -0
- package/tools/cli.js +99 -0
- package/tools/cli.ts +100 -0
- package/tools/editor-server.d.ts +29 -0
- package/tools/editor-server.js +162 -0
- package/tools/editor-server.ts +176 -0
- package/tools/explore.d.ts +1 -0
- package/tools/explore.js +305 -0
- package/tools/explore.ts +250 -0
- package/tools/game/audio.d.ts +19 -0
- package/tools/game/audio.js +143 -0
- package/tools/game/audio.ts +130 -0
- package/tools/game/build.d.ts +46 -0
- package/tools/game/build.js +259 -0
- package/tools/game/build.ts +283 -0
- package/tools/game/defaults.d.ts +26 -0
- package/tools/game/defaults.js +82 -0
- package/tools/game/defaults.ts +83 -0
- package/tools/game/font.d.ts +3 -0
- package/tools/game/font.js +124 -0
- package/tools/game/font.ts +126 -0
- package/tools/game/kit.d.ts +7 -0
- package/tools/game/kit.js +7 -0
- package/tools/game/kit.ts +10 -0
- package/tools/game/new.d.ts +8 -0
- package/tools/game/new.js +39 -0
- package/tools/game/new.ts +38 -0
- package/tools/game/target.d.ts +7 -0
- package/tools/game/target.js +41 -0
- package/tools/game/target.ts +42 -0
- package/tools/game/template/resources.ts.tmpl +43 -0
- package/tools/game/template/rooms/1.room.yaml +9 -0
- package/tools/game/template/rooms/1.yarn +17 -0
- package/tools/game/template/scripts/0.sc +24 -0
- package/tools/game.d.ts +1 -0
- package/tools/game.js +77 -0
- package/tools/game.ts +70 -0
- package/tools/mod-build.d.ts +28 -0
- package/tools/mod-build.js +173 -0
- package/tools/mod-build.ts +179 -0
- package/tools/mod-target.d.ts +16 -0
- package/tools/mod-target.js +26 -0
- package/tools/mod-target.ts +32 -0
- package/tools/mod.d.ts +1 -0
- package/tools/mod.js +47 -0
- package/tools/mod.ts +45 -0
- package/tools/msg.d.ts +1 -0
- package/tools/msg.js +40 -0
- package/tools/msg.ts +36 -0
- package/tools/node-files.d.ts +3 -0
- package/tools/node-files.js +34 -0
- package/tools/node-files.ts +33 -0
- package/tools/patches.d.ts +3 -0
- package/tools/patches.js +13 -0
- package/tools/patches.ts +13 -0
- package/tools/pic-build.d.ts +9 -0
- package/tools/pic-build.js +26 -0
- package/tools/pic-build.ts +27 -0
- package/tools/pic.d.ts +1 -0
- package/tools/pic.js +64 -0
- package/tools/pic.ts +61 -0
- package/tools/play.d.ts +1 -0
- package/tools/play.js +54 -0
- package/tools/play.ts +54 -0
- package/tools/png.d.ts +16 -0
- package/tools/png.js +124 -0
- package/tools/png.ts +123 -0
- package/tools/res.d.ts +1 -0
- package/tools/res.js +48 -0
- package/tools/res.ts +50 -0
- package/tools/scenario.d.ts +1 -0
- package/tools/scenario.js +74 -0
- package/tools/scenario.ts +80 -0
- package/tools/site/build.d.ts +8 -0
- package/tools/site/build.js +110 -0
- package/tools/site/build.ts +116 -0
- package/tools/site/markdown.d.ts +11 -0
- package/tools/site/markdown.js +88 -0
- package/tools/site/markdown.ts +84 -0
- package/tools/view-build.d.ts +34 -0
- package/tools/view-build.js +47 -0
- package/tools/view-build.ts +71 -0
- package/tools/view.d.ts +1 -0
- package/tools/view.js +54 -0
- package/tools/view.ts +52 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import cytoscape from "cytoscape";
|
|
2
|
+
import type { ExitKind } from "../../../../packages/sci/src/index.ts";
|
|
3
|
+
import { el, thumbnail, type Explorer } from "./context.ts";
|
|
4
|
+
|
|
5
|
+
/** Rooms linked to this many others are hubs (combat): their links are hidden by default. */
|
|
6
|
+
const HUB_DEGREE = 20;
|
|
7
|
+
|
|
8
|
+
export interface RoomMap {
|
|
9
|
+
element: HTMLElement;
|
|
10
|
+
select(room: number | undefined): void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The room graph: one node per room (thumbnail of its pic), grouped by hundreds, which is
|
|
15
|
+
* how Sierra numbered areas. One edge per connected pair; arrows show direction, and a
|
|
16
|
+
* dashed edge means the only evidence is the target checking where the hero came from.
|
|
17
|
+
*/
|
|
18
|
+
export function createRoomMap(ex: Explorer, onSelect: (room: number) => void): RoomMap {
|
|
19
|
+
const { rooms, exits } = ex.index;
|
|
20
|
+
const container = el("div", { id: "map" });
|
|
21
|
+
|
|
22
|
+
type Pair = { a: number; b: number; ab: Set<ExitKind>; ba: Set<ExitKind> };
|
|
23
|
+
const pairs = new Map<string, Pair>();
|
|
24
|
+
for (const e of exits) {
|
|
25
|
+
const [a, b] = e.from < e.to ? [e.from, e.to] : [e.to, e.from];
|
|
26
|
+
const key = `${a}-${b}`;
|
|
27
|
+
const p = pairs.get(key) ?? { a, b, ab: new Set(), ba: new Set() };
|
|
28
|
+
(e.from === a ? p.ab : p.ba).add(e.kind);
|
|
29
|
+
pairs.set(key, p);
|
|
30
|
+
}
|
|
31
|
+
const degree = new Map<number, number>();
|
|
32
|
+
for (const p of pairs.values()) for (const r of [p.a, p.b]) degree.set(r, (degree.get(r) ?? 0) + 1);
|
|
33
|
+
const hubs = new Set([...degree].filter(([, d]) => d >= HUB_DEGREE).map(([r]) => r));
|
|
34
|
+
|
|
35
|
+
const areas = new Set(rooms.map((r) => Math.floor(r.number / 100)));
|
|
36
|
+
const elements: cytoscape.ElementDefinition[] = [
|
|
37
|
+
// Area boxes are plain nodes drawn behind the rooms; they get sized after layout.
|
|
38
|
+
...[...areas].map((a) => ({ data: { id: `area${a}`, label: `${a}00s`, w: 1, h: 1 }, classes: "area", selectable: false, grabbable: false })),
|
|
39
|
+
...rooms.map((r) => ({
|
|
40
|
+
data: { id: `r${r.number}`, label: `${r.number} ${r.name.replace(/^rm\d+$/, "")}`.trim(), area: Math.floor(r.number / 100), room: r.number },
|
|
41
|
+
classes: hubs.has(r.number) ? "hub" : "",
|
|
42
|
+
})),
|
|
43
|
+
...[...pairs.values()].map((p) => {
|
|
44
|
+
const kinds = new Set([...p.ab, ...p.ba]);
|
|
45
|
+
const entryOnly = [...kinds].every((k) => k === "entry");
|
|
46
|
+
const direction = kinds.has("direction") && ![...kinds].some((k) => k !== "direction" && k !== "entry");
|
|
47
|
+
// Edge runs a → b; arrows at whichever ends are entered.
|
|
48
|
+
const classes = [entryOnly ? "entry" : direction ? "direction" : "code", p.ab.size ? "to-b" : "", p.ba.size ? "to-a" : ""];
|
|
49
|
+
if (hubs.has(p.a) || hubs.has(p.b)) classes.push("hub-link");
|
|
50
|
+
return { data: { id: `e${p.a}-${p.b}`, source: `r${p.a}`, target: `r${p.b}`, kinds: [...kinds].join(", ") }, classes: classes.join(" ") };
|
|
51
|
+
}),
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
const cy = cytoscape({
|
|
55
|
+
container,
|
|
56
|
+
elements,
|
|
57
|
+
minZoom: 0.15,
|
|
58
|
+
maxZoom: 3,
|
|
59
|
+
style: [
|
|
60
|
+
{ selector: "node", style: {
|
|
61
|
+
"z-index-compare": "manual", "z-index": 2,
|
|
62
|
+
shape: "round-rectangle", width: 64, height: 46, "background-color": "#262626",
|
|
63
|
+
"border-width": 1, "border-color": "#3a3a3a",
|
|
64
|
+
label: "data(label)", color: "#ccc", "font-size": 10, "font-family": "ui-monospace, Menlo, monospace",
|
|
65
|
+
"text-valign": "bottom", "text-margin-y": 3, "text-background-color": "#111", "text-background-opacity": 0.7, "text-background-padding": "1px",
|
|
66
|
+
} },
|
|
67
|
+
{ selector: "node[thumb]", style: { "background-image": "data(thumb)", "background-fit": "cover" } },
|
|
68
|
+
{ selector: ".area", style: {
|
|
69
|
+
shape: "round-rectangle", width: "data(w)", height: "data(h)", "z-index": 0, events: "no",
|
|
70
|
+
"background-color": "#181818", "border-color": "#2e2e2e", "border-width": 1,
|
|
71
|
+
label: "data(label)", "text-valign": "top", "text-halign": "center", "text-margin-y": -4, color: "#777", "font-size": 16,
|
|
72
|
+
"text-background-opacity": 0,
|
|
73
|
+
} },
|
|
74
|
+
{ selector: "node.hub", style: { "border-color": "#a05050", "border-width": 2 } },
|
|
75
|
+
{ selector: "node:selected", style: { "border-color": "#e8b04a", "border-width": 3 } },
|
|
76
|
+
{ selector: "edge", style: {
|
|
77
|
+
"z-index-compare": "manual", "z-index": 1,
|
|
78
|
+
width: 1.6, "curve-style": "bezier", "line-color": "#7fb3e8", opacity: 0.8,
|
|
79
|
+
"target-arrow-color": "#7fb3e8", "source-arrow-color": "#7fb3e8", "arrow-scale": 0.8,
|
|
80
|
+
} },
|
|
81
|
+
{ selector: "edge.to-b", style: { "target-arrow-shape": "triangle" } },
|
|
82
|
+
{ selector: "edge.to-a", style: { "source-arrow-shape": "triangle" } },
|
|
83
|
+
{ selector: "edge.direction", style: { "line-color": "#6fb07a", "target-arrow-color": "#6fb07a", "source-arrow-color": "#6fb07a" } },
|
|
84
|
+
{ selector: "edge.entry", style: { "line-style": "dashed", "line-color": "#666", "target-arrow-color": "#666", "source-arrow-color": "#666" } },
|
|
85
|
+
{ selector: "edge.hub-link", style: { display: "none" } },
|
|
86
|
+
{ selector: ".faded", style: { opacity: 0.18 } },
|
|
87
|
+
{ selector: "edge.lit", style: { width: 3, opacity: 1, "z-index": 10 } },
|
|
88
|
+
],
|
|
89
|
+
layout: { name: "preset" },
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Lay out once the map is in the page: cytoscape measures its container.
|
|
93
|
+
let laidOut = false;
|
|
94
|
+
const layout = () => {
|
|
95
|
+
if (laidOut || !container.isConnected) return;
|
|
96
|
+
laidOut = true;
|
|
97
|
+
cy.resize();
|
|
98
|
+
layoutAreas(cy);
|
|
99
|
+
cy.fit(undefined, 20);
|
|
100
|
+
if (pending !== undefined) select(pending);
|
|
101
|
+
};
|
|
102
|
+
let pending: number | undefined;
|
|
103
|
+
// Cytoscape caches its container's size; keep it current as the window changes.
|
|
104
|
+
new ResizeObserver(() => cy.resize()).observe(container);
|
|
105
|
+
|
|
106
|
+
cy.on("tap", "node", (evt) => {
|
|
107
|
+
const room = evt.target.data("room");
|
|
108
|
+
if (room !== undefined) onSelect(room);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Thumbnails load in the background, a few at a time.
|
|
112
|
+
void (async () => {
|
|
113
|
+
for (const r of rooms) {
|
|
114
|
+
if (r.picture < 0) continue;
|
|
115
|
+
const canvas = await ex.pic(r.picture).catch(() => undefined);
|
|
116
|
+
if (canvas) cy.getElementById(`r${r.number}`).data("thumb", thumbnail(canvas));
|
|
117
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
118
|
+
}
|
|
119
|
+
})();
|
|
120
|
+
|
|
121
|
+
const swatch = (css: string) => {
|
|
122
|
+
const i = el("i");
|
|
123
|
+
i.style.cssText = css;
|
|
124
|
+
return i;
|
|
125
|
+
};
|
|
126
|
+
const hubToggle = el("input", { type: "checkbox" });
|
|
127
|
+
hubToggle.addEventListener("change", () => cy.edges(".hub-link").style("display", hubToggle.checked ? "element" : "none"));
|
|
128
|
+
const legend = el("div", { className: "legend" },
|
|
129
|
+
el("span", {}, swatch("border-color: #7fb3e8"), "script exit"),
|
|
130
|
+
el("span", {}, swatch("border-color: #6fb07a"), "north/south/east/west"),
|
|
131
|
+
el("span", {}, swatch("border-color: #666; border-top-style: dashed"), "entry check only"),
|
|
132
|
+
el("label", {}, hubToggle, ` combat links (${[...hubs].join(", ")})`),
|
|
133
|
+
);
|
|
134
|
+
const element = el("div", {}, container, legend);
|
|
135
|
+
element.style.cssText = "position: absolute; inset: 0;";
|
|
136
|
+
|
|
137
|
+
function select(room: number | undefined) {
|
|
138
|
+
pending = room;
|
|
139
|
+
if (!laidOut) return;
|
|
140
|
+
cy.elements().removeClass("faded lit").unselect();
|
|
141
|
+
if (room === undefined) return;
|
|
142
|
+
const node = cy.getElementById(`r${room}`);
|
|
143
|
+
if (node.empty()) return;
|
|
144
|
+
node.select();
|
|
145
|
+
const hood = node.closedNeighborhood();
|
|
146
|
+
cy.elements().not(hood).not(".area").addClass("faded");
|
|
147
|
+
node.connectedEdges().addClass("lit");
|
|
148
|
+
if (!node.visible()) return;
|
|
149
|
+
const bb = cy.extent();
|
|
150
|
+
const p = node.position();
|
|
151
|
+
if (p.x < bb.x1 || p.x > bb.x2 || p.y < bb.y1 || p.y > bb.y2) cy.animate({ center: { eles: node } }, { duration: 300 });
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
element,
|
|
156
|
+
select(room) {
|
|
157
|
+
select(room);
|
|
158
|
+
layout();
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Each area (a hundreds series of room numbers) gets its own force layout, over its rooms
|
|
165
|
+
* and the links inside it, in a box sized for its room count; boxes are packed into rows.
|
|
166
|
+
* One global force layout of compound nodes blows apart, and cross-area links (and combat)
|
|
167
|
+
* would pull everything into a knot anyway.
|
|
168
|
+
*/
|
|
169
|
+
function layoutAreas(cy: cytoscape.Core) {
|
|
170
|
+
const GAP = 110;
|
|
171
|
+
const MAX_ROW = 2800;
|
|
172
|
+
let x = 0, y = 0, rowHeight = 0;
|
|
173
|
+
for (const area of cy.nodes(".area").sort((a, b) => Number(a.id().slice(4)) - Number(b.id().slice(4)))) {
|
|
174
|
+
const rooms = cy.nodes(`[area = ${area.id().slice(4)}]`);
|
|
175
|
+
const inner = rooms.connectedEdges().filter((e) => rooms.contains(e.source()) && rooms.contains(e.target()) && !e.hasClass("hub-link"));
|
|
176
|
+
rooms.union(inner).layout({
|
|
177
|
+
name: "cose", animate: false, randomize: true, fit: false, nodeDimensionsIncludeLabels: true,
|
|
178
|
+
idealEdgeLength: () => 60, nodeRepulsion: () => 6000, gravity: 1.2, numIter: 1500, componentSpacing: 40,
|
|
179
|
+
} as cytoscape.LayoutOptions).run();
|
|
180
|
+
// Move the area, at its natural size, to the next slot in the current row.
|
|
181
|
+
const bb = rooms.boundingBox({ includeLabels: true });
|
|
182
|
+
if (x > 0 && x + bb.w > MAX_ROW) {
|
|
183
|
+
x = 0;
|
|
184
|
+
y += rowHeight + GAP;
|
|
185
|
+
rowHeight = 0;
|
|
186
|
+
}
|
|
187
|
+
const dx = x - bb.x1, dy = y - bb.y1;
|
|
188
|
+
rooms.positions((n) => ({ x: n.position().x + dx, y: n.position().y + dy }));
|
|
189
|
+
area.data({ w: bb.w + 30, h: bb.h + 30 }).position({ x: x + bb.w / 2, y: y + bb.h / 2 });
|
|
190
|
+
x += bb.w + GAP;
|
|
191
|
+
rowHeight = Math.max(rowHeight, bb.h);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ResourceManager, type FileSource } from "../../../packages/sci/src/index.ts";
|
|
2
|
+
/** Where the page is served from: / in development, wherever a built site is put. */
|
|
3
|
+
export declare const BASE: string;
|
|
4
|
+
/**
|
|
5
|
+
* FileSource over game/: the dev server's mount (see vite.config.ts), or the game copied
|
|
6
|
+
* next to a built site, with its file names in game/files.json (`pnpm game site`).
|
|
7
|
+
*/
|
|
8
|
+
export declare const httpFiles: FileSource;
|
|
9
|
+
/**
|
|
10
|
+
* The mods to load: `?mods=a,b` in the URL, else the last choice (per browser). `?mods=`
|
|
11
|
+
* (empty) turns them off.
|
|
12
|
+
*/
|
|
13
|
+
export declare function activeMods(): string[];
|
|
14
|
+
export declare function rememberMods(mods: string[]): void;
|
|
15
|
+
/** Mods built into out/mods (see `pnpm mod build`); a built site has none. */
|
|
16
|
+
export declare function availableMods(): Promise<string[]>;
|
|
17
|
+
/** The game's resources with the active mods layered on top, as patch directories. */
|
|
18
|
+
export declare function openGame(): Promise<{
|
|
19
|
+
rm: ResourceManager;
|
|
20
|
+
mods: string[];
|
|
21
|
+
}>;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { APP_ID } from "./app.js";
|
|
2
|
+
import { ResourceManager } from "../../../packages/sci/src/index.js";
|
|
3
|
+
/** Where the page is served from: / in development, wherever a built site is put. */
|
|
4
|
+
export const BASE = import.meta.env.BASE_URL;
|
|
5
|
+
/**
|
|
6
|
+
* FileSource over game/: the dev server's mount (see vite.config.ts), or the game copied
|
|
7
|
+
* next to a built site, with its file names in game/files.json (`pnpm game site`).
|
|
8
|
+
*/
|
|
9
|
+
export const httpFiles = {
|
|
10
|
+
async read(path) {
|
|
11
|
+
const res = await fetch(`${BASE}game/${path}`);
|
|
12
|
+
return res.ok ? new Uint8Array(await res.arrayBuffer()) : undefined;
|
|
13
|
+
},
|
|
14
|
+
async readRange(path, offset, length) {
|
|
15
|
+
const res = await fetch(`${BASE}game/${path}`, { headers: { Range: `bytes=${offset}-${offset + length - 1}` } });
|
|
16
|
+
return new Uint8Array(await res.arrayBuffer());
|
|
17
|
+
},
|
|
18
|
+
async list(dir) {
|
|
19
|
+
if (import.meta.env.DEV)
|
|
20
|
+
return (await fetch(`/__list?dir=${encodeURIComponent(dir)}`)).json();
|
|
21
|
+
staticListing ??= fetch(`${BASE}game/files.json`).then((r) => (r.ok ? r.json() : {}));
|
|
22
|
+
return (await staticListing)[dir] ?? [];
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
let staticListing;
|
|
26
|
+
// --- Mods -------------------------------------------------------------------------------
|
|
27
|
+
const MODS_KEY = `${APP_ID}.mods`;
|
|
28
|
+
/**
|
|
29
|
+
* The mods to load: `?mods=a,b` in the URL, else the last choice (per browser). `?mods=`
|
|
30
|
+
* (empty) turns them off.
|
|
31
|
+
*/
|
|
32
|
+
export function activeMods() {
|
|
33
|
+
const param = new URLSearchParams(location.search).get("mods");
|
|
34
|
+
let list = param;
|
|
35
|
+
if (list === null) {
|
|
36
|
+
try {
|
|
37
|
+
list = localStorage.getItem(MODS_KEY);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
list = null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return (list ?? "").split(",").map((m) => m.trim()).filter((m) => /^[\w.-]+$/.test(m));
|
|
44
|
+
}
|
|
45
|
+
export function rememberMods(mods) {
|
|
46
|
+
try {
|
|
47
|
+
localStorage.setItem(MODS_KEY, mods.join(","));
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
/* private mode: only the URL carries it */
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/** Mods built into out/mods (see `pnpm mod build`); a built site has none. */
|
|
54
|
+
export async function availableMods() {
|
|
55
|
+
if (!import.meta.env.DEV)
|
|
56
|
+
return [];
|
|
57
|
+
try {
|
|
58
|
+
return await (await fetch("/__mods")).json();
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/** The game's resources with the active mods layered on top, as patch directories. */
|
|
65
|
+
export async function openGame() {
|
|
66
|
+
const mods = activeMods();
|
|
67
|
+
const rm = await ResourceManager.open(httpFiles, ["", "PATCHES", ...mods.map((m) => `MODS/${m}`)]);
|
|
68
|
+
return { rm, mods };
|
|
69
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { APP_ID } from "./app.ts";
|
|
2
|
+
import { ResourceManager, type FileSource } from "../../../packages/sci/src/index.ts";
|
|
3
|
+
|
|
4
|
+
/** Where the page is served from: / in development, wherever a built site is put. */
|
|
5
|
+
export const BASE = import.meta.env.BASE_URL;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* FileSource over game/: the dev server's mount (see vite.config.ts), or the game copied
|
|
9
|
+
* next to a built site, with its file names in game/files.json (`pnpm game site`).
|
|
10
|
+
*/
|
|
11
|
+
export const httpFiles: FileSource = {
|
|
12
|
+
async read(path) {
|
|
13
|
+
const res = await fetch(`${BASE}game/${path}`);
|
|
14
|
+
return res.ok ? new Uint8Array(await res.arrayBuffer()) : undefined;
|
|
15
|
+
},
|
|
16
|
+
async readRange(path, offset, length) {
|
|
17
|
+
const res = await fetch(`${BASE}game/${path}`, { headers: { Range: `bytes=${offset}-${offset + length - 1}` } });
|
|
18
|
+
return new Uint8Array(await res.arrayBuffer());
|
|
19
|
+
},
|
|
20
|
+
async list(dir) {
|
|
21
|
+
if (import.meta.env.DEV) return (await fetch(`/__list?dir=${encodeURIComponent(dir)}`)).json();
|
|
22
|
+
staticListing ??= fetch(`${BASE}game/files.json`).then((r) => (r.ok ? r.json() : {}));
|
|
23
|
+
return (await staticListing)[dir] ?? [];
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
let staticListing: Promise<Record<string, string[]>> | undefined;
|
|
27
|
+
|
|
28
|
+
// --- Mods -------------------------------------------------------------------------------
|
|
29
|
+
|
|
30
|
+
const MODS_KEY = `${APP_ID}.mods`;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The mods to load: `?mods=a,b` in the URL, else the last choice (per browser). `?mods=`
|
|
34
|
+
* (empty) turns them off.
|
|
35
|
+
*/
|
|
36
|
+
export function activeMods(): string[] {
|
|
37
|
+
const param = new URLSearchParams(location.search).get("mods");
|
|
38
|
+
let list = param;
|
|
39
|
+
if (list === null) {
|
|
40
|
+
try {
|
|
41
|
+
list = localStorage.getItem(MODS_KEY);
|
|
42
|
+
} catch {
|
|
43
|
+
list = null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return (list ?? "").split(",").map((m) => m.trim()).filter((m) => /^[\w.-]+$/.test(m));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function rememberMods(mods: string[]): void {
|
|
50
|
+
try {
|
|
51
|
+
localStorage.setItem(MODS_KEY, mods.join(","));
|
|
52
|
+
} catch {
|
|
53
|
+
/* private mode: only the URL carries it */
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Mods built into out/mods (see `pnpm mod build`); a built site has none. */
|
|
58
|
+
export async function availableMods(): Promise<string[]> {
|
|
59
|
+
if (!import.meta.env.DEV) return [];
|
|
60
|
+
try {
|
|
61
|
+
return await (await fetch("/__mods")).json();
|
|
62
|
+
} catch {
|
|
63
|
+
return [];
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** The game's resources with the active mods layered on top, as patch directories. */
|
|
68
|
+
export async function openGame(): Promise<{ rm: ResourceManager; mods: string[] }> {
|
|
69
|
+
const mods = activeMods();
|
|
70
|
+
const rm = await ResourceManager.open(httpFiles, ["", "PATCHES", ...mods.map((m) => `MODS/${m}`)]);
|
|
71
|
+
return { rm, mods };
|
|
72
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Vm } from "../../../packages/sci/src/index.ts";
|
|
2
|
+
/** Input to play by frame number, while the game runs as fast as it can, until frame `until`. */
|
|
3
|
+
export interface FastForward {
|
|
4
|
+
script: Record<number, () => void>;
|
|
5
|
+
until: number;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* What the viewer knows about one game beyond its resources: so far, how to get from the
|
|
9
|
+
* title screen to a character who can walk into rooms, quickly (the player's room links and
|
|
10
|
+
* the editor use it). Profiles are the modules in ./games/; a game without one starts at
|
|
11
|
+
* its title and the player gets there by hand.
|
|
12
|
+
*/
|
|
13
|
+
export interface GameProfile {
|
|
14
|
+
name: string;
|
|
15
|
+
/** Whether the loaded game is this one. */
|
|
16
|
+
matches(vm: Vm): boolean;
|
|
17
|
+
quickStart(vm: Vm): FastForward;
|
|
18
|
+
}
|
|
19
|
+
export declare function gameProfile(vm: Vm): GameProfile | undefined;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Vm } from "../../../packages/sci/src/index.ts";
|
|
2
|
+
|
|
3
|
+
/** Input to play by frame number, while the game runs as fast as it can, until frame `until`. */
|
|
4
|
+
export interface FastForward {
|
|
5
|
+
script: Record<number, () => void>;
|
|
6
|
+
until: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* What the viewer knows about one game beyond its resources: so far, how to get from the
|
|
11
|
+
* title screen to a character who can walk into rooms, quickly (the player's room links and
|
|
12
|
+
* the editor use it). Profiles are the modules in ./games/; a game without one starts at
|
|
13
|
+
* its title and the player gets there by hand.
|
|
14
|
+
*/
|
|
15
|
+
export interface GameProfile {
|
|
16
|
+
name: string;
|
|
17
|
+
/** Whether the loaded game is this one. */
|
|
18
|
+
matches(vm: Vm): boolean;
|
|
19
|
+
quickStart(vm: Vm): FastForward;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const profiles = Object.values(import.meta.glob<GameProfile>("./games/*.ts", { eager: true, import: "profile" }));
|
|
23
|
+
|
|
24
|
+
export function gameProfile(vm: Vm): GameProfile | undefined {
|
|
25
|
+
return profiles.find((p) => p.matches(vm));
|
|
26
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|