sci2-ts 0.0.0-stage → 0.2.2
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 +78 -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,473 @@
|
|
|
1
|
+
import { ResourceType, ScriptWorld, disassemble, formatDisassembly, celToRgba, emptyPalette, hasRef, mergePalette, parseFont, parseMessages, renderText, wrapText, parseHunkPalette, parsePic, parseView, resourceTypeName, } from "../../../packages/sci/src/index.js";
|
|
2
|
+
import { assetKey, buildAssetIndex, matches } from "./asset-index.js";
|
|
3
|
+
import { openGame } from "./files.js";
|
|
4
|
+
import { renderSound, soundSummary, stopSoundPreview, toggleSoundPreview } from "./sound-preview.js";
|
|
5
|
+
const hexDump = (data, max = 2048) => {
|
|
6
|
+
const lines = [];
|
|
7
|
+
for (let i = 0; i < Math.min(data.length, max); i += 16) {
|
|
8
|
+
const row = data.subarray(i, i + 16);
|
|
9
|
+
const hex = [...row].map((b) => b.toString(16).padStart(2, "0")).join(" ");
|
|
10
|
+
const ascii = [...row].map((b) => (b >= 32 && b < 127 ? String.fromCharCode(b) : ".")).join("");
|
|
11
|
+
lines.push(`${i.toString(16).padStart(8, "0")} ${hex.padEnd(47)} ${ascii}`);
|
|
12
|
+
}
|
|
13
|
+
if (data.length > max)
|
|
14
|
+
lines.push(`… ${data.length - max} more bytes`);
|
|
15
|
+
return lines.join("\n");
|
|
16
|
+
};
|
|
17
|
+
const $ = (id) => document.getElementById(id);
|
|
18
|
+
const el = (tag, props = {}) => Object.assign(document.createElement(tag), props);
|
|
19
|
+
// Mods show up as patch resources (?mods=, or the player's last choice).
|
|
20
|
+
const { rm } = await openGame();
|
|
21
|
+
const all = rm.list();
|
|
22
|
+
const basePalette = parseHunkPalette((await rm.load({ type: ResourceType.Palette, number: 999 })).data) ?? emptyPalette();
|
|
23
|
+
/** Palette a room pic installs, used to colour views the way they'd appear in that room. */
|
|
24
|
+
const roomPalette = async (picNo) => picNo === undefined ? basePalette : mergePalette(basePalette, parsePic((await rm.load({ type: ResourceType.Pic, number: picNo })).data).palette);
|
|
25
|
+
// SCI's 320x200 was shown on 4:3 monitors, so pixels were 1.2x taller than wide.
|
|
26
|
+
let aspect = true;
|
|
27
|
+
const SCALE = 3;
|
|
28
|
+
// Remap colours (shadows): approximate as translucent black until scripts drive them.
|
|
29
|
+
const REMAP = new Map([[253, 64], [254, 110]]);
|
|
30
|
+
const celCanvas = (cel, palette, mirror = false) => {
|
|
31
|
+
const canvas = el("canvas", { width: Math.max(cel.width, 1), height: Math.max(cel.height, 1) });
|
|
32
|
+
if (cel.width && cel.height) {
|
|
33
|
+
canvas.getContext("2d").putImageData(new ImageData(celToRgba(cel, palette, { mirror, remap: REMAP }), cel.width, cel.height), 0, 0);
|
|
34
|
+
}
|
|
35
|
+
return sized(canvas);
|
|
36
|
+
};
|
|
37
|
+
const sized = (canvas, scale = SCALE) => {
|
|
38
|
+
canvas.style.width = `${canvas.width * scale}px`;
|
|
39
|
+
canvas.style.height = `${canvas.height * scale * (aspect ? 1.2 : 1)}px`;
|
|
40
|
+
return canvas;
|
|
41
|
+
};
|
|
42
|
+
// --- Renderers ---------------------------------------------------------------
|
|
43
|
+
const renderPalette = (pal) => {
|
|
44
|
+
const grid = el("div", { className: "swatches" });
|
|
45
|
+
for (let i = 0; i < 256; i++) {
|
|
46
|
+
const [r, g, b] = pal.rgb.subarray(i * 3, i * 3 + 3);
|
|
47
|
+
grid.append(el("div", {
|
|
48
|
+
className: pal.used[i] ? "sw" : "sw unused",
|
|
49
|
+
title: `${i}: rgb(${r}, ${g}, ${b})${pal.used[i] ? "" : " (not set here)"}`,
|
|
50
|
+
style: `background: rgb(${r} ${g} ${b})`,
|
|
51
|
+
}));
|
|
52
|
+
}
|
|
53
|
+
return grid;
|
|
54
|
+
};
|
|
55
|
+
const renderPic = (data) => {
|
|
56
|
+
const pic = parsePic(data);
|
|
57
|
+
const canvas = composePic(data);
|
|
58
|
+
const wrap = el("div");
|
|
59
|
+
wrap.append(sized(canvas), el("p", {
|
|
60
|
+
className: "meta",
|
|
61
|
+
textContent: `${pic.width}×${pic.height} · ${pic.cels.length} layer(s): ${pic.cels.map((c) => `${c.width}×${c.height} @${c.x},${c.y} pri ${c.priority}`).join("; ")}`,
|
|
62
|
+
}));
|
|
63
|
+
return wrap;
|
|
64
|
+
};
|
|
65
|
+
let animTimer;
|
|
66
|
+
const renderView = async (data) => {
|
|
67
|
+
const view = parseView(data);
|
|
68
|
+
const wrap = el("div");
|
|
69
|
+
const picSelect = el("select");
|
|
70
|
+
picSelect.append(el("option", { value: "", textContent: "base palette only" }));
|
|
71
|
+
for (const p of rm.list(ResourceType.Pic))
|
|
72
|
+
picSelect.append(el("option", { value: String(p.number), textContent: `room palette: pic ${p.number}` }));
|
|
73
|
+
const body = el("div");
|
|
74
|
+
const draw = async () => {
|
|
75
|
+
clearInterval(animTimer);
|
|
76
|
+
const room = await roomPalette(picSelect.value ? Number(picSelect.value) : undefined);
|
|
77
|
+
const palette = mergePalette(room, view.palette);
|
|
78
|
+
body.replaceChildren();
|
|
79
|
+
view.loops.forEach((loop, i) => {
|
|
80
|
+
const row = el("div", { className: "loop" });
|
|
81
|
+
row.append(el("span", { className: "meta", textContent: `loop ${i}${loop.mirror ? " (mirror)" : ""}` }));
|
|
82
|
+
for (const cel of loop.cels)
|
|
83
|
+
row.append(celCanvas(cel, palette, loop.mirror));
|
|
84
|
+
body.append(row);
|
|
85
|
+
});
|
|
86
|
+
// Animate loop 0 like the game would, anchored at the cel's origin point.
|
|
87
|
+
const loop = view.loops[0];
|
|
88
|
+
if (loop && loop.cels.length > 1) {
|
|
89
|
+
const stage = el("div", { className: "stage" });
|
|
90
|
+
body.prepend(stage);
|
|
91
|
+
let frame = 0;
|
|
92
|
+
const tick = () => stage.replaceChildren(celCanvas(loop.cels[frame++ % loop.cels.length], palette, loop.mirror));
|
|
93
|
+
tick();
|
|
94
|
+
animTimer = window.setInterval(tick, 120);
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
picSelect.onchange = draw;
|
|
98
|
+
wrap.append(el("p", { className: "meta", textContent: `${view.loops.length} loops · ${view.palette ? "embedded palette" : "no embedded palette"}` }), picSelect, body);
|
|
99
|
+
await draw();
|
|
100
|
+
return wrap;
|
|
101
|
+
};
|
|
102
|
+
/** Draws a 1-bit text mask onto a canvas in the given colour. */
|
|
103
|
+
const textCanvas = (font, lines, color, align = "left") => {
|
|
104
|
+
const bmp = renderText(font, lines, align);
|
|
105
|
+
const canvas = el("canvas", { width: bmp.width, height: bmp.height });
|
|
106
|
+
const rgba = new Uint8ClampedArray(bmp.width * bmp.height * 4);
|
|
107
|
+
bmp.pixels.forEach((ink, i) => {
|
|
108
|
+
if (ink)
|
|
109
|
+
rgba.set([...color, 255], i * 4);
|
|
110
|
+
});
|
|
111
|
+
canvas.getContext("2d").putImageData(new ImageData(rgba, bmp.width, bmp.height), 0, 0);
|
|
112
|
+
return sized(canvas);
|
|
113
|
+
};
|
|
114
|
+
const loadFont = async (n) => parseFont((await rm.load({ type: ResourceType.Font, number: n })).data);
|
|
115
|
+
const renderFont = (data) => {
|
|
116
|
+
const font = parseFont(data);
|
|
117
|
+
const wrap = el("div");
|
|
118
|
+
const chars = font.glyphs.map((_, i) => (i >= 32 ? String.fromCharCode(i) : "")).join("");
|
|
119
|
+
const rows = chars.match(/.{1,32}/g) ?? [];
|
|
120
|
+
wrap.append(el("p", { className: "meta", textContent: `${font.glyphs.length} glyphs · line height ${font.height}px` }), textCanvas(font, rows, [232, 176, 74]), el("p", { className: "meta", textContent: "Sample" }), textCanvas(font, wrapText(font, "The quick brown fox jumps over the lazy dog. 0123456789, punctuation: ?!;:", 200), [221, 221, 221]));
|
|
121
|
+
return wrap;
|
|
122
|
+
};
|
|
123
|
+
// Talker ids come from scripts; these two are clear from usage counts across all messages.
|
|
124
|
+
const TALKERS = { 97: "Hero", 99: "Narrator" };
|
|
125
|
+
const renderMessages = async (data, room) => {
|
|
126
|
+
const file = parseMessages(data);
|
|
127
|
+
const fontSelect = el("select");
|
|
128
|
+
for (const f of rm.list(ResourceType.Font))
|
|
129
|
+
fontSelect.append(el("option", { value: String(f.number), textContent: `font ${f.number}` }));
|
|
130
|
+
const box = el("div", { className: "dialog" });
|
|
131
|
+
const table = el("table", { className: "messages" });
|
|
132
|
+
table.innerHTML = "<thead><tr><th>noun</th><th>verb</th><th>cond</th><th>seq</th><th>talker</th><th>text</th></tr></thead>";
|
|
133
|
+
const tbody = el("tbody");
|
|
134
|
+
table.append(tbody);
|
|
135
|
+
let selected = file.records[0];
|
|
136
|
+
const preview = async () => {
|
|
137
|
+
if (!selected)
|
|
138
|
+
return;
|
|
139
|
+
const font = await loadFont(Number(fontSelect.value));
|
|
140
|
+
const target = hasRef(selected) ? file.records.find((r) => r.noun === selected.ref.noun && r.verb === selected.ref.verb && r.cond === selected.ref.cond && r.seq === selected.ref.seq) : selected;
|
|
141
|
+
const text = target?.text ?? "(missing reference)";
|
|
142
|
+
box.replaceChildren(textCanvas(font, wrapText(font, text, 200), [255, 255, 255]));
|
|
143
|
+
};
|
|
144
|
+
for (const r of file.records) {
|
|
145
|
+
const tr = el("tr");
|
|
146
|
+
const text = hasRef(r) ? `→ ${r.ref.noun}/${r.ref.verb}/${r.ref.cond}/${r.ref.seq}` : r.text;
|
|
147
|
+
for (const cell of [r.noun, r.verb, r.cond, r.seq, TALKERS[r.talker] ?? r.talker, text])
|
|
148
|
+
tr.append(el("td", { textContent: String(cell) }));
|
|
149
|
+
tr.onclick = () => {
|
|
150
|
+
tbody.querySelector(".active")?.classList.remove("active");
|
|
151
|
+
tr.classList.add("active");
|
|
152
|
+
selected = r;
|
|
153
|
+
preview();
|
|
154
|
+
};
|
|
155
|
+
tbody.append(tr);
|
|
156
|
+
}
|
|
157
|
+
fontSelect.onchange = preview;
|
|
158
|
+
await preview();
|
|
159
|
+
const wrap = el("div");
|
|
160
|
+
wrap.append(el("p", { className: "meta", textContent: `room/module ${room} · format v${file.version} · ${file.records.length} messages · click a row to preview in a game font` }), fontSelect, box, table);
|
|
161
|
+
return wrap;
|
|
162
|
+
};
|
|
163
|
+
let worldPromise;
|
|
164
|
+
const getWorld = () => (worldPromise ??= ScriptWorld.open(rm).then(async (w) => {
|
|
165
|
+
await w.loadAllClasses();
|
|
166
|
+
return w;
|
|
167
|
+
}));
|
|
168
|
+
const escapeHtml = (s) => s.replace(/[&<>]/g, (c) => ({ "&": "&", "<": "<", ">": ">" })[c]);
|
|
169
|
+
const renderScript = async (number) => {
|
|
170
|
+
const world = await getWorld();
|
|
171
|
+
const script = await world.script(number);
|
|
172
|
+
const functions = disassemble(script, world.context());
|
|
173
|
+
const listing = el("pre", { className: "asm" });
|
|
174
|
+
listing.innerHTML = formatDisassembly(script, functions)
|
|
175
|
+
.split("\n")
|
|
176
|
+
.map((line) => {
|
|
177
|
+
const html = escapeHtml(line);
|
|
178
|
+
if (line.startsWith("("))
|
|
179
|
+
return `<span class="fn" id="fn-${line.slice(1, -1).replace(/\W/g, "_")}">${html}</span>`;
|
|
180
|
+
if (line.startsWith(";"))
|
|
181
|
+
return `<span class="c">${html}</span>`;
|
|
182
|
+
return html.replace(/(;.*)$/, '<span class="c">$1</span>');
|
|
183
|
+
})
|
|
184
|
+
.join("\n");
|
|
185
|
+
const jump = el("select");
|
|
186
|
+
jump.append(el("option", { textContent: `${functions.length} functions…` }));
|
|
187
|
+
for (const fn of functions)
|
|
188
|
+
jump.append(el("option", { value: fn.label.replace(/\W/g, "_"), textContent: fn.label }));
|
|
189
|
+
jump.onchange = () => document.getElementById(`fn-${jump.value}`)?.scrollIntoView({ block: "start" });
|
|
190
|
+
const wrap = el("div");
|
|
191
|
+
wrap.append(el("p", {
|
|
192
|
+
className: "meta",
|
|
193
|
+
textContent: `${script.objects.length} objects · ${script.locals.length} locals · ${functions.reduce((n, f) => n + f.instructions.length, 0)} instructions`,
|
|
194
|
+
}), jump, listing);
|
|
195
|
+
return wrap;
|
|
196
|
+
};
|
|
197
|
+
// --- Thumbnails ----------------------------------------------------------------
|
|
198
|
+
/** A picture's layers composed at full size (the list scales it down with CSS). */
|
|
199
|
+
const composePic = (data) => {
|
|
200
|
+
const pic = parsePic(data);
|
|
201
|
+
const palette = mergePalette(basePalette, pic.palette);
|
|
202
|
+
const canvas = el("canvas", { width: pic.width, height: pic.height });
|
|
203
|
+
const ctx = canvas.getContext("2d");
|
|
204
|
+
for (const cel of [...pic.cels].sort((a, b) => a.priority - b.priority)) {
|
|
205
|
+
if (!cel.width || !cel.height)
|
|
206
|
+
continue;
|
|
207
|
+
const layer = el("canvas", { width: cel.width, height: cel.height });
|
|
208
|
+
layer.getContext("2d").putImageData(new ImageData(celToRgba(cel, palette, { remap: REMAP }), cel.width, cel.height), 0, 0);
|
|
209
|
+
ctx.drawImage(layer, cel.x, cel.y);
|
|
210
|
+
}
|
|
211
|
+
return canvas;
|
|
212
|
+
};
|
|
213
|
+
/** What a list row shows next to the number: a picture, a sprite, a font sample, a line. */
|
|
214
|
+
const thumbnail = async (info) => {
|
|
215
|
+
const { data } = await rm.load(info);
|
|
216
|
+
switch (info.type) {
|
|
217
|
+
case ResourceType.Pic:
|
|
218
|
+
return composePic(data);
|
|
219
|
+
case ResourceType.View: {
|
|
220
|
+
const view = parseView(data);
|
|
221
|
+
const cel = view.loops.flatMap((l) => l.cels).find((c) => c.width && c.height);
|
|
222
|
+
if (!cel)
|
|
223
|
+
return undefined;
|
|
224
|
+
const palette = mergePalette(basePalette, view.palette);
|
|
225
|
+
const canvas = el("canvas", { width: cel.width, height: cel.height });
|
|
226
|
+
canvas.getContext("2d").putImageData(new ImageData(celToRgba(cel, palette, { remap: REMAP }), cel.width, cel.height), 0, 0);
|
|
227
|
+
return canvas;
|
|
228
|
+
}
|
|
229
|
+
case ResourceType.Font: {
|
|
230
|
+
const font = parseFont(data);
|
|
231
|
+
const bmp = renderText(font, ["Aa Bb 123"], "left");
|
|
232
|
+
const canvas = el("canvas", { width: Math.max(bmp.width, 1), height: Math.max(bmp.height, 1) });
|
|
233
|
+
const rgba = new Uint8ClampedArray(canvas.width * canvas.height * 4);
|
|
234
|
+
bmp.pixels.forEach((ink, i) => ink && rgba.set([221, 221, 221, 255], i * 4));
|
|
235
|
+
canvas.getContext("2d").putImageData(new ImageData(rgba, canvas.width, canvas.height), 0, 0);
|
|
236
|
+
return canvas;
|
|
237
|
+
}
|
|
238
|
+
case ResourceType.Message: {
|
|
239
|
+
const first = parseMessages(data).records.find((r) => r.text);
|
|
240
|
+
return first ? el("span", { className: "snippet", textContent: first.text }) : undefined;
|
|
241
|
+
}
|
|
242
|
+
case ResourceType.Sound:
|
|
243
|
+
return el("span", { className: "snippet", textContent: soundSummary(data) });
|
|
244
|
+
default:
|
|
245
|
+
return undefined;
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
/** Rows render their thumbnail when they scroll into view. */
|
|
249
|
+
const thumbs = new IntersectionObserver((entries) => {
|
|
250
|
+
for (const entry of entries) {
|
|
251
|
+
if (!entry.isIntersecting)
|
|
252
|
+
continue;
|
|
253
|
+
const li = entry.target;
|
|
254
|
+
thumbs.unobserve(li);
|
|
255
|
+
const slot = li.querySelector(".thumb");
|
|
256
|
+
if (!slot || !li.info)
|
|
257
|
+
continue;
|
|
258
|
+
thumbnail(li.info)
|
|
259
|
+
.then((t) => t && slot.replaceChildren(t))
|
|
260
|
+
.catch(() => slot.replaceChildren());
|
|
261
|
+
}
|
|
262
|
+
}, { root: $("list"), rootMargin: "200px 0px" });
|
|
263
|
+
// --- Shell -------------------------------------------------------------------
|
|
264
|
+
const types = [...new Set(all.map((r) => r.type))];
|
|
265
|
+
const typeSelect = $("type");
|
|
266
|
+
typeSelect.innerHTML = types
|
|
267
|
+
.map((t) => `<option value="${t}">${resourceTypeName[t]} (${all.filter((r) => r.type === t).length})</option>`)
|
|
268
|
+
.join("");
|
|
269
|
+
let current;
|
|
270
|
+
/** Only the latest selection may fill the view (arrowing quickly starts many loads). */
|
|
271
|
+
let showing = 0;
|
|
272
|
+
const show = async (info, li) => {
|
|
273
|
+
const token = ++showing;
|
|
274
|
+
current = { info, li };
|
|
275
|
+
clearInterval(animTimer);
|
|
276
|
+
stopSoundPreview();
|
|
277
|
+
document.querySelector("li.active")?.classList.remove("active");
|
|
278
|
+
li.classList.add("active");
|
|
279
|
+
li.scrollIntoView({ block: "nearest" });
|
|
280
|
+
history.replaceState(null, "", `#${resourceTypeName[info.type]}/${info.number}`);
|
|
281
|
+
const res = await rm.load(info);
|
|
282
|
+
if (token !== showing)
|
|
283
|
+
return;
|
|
284
|
+
$("title").textContent = `${resourceTypeName[info.type]} ${info.number} · ${res.data.length} bytes · ${info.source}`;
|
|
285
|
+
showUses(info);
|
|
286
|
+
const view = $("view");
|
|
287
|
+
let content;
|
|
288
|
+
try {
|
|
289
|
+
switch (info.type) {
|
|
290
|
+
case ResourceType.Pic:
|
|
291
|
+
content = renderPic(res.data);
|
|
292
|
+
break;
|
|
293
|
+
case ResourceType.View:
|
|
294
|
+
content = await renderView(res.data);
|
|
295
|
+
break;
|
|
296
|
+
case ResourceType.Script:
|
|
297
|
+
case ResourceType.Heap:
|
|
298
|
+
content = await renderScript(info.number);
|
|
299
|
+
break;
|
|
300
|
+
case ResourceType.Font:
|
|
301
|
+
content = renderFont(res.data);
|
|
302
|
+
break;
|
|
303
|
+
case ResourceType.Message:
|
|
304
|
+
content = await renderMessages(res.data, info.number);
|
|
305
|
+
break;
|
|
306
|
+
case ResourceType.Palette:
|
|
307
|
+
content = renderPalette(parseHunkPalette(res.data) ?? emptyPalette());
|
|
308
|
+
break;
|
|
309
|
+
case ResourceType.Sound:
|
|
310
|
+
content = await renderSound(rm, info.number, res.data, () => token === showing);
|
|
311
|
+
break;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
catch (e) {
|
|
315
|
+
content = el("p", { className: "error", textContent: String(e) });
|
|
316
|
+
}
|
|
317
|
+
if (token !== showing) {
|
|
318
|
+
// A view's animation started for a resource that's no longer selected.
|
|
319
|
+
if (info.type === ResourceType.View)
|
|
320
|
+
clearInterval(animTimer);
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
view.replaceChildren(...(content ? [content] : []));
|
|
324
|
+
$("hex").textContent = hexDump(res.data);
|
|
325
|
+
};
|
|
326
|
+
/** The objects and rooms that use the resource, and what the game says about them. */
|
|
327
|
+
const showUses = (info) => {
|
|
328
|
+
const meta = assetIndex?.get(assetKey(info.type, info.number));
|
|
329
|
+
$("uses").replaceChildren(...(meta?.uses ?? []).slice(0, 12).map((u) => {
|
|
330
|
+
const p = el("p", { className: "use" });
|
|
331
|
+
p.append(el("b", { textContent: u.label }));
|
|
332
|
+
if (u.text)
|
|
333
|
+
p.append(" · ", el("q", { textContent: u.text }));
|
|
334
|
+
return p;
|
|
335
|
+
}));
|
|
336
|
+
};
|
|
337
|
+
const THUMBNAILS = new Set([ResourceType.Pic, ResourceType.View, ResourceType.Font, ResourceType.Message, ResourceType.Sound]);
|
|
338
|
+
const rows = () => [...$("list").querySelectorAll("li")].filter((li) => "info" in li);
|
|
339
|
+
const search = $("search");
|
|
340
|
+
let assetIndex;
|
|
341
|
+
const renderList = () => {
|
|
342
|
+
const type = Number(typeSelect.value);
|
|
343
|
+
const withThumbs = THUMBNAILS.has(type);
|
|
344
|
+
$("list").className = withThumbs ? `thumbs type-${resourceTypeName[type]}` : "";
|
|
345
|
+
thumbs.disconnect();
|
|
346
|
+
const items = all
|
|
347
|
+
.filter((r) => r.type === type)
|
|
348
|
+
.filter((r) => matches(search.value, r.number, assetIndex?.get(assetKey(r.type, r.number))))
|
|
349
|
+
.map((r) => {
|
|
350
|
+
const li = Object.assign(el("li"), { info: r });
|
|
351
|
+
if (withThumbs)
|
|
352
|
+
li.append(el("span", { className: "thumb" }));
|
|
353
|
+
const label = el("span", { className: "num", innerHTML: `<span>${r.number}${r.source === "patch" ? " <small>patch</small>" : ""}</span>` });
|
|
354
|
+
const tags = assetIndex?.get(assetKey(r.type, r.number))?.tags;
|
|
355
|
+
if (tags?.length)
|
|
356
|
+
label.append(el("span", { className: "tags", textContent: tags.slice(0, 3).join(" · "), title: tags.join(" · ") }));
|
|
357
|
+
li.append(label);
|
|
358
|
+
li.onclick = () => show(r, li);
|
|
359
|
+
if (withThumbs)
|
|
360
|
+
thumbs.observe(li);
|
|
361
|
+
// Keep the selection across filtering.
|
|
362
|
+
if (current && current.info === r)
|
|
363
|
+
(li.classList.add("active"), (current.li = li));
|
|
364
|
+
return li;
|
|
365
|
+
});
|
|
366
|
+
$("list").replaceChildren(...(items.length ? items : [el("li", { className: "empty", textContent: search.value ? "Nothing matches" : "None" })]));
|
|
367
|
+
};
|
|
368
|
+
/** Selects the row `delta` away from the current one (or the first/last). */
|
|
369
|
+
const step = (delta) => {
|
|
370
|
+
const list = rows();
|
|
371
|
+
if (!list.length)
|
|
372
|
+
return;
|
|
373
|
+
const at = current ? list.indexOf(current.li) : -1;
|
|
374
|
+
const next = delta === "first" ? 0 : delta === "last" ? list.length - 1 : Math.max(0, Math.min(list.length - 1, at < 0 ? 0 : at + delta));
|
|
375
|
+
const li = list[next];
|
|
376
|
+
void show(li.info, li);
|
|
377
|
+
};
|
|
378
|
+
const switchType = (delta) => {
|
|
379
|
+
const i = Math.max(0, Math.min(types.length - 1, typeSelect.selectedIndex + delta));
|
|
380
|
+
if (i === typeSelect.selectedIndex)
|
|
381
|
+
return;
|
|
382
|
+
typeSelect.selectedIndex = i;
|
|
383
|
+
renderList();
|
|
384
|
+
step("first");
|
|
385
|
+
};
|
|
386
|
+
// Keyboard: ↑/↓ (k/j) next and previous, PageUp/PageDown by ten, Home/End, ←/→ the type,
|
|
387
|
+
// "/" the search box (where ↑/↓ and Enter still move through the results).
|
|
388
|
+
window.addEventListener("keydown", (e) => {
|
|
389
|
+
const target = e.target instanceof HTMLElement ? e.target : document.body;
|
|
390
|
+
if (e.metaKey || e.ctrlKey || e.altKey)
|
|
391
|
+
return;
|
|
392
|
+
if (target === search) {
|
|
393
|
+
if (e.key === "ArrowDown" || e.key === "ArrowUp")
|
|
394
|
+
(e.preventDefault(), step(e.key === "ArrowDown" ? 1 : -1));
|
|
395
|
+
else if (e.key === "Enter")
|
|
396
|
+
(e.preventDefault(), current ? search.blur() : step("first"));
|
|
397
|
+
else if (e.key === "Escape")
|
|
398
|
+
(search.value = "", renderList(), search.blur());
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
if (e.key === "/" && !target.matches("input, textarea")) {
|
|
402
|
+
e.preventDefault();
|
|
403
|
+
search.focus();
|
|
404
|
+
search.select();
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
if (target.matches("input, textarea, select:not(#type)"))
|
|
408
|
+
return;
|
|
409
|
+
if (e.key === " " && !target.matches("button") && toggleSoundPreview())
|
|
410
|
+
return void e.preventDefault();
|
|
411
|
+
const keys = {
|
|
412
|
+
ArrowDown: () => step(1), j: () => step(1),
|
|
413
|
+
ArrowUp: () => step(-1), k: () => step(-1),
|
|
414
|
+
PageDown: () => step(10), PageUp: () => step(-10),
|
|
415
|
+
Home: () => step("first"), End: () => step("last"),
|
|
416
|
+
ArrowLeft: () => switchType(-1), ArrowRight: () => switchType(1),
|
|
417
|
+
};
|
|
418
|
+
const action = keys[e.key];
|
|
419
|
+
if (!action)
|
|
420
|
+
return;
|
|
421
|
+
e.preventDefault();
|
|
422
|
+
action();
|
|
423
|
+
});
|
|
424
|
+
search.addEventListener("input", () => {
|
|
425
|
+
renderList();
|
|
426
|
+
if (!current || !rows().includes(current.li))
|
|
427
|
+
step("first");
|
|
428
|
+
});
|
|
429
|
+
typeSelect.onchange = () => {
|
|
430
|
+
renderList();
|
|
431
|
+
step("first");
|
|
432
|
+
typeSelect.blur(); // so the arrow keys go back to the list
|
|
433
|
+
};
|
|
434
|
+
$("aspect").onchange = (e) => {
|
|
435
|
+
aspect = e.target.checked;
|
|
436
|
+
if (current)
|
|
437
|
+
show(current.info, current.li);
|
|
438
|
+
};
|
|
439
|
+
/** Goes where the URL says (#pic/130), else to the first picture; also on edits and Back. */
|
|
440
|
+
const goToHash = () => {
|
|
441
|
+
const [hashType, hashNumber] = decodeURIComponent(location.hash.slice(1)).split("/");
|
|
442
|
+
const type = types.find((t) => resourceTypeName[t] === hashType) ?? ResourceType.Pic;
|
|
443
|
+
if (Number(typeSelect.value) !== type || !rows().length) {
|
|
444
|
+
typeSelect.value = String(type);
|
|
445
|
+
renderList();
|
|
446
|
+
}
|
|
447
|
+
const find = () => rows().find((li) => li.info.number === Number(hashNumber));
|
|
448
|
+
let row = find();
|
|
449
|
+
if (!row && search.value && hashNumber) {
|
|
450
|
+
// Asked for something the search hides: show everything again.
|
|
451
|
+
search.value = "";
|
|
452
|
+
renderList();
|
|
453
|
+
row = find();
|
|
454
|
+
}
|
|
455
|
+
if (row)
|
|
456
|
+
void show(row.info, row);
|
|
457
|
+
else
|
|
458
|
+
step("first");
|
|
459
|
+
};
|
|
460
|
+
window.addEventListener("hashchange", goToHash);
|
|
461
|
+
goToHash();
|
|
462
|
+
// Names and descriptions from the scripts (a couple of seconds): then search finds them.
|
|
463
|
+
const indexStatus = $("index-status");
|
|
464
|
+
getWorld()
|
|
465
|
+
.then((world) => buildAssetIndex(rm, world, (done, total) => (indexStatus.textContent = `Indexing the scripts… ${done}/${total}`)))
|
|
466
|
+
.then((index) => {
|
|
467
|
+
assetIndex = index;
|
|
468
|
+
indexStatus.textContent = `${index.size} assets named by the game's scripts`;
|
|
469
|
+
renderList();
|
|
470
|
+
if (current)
|
|
471
|
+
(showUses(current.info), current.li.scrollIntoView({ block: "nearest" }));
|
|
472
|
+
})
|
|
473
|
+
.catch((e) => (indexStatus.textContent = `No index: ${e.message}`));
|