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,62 @@
|
|
|
1
|
+
import { AdLibDriver, type AdLibBank, type MidiOutput } from "../../../packages/sci/src/index.ts";
|
|
2
|
+
import workletUrl from "./opl-worklet.ts?worker&url";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* AdLib music: Sierra's driver runs here and batches OPL register writes to the chip
|
|
6
|
+
* emulator in an AudioWorklet (flushed once per microtask, i.e. per burst of MIDI events).
|
|
7
|
+
*/
|
|
8
|
+
export class AdLibOutput implements MidiOutput {
|
|
9
|
+
private driver: AdLibDriver;
|
|
10
|
+
private queue: number[] = [];
|
|
11
|
+
private flushScheduled = false;
|
|
12
|
+
|
|
13
|
+
private constructor(
|
|
14
|
+
private readonly node: AudioWorkletNode,
|
|
15
|
+
private readonly bank: AdLibBank,
|
|
16
|
+
) {
|
|
17
|
+
this.driver = this.newDriver();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
static async create(ctx: AudioContext, bank: AdLibBank): Promise<AdLibOutput> {
|
|
21
|
+
await ctx.audioWorklet.addModule(workletUrl);
|
|
22
|
+
const node = new AudioWorkletNode(ctx, "opl2", { outputChannelCount: [2] });
|
|
23
|
+
node.connect(ctx.destination);
|
|
24
|
+
return new AdLibOutput(node, bank);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
private newDriver(): AdLibDriver {
|
|
28
|
+
const chip = {
|
|
29
|
+
write: (reg: number, value: number) => {
|
|
30
|
+
this.queue.push(reg, value);
|
|
31
|
+
if (!this.flushScheduled) {
|
|
32
|
+
this.flushScheduled = true;
|
|
33
|
+
queueMicrotask(() => {
|
|
34
|
+
this.flushScheduled = false;
|
|
35
|
+
this.node.port.postMessage(this.queue);
|
|
36
|
+
this.queue = [];
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
return new AdLibDriver(chip, this.bank);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Silences the chip and starts from a clean driver (used when switching devices). */
|
|
45
|
+
reset(): void {
|
|
46
|
+
this.queue = [];
|
|
47
|
+
this.node.port.postMessage("reset");
|
|
48
|
+
this.driver = this.newDriver();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
send(bytes: number[]): void {
|
|
52
|
+
this.driver.send(bytes);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
claimVoices(channel: number, count: number): void {
|
|
56
|
+
this.driver.claimVoices(channel, count);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
setMasterVolume(volume: number): void {
|
|
60
|
+
this.driver.setMasterVolume(volume);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { ResourceType, type ResourceManager, type ScriptWorld } from "../../../packages/sci/src/index.ts";
|
|
2
|
+
/**
|
|
3
|
+
* What the game says about its assets. SCI resources carry no names or descriptions, so
|
|
4
|
+
* they come from the scripts: the room that draws a picture, the objects whose `view` is a
|
|
5
|
+
* view, and what the game says when you Look at them (the room's or the object's noun, verb
|
|
6
|
+
* 1, in the room's message file); sounds from Sound objects that declare their number.
|
|
7
|
+
* Only what's declared on objects is found, not what a script picks at run time.
|
|
8
|
+
*/
|
|
9
|
+
export interface AssetMeta {
|
|
10
|
+
/** Short labels: room and object names ("rm250", "igor", "room 250"). */
|
|
11
|
+
tags: string[];
|
|
12
|
+
/** Who uses it, for the preview: "igor in room 250: Igor is digging…". */
|
|
13
|
+
uses: {
|
|
14
|
+
label: string;
|
|
15
|
+
text?: string;
|
|
16
|
+
}[];
|
|
17
|
+
}
|
|
18
|
+
export type AssetIndex = Map<string, AssetMeta>;
|
|
19
|
+
export declare const assetKey: (type: ResourceType, number: number) => string;
|
|
20
|
+
export declare function buildAssetIndex(rm: ResourceManager, world: ScriptWorld, onProgress?: (done: number, total: number) => void): Promise<AssetIndex>;
|
|
21
|
+
/** Whether every word of `query` is in the asset's number, tags or descriptions. */
|
|
22
|
+
export declare function matches(query: string, number: number, meta: AssetMeta | undefined): boolean;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { ResourceType, analyzeGame, parseMessages } from "../../../packages/sci/src/index.js";
|
|
2
|
+
export const assetKey = (type, number) => `${type}:${number}`;
|
|
3
|
+
const LOOK = 1;
|
|
4
|
+
export async function buildAssetIndex(rm, world, onProgress) {
|
|
5
|
+
const index = await analyzeGame(rm, { world, onProgress });
|
|
6
|
+
const out = new Map();
|
|
7
|
+
const add = (type, number, tags, use) => {
|
|
8
|
+
const key = assetKey(type, number);
|
|
9
|
+
const meta = out.get(key) ?? { tags: [], uses: [] };
|
|
10
|
+
for (const t of tags)
|
|
11
|
+
if (t && !meta.tags.includes(t))
|
|
12
|
+
meta.tags.push(t);
|
|
13
|
+
if (use && !meta.uses.some((u) => u.label === use.label))
|
|
14
|
+
meta.uses.push(use);
|
|
15
|
+
out.set(key, meta);
|
|
16
|
+
};
|
|
17
|
+
// Message files, by module (a room's own file is its number).
|
|
18
|
+
const files = new Map();
|
|
19
|
+
const messages = async (n) => {
|
|
20
|
+
if (!files.has(n)) {
|
|
21
|
+
const id = { type: ResourceType.Message, number: n };
|
|
22
|
+
files.set(n, rm.has(id) ? parseMessages((await rm.load(id)).data) : undefined);
|
|
23
|
+
}
|
|
24
|
+
return files.get(n);
|
|
25
|
+
};
|
|
26
|
+
/** What Look says about a noun: the first line, preferring no condition. */
|
|
27
|
+
const look = async (module, noun) => {
|
|
28
|
+
if (!noun)
|
|
29
|
+
return undefined;
|
|
30
|
+
const records = (await messages(module))?.records.filter((r) => r.noun === noun && r.verb === LOOK && r.text) ?? [];
|
|
31
|
+
return (records.find((r) => r.cond === 0) ?? records[0])?.text;
|
|
32
|
+
};
|
|
33
|
+
const roomName = new Map(index.rooms.map((r) => [r.number, r.name]));
|
|
34
|
+
for (const room of index.rooms) {
|
|
35
|
+
const text = await look(room.number, room.noun);
|
|
36
|
+
if (room.picture >= 0)
|
|
37
|
+
add(ResourceType.Pic, room.picture, [room.name, `room ${room.number}`], { label: `${room.name} (room ${room.number})`, text });
|
|
38
|
+
add(ResourceType.Script, room.number, [room.name, "room"], { label: `room ${room.number}`, text });
|
|
39
|
+
add(ResourceType.Message, room.number, [room.name, `room ${room.number}`], { label: `${room.name}'s text`, text });
|
|
40
|
+
}
|
|
41
|
+
for (const script of index.scripts) {
|
|
42
|
+
const where = script.isRoom ? `room ${script.number}` : `script ${script.number}`;
|
|
43
|
+
const owner = roomName.get(script.number);
|
|
44
|
+
for (const obj of script.objects) {
|
|
45
|
+
const prop = (name) => obj.props.find((p) => p.name === name)?.value;
|
|
46
|
+
const view = prop("view");
|
|
47
|
+
const picture = prop("picture");
|
|
48
|
+
const noun = prop("noun") ?? 0;
|
|
49
|
+
const text = await look(script.number, noun);
|
|
50
|
+
if (view !== undefined && view > 0 && view < 0x8000 && rm.has({ type: ResourceType.View, number: view })) {
|
|
51
|
+
add(ResourceType.View, view, [obj.name, where, ...(owner ? [owner] : [])], { label: `${obj.name} in ${where}`, text });
|
|
52
|
+
}
|
|
53
|
+
if (picture !== undefined && picture > 0 && picture < 0x8000 && !script.isRoom) {
|
|
54
|
+
add(ResourceType.Pic, picture, [obj.name, where], { label: `${obj.name} in ${where}` });
|
|
55
|
+
}
|
|
56
|
+
// Sound objects that declare their number (room effects, mostly; music is set by code).
|
|
57
|
+
const sound = prop("number");
|
|
58
|
+
if (/sound|song|music/i.test(obj.className) && sound !== undefined && sound > 0 && rm.has({ type: ResourceType.Sound, number: sound })) {
|
|
59
|
+
add(ResourceType.Sound, sound, [obj.name, where, ...(owner ? [owner] : [])], { label: `${obj.name} in ${where}` });
|
|
60
|
+
}
|
|
61
|
+
if (!script.isRoom && obj.name)
|
|
62
|
+
add(ResourceType.Script, script.number, [obj.name]);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
/** Whether every word of `query` is in the asset's number, tags or descriptions. */
|
|
68
|
+
export function matches(query, number, meta) {
|
|
69
|
+
const words = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
70
|
+
if (!words.length)
|
|
71
|
+
return true;
|
|
72
|
+
const haystack = [String(number), ...(meta?.tags ?? []), ...(meta?.uses.flatMap((u) => [u.label, u.text ?? ""]) ?? [])].join(" ").toLowerCase();
|
|
73
|
+
return words.every((w) => haystack.includes(w));
|
|
74
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { ResourceType, analyzeGame, parseMessages, type MessageFile, type ResourceManager, type ScriptWorld } from "../../../packages/sci/src/index.ts";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* What the game says about its assets. SCI resources carry no names or descriptions, so
|
|
5
|
+
* they come from the scripts: the room that draws a picture, the objects whose `view` is a
|
|
6
|
+
* view, and what the game says when you Look at them (the room's or the object's noun, verb
|
|
7
|
+
* 1, in the room's message file); sounds from Sound objects that declare their number.
|
|
8
|
+
* Only what's declared on objects is found, not what a script picks at run time.
|
|
9
|
+
*/
|
|
10
|
+
export interface AssetMeta {
|
|
11
|
+
/** Short labels: room and object names ("rm250", "igor", "room 250"). */
|
|
12
|
+
tags: string[];
|
|
13
|
+
/** Who uses it, for the preview: "igor in room 250: Igor is digging…". */
|
|
14
|
+
uses: { label: string; text?: string }[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type AssetIndex = Map<string, AssetMeta>;
|
|
18
|
+
|
|
19
|
+
export const assetKey = (type: ResourceType, number: number) => `${type}:${number}`;
|
|
20
|
+
|
|
21
|
+
const LOOK = 1;
|
|
22
|
+
|
|
23
|
+
export async function buildAssetIndex(rm: ResourceManager, world: ScriptWorld, onProgress?: (done: number, total: number) => void): Promise<AssetIndex> {
|
|
24
|
+
const index = await analyzeGame(rm, { world, onProgress });
|
|
25
|
+
const out: AssetIndex = new Map();
|
|
26
|
+
const add = (type: ResourceType, number: number, tags: string[], use?: { label: string; text?: string }) => {
|
|
27
|
+
const key = assetKey(type, number);
|
|
28
|
+
const meta = out.get(key) ?? { tags: [], uses: [] };
|
|
29
|
+
for (const t of tags) if (t && !meta.tags.includes(t)) meta.tags.push(t);
|
|
30
|
+
if (use && !meta.uses.some((u) => u.label === use.label)) meta.uses.push(use);
|
|
31
|
+
out.set(key, meta);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// Message files, by module (a room's own file is its number).
|
|
35
|
+
const files = new Map<number, MessageFile | undefined>();
|
|
36
|
+
const messages = async (n: number) => {
|
|
37
|
+
if (!files.has(n)) {
|
|
38
|
+
const id = { type: ResourceType.Message, number: n };
|
|
39
|
+
files.set(n, rm.has(id) ? parseMessages((await rm.load(id)).data) : undefined);
|
|
40
|
+
}
|
|
41
|
+
return files.get(n);
|
|
42
|
+
};
|
|
43
|
+
/** What Look says about a noun: the first line, preferring no condition. */
|
|
44
|
+
const look = async (module: number, noun: number): Promise<string | undefined> => {
|
|
45
|
+
if (!noun) return undefined;
|
|
46
|
+
const records = (await messages(module))?.records.filter((r) => r.noun === noun && r.verb === LOOK && r.text) ?? [];
|
|
47
|
+
return (records.find((r) => r.cond === 0) ?? records[0])?.text;
|
|
48
|
+
};
|
|
49
|
+
const roomName = new Map(index.rooms.map((r) => [r.number, r.name]));
|
|
50
|
+
|
|
51
|
+
for (const room of index.rooms) {
|
|
52
|
+
const text = await look(room.number, room.noun);
|
|
53
|
+
if (room.picture >= 0) add(ResourceType.Pic, room.picture, [room.name, `room ${room.number}`], { label: `${room.name} (room ${room.number})`, text });
|
|
54
|
+
add(ResourceType.Script, room.number, [room.name, "room"], { label: `room ${room.number}`, text });
|
|
55
|
+
add(ResourceType.Message, room.number, [room.name, `room ${room.number}`], { label: `${room.name}'s text`, text });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
for (const script of index.scripts) {
|
|
59
|
+
const where = script.isRoom ? `room ${script.number}` : `script ${script.number}`;
|
|
60
|
+
const owner = roomName.get(script.number);
|
|
61
|
+
for (const obj of script.objects) {
|
|
62
|
+
const prop = (name: string) => obj.props.find((p) => p.name === name)?.value;
|
|
63
|
+
const view = prop("view");
|
|
64
|
+
const picture = prop("picture");
|
|
65
|
+
const noun = prop("noun") ?? 0;
|
|
66
|
+
const text = await look(script.number, noun);
|
|
67
|
+
if (view !== undefined && view > 0 && view < 0x8000 && rm.has({ type: ResourceType.View, number: view })) {
|
|
68
|
+
add(ResourceType.View, view, [obj.name, where, ...(owner ? [owner] : [])], { label: `${obj.name} in ${where}`, text });
|
|
69
|
+
}
|
|
70
|
+
if (picture !== undefined && picture > 0 && picture < 0x8000 && !script.isRoom) {
|
|
71
|
+
add(ResourceType.Pic, picture, [obj.name, where], { label: `${obj.name} in ${where}` });
|
|
72
|
+
}
|
|
73
|
+
// Sound objects that declare their number (room effects, mostly; music is set by code).
|
|
74
|
+
const sound = prop("number");
|
|
75
|
+
if (/sound|song|music/i.test(obj.className) && sound !== undefined && sound > 0 && rm.has({ type: ResourceType.Sound, number: sound })) {
|
|
76
|
+
add(ResourceType.Sound, sound, [obj.name, where, ...(owner ? [owner] : [])], { label: `${obj.name} in ${where}` });
|
|
77
|
+
}
|
|
78
|
+
if (!script.isRoom && obj.name) add(ResourceType.Script, script.number, [obj.name]);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return out;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Whether every word of `query` is in the asset's number, tags or descriptions. */
|
|
85
|
+
export function matches(query: string, number: number, meta: AssetMeta | undefined): boolean {
|
|
86
|
+
const words = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
87
|
+
if (!words.length) return true;
|
|
88
|
+
const haystack = [String(number), ...(meta?.tags ?? []), ...(meta?.uses.flatMap((u) => [u.label, u.text ?? ""]) ?? [])].join(" ").toLowerCase();
|
|
89
|
+
return words.every((w) => haystack.includes(w));
|
|
90
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type AudioClip, type AudioOutput, type FileSource } from "../../../packages/sci/src/index.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Plays digital clips through Web Audio. Clips are fetched (HTTP range requests into the
|
|
4
|
+
* 515 MB RESOURCE.AUD), decoded from SOL and cached. Browsers only allow audio after a
|
|
5
|
+
* user gesture, so `unlock()` must be called from a click/keypress handler.
|
|
6
|
+
*/
|
|
7
|
+
export declare class WebAudioOutput implements AudioOutput {
|
|
8
|
+
private readonly files;
|
|
9
|
+
private readonly preloaded;
|
|
10
|
+
readonly ctx: AudioContext;
|
|
11
|
+
private readonly buffers;
|
|
12
|
+
private readonly playing;
|
|
13
|
+
private readonly paused;
|
|
14
|
+
/** Incremented per play/stop so late-arriving decodes don't start stale clips. */
|
|
15
|
+
private readonly tokens;
|
|
16
|
+
constructor(files: FileSource, preloaded: (file: string) => Uint8Array | undefined);
|
|
17
|
+
unlock(): void;
|
|
18
|
+
private load;
|
|
19
|
+
private startNode;
|
|
20
|
+
play(channel: string, clip: AudioClip, { loop, volume }: {
|
|
21
|
+
loop: boolean;
|
|
22
|
+
volume: number;
|
|
23
|
+
}): void;
|
|
24
|
+
stop(channel: string): void;
|
|
25
|
+
pause(channel: string): void;
|
|
26
|
+
resume(channel: string): void;
|
|
27
|
+
setVolume(channel: string, volume: number): void;
|
|
28
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { decodeSol } from "../../../packages/sci/src/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Plays digital clips through Web Audio. Clips are fetched (HTTP range requests into the
|
|
4
|
+
* 515 MB RESOURCE.AUD), decoded from SOL and cached. Browsers only allow audio after a
|
|
5
|
+
* user gesture, so `unlock()` must be called from a click/keypress handler.
|
|
6
|
+
*/
|
|
7
|
+
export class WebAudioOutput {
|
|
8
|
+
files;
|
|
9
|
+
preloaded;
|
|
10
|
+
ctx = new AudioContext();
|
|
11
|
+
buffers = new Map();
|
|
12
|
+
playing = new Map();
|
|
13
|
+
paused = new Map();
|
|
14
|
+
/** Incremented per play/stop so late-arriving decodes don't start stale clips. */
|
|
15
|
+
tokens = new Map();
|
|
16
|
+
constructor(files, preloaded) {
|
|
17
|
+
this.files = files;
|
|
18
|
+
this.preloaded = preloaded;
|
|
19
|
+
}
|
|
20
|
+
unlock() {
|
|
21
|
+
if (this.ctx.state !== "running")
|
|
22
|
+
void this.ctx.resume();
|
|
23
|
+
}
|
|
24
|
+
load(clip) {
|
|
25
|
+
const key = `${clip.file}:${clip.offset}`;
|
|
26
|
+
let p = this.buffers.get(key);
|
|
27
|
+
if (!p) {
|
|
28
|
+
p = (async () => {
|
|
29
|
+
const whole = this.preloaded(clip.file);
|
|
30
|
+
const bytes = whole ? whole.subarray(clip.offset, clip.offset + clip.length) : await this.files.readRange(clip.file, clip.offset, clip.length);
|
|
31
|
+
const sol = decodeSol(bytes);
|
|
32
|
+
if (!sol || !sol.samples.length)
|
|
33
|
+
return undefined;
|
|
34
|
+
const frames = sol.samples.length / sol.channels;
|
|
35
|
+
const buffer = this.ctx.createBuffer(sol.channels, frames, sol.rate);
|
|
36
|
+
for (let c = 0; c < sol.channels; c++) {
|
|
37
|
+
const data = buffer.getChannelData(c);
|
|
38
|
+
for (let i = 0; i < frames; i++)
|
|
39
|
+
data[i] = sol.samples[i * sol.channels + c];
|
|
40
|
+
}
|
|
41
|
+
return buffer;
|
|
42
|
+
})();
|
|
43
|
+
this.buffers.set(key, p);
|
|
44
|
+
}
|
|
45
|
+
return p;
|
|
46
|
+
}
|
|
47
|
+
startNode(channel, buffer, offset, loop, volume) {
|
|
48
|
+
const gain = this.ctx.createGain();
|
|
49
|
+
gain.gain.value = volume;
|
|
50
|
+
gain.connect(this.ctx.destination);
|
|
51
|
+
const source = this.ctx.createBufferSource();
|
|
52
|
+
source.buffer = buffer;
|
|
53
|
+
source.loop = loop;
|
|
54
|
+
source.connect(gain);
|
|
55
|
+
source.onended = () => {
|
|
56
|
+
if (this.playing.get(channel)?.source === source)
|
|
57
|
+
this.playing.delete(channel);
|
|
58
|
+
};
|
|
59
|
+
source.start(0, offset);
|
|
60
|
+
this.playing.set(channel, { source, gain, buffer, startedAt: this.ctx.currentTime - offset, offset, loop });
|
|
61
|
+
}
|
|
62
|
+
play(channel, clip, { loop, volume }) {
|
|
63
|
+
this.stop(channel);
|
|
64
|
+
const token = (this.tokens.get(channel) ?? 0) + 1;
|
|
65
|
+
this.tokens.set(channel, token);
|
|
66
|
+
void this.load(clip).then((buffer) => {
|
|
67
|
+
if (!buffer || this.tokens.get(channel) !== token)
|
|
68
|
+
return;
|
|
69
|
+
this.startNode(channel, buffer, 0, loop, volume);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
stop(channel) {
|
|
73
|
+
this.tokens.set(channel, (this.tokens.get(channel) ?? 0) + 1);
|
|
74
|
+
this.paused.delete(channel);
|
|
75
|
+
const p = this.playing.get(channel);
|
|
76
|
+
if (!p)
|
|
77
|
+
return;
|
|
78
|
+
this.playing.delete(channel);
|
|
79
|
+
p.source.onended = null;
|
|
80
|
+
p.source.stop();
|
|
81
|
+
p.gain.disconnect();
|
|
82
|
+
}
|
|
83
|
+
pause(channel) {
|
|
84
|
+
const p = this.playing.get(channel);
|
|
85
|
+
if (!p)
|
|
86
|
+
return;
|
|
87
|
+
this.stop(channel);
|
|
88
|
+
this.paused.set(channel, { ...p, offset: (this.ctx.currentTime - p.startedAt) % p.buffer.duration });
|
|
89
|
+
}
|
|
90
|
+
resume(channel) {
|
|
91
|
+
const p = this.paused.get(channel);
|
|
92
|
+
if (!p)
|
|
93
|
+
return;
|
|
94
|
+
this.paused.delete(channel);
|
|
95
|
+
this.startNode(channel, p.buffer, p.offset, p.loop, p.gain.gain.value);
|
|
96
|
+
}
|
|
97
|
+
setVolume(channel, volume) {
|
|
98
|
+
const p = this.playing.get(channel);
|
|
99
|
+
if (p)
|
|
100
|
+
p.gain.gain.value = volume;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { decodeSol, type AudioClip, type AudioOutput, type FileSource } from "../../../packages/sci/src/index.ts";
|
|
2
|
+
|
|
3
|
+
interface Playing {
|
|
4
|
+
source: AudioBufferSourceNode;
|
|
5
|
+
gain: GainNode;
|
|
6
|
+
buffer: AudioBuffer;
|
|
7
|
+
startedAt: number;
|
|
8
|
+
offset: number;
|
|
9
|
+
loop: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Plays digital clips through Web Audio. Clips are fetched (HTTP range requests into the
|
|
14
|
+
* 515 MB RESOURCE.AUD), decoded from SOL and cached. Browsers only allow audio after a
|
|
15
|
+
* user gesture, so `unlock()` must be called from a click/keypress handler.
|
|
16
|
+
*/
|
|
17
|
+
export class WebAudioOutput implements AudioOutput {
|
|
18
|
+
readonly ctx = new AudioContext();
|
|
19
|
+
private readonly buffers = new Map<string, Promise<AudioBuffer | undefined>>();
|
|
20
|
+
private readonly playing = new Map<string, Playing>();
|
|
21
|
+
private readonly paused = new Map<string, Playing>();
|
|
22
|
+
/** Incremented per play/stop so late-arriving decodes don't start stale clips. */
|
|
23
|
+
private readonly tokens = new Map<string, number>();
|
|
24
|
+
|
|
25
|
+
constructor(
|
|
26
|
+
private readonly files: FileSource,
|
|
27
|
+
private readonly preloaded: (file: string) => Uint8Array | undefined,
|
|
28
|
+
) {}
|
|
29
|
+
|
|
30
|
+
unlock(): void {
|
|
31
|
+
if (this.ctx.state !== "running") void this.ctx.resume();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
private load(clip: AudioClip): Promise<AudioBuffer | undefined> {
|
|
35
|
+
const key = `${clip.file}:${clip.offset}`;
|
|
36
|
+
let p = this.buffers.get(key);
|
|
37
|
+
if (!p) {
|
|
38
|
+
p = (async () => {
|
|
39
|
+
const whole = this.preloaded(clip.file);
|
|
40
|
+
const bytes = whole ? whole.subarray(clip.offset, clip.offset + clip.length) : await this.files.readRange(clip.file, clip.offset, clip.length);
|
|
41
|
+
const sol = decodeSol(bytes);
|
|
42
|
+
if (!sol || !sol.samples.length) return undefined;
|
|
43
|
+
const frames = sol.samples.length / sol.channels;
|
|
44
|
+
const buffer = this.ctx.createBuffer(sol.channels, frames, sol.rate);
|
|
45
|
+
for (let c = 0; c < sol.channels; c++) {
|
|
46
|
+
const data = buffer.getChannelData(c);
|
|
47
|
+
for (let i = 0; i < frames; i++) data[i] = sol.samples[i * sol.channels + c]!;
|
|
48
|
+
}
|
|
49
|
+
return buffer;
|
|
50
|
+
})();
|
|
51
|
+
this.buffers.set(key, p);
|
|
52
|
+
}
|
|
53
|
+
return p;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private startNode(channel: string, buffer: AudioBuffer, offset: number, loop: boolean, volume: number): void {
|
|
57
|
+
const gain = this.ctx.createGain();
|
|
58
|
+
gain.gain.value = volume;
|
|
59
|
+
gain.connect(this.ctx.destination);
|
|
60
|
+
const source = this.ctx.createBufferSource();
|
|
61
|
+
source.buffer = buffer;
|
|
62
|
+
source.loop = loop;
|
|
63
|
+
source.connect(gain);
|
|
64
|
+
source.onended = () => {
|
|
65
|
+
if (this.playing.get(channel)?.source === source) this.playing.delete(channel);
|
|
66
|
+
};
|
|
67
|
+
source.start(0, offset);
|
|
68
|
+
this.playing.set(channel, { source, gain, buffer, startedAt: this.ctx.currentTime - offset, offset, loop });
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
play(channel: string, clip: AudioClip, { loop, volume }: { loop: boolean; volume: number }): void {
|
|
72
|
+
this.stop(channel);
|
|
73
|
+
const token = (this.tokens.get(channel) ?? 0) + 1;
|
|
74
|
+
this.tokens.set(channel, token);
|
|
75
|
+
void this.load(clip).then((buffer) => {
|
|
76
|
+
if (!buffer || this.tokens.get(channel) !== token) return;
|
|
77
|
+
this.startNode(channel, buffer, 0, loop, volume);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
stop(channel: string): void {
|
|
82
|
+
this.tokens.set(channel, (this.tokens.get(channel) ?? 0) + 1);
|
|
83
|
+
this.paused.delete(channel);
|
|
84
|
+
const p = this.playing.get(channel);
|
|
85
|
+
if (!p) return;
|
|
86
|
+
this.playing.delete(channel);
|
|
87
|
+
p.source.onended = null;
|
|
88
|
+
p.source.stop();
|
|
89
|
+
p.gain.disconnect();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
pause(channel: string): void {
|
|
93
|
+
const p = this.playing.get(channel);
|
|
94
|
+
if (!p) return;
|
|
95
|
+
this.stop(channel);
|
|
96
|
+
this.paused.set(channel, { ...p, offset: (this.ctx.currentTime - p.startedAt) % p.buffer.duration });
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
resume(channel: string): void {
|
|
100
|
+
const p = this.paused.get(channel);
|
|
101
|
+
if (!p) return;
|
|
102
|
+
this.paused.delete(channel);
|
|
103
|
+
this.startNode(channel, p.buffer, p.offset, p.loop, p.gain.gain.value);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
setVolume(channel: string, volume: number): void {
|
|
107
|
+
const p = this.playing.get(channel);
|
|
108
|
+
if (p) p.gain.gain.value = volume;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Direct manipulation for the room editor: handles drawn from the room's YAML over the
|
|
3
|
+
* game, which turn drags and brush strokes into edits of the YAML text. Edits replace only
|
|
4
|
+
* the value they change (a `[x, y]`, a `[l, t, r, b]`, a list of points), so comments and
|
|
5
|
+
* layout stay as written.
|
|
6
|
+
*/
|
|
7
|
+
type Point = [number, number];
|
|
8
|
+
type Path = (string | number)[];
|
|
9
|
+
/** `text` with the value at `path` replaced by `value`, keeping the rest as written. */
|
|
10
|
+
export declare function setYamlValue(text: string, path: Path, value: number[] | Point[]): string;
|
|
11
|
+
/** `text` with `value` added at the end of the list at `path` (made if missing). */
|
|
12
|
+
export declare function appendYamlItem(text: string, path: Path, value: Point[]): string;
|
|
13
|
+
export type EditMode = "play" | "move" | "floor" | "obstacle";
|
|
14
|
+
export interface EditHost {
|
|
15
|
+
/** The room's YAML as it is in the editor now. */
|
|
16
|
+
yaml(): string;
|
|
17
|
+
/** Replaces the YAML in the editor (an undoable edit). */
|
|
18
|
+
setYaml(text: string): void;
|
|
19
|
+
/** Where the room plane is on screen (game pixels). */
|
|
20
|
+
roomOffset(): Point;
|
|
21
|
+
/** A prop's drawn box right now (room coordinates), to grab it by. */
|
|
22
|
+
spriteBox(name: string): {
|
|
23
|
+
x: number;
|
|
24
|
+
y: number;
|
|
25
|
+
w: number;
|
|
26
|
+
h: number;
|
|
27
|
+
} | undefined;
|
|
28
|
+
/** Moves a prop in the running game while it's dragged (the rebuild makes it stick). */
|
|
29
|
+
previewProp(name: string, at: Point): void;
|
|
30
|
+
/** Hold rebuilding while a drag or stroke is in progress. */
|
|
31
|
+
holdBuild(hold: boolean): void;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* An SVG layer over the game. In "move" mode, everything the YAML places has a handle;
|
|
35
|
+
* "floor" and "obstacle" are brushes that draw a polygon. In "play" it's out of the way.
|
|
36
|
+
*/
|
|
37
|
+
export declare class EditLayer {
|
|
38
|
+
private readonly host;
|
|
39
|
+
readonly svg: SVGSVGElement;
|
|
40
|
+
mode: EditMode;
|
|
41
|
+
private spec;
|
|
42
|
+
private specText;
|
|
43
|
+
private drag;
|
|
44
|
+
private stroke;
|
|
45
|
+
private lastWrite;
|
|
46
|
+
constructor(host: EditHost);
|
|
47
|
+
setMode(mode: EditMode): void;
|
|
48
|
+
/** Room coordinates under the pointer. */
|
|
49
|
+
private point;
|
|
50
|
+
private readSpec;
|
|
51
|
+
render(): void;
|
|
52
|
+
private begin;
|
|
53
|
+
/** Keep getting the pointer's moves outside the layer (not for synthetic events). */
|
|
54
|
+
private capture;
|
|
55
|
+
private down;
|
|
56
|
+
private move;
|
|
57
|
+
private up;
|
|
58
|
+
/** The stroke so far as a polygon in the YAML (simplified to its corners). */
|
|
59
|
+
private writeStroke;
|
|
60
|
+
}
|
|
61
|
+
/** Ramer-Douglas-Peucker: drop points within `epsilon` of the line through their neighbours. */
|
|
62
|
+
export declare function simplify(points: Point[], epsilon: number): Point[];
|
|
63
|
+
export {};
|